Skip to main content

sz_orm_query/
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 std::fmt;
27use crate::ValidationError;
28use sz_orm_model::DEFAULT_BATCH_SIZE;
29use sz_orm_model::Dialect;
30use sz_orm_model::Model;
31use sz_orm_model::Value;
32
33/// 用于构造 SQL 查询的查询构造器
34pub struct QueryBuilder<M: Model> {
35    table: Option<String>,
36    select_columns: Vec<String>,
37    where_conditions: Vec<WhereCondition>,
38    order_by: Vec<OrderClause>,
39    group_by: Vec<String>,
40    having_conditions: Vec<WhereCondition>,
41    limit_value: Option<usize>,
42    offset_value: Option<usize>,
43    joins: Vec<JoinClause>,
44    dialect: Box<dyn Dialect>,
45    /// P0-1:是否禁用软删除过滤(true 表示禁用,查询包含已删除记录)
46    soft_delete_disabled: bool,
47    /// P0-3:当前租户 ID(运行时注入)。设置后自动追加 `WHERE {tenant_field} = ?`
48    tenant_id_value: Option<i64>,
49    /// P0-3:是否禁用租户过滤(true 表示禁用,跨租户查询)
50    tenant_disabled: bool,
51    /// P2-5:Keyset 分页游标条件(field, value, direction)
52    ///
53    /// 设置后,`build_select`/`build_select_with_params` 会追加 `WHERE {field} > ?` 或
54    /// `WHERE {field} < ?` 条件(取决于排序方向),实现基于游标的高效分页。
55    /// 与 OFFSET 分页相比,Keyset 分页在大数据集下性能稳定,不受数据插入/删除影响。
56    keyset_cursor: Option<KeysetCursor>,
57    #[allow(dead_code)]
58    model: std::marker::PhantomData<M>,
59}
60
61/// P2-5:Keyset 分页游标
62///
63/// 表示一个基于排序字段值的分页游标。结合 `ORDER BY {field} {direction}` 和
64/// `WHERE {field} {op} ?` 实现游标分页。
65///
66/// - `After(value)` + `Asc`:查询 `field > value` 的记录(下一页)
67/// - `Before(value)` + `Desc`:查询 `field < value` 的记录(上一页)
68#[derive(Debug, Clone)]
69struct KeysetCursor {
70    /// 排序字段名
71    field: String,
72    /// 游标值(上一页/下一页最后一行的该字段值)
73    value: Value,
74    /// 游标方向:After = 下一页(field > value),Before = 上一页(field < value)
75    direction: KeysetDirection,
76}
77
78/// P2-5:Keyset 游标方向
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80enum KeysetDirection {
81    /// 下一页:`WHERE field > cursor_value`(配合 ASC 排序)
82    After,
83    /// 上一页:`WHERE field < cursor_value`(配合 DESC 排序)
84    Before,
85}
86
87#[derive(Debug, Clone)]
88#[allow(dead_code)]
89enum WhereCondition {
90    /// 原始字符串条件(AND)— **存在注入风险,不推荐使用**
91    ///
92    /// 保留以兼容复杂表达式如 `age > 18 AND status = 'active'`。
93    /// 调用方必须确保字符串来自可信来源。
94    And(String),
95    /// 原始字符串条件(OR)— **存在注入风险,不推荐使用**
96    Or(String),
97    /// P0-2:参数化等值条件 `field = ?`
98    Eq(String, Value),
99    /// P0-2:参数化不等条件 `field != ?`
100    Ne(String, Value),
101    /// P0-2:参数化大于条件 `field > ?`
102    Gt(String, Value),
103    /// P0-2:参数化大于等于条件 `field >= ?`
104    Ge(String, Value),
105    /// P0-2:参数化小于条件 `field < ?`
106    Lt(String, Value),
107    /// P0-2:参数化小于等于条件 `field <= ?`
108    Le(String, Value),
109    /// P0-2:参数化 LIKE 条件 `field LIKE ?`
110    Like(String, Value),
111    /// P0-2:参数化 OR 等值条件 `OR field = ?`
112    OrEq(String, Value),
113    /// P0-2:参数化 OR 不等条件 `OR field != ?`
114    OrNe(String, Value),
115    /// P0-2:参数化 OR 大于条件 `OR field > ?`
116    OrGt(String, Value),
117    /// P0-2:参数化 OR 大于等于条件 `OR field >= ?`
118    OrGe(String, Value),
119    /// P0-2:参数化 OR 小于条件 `OR field < ?`
120    OrLt(String, Value),
121    /// P0-2:参数化 OR 小于等于条件 `OR field <= ?`
122    OrLe(String, Value),
123    /// P0-2:参数化 OR LIKE 条件 `OR field LIKE ?`
124    OrLike(String, Value),
125    In(String, Vec<Value>),
126    NotIn(String, Vec<Value>),
127    Between(String, Value, Value),
128    NotBetween(String, Value, Value),
129    Null(String),
130    NotNull(String),
131    Exists(String),
132    NotExists(String),
133}
134
135#[derive(Debug, Clone)]
136struct OrderClause {
137    field: String,
138    direction: OrderDirection,
139}
140
141#[derive(Debug, Clone)]
142enum OrderDirection {
143    Asc,
144    Desc,
145}
146
147#[derive(Debug, Clone)]
148#[allow(dead_code)]
149enum JoinClause {
150    Inner(String, String, String),
151    Left(String, String, String),
152    Right(String, String, String),
153    Cross(String, String),
154}
155
156impl<M: Model> QueryBuilder<M> {
157    pub fn new(dialect: Box<dyn Dialect>) -> Self {
158        Self {
159            table: None,
160            select_columns: vec!["*".to_string()],
161            where_conditions: Vec::new(),
162            order_by: Vec::new(),
163            group_by: Vec::new(),
164            having_conditions: Vec::new(),
165            limit_value: None,
166            offset_value: None,
167            joins: Vec::new(),
168            dialect,
169            soft_delete_disabled: false,
170            tenant_id_value: None,
171            tenant_disabled: false,
172            keyset_cursor: None,
173            model: std::marker::PhantomData,
174        }
175    }
176
177    pub fn table(mut self, table: impl Into<String>) -> Self {
178        self.table = Some(table.into());
179        self
180    }
181
182    /// P0-1:临时禁用软删除过滤,用于查询已删除的记录。
183    ///
184    /// 等价于 SeaORM 的 `Entity::find().filter(Column::DeletedAt.is_not_null())`
185    /// 或 Laravel Eloquent 的 `Model::withTrashed()`。
186    ///
187    /// # 示例
188    ///
189    /// ```ignore
190    /// use sz_orm_query::query::QueryBuilder;
191    /// use sz_orm_model::dialect::MySqlDialect;
192    ///
193    /// // 查询包含已软删除的用户
194    /// let sql = QueryBuilder::<User>::new(Box::new(MySqlDialect))
195    ///     .table("users")
196    ///     .without_soft_delete()
197    ///     .build_select();
198    /// // 不会自动追加 WHERE deleted_at IS NULL
199    /// ```
200    pub fn without_soft_delete(mut self) -> Self {
201        self.soft_delete_disabled = true;
202        self
203    }
204
205    /// P0-1:返回软删除过滤是否被禁用
206    pub fn is_soft_delete_disabled(&self) -> bool {
207        self.soft_delete_disabled
208    }
209
210    /// P0-1:返回当前 Model 的软删除字段名(若启用)
211    ///
212    /// 内部使用,用于 `build_*` 方法决定是否追加 `WHERE {field} IS NULL`。
213    fn soft_delete_field(&self) -> Option<&'static str> {
214        if self.soft_delete_disabled {
215            return None;
216        }
217        M::soft_delete_field()
218    }
219
220    /// P0-1:构造软删除过滤条件 SQL 片段(不含 `AND` 前缀)
221    ///
222    /// 返回 `None` 表示无需过滤;返回 `Some(sql)` 表示追加 `AND {sql}` 到 WHERE 子句。
223    fn build_soft_delete_condition(&self) -> Option<String> {
224        self.soft_delete_field()
225            .map(|field| format!("{} IS NULL", self.dialect.quote(field)))
226    }
227
228    // ===================== P0-3 多租户过滤 =====================
229
230    /// P0-3:设置当前租户 ID,启用多租户自动过滤。
231    ///
232    /// 当 `M::tenant_field()` 返回 `Some(field)` 时,`QueryBuilder` 会在以下场景
233    /// 自动追加 `WHERE {field} = ?`(参数化,值通过 `params` 绑定):
234    /// - `build_select` / `build_select_with_params`
235    /// - `build_count` / `build_exists` / `build_max` / `build_min` / `build_sum` / `build_avg`
236    /// - `build_update` / `build_update_with_params`(防止跨租户更新)
237    /// - `build_delete` / `build_delete_with_params`(防止跨租户删除)
238    ///
239    /// 使用 `without_tenant()` 可临时禁用租户过滤(用于跨租户管理查询)。
240    ///
241    /// # 示例
242    ///
243    /// ```ignore
244    /// use sz_orm_query::query::QueryBuilder;
245    /// use sz_orm_model::dialect::MySqlDialect;
246    ///
247    /// let (sql, params) = QueryBuilder::<Order>::new(Box::new(MySqlDialect))
248    ///     .table("orders")
249    ///     .with_tenant_id(42)
250    ///     .build_select_with_params();
251    /// // sql => "SELECT * FROM `orders` WHERE `tenant_id` = ?"
252    /// // params => [Value::I64(42)]
253    /// ```
254    pub fn with_tenant_id(mut self, tenant_id: i64) -> Self {
255        self.tenant_id_value = Some(tenant_id);
256        self
257    }
258
259    /// P0-3:临时禁用租户过滤,用于跨租户管理查询。
260    ///
261    /// 等价于 Laravel Eloquent 的全局作用域禁用。
262    pub fn without_tenant(mut self) -> Self {
263        self.tenant_disabled = true;
264        self
265    }
266
267    /// P0-3:返回租户过滤是否被禁用
268    pub fn is_tenant_disabled(&self) -> bool {
269        self.tenant_disabled
270    }
271
272    /// P0-3:返回当前 Model 的租户字段名(若启用且未禁用)
273    ///
274    /// 内部使用,用于 `build_*` 方法决定是否追加租户条件。
275    fn tenant_field(&self) -> Option<&'static str> {
276        if self.tenant_disabled {
277            return None;
278        }
279        M::tenant_field()
280    }
281
282    /// P0-3:返回当前租户 ID(若设置了且未禁用)
283    fn tenant_id_value(&self) -> Option<i64> {
284        if self.tenant_disabled {
285            return None;
286        }
287        self.tenant_id_value
288    }
289
290    /// P0-3:构造租户过滤条件(SQL 片段 + 参数值)
291    ///
292    /// 返回 `None` 表示无需过滤;返回 `Some((sql, value))` 表示追加 `AND {sql}` 到 WHERE 子句,
293    /// 并将 `value` 加入参数列表。
294    fn build_tenant_condition(&self) -> Option<(String, Value)> {
295        let field = self.tenant_field()?;
296        let tid = self.tenant_id_value()?;
297        Some((
298            format!("{} = ?", self.dialect.quote(field)),
299            Value::I64(tid),
300        ))
301    }
302
303    /// 设置 SELECT 列。
304    ///
305    /// **M-3 安全警告**:本方法直接拼接 `columns` 到 SQL,**不**进行标识符校验或 quote。
306    /// 调用方必须确保 `columns` 来自可信来源(硬编码或经 `sql_safety::validate_identifier`
307    /// 校验)。若列名可能来自不可信输入,请使用 [`QueryBuilder::select_quoted`]。
308    ///
309    /// 本方法保留原行为以兼容复杂表达式(如 `COUNT(*)`、`users.id AS uid`)。
310    pub fn select(mut self, columns: Vec<&str>) -> Self {
311        self.select_columns = columns.into_iter().map(|s| s.to_string()).collect();
312        self
313    }
314
315    /// M-3 修复:安全的 SELECT 列设置,自动校验每个列名并 quote。
316    ///
317    /// 每个 `column` 必须通过 `sql_safety::validate_identifier` 校验
318    /// (仅允许 ASCII 字母数字 + 下划线,不以数字开头,长度 1-63)。
319    /// 校验失败时返回 `DbError::InvalidInput`。
320    ///
321    /// 对于复杂表达式(如 `COUNT(*)`、`users.id AS uid`),请使用 [`QueryBuilder::select`]
322    /// 并自行确保安全。
323    pub fn select_quoted(mut self, columns: Vec<&str>) -> Result<Self, sz_orm_model::DbError> {
324        let mut quoted = Vec::with_capacity(columns.len());
325        for col in columns {
326            sz_orm_model::sql_safety::validate_identifier(col, "select column")?;
327            quoted.push(self.dialect.quote(col));
328        }
329        self.select_columns = quoted;
330        Ok(self)
331    }
332
333    /// 添加原始字符串 WHERE 条件(AND 关系)。
334    ///
335    /// **⚠️ P0-2 安全警告(v1.3.0+)**:本方法直接拼接 `condition` 到 SQL,
336    /// **存在 SQL 注入风险**。仅在以下场景使用:
337    /// - 条件来自硬编码字符串(如 `where_cond("age > 18")`)
338    /// - 条件含复杂表达式(如 `where_cond("status = 'active' AND role = 'admin'")`)
339    ///
340    /// **禁止**将用户输入拼接到 `condition` 中。若值来自不可信来源,
341    /// 必须使用参数化方法:[`where_eq`](Self::where_eq) / [`where_ne`](Self::where_ne) /
342    /// [`where_gt`](Self::where_gt) / [`where_lt`](Self::where_lt) / [`where_like`](Self::where_like)。
343    ///
344    /// # 推荐迁移
345    ///
346    /// ```ignore
347    /// // ❌ 危险:字符串拼接
348    /// builder.where_cond(format!("name = '{}'", user_input));
349    ///
350    /// // ✅ 安全:参数化绑定
351    /// builder.where_eq("name", Value::String(user_input.to_string()));
352    /// ```
353    #[deprecated(
354        since = "1.3.0",
355        note = "P0-2: 字符串拼接存在 SQL 注入风险,请使用 where_eq/where_ne/where_gt/where_lt/where_like 等参数化方法"
356    )]
357    pub fn where_cond(mut self, condition: impl Into<String>) -> Self {
358        self.where_conditions
359            .push(WhereCondition::And(condition.into()));
360        self
361    }
362
363    /// 添加原始字符串 WHERE 条件(OR 关系)。
364    ///
365    /// **⚠️ P0-2 安全警告**:同 [`where_cond`](Self::where_cond),存在注入风险。
366    #[deprecated(
367        since = "1.3.0",
368        note = "P0-2: 字符串拼接存在 SQL 注入风险,请使用参数化方法"
369    )]
370    pub fn or_where(mut self, condition: impl Into<String>) -> Self {
371        self.where_conditions
372            .push(WhereCondition::Or(condition.into()));
373        self
374    }
375
376    /// P0-2:参数化等值条件 `field = ?`(AND 关系)。
377    ///
378    /// 值通过 `?` 占位符绑定,杜绝 SQL 注入。
379    ///
380    /// # 示例
381    ///
382    /// ```ignore
383    /// use sz_orm_model::Value;
384    ///
385    /// builder
386    ///     .where_eq("status", Value::String("active".into()))
387    ///     .where_eq("tenant_id", Value::I64(42));
388    /// ```
389    pub fn where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
390        self.where_conditions
391            .push(WhereCondition::Eq(field.into(), value));
392        self
393    }
394
395    /// P0-2:参数化不等条件 `field != ?`(AND 关系)。
396    pub fn where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
397        self.where_conditions
398            .push(WhereCondition::Ne(field.into(), value));
399        self
400    }
401
402    /// P0-2:参数化大于条件 `field > ?`(AND 关系)。
403    pub fn where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
404        self.where_conditions
405            .push(WhereCondition::Gt(field.into(), value));
406        self
407    }
408
409    /// P0-2:参数化大于等于条件 `field >= ?`(AND 关系)。
410    pub fn where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
411        self.where_conditions
412            .push(WhereCondition::Ge(field.into(), value));
413        self
414    }
415
416    /// P0-2:参数化小于条件 `field < ?`(AND 关系)。
417    pub fn where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
418        self.where_conditions
419            .push(WhereCondition::Lt(field.into(), value));
420        self
421    }
422
423    /// P0-2:参数化小于等于条件 `field <= ?`(AND 关系)。
424    pub fn where_le(mut self, field: impl Into<String>, value: Value) -> Self {
425        self.where_conditions
426            .push(WhereCondition::Le(field.into(), value));
427        self
428    }
429
430    /// P0-2:参数化 LIKE 条件 `field LIKE ?`(AND 关系)。
431    ///
432    /// 调用方负责在 `pattern` 中包含 `%` 通配符。
433    ///
434    /// # 示例
435    ///
436    /// ```ignore
437    /// use sz_orm_model::Value;
438    ///
439    /// builder.where_like("name", Value::String("%alice%".into()));
440    /// ```
441    pub fn where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
442        self.where_conditions
443            .push(WhereCondition::Like(field.into(), pattern));
444        self
445    }
446
447    /// P0-2:参数化 OR 等值条件 `OR field = ?`。
448    ///
449    /// 值通过 `?` 占位符绑定,杜绝 SQL 注入。OR 条件会与相邻的 OR 条件组合成 `(cond1 OR cond2)` 形式。
450    pub fn or_where_eq(mut self, field: impl Into<String>, value: Value) -> Self {
451        self.where_conditions
452            .push(WhereCondition::OrEq(field.into(), value));
453        self
454    }
455
456    /// P0-2:参数化 OR 不等条件 `OR field != ?`。
457    pub fn or_where_ne(mut self, field: impl Into<String>, value: Value) -> Self {
458        self.where_conditions
459            .push(WhereCondition::OrNe(field.into(), value));
460        self
461    }
462
463    /// P0-2:参数化 OR 大于条件 `OR field > ?`。
464    pub fn or_where_gt(mut self, field: impl Into<String>, value: Value) -> Self {
465        self.where_conditions
466            .push(WhereCondition::OrGt(field.into(), value));
467        self
468    }
469
470    /// P0-2:参数化 OR 大于等于条件 `OR field >= ?`。
471    pub fn or_where_ge(mut self, field: impl Into<String>, value: Value) -> Self {
472        self.where_conditions
473            .push(WhereCondition::OrGe(field.into(), value));
474        self
475    }
476
477    /// P0-2:参数化 OR 小于条件 `OR field < ?`。
478    pub fn or_where_lt(mut self, field: impl Into<String>, value: Value) -> Self {
479        self.where_conditions
480            .push(WhereCondition::OrLt(field.into(), value));
481        self
482    }
483
484    /// P0-2:参数化 OR 小于等于条件 `OR field <= ?`。
485    pub fn or_where_le(mut self, field: impl Into<String>, value: Value) -> Self {
486        self.where_conditions
487            .push(WhereCondition::OrLe(field.into(), value));
488        self
489    }
490
491    /// P0-2:参数化 OR LIKE 条件 `OR field LIKE ?`。
492    pub fn or_where_like(mut self, field: impl Into<String>, pattern: Value) -> Self {
493        self.where_conditions
494            .push(WhereCondition::OrLike(field.into(), pattern));
495        self
496    }
497
498    pub fn where_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
499        self.where_conditions
500            .push(WhereCondition::In(field.into(), values));
501        self
502    }
503
504    pub fn where_not_in(mut self, field: impl Into<String>, values: Vec<Value>) -> Self {
505        self.where_conditions
506            .push(WhereCondition::NotIn(field.into(), values));
507        self
508    }
509
510    pub fn where_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
511        self.where_conditions
512            .push(WhereCondition::Between(field.into(), start, end));
513        self
514    }
515
516    pub fn where_not_between(mut self, field: impl Into<String>, start: Value, end: Value) -> Self {
517        self.where_conditions
518            .push(WhereCondition::NotBetween(field.into(), start, end));
519        self
520    }
521
522    pub fn where_null(mut self, field: impl Into<String>) -> Self {
523        self.where_conditions
524            .push(WhereCondition::Null(field.into()));
525        self
526    }
527
528    pub fn where_not_null(mut self, field: impl Into<String>) -> Self {
529        self.where_conditions
530            .push(WhereCondition::NotNull(field.into()));
531        self
532    }
533
534    pub fn order_by(mut self, field: impl Into<String>) -> Self {
535        self.order_by.push(OrderClause {
536            field: field.into(),
537            direction: OrderDirection::Asc,
538        });
539        self
540    }
541
542    pub fn order_desc(mut self, field: impl Into<String>) -> Self {
543        self.order_by.push(OrderClause {
544            field: field.into(),
545            direction: OrderDirection::Desc,
546        });
547        self
548    }
549
550    pub fn group_by(mut self, field: impl Into<String>) -> Self {
551        self.group_by.push(field.into());
552        self
553    }
554
555    pub fn having(mut self, condition: impl Into<String>) -> Self {
556        self.having_conditions
557            .push(WhereCondition::And(condition.into()));
558        self
559    }
560
561    pub fn limit(mut self, limit: usize) -> Self {
562        self.limit_value = Some(limit);
563        self
564    }
565
566    pub fn offset(mut self, offset: usize) -> Self {
567        self.offset_value = Some(offset);
568        self
569    }
570
571    pub fn page(mut self, page: usize, page_size: usize) -> Self {
572        self.limit_value = Some(page_size);
573        self.offset_value = Some((page.saturating_sub(1)) * page_size);
574        self
575    }
576
577    /// P2-5:基于游标的 Keyset 分页 — 查询指定字段值之后的记录(下一页)
578    ///
579    /// 生成 `WHERE {field} > ? ORDER BY {field} ASC LIMIT {page_size}`。
580    /// 适用于按主键或时间戳递增遍历大表的场景,性能不受数据插入/删除影响。
581    ///
582    /// # 参数
583    ///
584    /// - `field`:排序字段(通常是主键或索引列,如 `id`、`created_at`)
585    /// - `cursor_value`:当前页最后一条记录的该字段值
586    /// - `page_size`:每页大小
587    ///
588    /// # 示例
589    ///
590    /// ```
591    /// use sz_orm_query::QueryBuilder;
592    /// use sz_orm_model::{DbType, dialect::get_dialect, Value};
593    /// # use sz_orm_model::{Model, ModelExt};
594    /// # #[derive(Clone, Debug)]
595    /// # struct User { id: i64 }
596    /// # impl Model for User {
597    /// #     type PrimaryKey = i64;
598    /// #     fn table_name() -> &'static str { "users" }
599    /// #     fn pk(&self) -> i64 { self.id }
600    /// #     fn set_pk(&mut self, pk: i64) { self.id = pk; }
601    /// # }
602    /// # impl ModelExt for User {
603    /// #     fn columns() -> Vec<&'static str> { vec!["id"] }
604    /// #     fn fillable() -> Vec<&'static str> { vec![] }
605    /// #     fn guarded() -> Vec<&'static str> { vec!["id"] }
606    /// #     fn hidden() -> Vec<&'static str> { vec![] }
607    /// #     fn relations() -> std::collections::HashMap<&'static str, sz_orm_model::Relation> { Default::default() }
608    /// #     fn fill(&mut self, _: std::collections::HashMap<String, Value>) {}
609    /// #     fn to_json(&self) -> serde_json::Value { serde_json::json!({}) }
610    /// # }
611    /// let dialect = get_dialect(DbType::MySQL).unwrap();
612    /// let builder = QueryBuilder::<User>::new(dialect)
613    ///     .keyset_after("id", Value::I64(100), 20);
614    /// let (sql, params) = builder.build_select_with_params();
615    /// // 方言会引用字段名(如 MySQL 的 `id`),去除引号后检查
616    /// let sql_clean = sql.replace('`', "").replace('"', "");
617    /// assert!(sql_clean.contains("id > ?"));
618    /// assert!(sql.to_uppercase().contains("ORDER BY"));
619    /// assert!(sql.to_uppercase().contains("ASC"));
620    /// assert!(sql.contains("LIMIT 20"));
621    /// assert_eq!(params, vec![Value::I64(100)]);
622    /// ```
623    pub fn keyset_after(
624        mut self,
625        field: impl Into<String>,
626        cursor_value: Value,
627        page_size: usize,
628    ) -> Self {
629        let field_str = field.into();
630        // 自动设置 ORDER BY ASC(若字段已存在则更新方向为 ASC,保证 keyset 语义一致)
631        if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
632            existing.direction = OrderDirection::Asc;
633        } else {
634            self.order_by.push(OrderClause {
635                field: field_str.clone(),
636                direction: OrderDirection::Asc,
637            });
638        }
639        self.limit_value = Some(page_size);
640        // 清除 offset(keyset 与 offset 互斥)
641        self.offset_value = None;
642        self.keyset_cursor = Some(KeysetCursor {
643            field: field_str,
644            value: cursor_value,
645            direction: KeysetDirection::After,
646        });
647        self
648    }
649
650    /// P2-5:基于游标的 Keyset 分页 — 查询指定字段值之前的记录(上一页)
651    ///
652    /// 生成 `WHERE {field} < ? ORDER BY {field} DESC LIMIT {page_size}`。
653    /// 适用于反向遍历场景。
654    ///
655    /// # 参数
656    ///
657    /// - `field`:排序字段
658    /// - `cursor_value`:当前页第一条记录的该字段值
659    /// - `page_size`:每页大小
660    ///
661    /// # 示例
662    ///
663    /// ```
664    /// use sz_orm_query::QueryBuilder;
665    /// use sz_orm_model::{DbType, dialect::get_dialect, Value};
666    /// # use sz_orm_model::{Model, ModelExt};
667    /// # #[derive(Clone, Debug)]
668    /// # struct User { id: i64 }
669    /// # impl Model for User {
670    /// #     type PrimaryKey = i64;
671    /// #     fn table_name() -> &'static str { "users" }
672    /// #     fn pk(&self) -> i64 { self.id }
673    /// #     fn set_pk(&mut self, pk: i64) { self.id = pk; }
674    /// # }
675    /// # impl ModelExt for User {
676    /// #     fn columns() -> Vec<&'static str> { vec!["id"] }
677    /// #     fn fillable() -> Vec<&'static str> { vec![] }
678    /// #     fn guarded() -> Vec<&'static str> { vec!["id"] }
679    /// #     fn hidden() -> Vec<&'static str> { vec![] }
680    /// #     fn relations() -> std::collections::HashMap<&'static str, sz_orm_model::Relation> { Default::default() }
681    /// #     fn fill(&mut self, _: std::collections::HashMap<String, Value>) {}
682    /// #     fn to_json(&self) -> serde_json::Value { serde_json::json!({}) }
683    /// # }
684    /// let dialect = get_dialect(DbType::MySQL).unwrap();
685    /// let builder = QueryBuilder::<User>::new(dialect)
686    ///     .keyset_before("id", Value::I64(100), 20);
687    /// let (sql, params) = builder.build_select_with_params();
688    /// // 方言会引用字段名(如 MySQL 的 `id`),去除引号后检查
689    /// let sql_clean = sql.replace('`', "").replace('"', "");
690    /// assert!(sql_clean.contains("id < ?"));
691    /// assert!(sql.to_uppercase().contains("ORDER BY"));
692    /// assert!(sql.to_uppercase().contains("DESC"));
693    /// assert!(sql.contains("LIMIT 20"));
694    /// assert_eq!(params, vec![Value::I64(100)]);
695    /// ```
696    pub fn keyset_before(
697        mut self,
698        field: impl Into<String>,
699        cursor_value: Value,
700        page_size: usize,
701    ) -> Self {
702        let field_str = field.into();
703        // 自动设置 ORDER BY DESC(若字段已存在则更新方向为 DESC,保证 keyset 语义一致)
704        if let Some(existing) = self.order_by.iter_mut().find(|o| o.field == field_str) {
705            existing.direction = OrderDirection::Desc;
706        } else {
707            self.order_by.push(OrderClause {
708                field: field_str.clone(),
709                direction: OrderDirection::Desc,
710            });
711        }
712        self.limit_value = Some(page_size);
713        self.offset_value = None;
714        self.keyset_cursor = Some(KeysetCursor {
715            field: field_str,
716            value: cursor_value,
717            direction: KeysetDirection::Before,
718        });
719        self
720    }
721
722    pub fn join_inner(
723        mut self,
724        table: impl Into<String>,
725        on_left: impl Into<String>,
726        on_right: impl Into<String>,
727    ) -> Self {
728        self.joins.push(JoinClause::Inner(
729            table.into(),
730            on_left.into(),
731            on_right.into(),
732        ));
733        self
734    }
735
736    pub fn join_left(
737        mut self,
738        table: impl Into<String>,
739        on_left: impl Into<String>,
740        on_right: impl Into<String>,
741    ) -> Self {
742        self.joins.push(JoinClause::Left(
743            table.into(),
744            on_left.into(),
745            on_right.into(),
746        ));
747        self
748    }
749
750    pub fn join_right(
751        mut self,
752        table: impl Into<String>,
753        on_left: impl Into<String>,
754        on_right: impl Into<String>,
755    ) -> Self {
756        self.joins.push(JoinClause::Right(
757            table.into(),
758            on_left.into(),
759            on_right.into(),
760        ));
761        self
762    }
763
764    /// 构建 SELECT SQL 语句
765    ///
766    /// L-5 修复:补充示例文档
767    ///
768    /// 根据 `table`、`select_columns`、`where_conditions`、`joins`、`order_by`、
769    /// `group_by`、`having`、`limit`、`offset` 等条件拼装最终 SQL。
770    /// 若未通过 `table()` 指定表名,则使用 `M::table_name()`。
771    ///
772    /// # 示例
773    ///
774    /// ```ignore
775    /// use sz_orm_query::query::QueryBuilder;
776    /// use sz_orm_model::dialect::MySqlDialect;
777    /// use sz_orm_query::model::Model;
778    ///
779    /// #[derive(Default)]
780    /// struct User;
781    /// impl Model for User {
782    ///     type PrimaryKey = i64;
783    ///     fn table_name() -> &'static str { "users" }
784    ///     fn pk(&self) -> Self::PrimaryKey { 0 }
785    ///     fn set_pk(&mut self, _: Self::PrimaryKey) {}
786    /// }
787    ///
788    /// let sql = QueryBuilder::<User>::new(Box::new(MySqlDialect))
789    ///     .select(vec!["id", "name"])
790    ///     .where_cond("age > 18")
791    ///     .order_by("id DESC")
792    ///     .limit(10)
793    ///     .build_select();
794    /// // sql => "SELECT id, name FROM `users` WHERE age > 18 ORDER BY id DESC LIMIT 10"
795    /// ```
796    #[tracing::instrument(skip(self), fields(op = "select"))]
797    pub fn build_select(&self) -> String {
798        let table = self
799            .table
800            .clone()
801            .unwrap_or_else(|| M::table_name().to_string());
802
803        let columns = if self.select_columns.is_empty() {
804            "*".to_string()
805        } else {
806            self.select_columns.join(", ")
807        };
808
809        let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
810
811        for join in &self.joins {
812            match join {
813                JoinClause::Inner(t, l, r) => {
814                    sql.push_str(&format!(
815                        " INNER JOIN {} ON {} = {}",
816                        self.dialect.quote(t),
817                        self.dialect.quote(l),
818                        self.dialect.quote(r)
819                    ));
820                }
821                JoinClause::Left(t, l, r) => {
822                    sql.push_str(&format!(
823                        " LEFT JOIN {} ON {} = {}",
824                        self.dialect.quote(t),
825                        self.dialect.quote(l),
826                        self.dialect.quote(r)
827                    ));
828                }
829                JoinClause::Right(t, l, r) => {
830                    sql.push_str(&format!(
831                        " RIGHT JOIN {} ON {} = {}",
832                        self.dialect.quote(t),
833                        self.dialect.quote(l),
834                        self.dialect.quote(r)
835                    ));
836                }
837                JoinClause::Cross(t, on) => {
838                    sql.push_str(&format!(
839                        " CROSS JOIN {} ON {}",
840                        self.dialect.quote(t),
841                        self.dialect.quote(on)
842                    ));
843                }
844            }
845        }
846
847        // P0-1:build_where_clause 内部已处理软删除条件,即使 where_conditions 为空也可能返回非空
848        let where_clause = self.build_where_clause();
849        if !where_clause.is_empty() {
850            sql.push_str(&where_clause);
851        }
852
853        if !self.group_by.is_empty() {
854            let cols: Vec<String> = self
855                .group_by
856                .iter()
857                .map(|c| self.dialect.quote(c))
858                .collect();
859            sql.push_str(" GROUP BY ");
860            sql.push_str(&cols.join(", "));
861        }
862
863        if !self.having_conditions.is_empty() {
864            sql.push_str(" HAVING ");
865            for (i, cond) in self.having_conditions.iter().enumerate() {
866                if i > 0 {
867                    sql.push_str(" AND ");
868                }
869                if let WhereCondition::And(c) = cond {
870                    sql.push_str(c);
871                }
872            }
873        }
874
875        if !self.order_by.is_empty() {
876            let order_cols: Vec<String> = self
877                .order_by
878                .iter()
879                .map(|o| {
880                    let dir = match o.direction {
881                        OrderDirection::Asc => " ASC",
882                        OrderDirection::Desc => " DESC",
883                    };
884                    format!("{}{}", self.dialect.quote(&o.field), dir)
885                })
886                .collect();
887            sql.push_str(" ORDER BY ");
888            sql.push_str(&order_cols.join(", "));
889        }
890
891        if let Some(limit) = self.limit_value {
892            sql.push_str(&format!(" LIMIT {}", limit));
893        }
894
895        if let Some(offset) = self.offset_value {
896            sql.push_str(&format!(" OFFSET {}", offset));
897        }
898
899        sql
900    }
901
902    /// 构建 WHERE 子句(处理所有条件类型:And/Or/In/NotIn/Between/Null/Eq/Ne/Gt/Lt/Like 等)
903    ///
904    /// P0-1:自动追加软删除过滤条件(`AND {soft_delete_field} IS NULL`)
905    ///
906    /// 返回空字符串表示无 WHERE 子句
907    fn build_where_clause(&self) -> String {
908        self.build_where_clause_with_options(true)
909    }
910
911    /// 构建 WHERE 子句(可控制是否追加软删除过滤)。
912    ///
913    /// `include_soft_delete = true`:追加 `AND {soft_delete_field} IS NULL`(默认行为)
914    /// `include_soft_delete = false`:不追加软删除过滤(用于 `build_force_delete`)
915    ///
916    /// P0-3:租户条件总是追加(若启用),不受 `include_soft_delete` 控制。
917    /// 物理删除也应受租户隔离约束,跨租户操作需显式 `without_tenant()`。
918    fn build_where_clause_with_options(&self, include_soft_delete: bool) -> String {
919        // P0-1:构造软删除条件(若有且启用)
920        let soft_delete_cond = if include_soft_delete {
921            self.build_soft_delete_condition()
922        } else {
923            None
924        };
925
926        // P0-3:构造租户条件(若有且启用)— 无参数版本内嵌转义值
927        let tenant_cond = self.build_tenant_condition().map(|(sql, value)| {
928            // sql 形如 "`tenant_id` = ?",将 ? 替换为内嵌值
929            sql.replacen('?', &value.to_param_with_dialect(&*self.dialect), 1)
930        });
931
932        // 无用户条件且无软删除条件且无租户条件且无 keyset 游标 → 空 WHERE
933        if self.where_conditions.is_empty()
934            && soft_delete_cond.is_none()
935            && tenant_cond.is_none()
936            && self.keyset_cursor.is_none()
937        {
938            return String::new();
939        }
940
941        // 将每个条件转换为字符串,OR 条件标记前缀
942        let mut conditions: Vec<String> = self
943            .where_conditions
944            .iter()
945            .map(|cond| match cond {
946                WhereCondition::And(c) => c.clone(),
947                WhereCondition::Or(c) => format!("OR {}", c),
948                // P0-2:参数化条件在无参数版本中直接 inline 值(用于 build_select 等无参数绑定场景)
949                WhereCondition::Eq(f, v) => format!(
950                    "{} = {}",
951                    self.dialect.quote(f),
952                    v.to_param_with_dialect(&*self.dialect)
953                ),
954                WhereCondition::Ne(f, v) => format!(
955                    "{} != {}",
956                    self.dialect.quote(f),
957                    v.to_param_with_dialect(&*self.dialect)
958                ),
959                WhereCondition::Gt(f, v) => format!(
960                    "{} > {}",
961                    self.dialect.quote(f),
962                    v.to_param_with_dialect(&*self.dialect)
963                ),
964                WhereCondition::Ge(f, v) => format!(
965                    "{} >= {}",
966                    self.dialect.quote(f),
967                    v.to_param_with_dialect(&*self.dialect)
968                ),
969                WhereCondition::Lt(f, v) => format!(
970                    "{} < {}",
971                    self.dialect.quote(f),
972                    v.to_param_with_dialect(&*self.dialect)
973                ),
974                WhereCondition::Le(f, v) => format!(
975                    "{} <= {}",
976                    self.dialect.quote(f),
977                    v.to_param_with_dialect(&*self.dialect)
978                ),
979                WhereCondition::Like(f, v) => format!(
980                    "{} LIKE {}",
981                    self.dialect.quote(f),
982                    v.to_param_with_dialect(&*self.dialect)
983                ),
984                WhereCondition::OrEq(f, v) => format!(
985                    "OR {} = {}",
986                    self.dialect.quote(f),
987                    v.to_param_with_dialect(&*self.dialect)
988                ),
989                WhereCondition::OrNe(f, v) => format!(
990                    "OR {} != {}",
991                    self.dialect.quote(f),
992                    v.to_param_with_dialect(&*self.dialect)
993                ),
994                WhereCondition::OrGt(f, v) => format!(
995                    "OR {} > {}",
996                    self.dialect.quote(f),
997                    v.to_param_with_dialect(&*self.dialect)
998                ),
999                WhereCondition::OrGe(f, v) => format!(
1000                    "OR {} >= {}",
1001                    self.dialect.quote(f),
1002                    v.to_param_with_dialect(&*self.dialect)
1003                ),
1004                WhereCondition::OrLt(f, v) => format!(
1005                    "OR {} < {}",
1006                    self.dialect.quote(f),
1007                    v.to_param_with_dialect(&*self.dialect)
1008                ),
1009                WhereCondition::OrLe(f, v) => format!(
1010                    "OR {} <= {}",
1011                    self.dialect.quote(f),
1012                    v.to_param_with_dialect(&*self.dialect)
1013                ),
1014                WhereCondition::OrLike(f, v) => format!(
1015                    "OR {} LIKE {}",
1016                    self.dialect.quote(f),
1017                    v.to_param_with_dialect(&*self.dialect)
1018                ),
1019                WhereCondition::In(f, vals) => {
1020                    // v0.2.2 修复 H-1:使用方言感知的转义
1021                    let vals_str: Vec<String> = vals
1022                        .iter()
1023                        .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1024                        .collect();
1025                    format!("{} IN ({})", self.dialect.quote(f), vals_str.join(", "))
1026                }
1027                WhereCondition::NotIn(f, vals) => {
1028                    let vals_str: Vec<String> = vals
1029                        .iter()
1030                        .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1031                        .collect();
1032                    format!("{} NOT IN ({})", self.dialect.quote(f), vals_str.join(", "))
1033                }
1034                WhereCondition::Between(f, start, end) => {
1035                    format!(
1036                        "{} BETWEEN {} AND {}",
1037                        self.dialect.quote(f),
1038                        start.to_param_with_dialect(&*self.dialect),
1039                        end.to_param_with_dialect(&*self.dialect)
1040                    )
1041                }
1042                WhereCondition::NotBetween(f, start, end) => {
1043                    format!(
1044                        "{} NOT BETWEEN {} AND {}",
1045                        self.dialect.quote(f),
1046                        start.to_param_with_dialect(&*self.dialect),
1047                        end.to_param_with_dialect(&*self.dialect)
1048                    )
1049                }
1050                WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1051                WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1052                WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1053                WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1054            })
1055            .collect();
1056
1057        // P0-1:追加软删除条件(作为最后一个 AND 条件)
1058        if let Some(sd_cond) = soft_delete_cond {
1059            conditions.push(sd_cond);
1060        }
1061
1062        // P0-3:追加租户条件(在软删除之后,作为 AND 条件)
1063        if let Some(t_cond) = tenant_cond {
1064            conditions.push(t_cond);
1065        }
1066
1067        // P2-5:追加 keyset 游标条件(在租户条件之后,内嵌转义值)
1068        if let Some(ref cursor) = self.keyset_cursor {
1069            let op = match cursor.direction {
1070                KeysetDirection::After => ">",
1071                KeysetDirection::Before => "<",
1072            };
1073            conditions.push(format!(
1074                "{} {} {}",
1075                self.dialect.quote(&cursor.field),
1076                op,
1077                cursor.value.to_param_with_dialect(&*self.dialect)
1078            ));
1079        }
1080
1081        if conditions.is_empty() {
1082            return String::new();
1083        }
1084
1085        // OR 分组逻辑:将相邻的 OR 条件组合成 (cond1 OR cond2) 形式
1086        // 边界处理:如果第一个条件就是 OR(不合理但需防御),当作 AND 处理
1087        let mut groups: Vec<Vec<String>> = Vec::new();
1088        let mut current_group: Vec<String> = Vec::new();
1089        for cond in conditions.iter() {
1090            if let Some(stripped) = cond.strip_prefix("OR ") {
1091                // OR 条件:无论是否首个,都把 OR 前缀去掉当作普通条件加入当前组
1092                current_group.push(stripped.to_string());
1093            } else {
1094                // AND 条件:如果当前组非空,先保存
1095                if !current_group.is_empty() {
1096                    groups.push(std::mem::take(&mut current_group));
1097                }
1098                current_group.push(cond.clone());
1099            }
1100        }
1101        if !current_group.is_empty() {
1102            groups.push(current_group);
1103        }
1104
1105        let group_strs: Vec<String> = groups
1106            .iter()
1107            .map(|g| {
1108                if g.len() == 1 {
1109                    g[0].clone()
1110                } else {
1111                    format!("({})", g.join(" OR "))
1112                }
1113            })
1114            .collect();
1115
1116        format!(" WHERE {}", group_strs.join(" AND "))
1117    }
1118
1119    #[tracing::instrument(skip(self, data), fields(op = "insert"))]
1120    pub fn build_insert(&self, data: &std::collections::HashMap<String, Value>) -> String {
1121        let table = self
1122            .table
1123            .clone()
1124            .unwrap_or_else(|| M::table_name().to_string());
1125
1126        if data.is_empty() {
1127            return String::new();
1128        }
1129
1130        let columns: Vec<String> = data.keys().map(|k| self.dialect.quote(k)).collect();
1131        // v0.2.2 修复 H-1:使用方言感知的转义
1132        let values: Vec<String> = data
1133            .values()
1134            .map(|v| v.to_param_with_dialect(&*self.dialect).to_string())
1135            .collect();
1136
1137        format!(
1138            "INSERT INTO {} ({}) VALUES ({})",
1139            self.dialect.quote(&table),
1140            columns.join(", "),
1141            values.join(", ")
1142        )
1143    }
1144
1145    #[tracing::instrument(skip(self, data), fields(op = "update"))]
1146    pub fn build_update(&self, data: &std::collections::HashMap<String, Value>) -> String {
1147        let table = self
1148            .table
1149            .clone()
1150            .unwrap_or_else(|| M::table_name().to_string());
1151
1152        if data.is_empty() {
1153            return String::new();
1154        }
1155
1156        let set_clauses: Vec<String> = data
1157            .iter()
1158            .map(|(k, v)| {
1159                format!(
1160                    "{} = {}",
1161                    self.dialect.quote(k),
1162                    v.to_param_with_dialect(&*self.dialect)
1163                )
1164            })
1165            .collect();
1166
1167        let mut sql = format!(
1168            "UPDATE {} SET {}",
1169            self.dialect.quote(&table),
1170            set_clauses.join(", ")
1171        );
1172
1173        sql.push_str(&self.build_where_clause());
1174        sql
1175    }
1176
1177    /// 构建 DELETE SQL 语句。
1178    ///
1179    /// **P0-1 软删除集成(v1.3.0+)**:当 `M: Model` 实现了 `soft_delete_field()`
1180    /// 返回 `Some(field)` 且未调用 `without_soft_delete()` 时,本方法自动生成
1181    /// `UPDATE {table} SET {field} = NOW() WHERE ...` 而非 `DELETE FROM ...`。
1182    ///
1183    /// 这与 SeaORM 的 `ActiveModelBehavior::after_delete` + `ActiveValue::Set`
1184    /// 行为对齐:删除操作实际是软删除 UPDATE。
1185    ///
1186    /// 若需物理删除,请使用 [`build_force_delete`](Self::build_force_delete)。
1187    #[tracing::instrument(skip(self), fields(op = "delete"))]
1188    pub fn build_delete(&self) -> String {
1189        let table = self
1190            .table
1191            .clone()
1192            .unwrap_or_else(|| M::table_name().to_string());
1193
1194        // P0-1:软删除启用时转为 UPDATE SET {field} = NOW()
1195        if let Some(field) = self.soft_delete_field() {
1196            let where_clause = self.build_where_clause();
1197            return format!(
1198                "UPDATE {} SET {} = NOW(){}",
1199                self.dialect.quote(&table),
1200                self.dialect.quote(field),
1201                where_clause
1202            );
1203        }
1204
1205        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1206        sql.push_str(&self.build_where_clause());
1207        sql
1208    }
1209
1210    /// 构建物理 DELETE SQL 语句(绕过软删除)。
1211    ///
1212    /// 即使 Model 实现了 `soft_delete_field()`,也生成 `DELETE FROM ...`,
1213    /// 且**不追加** `WHERE deleted_at IS NULL` 过滤(保留用户指定的 WHERE 条件)。
1214    /// 用于管理员强制清除场景。
1215    ///
1216    /// # 安全警告
1217    ///
1218    /// 物理删除不可恢复,请谨慎使用。
1219    pub fn build_force_delete(&self) -> String {
1220        let table = self
1221            .table
1222            .clone()
1223            .unwrap_or_else(|| M::table_name().to_string());
1224
1225        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1226        // P0-1:物理删除不追加软删除过滤(include_soft_delete = false)
1227        sql.push_str(&self.build_where_clause_with_options(false));
1228        sql
1229    }
1230
1231    // ===================== 参数绑定版本(v1.1.0 新增) =====================
1232
1233    /// 构建 WHERE 子句(参数绑定版本)。
1234    ///
1235    /// P0-1:自动追加软删除过滤条件(`AND {soft_delete_field} IS NULL`,无参数)
1236    ///
1237    /// 将 `In`/`NotIn`/`Between`/`NotBetween`/`Eq`/`Ne`/`Gt`/`Ge`/`Lt`/`Le`/`Like`
1238    /// 条件中的值替换为 `?` 占位符,值收集到 `params` 向量中。
1239    /// `And`/`Or`/`Exists` 等原始字符串条件不提取参数(调用方负责安全)。
1240    fn build_where_clause_with_params(&self) -> (String, Vec<Value>) {
1241        // 默认包含软删除条件
1242        self.build_where_clause_with_params_options(true)
1243    }
1244
1245    /// 构建参数化 WHERE 子句(可控是否包含软删除条件)
1246    ///
1247    /// # 参数
1248    ///
1249    /// - `include_soft_delete`:true 时追加软删除条件;false 时跳过(用于物理删除等场景)
1250    ///
1251    /// P0-3:租户条件总是追加(若启用),不受 `include_soft_delete` 控制。
1252    /// 租户值通过 `?` 占位符绑定,加入 `params` 列表末尾。
1253    fn build_where_clause_with_params_options(
1254        &self,
1255        include_soft_delete: bool,
1256    ) -> (String, Vec<Value>) {
1257        // P0-1:构造软删除条件(若有且启用,无参数)
1258        let soft_delete_cond = if include_soft_delete {
1259            self.build_soft_delete_condition()
1260        } else {
1261            None
1262        };
1263
1264        // P0-3:构造租户条件(若有且启用)— 参数化版本保留 (sql, value)
1265        let tenant_cond = self.build_tenant_condition();
1266
1267        // 无用户条件且无软删除条件且无租户条件且无 keyset 游标 → 空 WHERE
1268        if self.where_conditions.is_empty()
1269            && soft_delete_cond.is_none()
1270            && tenant_cond.is_none()
1271            && self.keyset_cursor.is_none()
1272        {
1273            return (String::new(), Vec::new());
1274        }
1275
1276        let mut params = Vec::new();
1277
1278        let mut conditions: Vec<String> = self
1279            .where_conditions
1280            .iter()
1281            .map(|cond| match cond {
1282                WhereCondition::And(c) => c.clone(),
1283                WhereCondition::Or(c) => format!("OR {}", c),
1284                // P0-2:参数化条件使用 `?` 占位符
1285                WhereCondition::Eq(f, v) => {
1286                    params.push(v.clone());
1287                    format!("{} = ?", self.dialect.quote(f))
1288                }
1289                WhereCondition::Ne(f, v) => {
1290                    params.push(v.clone());
1291                    format!("{} != ?", self.dialect.quote(f))
1292                }
1293                WhereCondition::Gt(f, v) => {
1294                    params.push(v.clone());
1295                    format!("{} > ?", self.dialect.quote(f))
1296                }
1297                WhereCondition::Ge(f, v) => {
1298                    params.push(v.clone());
1299                    format!("{} >= ?", self.dialect.quote(f))
1300                }
1301                WhereCondition::Lt(f, v) => {
1302                    params.push(v.clone());
1303                    format!("{} < ?", self.dialect.quote(f))
1304                }
1305                WhereCondition::Le(f, v) => {
1306                    params.push(v.clone());
1307                    format!("{} <= ?", self.dialect.quote(f))
1308                }
1309                WhereCondition::Like(f, v) => {
1310                    params.push(v.clone());
1311                    format!("{} LIKE ?", self.dialect.quote(f))
1312                }
1313                WhereCondition::OrEq(f, v) => {
1314                    params.push(v.clone());
1315                    format!("OR {} = ?", self.dialect.quote(f))
1316                }
1317                WhereCondition::OrNe(f, v) => {
1318                    params.push(v.clone());
1319                    format!("OR {} != ?", self.dialect.quote(f))
1320                }
1321                WhereCondition::OrGt(f, v) => {
1322                    params.push(v.clone());
1323                    format!("OR {} > ?", self.dialect.quote(f))
1324                }
1325                WhereCondition::OrGe(f, v) => {
1326                    params.push(v.clone());
1327                    format!("OR {} >= ?", self.dialect.quote(f))
1328                }
1329                WhereCondition::OrLt(f, v) => {
1330                    params.push(v.clone());
1331                    format!("OR {} < ?", self.dialect.quote(f))
1332                }
1333                WhereCondition::OrLe(f, v) => {
1334                    params.push(v.clone());
1335                    format!("OR {} <= ?", self.dialect.quote(f))
1336                }
1337                WhereCondition::OrLike(f, v) => {
1338                    params.push(v.clone());
1339                    format!("OR {} LIKE ?", self.dialect.quote(f))
1340                }
1341                WhereCondition::In(f, vals) => {
1342                    let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1343                    params.extend(vals.iter().cloned());
1344                    format!("{} IN ({})", self.dialect.quote(f), placeholders.join(", "))
1345                }
1346                WhereCondition::NotIn(f, vals) => {
1347                    let placeholders: Vec<&str> = vals.iter().map(|_| "?").collect();
1348                    params.extend(vals.iter().cloned());
1349                    format!(
1350                        "{} NOT IN ({})",
1351                        self.dialect.quote(f),
1352                        placeholders.join(", ")
1353                    )
1354                }
1355                WhereCondition::Between(f, start, end) => {
1356                    params.push(start.clone());
1357                    params.push(end.clone());
1358                    format!("{} BETWEEN ? AND ?", self.dialect.quote(f))
1359                }
1360                WhereCondition::NotBetween(f, start, end) => {
1361                    params.push(start.clone());
1362                    params.push(end.clone());
1363                    format!("{} NOT BETWEEN ? AND ?", self.dialect.quote(f))
1364                }
1365                WhereCondition::Null(f) => format!("{} IS NULL", self.dialect.quote(f)),
1366                WhereCondition::NotNull(f) => format!("{} IS NOT NULL", self.dialect.quote(f)),
1367                WhereCondition::Exists(s) => format!("EXISTS ({})", s),
1368                WhereCondition::NotExists(s) => format!("NOT EXISTS ({})", s),
1369            })
1370            .collect();
1371
1372        // P0-1:追加软删除条件(作为最后一个 AND 条件,无参数)
1373        if let Some(sd_cond) = soft_delete_cond {
1374            conditions.push(sd_cond);
1375        }
1376
1377        // P0-3:追加租户条件(在软删除之后,参数化绑定)
1378        if let Some((t_sql, t_value)) = tenant_cond {
1379            conditions.push(t_sql);
1380            params.push(t_value);
1381        }
1382
1383        // P2-5:追加 keyset 游标条件(在租户条件之后,参数化绑定)
1384        if let Some(ref cursor) = self.keyset_cursor {
1385            let op = match cursor.direction {
1386                KeysetDirection::After => ">",
1387                KeysetDirection::Before => "<",
1388            };
1389            conditions.push(format!("{} {} ?", self.dialect.quote(&cursor.field), op));
1390            params.push(cursor.value.clone());
1391        }
1392
1393        if conditions.is_empty() {
1394            return (String::new(), params);
1395        }
1396
1397        // OR 分组逻辑:与 build_where_clause 相同
1398        let mut groups: Vec<Vec<String>> = Vec::new();
1399        let mut current_group: Vec<String> = Vec::new();
1400        for cond in conditions.iter() {
1401            if let Some(stripped) = cond.strip_prefix("OR ") {
1402                current_group.push(stripped.to_string());
1403            } else {
1404                if !current_group.is_empty() {
1405                    groups.push(std::mem::take(&mut current_group));
1406                }
1407                current_group.push(cond.clone());
1408            }
1409        }
1410        if !current_group.is_empty() {
1411            groups.push(current_group);
1412        }
1413
1414        let group_strs: Vec<String> = groups
1415            .iter()
1416            .map(|g| {
1417                if g.len() == 1 {
1418                    g[0].clone()
1419                } else {
1420                    format!("({})", g.join(" OR "))
1421                }
1422            })
1423            .collect();
1424
1425        (format!(" WHERE {}", group_strs.join(" AND ")), params)
1426    }
1427
1428    /// 构建 SELECT SQL(参数绑定版本)。
1429    ///
1430    /// WHERE 子句中的值使用 `?` 占位符,值通过 `params` 返回。
1431    /// 适用于 `Connection::query_with_params()`。
1432    pub fn build_select_with_params(&self) -> (String, Vec<Value>) {
1433        let table = self
1434            .table
1435            .clone()
1436            .unwrap_or_else(|| M::table_name().to_string());
1437        let columns = if self.select_columns.is_empty() {
1438            "*".to_string()
1439        } else {
1440            self.select_columns.join(", ")
1441        };
1442
1443        let mut sql = format!("SELECT {} FROM {}", columns, self.dialect.quote(&table));
1444
1445        for join in &self.joins {
1446            match join {
1447                JoinClause::Inner(t, l, r) => {
1448                    sql.push_str(&format!(
1449                        " INNER JOIN {} ON {} = {}",
1450                        self.dialect.quote(t),
1451                        self.dialect.quote(l),
1452                        self.dialect.quote(r)
1453                    ));
1454                }
1455                JoinClause::Left(t, l, r) => {
1456                    sql.push_str(&format!(
1457                        " LEFT JOIN {} ON {} = {}",
1458                        self.dialect.quote(t),
1459                        self.dialect.quote(l),
1460                        self.dialect.quote(r)
1461                    ));
1462                }
1463                JoinClause::Right(t, l, r) => {
1464                    sql.push_str(&format!(
1465                        " RIGHT JOIN {} ON {} = {}",
1466                        self.dialect.quote(t),
1467                        self.dialect.quote(l),
1468                        self.dialect.quote(r)
1469                    ));
1470                }
1471                JoinClause::Cross(t, on) => {
1472                    sql.push_str(&format!(
1473                        " CROSS JOIN {} ON {}",
1474                        self.dialect.quote(t),
1475                        self.dialect.quote(on)
1476                    ));
1477                }
1478            }
1479        }
1480
1481        let mut params = Vec::new();
1482        // P0-1:build_where_clause_with_params 内部已处理软删除条件
1483        let (where_clause, where_params) = self.build_where_clause_with_params();
1484        if !where_clause.is_empty() {
1485            sql.push_str(&where_clause);
1486            params = where_params;
1487        }
1488
1489        if !self.group_by.is_empty() {
1490            let cols: Vec<String> = self
1491                .group_by
1492                .iter()
1493                .map(|c| self.dialect.quote(c))
1494                .collect();
1495            sql.push_str(" GROUP BY ");
1496            sql.push_str(&cols.join(", "));
1497        }
1498
1499        if !self.having_conditions.is_empty() {
1500            sql.push_str(" HAVING ");
1501            for (i, cond) in self.having_conditions.iter().enumerate() {
1502                if i > 0 {
1503                    sql.push_str(" AND ");
1504                }
1505                if let WhereCondition::And(c) = cond {
1506                    sql.push_str(c);
1507                }
1508            }
1509        }
1510
1511        if !self.order_by.is_empty() {
1512            let order_cols: Vec<String> = self
1513                .order_by
1514                .iter()
1515                .map(|o| {
1516                    let dir = match o.direction {
1517                        OrderDirection::Asc => " ASC",
1518                        OrderDirection::Desc => " DESC",
1519                    };
1520                    format!("{}{}", self.dialect.quote(&o.field), dir)
1521                })
1522                .collect();
1523            sql.push_str(" ORDER BY ");
1524            sql.push_str(&order_cols.join(", "));
1525        }
1526
1527        if let Some(limit) = self.limit_value {
1528            sql.push_str(&format!(" LIMIT {}", limit));
1529        }
1530        if let Some(offset) = self.offset_value {
1531            sql.push_str(&format!(" OFFSET {}", offset));
1532        }
1533
1534        (sql, params)
1535    }
1536
1537    /// 构建 INSERT SQL(参数绑定版本)。
1538    pub fn build_insert_with_params(
1539        &self,
1540        data: &std::collections::HashMap<String, Value>,
1541    ) -> (String, Vec<Value>) {
1542        let table = self
1543            .table
1544            .clone()
1545            .unwrap_or_else(|| M::table_name().to_string());
1546        if data.is_empty() {
1547            return (String::new(), Vec::new());
1548        }
1549
1550        let mut columns = Vec::with_capacity(data.len());
1551        let mut params = Vec::with_capacity(data.len());
1552        let placeholders: Vec<&str> = data.iter().map(|_| "?").collect();
1553        for (k, v) in data.iter() {
1554            columns.push(self.dialect.quote(k));
1555            params.push(v.clone());
1556        }
1557
1558        let sql = format!(
1559            "INSERT INTO {} ({}) VALUES ({})",
1560            self.dialect.quote(&table),
1561            columns.join(", "),
1562            placeholders.join(", ")
1563        );
1564        (sql, params)
1565    }
1566
1567    /// P2-6:构建批量 INSERT SQL(参数绑定版本)。
1568    ///
1569    /// 生成 `INSERT INTO t (c1, c2) VALUES (?, ?), (?, ?), ...` 形式的多行插入 SQL。
1570    /// 所有行的列必须一致(取第一行的列顺序);空行列表返回空 SQL。
1571    ///
1572    /// **L3 实现深度**:使用参数化占位符 `?`,所有值通过 `params` 绑定,杜绝 SQL 注入。
1573    pub fn build_batch_insert_with_params(
1574        &self,
1575        rows: &[std::collections::HashMap<String, Value>],
1576    ) -> (String, Vec<Value>) {
1577        let table = self
1578            .table
1579            .clone()
1580            .unwrap_or_else(|| M::table_name().to_string());
1581        if rows.is_empty() {
1582            return (String::new(), Vec::new());
1583        }
1584
1585        // 取第一行的列作为列顺序(所有行必须一致)
1586        let first_row = &rows[0];
1587        let columns: Vec<String> = first_row.keys().cloned().collect();
1588        let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
1589
1590        let mut params = Vec::with_capacity(rows.len() * columns.len());
1591        let mut value_groups: Vec<String> = Vec::with_capacity(rows.len());
1592        for row in rows {
1593            let placeholders: Vec<String> = columns
1594                .iter()
1595                .map(|col| match row.get(col) {
1596                    Some(v) => {
1597                        params.push(v.clone());
1598                        "?".to_string()
1599                    }
1600                    None => "NULL".to_string(),
1601                })
1602                .collect();
1603            value_groups.push(format!("({})", placeholders.join(", ")));
1604        }
1605
1606        let sql = format!(
1607            "INSERT INTO {} ({}) VALUES {}",
1608            self.dialect.quote(&table),
1609            quoted_columns.join(", "),
1610            value_groups.join(", ")
1611        );
1612        (sql, params)
1613    }
1614
1615    /// P1-3:按 `DEFAULT_BATCH_SIZE` 分片的批量插入。
1616    ///
1617    /// 当 `rows` 超过 `DEFAULT_BATCH_SIZE`(默认 1000)时,自动拆分为多条
1618    /// `INSERT INTO ... VALUES ...` 语句,避免单条 SQL 超过数据库参数上限
1619    ///(如 MySQL `max_allowed_packet`、PostgreSQL 参数数量限制)。
1620    ///
1621    /// 返回 `(sql, params)` 向量,调用方应依次执行每条 SQL(建议在事务中执行)。
1622    ///
1623    /// # 示例
1624    ///
1625    /// ```ignore
1626    /// let chunks = builder.build_batch_insert_chunked(&rows);
1627    /// for (sql, params) in chunks {
1628    ///     conn.execute(&sql, &params).await?;
1629    /// }
1630    /// ```
1631    pub fn build_batch_insert_chunked(
1632        &self,
1633        rows: &[std::collections::HashMap<String, Value>],
1634    ) -> Vec<(String, Vec<Value>)> {
1635        if rows.is_empty() {
1636            return Vec::new();
1637        }
1638        let table = self
1639            .table
1640            .clone()
1641            .unwrap_or_else(|| M::table_name().to_string());
1642        let first_row = &rows[0];
1643        let columns: Vec<String> = first_row.keys().cloned().collect();
1644        let quoted_columns: Vec<String> = columns.iter().map(|c| self.dialect.quote(c)).collect();
1645
1646        rows.chunks(DEFAULT_BATCH_SIZE)
1647            .map(|chunk| {
1648                let mut params = Vec::with_capacity(chunk.len() * columns.len());
1649                let mut value_groups: Vec<String> = Vec::with_capacity(chunk.len());
1650                for row in chunk {
1651                    let placeholders: Vec<String> = columns
1652                        .iter()
1653                        .map(|col| match row.get(col) {
1654                            Some(v) => {
1655                                params.push(v.clone());
1656                                "?".to_string()
1657                            }
1658                            None => "NULL".to_string(),
1659                        })
1660                        .collect();
1661                    value_groups.push(format!("({})", placeholders.join(", ")));
1662                }
1663                let sql = format!(
1664                    "INSERT INTO {} ({}) VALUES {}",
1665                    self.dialect.quote(&table),
1666                    quoted_columns.join(", "),
1667                    value_groups.join(", ")
1668                );
1669                (sql, params)
1670            })
1671            .collect()
1672    }
1673
1674    /// P2-6:构建批量 Upsert SQL(参数绑定版本)。
1675    ///
1676    /// 在 `build_batch_insert_with_params` 基础上追加冲突处理子句:
1677    /// - MySQL: `ON DUPLICATE KEY UPDATE col=VALUES(col), ...`
1678    /// - PostgreSQL/SQLite: `ON CONFLICT (conflict_cols) DO UPDATE SET col=EXCLUDED.col, ...`
1679    /// - Oracle/SQL Server/ClickHouse/Db2: 返回 `Err(DbError::InvalidInput)`(不支持)
1680    ///
1681    /// # 参数
1682    /// - `rows`: 批量数据行(所有行的列必须一致)
1683    /// - `conflict_columns`: 冲突检测列(主键/唯一键);MySQL 自动检测可传空
1684    /// - `update_columns`: 冲突时更新的列;空切片表示更新所有非冲突列
1685    ///
1686    /// # 返回
1687    /// - `Ok((sql, params))`: 生成的 SQL 和参数列表
1688    /// - `Err(DbError::InvalidInput)`: 方言不支持 upsert 或 rows 为空
1689    ///
1690    /// **L3 实现深度**:
1691    /// 1. SQL 下推:冲突处理由数据库执行,非内存判断
1692    /// 2. 参数化:所有值通过 `?` 占位符绑定,不拼接用户值
1693    /// 3. 实际执行:生成标准 INSERT...ON CONFLICT/ON DUPLICATE KEY SQL
1694    pub fn build_batch_upsert_with_params(
1695        &self,
1696        rows: &[std::collections::HashMap<String, Value>],
1697        conflict_columns: &[&str],
1698        update_columns: &[&str],
1699    ) -> Result<(String, Vec<Value>), sz_orm_model::DbError> {
1700        if rows.is_empty() {
1701            return Err(sz_orm_model::DbError::InvalidInput(
1702                "build_batch_upsert_with_params: rows cannot be empty".to_string(),
1703            ));
1704        }
1705
1706        // 构建批量 INSERT 部分
1707        let (insert_sql, params) = self.build_batch_insert_with_params(rows);
1708        if insert_sql.is_empty() {
1709            return Err(sz_orm_model::DbError::InvalidInput(
1710                "build_batch_upsert_with_params: failed to build INSERT part".to_string(),
1711            ));
1712        }
1713
1714        // 取所有列名(原始未 quote)
1715        let all_columns: Vec<String> = rows[0].keys().cloned().collect();
1716
1717        // 调用方言生成冲突处理子句
1718        let conflict_clause = self
1719            .dialect
1720            .build_upsert_on_conflict(conflict_columns, update_columns, &all_columns)
1721            .ok_or_else(|| {
1722                sz_orm_model::DbError::InvalidInput(format!(
1723                    "build_batch_upsert_with_params: dialect {:?} does not support upsert (ON CONFLICT / ON DUPLICATE KEY UPDATE). Consider using MERGE statement or individual upserts instead.",
1724                    self.dialect.db_type()
1725                ))
1726            })?;
1727
1728        let sql = format!("{} {}", insert_sql, conflict_clause);
1729        Ok((sql, params))
1730    }
1731
1732    /// 构建 UPDATE SQL(参数绑定版本)。
1733    /// 参数顺序:SET 参数在前,WHERE 参数在后。
1734    pub fn build_update_with_params(
1735        &self,
1736        data: &std::collections::HashMap<String, Value>,
1737    ) -> (String, Vec<Value>) {
1738        let table = self
1739            .table
1740            .clone()
1741            .unwrap_or_else(|| M::table_name().to_string());
1742        if data.is_empty() {
1743            return (String::new(), Vec::new());
1744        }
1745
1746        let mut set_clauses = Vec::with_capacity(data.len());
1747        let mut params = Vec::with_capacity(data.len());
1748        for (k, v) in data.iter() {
1749            set_clauses.push(format!("{} = ?", self.dialect.quote(k)));
1750            params.push(v.clone());
1751        }
1752
1753        let mut sql = format!(
1754            "UPDATE {} SET {}",
1755            self.dialect.quote(&table),
1756            set_clauses.join(", ")
1757        );
1758
1759        // P0-1:build_where_clause_with_params 内部已处理软删除条件
1760        let (where_clause, where_params) = self.build_where_clause_with_params();
1761        if !where_clause.is_empty() {
1762            sql.push_str(&where_clause);
1763            params.extend(where_params);
1764        }
1765
1766        (sql, params)
1767    }
1768
1769    /// P1-4:批量更新指定 ID 的记录。
1770    ///
1771    /// 生成单条 `UPDATE table SET col1=?, col2=? WHERE pk IN (?, ?, ...)` SQL,
1772    /// 替代逐条执行 N 次 `UPDATE ... WHERE pk = ?`,减少网络往返和数据库负载。
1773    ///
1774    /// # 参数
1775    ///
1776    /// - `data`:要更新的字段键值对
1777    /// - `ids`:目标记录的主键值列表(使用 `M::pk_name()` 作为 WHERE 列)
1778    ///
1779    /// # 返回值
1780    ///
1781    /// `(sql, params)` — SET 参数在前,IN 参数在后。
1782    ///
1783    /// # 示例
1784    ///
1785    /// ```ignore
1786    /// let (sql, params) = builder.batch_update_with_params(
1787    ///     &hashmap! { "status".to_string() => Value::I64(1) },
1788    ///     &[Value::I64(1), Value::I64(2), Value::I64(3)],
1789    /// );
1790    /// // UPDATE "users" SET "status" = ? WHERE "id" IN (?, ?, ?)
1791    /// ```
1792    pub fn batch_update_with_params(
1793        &self,
1794        data: &std::collections::HashMap<String, Value>,
1795        ids: &[Value],
1796    ) -> (String, Vec<Value>) {
1797        let table = self
1798            .table
1799            .clone()
1800            .unwrap_or_else(|| M::table_name().to_string());
1801        if data.is_empty() || ids.is_empty() {
1802            return (String::new(), Vec::new());
1803        }
1804
1805        let mut set_clauses = Vec::with_capacity(data.len());
1806        let mut params = Vec::with_capacity(data.len() + ids.len());
1807        for (k, v) in data.iter() {
1808            set_clauses.push(format!("{} = ?", self.dialect.quote(k)));
1809            params.push(v.clone());
1810        }
1811
1812        let pk = M::pk_name();
1813        let placeholders: Vec<String> = ids.iter().map(|_| "?".to_string()).collect();
1814        params.extend_from_slice(ids);
1815
1816        let sql = format!(
1817            "UPDATE {} SET {} WHERE {} IN ({})",
1818            self.dialect.quote(&table),
1819            set_clauses.join(", "),
1820            self.dialect.quote(pk),
1821            placeholders.join(", ")
1822        );
1823        (sql, params)
1824    }
1825
1826    /// 构建 DELETE SQL(参数绑定版本)。
1827    ///
1828    /// **P0-1 软删除集成(v1.3.0+)**:当 Model 启用软删除时,自动生成
1829    /// `UPDATE {table} SET {field} = NOW() WHERE ...` 而非 `DELETE FROM ...`。
1830    /// 参数列表为空(NOW() 由数据库填充)。
1831    pub fn build_delete_with_params(&self) -> (String, Vec<Value>) {
1832        let table = self
1833            .table
1834            .clone()
1835            .unwrap_or_else(|| M::table_name().to_string());
1836
1837        // P0-1:软删除启用时转为 UPDATE SET {field} = NOW()
1838        if let Some(field) = self.soft_delete_field() {
1839            let (where_clause, where_params) = self.build_where_clause_with_params();
1840            let sql = format!(
1841                "UPDATE {} SET {} = NOW(){}",
1842                self.dialect.quote(&table),
1843                self.dialect.quote(field),
1844                where_clause
1845            );
1846            return (sql, where_params);
1847        }
1848
1849        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1850        let mut params = Vec::new();
1851
1852        let (where_clause, where_params) = self.build_where_clause_with_params();
1853        if !where_clause.is_empty() {
1854            sql.push_str(&where_clause);
1855            params = where_params;
1856        }
1857
1858        (sql, params)
1859    }
1860
1861    /// 构建物理 DELETE SQL(参数绑定版本,绕过软删除)。
1862    ///
1863    /// 即使 Model 启用软删除,也生成 `DELETE FROM ...`,且不追加软删除过滤。
1864    pub fn build_force_delete_with_params(&self) -> (String, Vec<Value>) {
1865        let table = self
1866            .table
1867            .clone()
1868            .unwrap_or_else(|| M::table_name().to_string());
1869
1870        let mut sql = format!("DELETE FROM {}", self.dialect.quote(&table));
1871        let mut params = Vec::new();
1872
1873        // P0-1:物理删除不追加软删除过滤,使用 build_where_clause_with_params_no_soft_delete
1874        let (where_clause, where_params) = self.build_where_clause_with_params_options(false);
1875        if !where_clause.is_empty() {
1876            sql.push_str(&where_clause);
1877            params = where_params;
1878        }
1879
1880        (sql, params)
1881    }
1882
1883    pub fn build_count(&self) -> String {
1884        let table = self
1885            .table
1886            .clone()
1887            .unwrap_or_else(|| M::table_name().to_string());
1888
1889        let mut sql = format!(
1890            "SELECT COUNT(*) as total FROM {}",
1891            self.dialect.quote(&table)
1892        );
1893        sql.push_str(&self.build_where_clause());
1894        sql
1895    }
1896
1897    pub fn build_exists(&self) -> String {
1898        let table = self
1899            .table
1900            .clone()
1901            .unwrap_or_else(|| M::table_name().to_string());
1902
1903        let mut sql = format!("SELECT 1 FROM {}", self.dialect.quote(&table));
1904        sql.push_str(&self.build_where_clause());
1905        sql.push_str(" LIMIT 1");
1906        format!("SELECT EXISTS({})", sql)
1907    }
1908
1909    pub fn build_max(&self, field: &str) -> String {
1910        let table = self
1911            .table
1912            .clone()
1913            .unwrap_or_else(|| M::table_name().to_string());
1914
1915        let mut sql = format!(
1916            "SELECT MAX({}) as max_val FROM {}",
1917            self.dialect.quote(field),
1918            self.dialect.quote(&table)
1919        );
1920        sql.push_str(&self.build_where_clause());
1921        sql
1922    }
1923
1924    pub fn build_min(&self, field: &str) -> String {
1925        let table = self
1926            .table
1927            .clone()
1928            .unwrap_or_else(|| M::table_name().to_string());
1929
1930        let mut sql = format!(
1931            "SELECT MIN({}) as min_val FROM {}",
1932            self.dialect.quote(field),
1933            self.dialect.quote(&table)
1934        );
1935        sql.push_str(&self.build_where_clause());
1936        sql
1937    }
1938
1939    pub fn build_sum(&self, field: &str) -> String {
1940        let table = self
1941            .table
1942            .clone()
1943            .unwrap_or_else(|| M::table_name().to_string());
1944
1945        let mut sql = format!(
1946            "SELECT SUM({}) as sum_val FROM {}",
1947            self.dialect.quote(field),
1948            self.dialect.quote(&table)
1949        );
1950        sql.push_str(&self.build_where_clause());
1951        sql
1952    }
1953
1954    pub fn build_avg(&self, field: &str) -> String {
1955        let table = self
1956            .table
1957            .clone()
1958            .unwrap_or_else(|| M::table_name().to_string());
1959
1960        let mut sql = format!(
1961            "SELECT AVG({}) as avg_val FROM {}",
1962            self.dialect.quote(field),
1963            self.dialect.quote(&table)
1964        );
1965        sql.push_str(&self.build_where_clause());
1966        sql
1967    }
1968
1969    /// 校验生成的 SELECT SQL 语句
1970    /// 检查 SQL 语法、JOIN 列名、表名合法性
1971    pub fn validate(&self) -> Result<(), ValidationError> {
1972        let sql = self.build_select();
1973        let mut errors: Vec<sz_orm_sql_validator::SqlValidationError> = Vec::new();
1974
1975        if let Err(e) = sz_orm_sql_validator::validate_select(&sql) {
1976            errors.push(e);
1977        }
1978
1979        // 校验 JOIN 子句产生的 SQL 是否合法
1980        if !self.joins.is_empty() {
1981            for join in &self.joins {
1982                match join {
1983                    JoinClause::Inner(_, left, right)
1984                    | JoinClause::Left(_, left, right)
1985                    | JoinClause::Right(_, left, right) => {
1986                        if let Err(e) = sz_orm_sql_validator::validate_column_name(left) {
1987                            errors.push(e);
1988                        }
1989                        if let Err(e) = sz_orm_sql_validator::validate_column_name(right) {
1990                            errors.push(e);
1991                        }
1992                    }
1993                    _ => {}
1994                }
1995            }
1996        }
1997
1998        // 校验表名合法性
1999        let table = self
2000            .table
2001            .clone()
2002            .unwrap_or_else(|| M::table_name().to_string());
2003        if let Err(e) = sz_orm_sql_validator::validate_table_name(&table) {
2004            errors.push(e);
2005        }
2006
2007        if errors.is_empty() {
2008            Ok(())
2009        } else {
2010            Err(errors.into())
2011        }
2012    }
2013
2014    /// 校验生成的 INSERT SQL 语句
2015    /// 含空数据检测(EmptyInsertData 错误)
2016    pub fn validate_insert(
2017        &self,
2018        data: &std::collections::HashMap<String, Value>,
2019    ) -> Result<(), ValidationError> {
2020        let sql = self.build_insert(data);
2021        let mut errors: Vec<sz_orm_sql_validator::SqlValidationError> = Vec::new();
2022
2023        if sql.is_empty() {
2024            errors.push(sz_orm_sql_validator::SqlValidationError::EmptyInsertData);
2025            return Err(errors.into());
2026        }
2027
2028        if let Err(e) = sz_orm_sql_validator::validate_insert(&sql) {
2029            errors.push(e);
2030        }
2031
2032        if errors.is_empty() {
2033            Ok(())
2034        } else {
2035            Err(errors.into())
2036        }
2037    }
2038
2039    /// 校验生成的 UPDATE SQL 语句
2040    /// 含空数据检测(EmptyUpdateData 错误)
2041    pub fn validate_update(
2042        &self,
2043        data: &std::collections::HashMap<String, Value>,
2044    ) -> Result<(), ValidationError> {
2045        let sql = self.build_update(data);
2046        let mut errors: Vec<sz_orm_sql_validator::SqlValidationError> = Vec::new();
2047
2048        if sql.is_empty() {
2049            errors.push(sz_orm_sql_validator::SqlValidationError::EmptyUpdateData);
2050            return Err(errors.into());
2051        }
2052
2053        if let Err(e) = sz_orm_sql_validator::validate_update(&sql) {
2054            errors.push(e);
2055        }
2056
2057        if errors.is_empty() {
2058            Ok(())
2059        } else {
2060            Err(errors.into())
2061        }
2062    }
2063
2064    /// 校验生成的 DELETE SQL 语句
2065    pub fn validate_delete(&self) -> Result<(), ValidationError> {
2066        let sql = self.build_delete();
2067        let mut errors: Vec<sz_orm_sql_validator::SqlValidationError> = Vec::new();
2068
2069        if let Err(e) = sz_orm_sql_validator::validate_delete(&sql) {
2070            errors.push(e);
2071        }
2072
2073        if errors.is_empty() {
2074            Ok(())
2075        } else {
2076            Err(errors.into())
2077        }
2078    }
2079}
2080
2081impl<M: Model> fmt::Debug for QueryBuilder<M> {
2082    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2083        f.debug_struct("QueryBuilder")
2084            .field("table", &self.table)
2085            .field("select_columns", &self.select_columns)
2086            .field("where_conditions", &self.where_conditions.len())
2087            .field("limit", &self.limit_value)
2088            .finish()
2089    }
2090}
2091
2092#[cfg(test)]
2093#[allow(deprecated)] // 测试 deprecated 的 where_cond / or_where 方法仍正常工作
2094mod tests {
2095    use super::*;
2096    use sz_orm_model::get_dialect;
2097    use sz_orm_model::DbType;
2098
2099    struct TestModel;
2100    impl Model for TestModel {
2101        type PrimaryKey = i64;
2102
2103        fn table_name() -> &'static str {
2104            "test_models"
2105        }
2106
2107        fn pk(&self) -> Self::PrimaryKey {
2108            1
2109        }
2110
2111        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2112    }
2113
2114    #[test]
2115    fn test_query_builder_select() -> Result<(), sz_orm_model::DbError> {
2116        let dialect = get_dialect(DbType::MySQL)?;
2117        let builder = QueryBuilder::<TestModel>::new(dialect);
2118
2119        let sql = builder
2120            .table("users")
2121            .select(vec!["id", "name"])
2122            .build_select();
2123        assert!(sql.contains("SELECT id, name FROM"));
2124        assert!(sql.contains("`users`"));
2125        Ok(())
2126    }
2127
2128    #[test]
2129    fn test_query_builder_where() -> Result<(), sz_orm_model::DbError> {
2130        let dialect = get_dialect(DbType::MySQL)?;
2131        let builder = QueryBuilder::<TestModel>::new(dialect);
2132
2133        let sql = builder
2134            .table("users")
2135            .where_cond("status = 'active'")
2136            .where_cond("age > 18")
2137            .build_select();
2138
2139        assert!(sql.contains("WHERE"));
2140        assert!(sql.contains("status = 'active'"));
2141        assert!(sql.contains("age > 18"));
2142        Ok(())
2143    }
2144
2145    #[test]
2146    fn test_query_builder_order_by() -> Result<(), sz_orm_model::DbError> {
2147        let dialect = get_dialect(DbType::MySQL)?;
2148        let builder = QueryBuilder::<TestModel>::new(dialect);
2149
2150        let sql = builder
2151            .table("users")
2152            .order_by("created_at")
2153            .order_desc("id")
2154            .build_select();
2155
2156        assert!(sql.contains("ORDER BY"));
2157        assert!(sql.contains("`created_at` ASC"));
2158        assert!(sql.contains("`id` DESC"));
2159        Ok(())
2160    }
2161
2162    #[test]
2163    fn test_query_builder_limit_offset() -> Result<(), sz_orm_model::DbError> {
2164        let dialect = get_dialect(DbType::MySQL)?;
2165        let builder = QueryBuilder::<TestModel>::new(dialect);
2166
2167        let sql = builder.table("users").limit(10).offset(20).build_select();
2168
2169        assert!(sql.contains("LIMIT 10"));
2170        assert!(sql.contains("OFFSET 20"));
2171        Ok(())
2172    }
2173
2174    #[test]
2175    fn test_query_builder_page() -> Result<(), sz_orm_model::DbError> {
2176        let dialect = get_dialect(DbType::MySQL)?;
2177        let builder = QueryBuilder::<TestModel>::new(dialect);
2178
2179        let sql = builder.table("users").page(3, 20).build_select();
2180
2181        assert!(sql.contains("LIMIT 20"));
2182        assert!(sql.contains("OFFSET 40"));
2183        Ok(())
2184    }
2185
2186    #[test]
2187    fn test_query_builder_insert() -> Result<(), sz_orm_model::DbError> {
2188        let dialect = get_dialect(DbType::MySQL)?;
2189        let builder = QueryBuilder::<TestModel>::new(dialect);
2190
2191        let mut data = std::collections::HashMap::new();
2192        data.insert("name".to_string(), Value::String("test".to_string()));
2193        data.insert("age".to_string(), Value::I64(25));
2194
2195        let sql = builder.table("users").build_insert(&data);
2196
2197        assert!(sql.contains("INSERT INTO"));
2198        assert!(sql.contains("`name`"));
2199        assert!(sql.contains("'test'"));
2200        Ok(())
2201    }
2202
2203    #[test]
2204    fn test_query_builder_update() -> Result<(), sz_orm_model::DbError> {
2205        let dialect = get_dialect(DbType::MySQL)?;
2206        let builder = QueryBuilder::<TestModel>::new(dialect);
2207
2208        let mut data = std::collections::HashMap::new();
2209        data.insert("name".to_string(), Value::String("updated".to_string()));
2210
2211        let sql = builder
2212            .table("users")
2213            .where_cond("id = 1")
2214            .build_update(&data);
2215
2216        assert!(sql.contains("UPDATE"));
2217        assert!(sql.contains("`name` = 'updated'"));
2218        assert!(sql.contains("WHERE"));
2219        Ok(())
2220    }
2221
2222    #[test]
2223    fn test_query_builder_delete() -> Result<(), sz_orm_model::DbError> {
2224        let dialect = get_dialect(DbType::MySQL)?;
2225        let builder = QueryBuilder::<TestModel>::new(dialect);
2226
2227        let sql = builder.table("users").where_cond("id = 1").build_delete();
2228
2229        assert!(sql.contains("DELETE FROM"));
2230        assert!(sql.contains("WHERE"));
2231        Ok(())
2232    }
2233
2234    #[test]
2235    fn test_query_builder_count() -> Result<(), sz_orm_model::DbError> {
2236        let dialect = get_dialect(DbType::MySQL)?;
2237        let builder = QueryBuilder::<TestModel>::new(dialect);
2238
2239        let sql = builder.table("users").build_count();
2240
2241        assert!(sql.contains("SELECT COUNT(*)"));
2242        assert!(sql.contains("FROM"));
2243        Ok(())
2244    }
2245
2246    #[test]
2247    fn test_query_builder_where_in() -> Result<(), sz_orm_model::DbError> {
2248        let dialect = get_dialect(DbType::MySQL)?;
2249        let builder = QueryBuilder::<TestModel>::new(dialect);
2250
2251        let sql = builder
2252            .table("users")
2253            .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2254            .build_select();
2255
2256        assert!(sql.contains("IN ("));
2257        Ok(())
2258    }
2259
2260    #[test]
2261    fn test_query_builder_where_between() -> Result<(), sz_orm_model::DbError> {
2262        let dialect = get_dialect(DbType::MySQL)?;
2263        let builder = QueryBuilder::<TestModel>::new(dialect);
2264
2265        let sql = builder
2266            .table("users")
2267            .where_between("age", Value::I64(18), Value::I64(30))
2268            .build_select();
2269
2270        assert!(sql.contains("BETWEEN"));
2271        Ok(())
2272    }
2273
2274    #[test]
2275    fn test_query_builder_where_null() -> Result<(), sz_orm_model::DbError> {
2276        let dialect = get_dialect(DbType::MySQL)?;
2277        let builder = QueryBuilder::<TestModel>::new(dialect);
2278
2279        let sql = builder
2280            .table("users")
2281            .where_null("deleted_at")
2282            .build_select();
2283
2284        assert!(sql.contains("IS NULL"));
2285        Ok(())
2286    }
2287
2288    #[test]
2289    fn test_query_builder_join() -> Result<(), sz_orm_model::DbError> {
2290        let dialect = get_dialect(DbType::MySQL)?;
2291        let builder = QueryBuilder::<TestModel>::new(dialect);
2292
2293        let sql = builder
2294            .table("users")
2295            .join_inner("posts", "users.id", "posts.user_id")
2296            .build_select();
2297
2298        assert!(sql.contains("INNER JOIN"));
2299        assert!(sql.contains("`posts`"));
2300        Ok(())
2301    }
2302
2303    #[test]
2304    fn test_query_builder_group_by() -> Result<(), sz_orm_model::DbError> {
2305        let dialect = get_dialect(DbType::MySQL)?;
2306        let builder = QueryBuilder::<TestModel>::new(dialect);
2307
2308        let sql = builder.table("users").group_by("status").build_select();
2309
2310        assert!(sql.contains("GROUP BY"));
2311        assert!(sql.contains("`status`"));
2312        Ok(())
2313    }
2314
2315    #[test]
2316    fn test_query_builder_max() -> Result<(), sz_orm_model::DbError> {
2317        let dialect = get_dialect(DbType::MySQL)?;
2318        let builder = QueryBuilder::<TestModel>::new(dialect);
2319
2320        let sql = builder.table("users").build_max("score");
2321
2322        assert!(sql.contains("MAX("));
2323        assert!(sql.contains("`score`"));
2324        Ok(())
2325    }
2326
2327    #[test]
2328    fn test_query_builder_min() -> Result<(), sz_orm_model::DbError> {
2329        let dialect = get_dialect(DbType::MySQL)?;
2330        let builder = QueryBuilder::<TestModel>::new(dialect);
2331
2332        let sql = builder.table("users").build_min("price");
2333
2334        assert!(sql.contains("MIN("));
2335        assert!(sql.contains("`price`"));
2336        Ok(())
2337    }
2338
2339    #[test]
2340    fn test_query_builder_sum() -> Result<(), sz_orm_model::DbError> {
2341        let dialect = get_dialect(DbType::MySQL)?;
2342        let builder = QueryBuilder::<TestModel>::new(dialect);
2343
2344        let sql = builder.table("orders").build_sum("amount");
2345
2346        assert!(sql.contains("SUM("));
2347        assert!(sql.contains("`amount`"));
2348        Ok(())
2349    }
2350
2351    #[test]
2352    fn test_query_builder_avg() -> Result<(), sz_orm_model::DbError> {
2353        let dialect = get_dialect(DbType::MySQL)?;
2354        let builder = QueryBuilder::<TestModel>::new(dialect);
2355
2356        let sql = builder.table("scores").build_avg("value");
2357
2358        assert!(sql.contains("AVG("));
2359        assert!(sql.contains("`value`"));
2360        Ok(())
2361    }
2362
2363    #[test]
2364    fn test_validator_select() -> Result<(), sz_orm_model::DbError> {
2365        let dialect = get_dialect(DbType::MySQL)?;
2366        let builder = QueryBuilder::<TestModel>::new(dialect);
2367
2368        let result = builder.table("users").select(vec!["id", "name"]).validate();
2369        assert!(result.is_ok());
2370        Ok(())
2371    }
2372
2373    #[test]
2374    fn test_validator_select_with_join() -> Result<(), sz_orm_model::DbError> {
2375        let dialect = get_dialect(DbType::MySQL)?;
2376        let builder = QueryBuilder::<TestModel>::new(dialect);
2377
2378        let result = builder
2379            .table("users")
2380            .join_inner("posts", "users.id", "posts.user_id")
2381            .validate();
2382        assert!(result.is_ok());
2383        Ok(())
2384    }
2385
2386    #[test]
2387    fn test_validator_insert() -> Result<(), sz_orm_model::DbError> {
2388        let dialect = get_dialect(DbType::MySQL)?;
2389        let builder = QueryBuilder::<TestModel>::new(dialect);
2390
2391        let mut data = std::collections::HashMap::new();
2392        data.insert("name".to_string(), Value::String("test".to_string()));
2393
2394        let result = builder.table("users").validate_insert(&data);
2395        assert!(result.is_ok());
2396        Ok(())
2397    }
2398
2399    #[test]
2400    fn test_validator_insert_empty_data() -> Result<(), sz_orm_model::DbError> {
2401        let dialect = get_dialect(DbType::MySQL)?;
2402        let builder = QueryBuilder::<TestModel>::new(dialect);
2403
2404        let data = std::collections::HashMap::new();
2405        let result = builder.table("users").validate_insert(&data);
2406        assert!(result.is_err());
2407        Ok(())
2408    }
2409
2410    #[test]
2411    fn test_validator_update() -> Result<(), sz_orm_model::DbError> {
2412        let dialect = get_dialect(DbType::MySQL)?;
2413        let builder = QueryBuilder::<TestModel>::new(dialect);
2414
2415        let mut data = std::collections::HashMap::new();
2416        data.insert("name".to_string(), Value::String("updated".to_string()));
2417
2418        let result = builder.table("users").validate_update(&data);
2419        assert!(result.is_ok());
2420        Ok(())
2421    }
2422
2423    #[test]
2424    fn test_validator_update_empty_data() -> Result<(), sz_orm_model::DbError> {
2425        let dialect = get_dialect(DbType::MySQL)?;
2426        let builder = QueryBuilder::<TestModel>::new(dialect);
2427
2428        let data = std::collections::HashMap::new();
2429        let result = builder.table("users").validate_update(&data);
2430        assert!(result.is_err());
2431        Ok(())
2432    }
2433
2434    #[test]
2435    fn test_validator_delete() -> Result<(), sz_orm_model::DbError> {
2436        let dialect = get_dialect(DbType::MySQL)?;
2437        let builder = QueryBuilder::<TestModel>::new(dialect);
2438
2439        let result = builder
2440            .table("users")
2441            .where_cond("id = 1")
2442            .validate_delete();
2443        assert!(result.is_ok());
2444        Ok(())
2445    }
2446
2447    #[test]
2448    fn test_validator_delete_no_where() -> Result<(), sz_orm_model::DbError> {
2449        let dialect = get_dialect(DbType::MySQL)?;
2450        let builder = QueryBuilder::<TestModel>::new(dialect);
2451
2452        // DELETE without WHERE still produces valid SQL (just no filter)
2453        let result = builder.table("users").validate_delete();
2454        assert!(result.is_ok());
2455        Ok(())
2456    }
2457
2458    // ==================== M-3 select_quoted 测试 ====================
2459
2460    #[test]
2461    fn test_m3_select_quoted_valid_columns() -> Result<(), sz_orm_model::DbError> {
2462        let dialect = get_dialect(DbType::MySQL)?;
2463        let builder = QueryBuilder::<TestModel>::new(dialect);
2464        let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2465        let sql = builder.build_select();
2466        // 应自动 quote 列名
2467        assert!(sql.contains("SELECT `id`, `name` FROM"));
2468        assert!(sql.contains("`users`"));
2469        Ok(())
2470    }
2471
2472    #[test]
2473    fn test_m3_select_quoted_rejects_sql_injection() -> Result<(), sz_orm_model::DbError> {
2474        let dialect = get_dialect(DbType::MySQL)?;
2475        let builder = QueryBuilder::<TestModel>::new(dialect);
2476
2477        // SQL 注入尝试:分号 + DROP TABLE
2478        let result = builder
2479            .table("users")
2480            .select_quoted(vec!["id; DROP TABLE users"]);
2481        assert!(result.is_err());
2482
2483        // 含引号
2484        let dialect = get_dialect(DbType::MySQL)?;
2485        let builder = QueryBuilder::<TestModel>::new(dialect);
2486        let result = builder.table("users").select_quoted(vec!["name'"]);
2487        assert!(result.is_err());
2488
2489        // 数字开头
2490        let dialect = get_dialect(DbType::MySQL)?;
2491        let builder = QueryBuilder::<TestModel>::new(dialect);
2492        let result = builder.table("users").select_quoted(vec!["1col"]);
2493        assert!(result.is_err());
2494
2495        // 含空格
2496        let dialect = get_dialect(DbType::MySQL)?;
2497        let builder = QueryBuilder::<TestModel>::new(dialect);
2498        let result = builder.table("users").select_quoted(vec!["col name"]);
2499        assert!(result.is_err());
2500        Ok(())
2501    }
2502
2503    #[test]
2504    fn test_m3_select_quoted_postgresql_dialect() -> Result<(), sz_orm_model::DbError> {
2505        let dialect = get_dialect(DbType::PostgreSQL)?;
2506        let builder = QueryBuilder::<TestModel>::new(dialect);
2507        let builder = builder.table("users").select_quoted(vec!["id", "name"])?;
2508        let sql = builder.build_select();
2509        // PostgreSQL 使用双引号
2510        assert!(sql.contains("SELECT \"id\", \"name\" FROM"));
2511        assert!(sql.contains("\"users\""));
2512        Ok(())
2513    }
2514
2515    // ==================== P0-1 软删除集成行为测试 ====================
2516
2517    /// 软删除测试模型:实现 soft_delete_field() 返回 "deleted_at"
2518    struct SoftDeleteModel;
2519    impl Model for SoftDeleteModel {
2520        type PrimaryKey = i64;
2521
2522        fn table_name() -> &'static str {
2523            "soft_users"
2524        }
2525
2526        fn pk(&self) -> Self::PrimaryKey {
2527            1
2528        }
2529
2530        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2531
2532        fn soft_delete_field() -> Option<&'static str> {
2533            Some("deleted_at")
2534        }
2535    }
2536
2537    /// 行为级测试 L3-1:软删除模型 build_select 自动追加 `WHERE deleted_at IS NULL`
2538    ///
2539    /// 用户视角:查询软删除模型时,自动过滤已删除记录,无需手动写条件。
2540    #[test]
2541    fn test_p01_soft_delete_select_auto_filter() -> Result<(), sz_orm_model::DbError> {
2542        let dialect = get_dialect(DbType::MySQL)?;
2543        let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2544        let sql = builder.table("soft_users").build_select();
2545        // 必须自动追加软删除过滤
2546        assert!(
2547            sql.contains("`deleted_at` IS NULL"),
2548            "软删除模型 SELECT 必须自动追加 `deleted_at` IS NULL,实际: {}",
2549            sql
2550        );
2551        Ok(())
2552    }
2553
2554    /// 行为级测试 L3-2:软删除模型 + 用户 WHERE 条件,软删除条件以 AND 追加
2555    #[test]
2556    fn test_p01_soft_delete_select_with_user_where() -> Result<(), sz_orm_model::DbError> {
2557        let dialect = get_dialect(DbType::MySQL)?;
2558        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2559            .table("soft_users")
2560            .where_eq("status", Value::String("active".into()))
2561            .build_select();
2562        // 用户条件 + 软删除条件 同时存在
2563        assert!(sql.contains("`status` = "), "用户条件应保留: {}", sql);
2564        assert!(
2565            sql.contains("`deleted_at` IS NULL"),
2566            "软删除条件应自动追加: {}",
2567            sql
2568        );
2569        Ok(())
2570    }
2571
2572    /// 行为级测试 L3-3:without_soft_delete() 临时禁用软删除过滤
2573    ///
2574    /// 用户视角:管理员查询已删除记录时,可禁用自动过滤。
2575    #[test]
2576    fn test_p01_soft_delete_without_soft_delete() -> Result<(), sz_orm_model::DbError> {
2577        let dialect = get_dialect(DbType::MySQL)?;
2578        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2579            .table("soft_users")
2580            .without_soft_delete()
2581            .build_select();
2582        // 不应包含软删除过滤
2583        assert!(
2584            !sql.contains("`deleted_at` IS NULL"),
2585            "without_soft_delete 应禁用过滤,实际: {}",
2586            sql
2587        );
2588        // 也应无 WHERE 子句(因为用户未提供任何条件)
2589        assert!(
2590            !sql.contains("WHERE"),
2591            "无用户条件 + 禁用软删除应无 WHERE 子句: {}",
2592            sql
2593        );
2594        Ok(())
2595    }
2596
2597    /// 行为级测试 L3-4:软删除模型 build_delete 自动转为 UPDATE
2598    ///
2599    /// 用户视角:调用 delete 实际是软删除 UPDATE,不是物理 DELETE。
2600    #[test]
2601    fn test_p01_soft_delete_delete_becomes_update() -> Result<(), sz_orm_model::DbError> {
2602        let dialect = get_dialect(DbType::MySQL)?;
2603        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2604            .table("soft_users")
2605            .where_eq("id", Value::I64(42))
2606            .build_delete();
2607        // 应生成 UPDATE 而非 DELETE
2608        assert!(
2609            sql.starts_with("UPDATE"),
2610            "软删除模型的 build_delete 应生成 UPDATE,实际: {}",
2611            sql
2612        );
2613        assert!(
2614            !sql.contains("DELETE FROM"),
2615            "不应生成 DELETE FROM: {}",
2616            sql
2617        );
2618        assert!(
2619            sql.contains("`deleted_at` = NOW()"),
2620            "应设置 deleted_at = NOW(): {}",
2621            sql
2622        );
2623        // 软删除条件应自动追加,防止更新已删除记录
2624        assert!(
2625            sql.contains("`deleted_at` IS NULL"),
2626            "软删除 UPDATE 应追加 deleted_at IS NULL 防止重复删除: {}",
2627            sql
2628        );
2629        Ok(())
2630    }
2631
2632    /// 行为级测试 L3-5:build_force_delete 物理删除,不追加软删除过滤
2633    ///
2634    /// 用户视角:管理员强制清除时使用 build_force_delete。
2635    #[test]
2636    fn test_p01_soft_delete_force_delete() -> Result<(), sz_orm_model::DbError> {
2637        let dialect = get_dialect(DbType::MySQL)?;
2638        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2639            .table("soft_users")
2640            .where_eq("id", Value::I64(99))
2641            .build_force_delete();
2642        // 应生成 DELETE FROM
2643        assert!(
2644            sql.starts_with("DELETE FROM"),
2645            "build_force_delete 应生成 DELETE FROM,实际: {}",
2646            sql
2647        );
2648        // 不应追加软删除过滤
2649        assert!(
2650            !sql.contains("`deleted_at` IS NULL"),
2651            "物理删除不应追加软删除过滤: {}",
2652            sql
2653        );
2654        Ok(())
2655    }
2656
2657    /// 行为级测试 L3-6:build_select_with_params 自动追加软删除条件(参数化版本)
2658    #[test]
2659    fn test_p01_soft_delete_select_with_params() -> Result<(), sz_orm_model::DbError> {
2660        let dialect = get_dialect(DbType::MySQL)?;
2661        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2662            .table("soft_users")
2663            .where_eq("id", Value::I64(1))
2664            .build_select_with_params();
2665        assert!(
2666            sql.contains("`deleted_at` IS NULL"),
2667            "参数化版本也应自动追加软删除: {}",
2668            sql
2669        );
2670        assert_eq!(params.len(), 1, "参数应为 1 个(用户 where_eq 的值)");
2671        assert_eq!(params[0], Value::I64(1));
2672        Ok(())
2673    }
2674
2675    /// 行为级测试 L3-7:build_delete_with_params 自动转为 UPDATE
2676    #[test]
2677    fn test_p01_soft_delete_delete_with_params_becomes_update() -> Result<(), sz_orm_model::DbError>
2678    {
2679        let dialect = get_dialect(DbType::MySQL)?;
2680        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2681            .table("soft_users")
2682            .where_eq("id", Value::I64(7))
2683            .build_delete_with_params();
2684        assert!(sql.starts_with("UPDATE"), "应生成 UPDATE: {}", sql);
2685        assert!(
2686            sql.contains("`deleted_at` = NOW()"),
2687            "应设置 NOW(): {}",
2688            sql
2689        );
2690        assert_eq!(params.len(), 1, "参数应为 1 个(WHERE 的值)");
2691        Ok(())
2692    }
2693
2694    /// 行为级测试 L3-8:build_force_delete_with_params 物理删除(参数化版本)
2695    #[test]
2696    fn test_p01_soft_delete_force_delete_with_params() -> Result<(), sz_orm_model::DbError> {
2697        let dialect = get_dialect(DbType::MySQL)?;
2698        let (sql, params) = QueryBuilder::<SoftDeleteModel>::new(dialect)
2699            .table("soft_users")
2700            .where_eq("id", Value::I64(11))
2701            .build_force_delete_with_params();
2702        assert!(sql.starts_with("DELETE FROM"), "应生成 DELETE: {}", sql);
2703        assert!(
2704            !sql.contains("`deleted_at` IS NULL"),
2705            "不应追加软删除过滤: {}",
2706            sql
2707        );
2708        assert_eq!(params.len(), 1);
2709        Ok(())
2710    }
2711
2712    /// 行为级测试 L3-9:非软删除模型 TestModel 不追加软删除条件
2713    ///
2714    /// 用户视角:未启用软删除的模型行为不变。
2715    #[test]
2716    fn test_p01_non_soft_delete_model_unchanged() -> Result<(), sz_orm_model::DbError> {
2717        let dialect = get_dialect(DbType::MySQL)?;
2718        let sql = QueryBuilder::<TestModel>::new(dialect)
2719            .table("users")
2720            .where_eq("id", Value::I64(1))
2721            .build_select();
2722        assert!(
2723            !sql.contains("deleted_at"),
2724            "非软删除模型不应追加 deleted_at: {}",
2725            sql
2726        );
2727        // build_delete 仍生成 DELETE FROM
2728        let dialect = get_dialect(DbType::MySQL)?;
2729        let del_sql = QueryBuilder::<TestModel>::new(dialect)
2730            .table("users")
2731            .where_eq("id", Value::I64(1))
2732            .build_delete();
2733        assert!(
2734            del_sql.starts_with("DELETE FROM"),
2735            "非软删除模型 build_delete 应生成 DELETE: {}",
2736            del_sql
2737        );
2738        Ok(())
2739    }
2740
2741    /// 行为级测试 L3-10:build_count 也应自动追加软删除条件
2742    #[test]
2743    fn test_p01_soft_delete_count_auto_filter() -> Result<(), sz_orm_model::DbError> {
2744        let dialect = get_dialect(DbType::MySQL)?;
2745        let sql = QueryBuilder::<SoftDeleteModel>::new(dialect)
2746            .table("soft_users")
2747            .build_count();
2748        assert!(
2749            sql.contains("`deleted_at` IS NULL"),
2750            "build_count 也应追加软删除过滤: {}",
2751            sql
2752        );
2753        Ok(())
2754    }
2755
2756    // ==================== P0-2 参数化查询注入防护测试 ====================
2757
2758    /// 行为级测试 L3-11:where_eq 使用 `?` 占位符,值收集到 params
2759    ///
2760    /// 用户视角:参数化查询杜绝 SQL 注入。
2761    #[test]
2762    fn test_p02_where_eq_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2763        let dialect = get_dialect(DbType::MySQL)?;
2764        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2765            .table("users")
2766            .where_eq("name", Value::String("alice".into()))
2767            .build_select_with_params();
2768        // SQL 中应含 `?` 占位符,不应内嵌值
2769        assert!(sql.contains("`name` = ?"), "应使用 ? 占位符: {}", sql);
2770        assert!(!sql.contains("'alice'"), "不应内嵌值到 SQL: {}", sql);
2771        assert_eq!(params.len(), 1);
2772        assert_eq!(params[0], Value::String("alice".into()));
2773        Ok(())
2774    }
2775
2776    /// 行为级测试 L3-12:where_like 使用 `?` 占位符
2777    #[test]
2778    fn test_p02_where_like_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2779        let dialect = get_dialect(DbType::MySQL)?;
2780        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2781            .table("users")
2782            .where_like("name", Value::String("%alice%".into()))
2783            .build_select_with_params();
2784        assert!(sql.contains("`name` LIKE ?"), "应使用 LIKE ?: {}", sql);
2785        assert!(!sql.contains("%alice%"), "不应内嵌 pattern: {}", sql);
2786        assert_eq!(params.len(), 1);
2787        Ok(())
2788    }
2789
2790    /// 行为级测试 L3-12a:where_ne 使用 `?` 占位符
2791    ///
2792    /// 验证 P0-2 参数化 API where_ne 生成 `field != ?` 且值不内嵌。
2793    #[test]
2794    fn test_p02_where_ne_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2795        let dialect = get_dialect(DbType::MySQL)?;
2796        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2797            .table("users")
2798            .where_ne("status", Value::I64(0))
2799            .build_select_with_params();
2800        assert!(sql.contains("`status` != ?"), "应使用 != ?: {}", sql);
2801        assert!(!sql.contains("!= 0"), "不应内嵌值: {}", sql);
2802        assert_eq!(params.len(), 1);
2803        assert_eq!(params[0], Value::I64(0));
2804        Ok(())
2805    }
2806
2807    /// 行为级测试 L3-12b:where_ge 使用 `?` 占位符
2808    ///
2809    /// 验证 P0-2 参数化 API where_ge 生成 `field >= ?` 且值不内嵌。
2810    #[test]
2811    fn test_p02_where_ge_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2812        let dialect = get_dialect(DbType::MySQL)?;
2813        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2814            .table("users")
2815            .where_ge("age", Value::I64(18))
2816            .build_select_with_params();
2817        assert!(sql.contains("`age` >= ?"), "应使用 >= ?: {}", sql);
2818        assert!(!sql.contains(">= 18"), "不应内嵌值: {}", sql);
2819        assert_eq!(params.len(), 1);
2820        assert_eq!(params[0], Value::I64(18));
2821        Ok(())
2822    }
2823
2824    /// 行为级测试 L3-12c:where_lt 使用 `?` 占位符
2825    ///
2826    /// 验证 P0-2 参数化 API where_lt 生成 `field < ?` 且值不内嵌。
2827    #[test]
2828    fn test_p02_where_lt_uses_placeholder() -> Result<(), sz_orm_model::DbError> {
2829        let dialect = get_dialect(DbType::MySQL)?;
2830        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2831            .table("users")
2832            .where_lt("score", Value::F64(60.0))
2833            .build_select_with_params();
2834        assert!(sql.contains("`score` < ?"), "应使用 < ?: {}", sql);
2835        assert!(!sql.contains("< 60"), "不应内嵌值: {}", sql);
2836        assert_eq!(params.len(), 1);
2837        assert_eq!(params[0], Value::F64(60.0));
2838        Ok(())
2839    }
2840
2841    /// 行为级测试 L3-13:注入攻击防护 - 值含 SQL 关键字也不会被解释执行
2842    ///
2843    /// 用户视角:即使用户输入 `'; DROP TABLE users; --`,也不会造成注入。
2844    #[test]
2845    fn test_p02_injection_protection_drop_table() -> Result<(), sz_orm_model::DbError> {
2846        let dialect = get_dialect(DbType::MySQL)?;
2847        let evil_input = "'; DROP TABLE users; --".to_string();
2848        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2849            .table("users")
2850            .where_eq("name", Value::String(evil_input.clone()))
2851            .build_select_with_params();
2852        // SQL 中不应出现 DROP TABLE
2853        assert!(!sql.contains("DROP TABLE"), "SQL 注入未防护: {}", sql);
2854        // 整个恶意字符串应作为单一参数传递
2855        assert_eq!(params.len(), 1);
2856        assert_eq!(params[0], Value::String(evil_input));
2857        // SQL 中只有 1 个 `?`
2858        assert_eq!(sql.matches('?').count(), 1);
2859        Ok(())
2860    }
2861
2862    /// 行为级测试 L3-14:注入攻击防护 - OR 1=1 经典攻击
2863    #[test]
2864    fn test_p02_injection_protection_or_one_equals_one() -> Result<(), sz_orm_model::DbError> {
2865        let dialect = get_dialect(DbType::MySQL)?;
2866        let evil = "' OR '1'='1".to_string();
2867        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2868            .table("users")
2869            .where_eq("name", Value::String(evil.clone()))
2870            .build_select_with_params();
2871        assert!(!sql.contains("OR '1'='1'"), "OR 1=1 注入未防护: {}", sql);
2872        assert_eq!(params.len(), 1);
2873        assert_eq!(params[0], Value::String(evil));
2874        Ok(())
2875    }
2876
2877    /// 行为级测试 L3-15:多参数顺序正确(WHERE a = ? AND b = ?)
2878    #[test]
2879    fn test_p02_multiple_params_order() -> Result<(), sz_orm_model::DbError> {
2880        let dialect = get_dialect(DbType::MySQL)?;
2881        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2882            .table("users")
2883            .where_eq("name", Value::String("alice".into()))
2884            .where_gt("age", Value::I64(18))
2885            .where_le("score", Value::F64(99.5))
2886            .build_select_with_params();
2887        assert_eq!(sql.matches('?').count(), 3, "应有 3 个占位符: {}", sql);
2888        assert_eq!(params.len(), 3);
2889        // 参数顺序应与 WHERE 子句出现顺序一致
2890        assert_eq!(params[0], Value::String("alice".into()));
2891        assert_eq!(params[1], Value::I64(18));
2892        assert_eq!(params[2], Value::F64(99.5));
2893        Ok(())
2894    }
2895
2896    /// 行为级测试 L3-16:where_in 参数化
2897    #[test]
2898    fn test_p02_where_in_uses_placeholders() -> Result<(), sz_orm_model::DbError> {
2899        let dialect = get_dialect(DbType::MySQL)?;
2900        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2901            .table("users")
2902            .where_in("id", vec![Value::I64(1), Value::I64(2), Value::I64(3)])
2903            .build_select_with_params();
2904        assert!(
2905            sql.contains("`id` IN (?, ?, ?)"),
2906            "应使用 3 个占位符: {}",
2907            sql
2908        );
2909        assert_eq!(params.len(), 3);
2910        Ok(())
2911    }
2912
2913    /// 行为级测试 L3-17:where_between 参数化
2914    #[test]
2915    fn test_p02_where_between_uses_placeholders() -> Result<(), sz_orm_model::DbError> {
2916        let dialect = get_dialect(DbType::MySQL)?;
2917        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2918            .table("users")
2919            .where_between("age", Value::I64(18), Value::I64(65))
2920            .build_select_with_params();
2921        assert!(
2922            sql.contains("`age` BETWEEN ? AND ?"),
2923            "应使用 2 个占位符: {}",
2924            sql
2925        );
2926        assert_eq!(params.len(), 2);
2927        assert_eq!(params[0], Value::I64(18));
2928        assert_eq!(params[1], Value::I64(65));
2929        Ok(())
2930    }
2931
2932    /// 行为级测试 L3-18:UPDATE 参数化版本 - SET 参数在前,WHERE 参数在后
2933    #[test]
2934    fn test_p02_update_params_order_set_before_where() -> Result<(), sz_orm_model::DbError> {
2935        let dialect = get_dialect(DbType::MySQL)?;
2936        let mut data = std::collections::HashMap::new();
2937        data.insert("name".to_string(), Value::String("bob".into()));
2938        data.insert("age".to_string(), Value::I64(30));
2939        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
2940            .table("users")
2941            .where_eq("id", Value::I64(99))
2942            .build_update_with_params(&data);
2943        // SET 子句应有 2 个占位符,WHERE 子句 1 个,共 3 个
2944        assert_eq!(sql.matches('?').count(), 3, "应有 3 个 ?: {}", sql);
2945        assert_eq!(params.len(), 3);
2946        // 前 2 个为 SET 参数,最后 1 个为 WHERE 参数
2947        // 注意:HashMap 迭代顺序未指定,仅校验 WHERE 参数在最后
2948        assert_eq!(params[2], Value::I64(99));
2949        Ok(())
2950    }
2951
2952    /// 行为级测试 L3-19:build_where_clause(无参数版本)参数化条件内嵌值
2953    ///
2954    /// 验证无参数版本(build_select)对参数化条件的处理:直接内嵌转义值。
2955    #[test]
2956    fn test_p02_build_where_clause_inlines_value() -> Result<(), sz_orm_model::DbError> {
2957        let dialect = get_dialect(DbType::MySQL)?;
2958        let sql = QueryBuilder::<TestModel>::new(dialect)
2959            .table("users")
2960            .where_eq("name", Value::String("alice".into()))
2961            .build_select();
2962        // 无参数版本应内嵌值(依赖 to_param_with_dialect 转义)
2963        assert!(
2964            sql.contains("`name` = "),
2965            "无参数版本应含 WHERE 条件: {}",
2966            sql
2967        );
2968        // 不应含 `?`(无参数版本)
2969        assert!(
2970            !sql.contains("`name` = ?"),
2971            "无参数版本不应使用 ? 占位符: {}",
2972            sql
2973        );
2974        Ok(())
2975    }
2976
2977    /// 行为级测试 L3-20:is_soft_delete_disabled 反映状态
2978    #[test]
2979    fn test_p01_is_soft_delete_disabled_flag() -> Result<(), sz_orm_model::DbError> {
2980        let dialect = get_dialect(DbType::MySQL)?;
2981        let builder = QueryBuilder::<SoftDeleteModel>::new(dialect);
2982        assert!(!builder.is_soft_delete_disabled(), "默认应启用软删除过滤");
2983        let builder =
2984            QueryBuilder::<SoftDeleteModel>::new(get_dialect(DbType::MySQL)?).without_soft_delete();
2985        assert!(
2986            builder.is_soft_delete_disabled(),
2987            "without_soft_delete 后应反映禁用状态"
2988        );
2989        Ok(())
2990    }
2991
2992    // ==================== P0-3 多租户自动过滤行为测试 ====================
2993
2994    /// 多租户测试模型:实现 tenant_field() 返回 "tenant_id"
2995    struct TenantModel;
2996    impl Model for TenantModel {
2997        type PrimaryKey = i64;
2998
2999        fn table_name() -> &'static str {
3000            "orders"
3001        }
3002
3003        fn pk(&self) -> Self::PrimaryKey {
3004            1
3005        }
3006
3007        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3008
3009        fn tenant_field() -> Option<&'static str> {
3010            Some("tenant_id")
3011        }
3012    }
3013
3014    /// 同时实现软删除 + 多租户的模型
3015    struct SoftDeleteAndTenantModel;
3016    impl Model for SoftDeleteAndTenantModel {
3017        type PrimaryKey = i64;
3018
3019        fn table_name() -> &'static str {
3020            "documents"
3021        }
3022
3023        fn pk(&self) -> Self::PrimaryKey {
3024            1
3025        }
3026
3027        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
3028
3029        fn soft_delete_field() -> Option<&'static str> {
3030            Some("deleted_at")
3031        }
3032
3033        fn tenant_field() -> Option<&'static str> {
3034            Some("tenant_id")
3035        }
3036    }
3037
3038    /// 行为级测试 L3-21:多租户模型 + with_tenant_id 自动追加 WHERE tenant_id = ?
3039    ///
3040    /// 用户视角:设置租户 ID 后,查询自动过滤当前租户数据。
3041    #[test]
3042    fn test_p03_tenant_select_auto_filter() -> Result<(), sz_orm_model::DbError> {
3043        let dialect = get_dialect(DbType::MySQL)?;
3044        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3045            .table("orders")
3046            .with_tenant_id(42)
3047            .build_select_with_params();
3048        assert!(
3049            sql.contains("`tenant_id` = ?"),
3050            "多租户模型应自动追加 tenant_id = ?: {}",
3051            sql
3052        );
3053        assert_eq!(params.len(), 1, "应有 1 个参数(tenant_id 值)");
3054        assert_eq!(params[0], Value::I64(42));
3055        Ok(())
3056    }
3057
3058    /// 行为级测试 L3-22:多租户模型 + 用户 WHERE 条件 + 租户条件
3059    #[test]
3060    fn test_p03_tenant_select_with_user_where() -> Result<(), sz_orm_model::DbError> {
3061        let dialect = get_dialect(DbType::MySQL)?;
3062        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3063            .table("orders")
3064            .with_tenant_id(7)
3065            .where_eq("status", Value::String("active".into()))
3066            .build_select_with_params();
3067        assert!(sql.contains("`status` = ?"), "用户条件应保留: {}", sql);
3068        assert!(
3069            sql.contains("`tenant_id` = ?"),
3070            "租户条件应自动追加: {}",
3071            sql
3072        );
3073        assert_eq!(params.len(), 2, "应有 2 个参数");
3074        // 第 1 个为用户 where_eq 的值,第 2 个为 tenant_id
3075        assert_eq!(params[0], Value::String("active".into()));
3076        assert_eq!(params[1], Value::I64(7));
3077        Ok(())
3078    }
3079
3080    /// 行为级测试 L3-23:without_tenant() 临时禁用租户过滤
3081    ///
3082    /// 用户视角:管理员跨租户查询时禁用自动过滤。
3083    #[test]
3084    fn test_p03_tenant_without_tenant() -> Result<(), sz_orm_model::DbError> {
3085        let dialect = get_dialect(DbType::MySQL)?;
3086        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3087            .table("orders")
3088            .with_tenant_id(42)
3089            .without_tenant()
3090            .build_select_with_params();
3091        assert!(
3092            !sql.contains("`tenant_id` = ?"),
3093            "without_tenant 应禁用过滤: {}",
3094            sql
3095        );
3096        assert_eq!(params.len(), 0, "不应有租户参数");
3097        Ok(())
3098    }
3099
3100    /// 行为级测试 L3-24:多租户模型 build_delete 自动追加租户条件
3101    ///
3102    /// 用户视角:删除操作自动限定在当前租户,防止跨租户删除。
3103    #[test]
3104    fn test_p03_tenant_delete_auto_filter() -> Result<(), sz_orm_model::DbError> {
3105        let dialect = get_dialect(DbType::MySQL)?;
3106        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3107            .table("orders")
3108            .with_tenant_id(99)
3109            .where_eq("id", Value::I64(1))
3110            .build_delete_with_params();
3111        assert!(
3112            sql.contains("`tenant_id` = ?"),
3113            "删除应自动追加租户条件: {}",
3114            sql
3115        );
3116        // 2 个参数:where_eq(id=1) + tenant_id=99
3117        assert_eq!(params.len(), 2);
3118        assert_eq!(params[0], Value::I64(1));
3119        assert_eq!(params[1], Value::I64(99));
3120        Ok(())
3121    }
3122
3123    /// 行为级测试 L3-25:多租户模型 build_update 自动追加租户条件
3124    #[test]
3125    fn test_p03_tenant_update_auto_filter() -> Result<(), sz_orm_model::DbError> {
3126        let dialect = get_dialect(DbType::MySQL)?;
3127        let mut data = std::collections::HashMap::new();
3128        data.insert("status".to_string(), Value::String("shipped".into()));
3129        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3130            .table("orders")
3131            .with_tenant_id(5)
3132            .where_eq("id", Value::I64(10))
3133            .build_update_with_params(&data);
3134        assert!(
3135            sql.contains("`tenant_id` = ?"),
3136            "更新应自动追加租户条件: {}",
3137            sql
3138        );
3139        // 3 个参数:SET status + WHERE id + tenant_id
3140        assert_eq!(params.len(), 3);
3141        // 最后一个应为 tenant_id
3142        assert_eq!(params[2], Value::I64(5));
3143        Ok(())
3144    }
3145
3146    /// 行为级测试 L3-26:多租户模型 build_count 自动追加租户条件
3147    #[test]
3148    fn test_p03_tenant_count_auto_filter() -> Result<(), sz_orm_model::DbError> {
3149        let dialect = get_dialect(DbType::MySQL)?;
3150        let sql = QueryBuilder::<TenantModel>::new(dialect)
3151            .table("orders")
3152            .with_tenant_id(42)
3153            .build_count();
3154        assert!(
3155            sql.contains("`tenant_id` = 42"),
3156            "build_count 应追加租户条件(无参数版本内嵌值): {}",
3157            sql
3158        );
3159        Ok(())
3160    }
3161
3162    /// 行为级测试 L3-27:非多租户模型 TestModel 不追加租户条件
3163    ///
3164    /// 用户视角:未启用多租户的模型行为不变。
3165    #[test]
3166    fn test_p03_non_tenant_model_unchanged() -> Result<(), sz_orm_model::DbError> {
3167        let dialect = get_dialect(DbType::MySQL)?;
3168        // 即使设置了 with_tenant_id,非多租户模型也不应追加
3169        let (sql, params) = QueryBuilder::<TestModel>::new(dialect)
3170            .table("users")
3171            .with_tenant_id(42)
3172            .build_select_with_params();
3173        assert!(
3174            !sql.contains("tenant_id"),
3175            "非多租户模型不应追加 tenant_id: {}",
3176            sql
3177        );
3178        assert_eq!(params.len(), 0);
3179        Ok(())
3180    }
3181
3182    /// 行为级测试 L3-28:多租户模型未设置 tenant_id 时不追加条件
3183    ///
3184    /// 用户视角:未设置租户 ID 时,查询不追加租户过滤(允许跨租户,需调用方保证安全)。
3185    #[test]
3186    fn test_p03_tenant_no_id_no_filter() -> Result<(), sz_orm_model::DbError> {
3187        let dialect = get_dialect(DbType::MySQL)?;
3188        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3189            .table("orders")
3190            .build_select_with_params();
3191        assert!(
3192            !sql.contains("tenant_id"),
3193            "未设置 tenant_id 时不应追加过滤: {}",
3194            sql
3195        );
3196        assert_eq!(params.len(), 0);
3197        Ok(())
3198    }
3199
3200    /// 行为级测试 L3-29:软删除 + 多租户组合,两个条件同时追加
3201    ///
3202    /// 用户视角:同时启用软删除和多租户时,查询自动追加两个条件。
3203    #[test]
3204    fn test_p03_soft_delete_and_tenant_combined() -> Result<(), sz_orm_model::DbError> {
3205        let dialect = get_dialect(DbType::MySQL)?;
3206        let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3207            .table("documents")
3208            .with_tenant_id(100)
3209            .where_eq("title", Value::String("report".into()))
3210            .build_select_with_params();
3211        // 软删除条件
3212        assert!(
3213            sql.contains("`deleted_at` IS NULL"),
3214            "应追加软删除条件: {}",
3215            sql
3216        );
3217        // 租户条件
3218        assert!(sql.contains("`tenant_id` = ?"), "应追加租户条件: {}", sql);
3219        // 用户条件
3220        assert!(sql.contains("`title` = ?"), "用户条件应保留: {}", sql);
3221        // 2 个参数:where_eq(title) + tenant_id(软删除 IS NULL 无参数)
3222        assert_eq!(params.len(), 2);
3223        assert_eq!(params[0], Value::String("report".into()));
3224        assert_eq!(params[1], Value::I64(100));
3225        Ok(())
3226    }
3227
3228    /// 行为级测试 L3-30:without_tenant + without_soft_delete 同时禁用
3229    #[test]
3230    fn test_p03_without_tenant_and_soft_delete() -> Result<(), sz_orm_model::DbError> {
3231        let dialect = get_dialect(DbType::MySQL)?;
3232        let (sql, params) = QueryBuilder::<SoftDeleteAndTenantModel>::new(dialect)
3233            .table("documents")
3234            .with_tenant_id(100)
3235            .without_tenant()
3236            .without_soft_delete()
3237            .build_select_with_params();
3238        assert!(
3239            !sql.contains("`deleted_at` IS NULL"),
3240            "应禁用软删除: {}",
3241            sql
3242        );
3243        assert!(!sql.contains("`tenant_id` = ?"), "应禁用租户: {}", sql);
3244        assert_eq!(params.len(), 0);
3245        Ok(())
3246    }
3247
3248    /// 行为级测试 L3-31:is_tenant_disabled 反映状态
3249    #[test]
3250    fn test_p03_is_tenant_disabled_flag() -> Result<(), sz_orm_model::DbError> {
3251        let dialect = get_dialect(DbType::MySQL)?;
3252        let builder = QueryBuilder::<TenantModel>::new(dialect);
3253        assert!(!builder.is_tenant_disabled(), "默认应启用租户过滤");
3254        let builder = QueryBuilder::<TenantModel>::new(get_dialect(DbType::MySQL)?)
3255            .with_tenant_id(1)
3256            .without_tenant();
3257        assert!(
3258            builder.is_tenant_disabled(),
3259            "without_tenant 后应反映禁用状态"
3260        );
3261        Ok(())
3262    }
3263
3264    /// 行为级测试 L3-32:build_force_delete 保留租户条件(防止跨租户物理删除)
3265    ///
3266    /// 用户视角:物理删除也应受租户隔离约束,跨租户操作需显式 without_tenant()。
3267    #[test]
3268    fn test_p03_tenant_force_delete_keeps_tenant_filter() -> Result<(), sz_orm_model::DbError> {
3269        let dialect = get_dialect(DbType::MySQL)?;
3270        let (sql, params) = QueryBuilder::<TenantModel>::new(dialect)
3271            .table("orders")
3272            .with_tenant_id(42)
3273            .where_eq("id", Value::I64(999))
3274            .build_force_delete_with_params();
3275        // 物理删除不应追加软删除(TenantModel 未实现软删除,无影响)
3276        // 但应保留租户条件
3277        assert!(
3278            sql.contains("`tenant_id` = ?"),
3279            "物理删除应保留租户条件: {}",
3280            sql
3281        );
3282        assert_eq!(params.len(), 2);
3283        assert_eq!(params[0], Value::I64(999));
3284        assert_eq!(params[1], Value::I64(42));
3285        Ok(())
3286    }
3287
3288    // ===== P1-3: build_batch_insert_chunked =====
3289
3290    #[test]
3291    fn test_batch_insert_chunked_single_chunk() {
3292        let dialect = get_dialect(DbType::MySQL).unwrap();
3293        let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3294        let rows = vec![
3295            [("id".to_string(), Value::I64(1)), ("name".to_string(), Value::String("A".to_string()))].into_iter().collect(),
3296            [("id".to_string(), Value::I64(2)), ("name".to_string(), Value::String("B".to_string()))].into_iter().collect(),
3297        ];
3298        let chunks = builder.build_batch_insert_chunked(&rows);
3299        assert_eq!(chunks.len(), 1);
3300        let (sql, params) = &chunks[0];
3301        assert!(sql.contains("INSERT INTO"));
3302        assert_eq!(params.len(), 4);
3303    }
3304
3305    #[test]
3306    fn test_batch_insert_chunked_multi_chunk() {
3307        let dialect = get_dialect(DbType::MySQL).unwrap();
3308        let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3309        // 生成 DEFAULT_BATCH_SIZE + 1 行,强制拆分为 2 个 chunk
3310        let n = DEFAULT_BATCH_SIZE + 1;
3311        let rows: Vec<std::collections::HashMap<String, Value>> = (0..n)
3312            .map(|i| [("id".to_string(), Value::I64(i as i64))].into_iter().collect())
3313            .collect();
3314        let chunks = builder.build_batch_insert_chunked(&rows);
3315        assert_eq!(chunks.len(), 2);
3316        // 第一个 chunk 有 DEFAULT_BATCH_SIZE 行
3317        assert!(chunks[0].0.contains("INSERT INTO"));
3318        assert_eq!(chunks[0].1.len(), DEFAULT_BATCH_SIZE);
3319        // 第二个 chunk 有 1 行
3320        assert_eq!(chunks[1].1.len(), 1);
3321    }
3322
3323    #[test]
3324    fn test_batch_insert_chunked_empty() {
3325        let dialect = get_dialect(DbType::MySQL).unwrap();
3326        let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3327        let chunks = builder.build_batch_insert_chunked(&[]);
3328        assert!(chunks.is_empty());
3329    }
3330
3331    // ===== P1-4: batch_update_with_params =====
3332
3333    #[test]
3334    fn test_batch_update_with_params() {
3335        let dialect = get_dialect(DbType::MySQL).unwrap();
3336        let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3337        let mut data = std::collections::HashMap::new();
3338        data.insert("status".to_string(), Value::I64(1));
3339        let ids = vec![Value::I64(1), Value::I64(2), Value::I64(3)];
3340        let (sql, params) = builder.batch_update_with_params(&data, &ids);
3341        assert!(sql.contains("UPDATE"));
3342        assert!(sql.contains("SET"));
3343        assert!(sql.contains("WHERE"));
3344        assert!(sql.contains("IN"), "sql should contain IN clause: {}", sql);
3345        assert_eq!(params.len(), 4); // 1 SET param + 3 IN params
3346        assert_eq!(params[0], Value::I64(1)); // SET status=1
3347        assert_eq!(params[1], Value::I64(1)); // WHERE id IN (1
3348        assert_eq!(params[2], Value::I64(2)); // , 2
3349        assert_eq!(params[3], Value::I64(3)); // , 3)
3350    }
3351
3352    #[test]
3353    fn test_batch_update_with_params_empty() {
3354        let dialect = get_dialect(DbType::MySQL).unwrap();
3355        let builder = QueryBuilder::<TestModel>::new(dialect).table("users");
3356        let mut data = std::collections::HashMap::new();
3357        data.insert("status".to_string(), Value::I64(1));
3358        assert_eq!(builder.batch_update_with_params(&data, &[]).0, "");
3359        assert_eq!(builder.batch_update_with_params(&std::collections::HashMap::new(), &[Value::I64(1)]).0, "");
3360    }
3361}