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