Skip to main content

sz_orm_query_builder/
lib.rs

1//! # SZ-ORM QueryBuilder — 独立 SQL 构造器(sea-query 风格)
2//!
3//! 一个不绑定 Model 的纯 SQL 构造器,可独立编译、独立发布到 crates.io。
4//!
5//! 设计灵感来自 [sea-query](https://crates.io/crates/sea-query):
6//! - 与 ORM 解耦:不依赖 `Model` trait,纯 SQL 构造
7//! - 多方言支持:通过 [`DbType`] 适配 MySQL/PostgreSQL/SQLite/Oracle
8//! - 链式 API:所有方法返回 `Self`
9//! - 零运行时开销:构造过程零数据库连接
10//!
11//! # 快速入门
12//!
13//! ```rust
14//! use sz_orm_core::DbType;
15//! use sz_orm_query_builder::{Query, SelectQuery};
16//!
17//! // SELECT
18//! let sql = Query::select()
19//!     .column("id")
20//!     .column("name")
21//!     .from("users")
22//!     .where_clause("age > 18")
23//!     .order_by("id", true)
24//!     .limit(10)
25//!     .build(DbType::MySQL);
26//! assert!(sql.contains("SELECT"));
27//! assert!(sql.contains("FROM `users`"));
28//!
29//! // INSERT
30//! let sql = Query::insert()
31//!     .into_table("users")
32//!     .value("name", "'Alice'")
33//!     .value("age", "30")
34//!     .build();
35//! assert!(sql.contains("INSERT INTO `users`"));
36//!
37//! // UPDATE
38//! let sql = Query::update()
39//!     .table("users")
40//!     .set("name", "'Bob'")
41//!     .where_clause("id = 1")
42//!     .build();
43//! assert!(sql.contains("UPDATE `users`"));
44//!
45//! // DELETE
46//! let sql = Query::delete()
47//!     .from_table("users")
48//!     .where_clause("id = 1")
49//!     .build();
50//! assert!(sql.contains("DELETE FROM"));
51//! ```
52//!
53//! # 与 sz-orm-core::QueryBuilder 的区别
54//!
55//! | 特性 | `sz-orm-core::QueryBuilder<M>` | sz-orm-query-builder::Query |
56//! |------|------------------------------|----------------------------|
57//! | 绑定 Model | 是(`<M: Model>`) | 否 |
58//! | 类型安全 | 编译期表/列校验 | 运行时字符串 |
59//! | 适用场景 | ORM 完整流程 | 纯 SQL 构造、动态查询 |
60//! | 依赖 | sz-orm-core 全部 | 仅 dialect 模块 |
61//! | 独立发布 | 否 | 是 |
62
63use sz_orm_core::{DbType, Value};
64
65// ============================================================================
66// BuiltQuery — 参数化查询构造结果
67// ============================================================================
68
69/// 参数化查询构造结果
70///
71/// 包含带 `?` 占位符的 SQL 字符串与按序绑定的参数列表。
72///
73/// # 安全性(P0 修复:SQL 注入防护)
74///
75/// 通过参数化绑定,将用户输入与 SQL 结构分离,从根源上杜绝 SQL 注入。
76/// 调用方应使用 `build_with_params()` 而非字符串拼接的 `where_clause()`,
77/// 尤其是当 WHERE 条件值来自不可信输入时。
78#[derive(Debug, Clone, Default)]
79pub struct BuiltQuery {
80    /// 含 `?` 占位符的 SQL 语句
81    pub sql: String,
82    /// 按出现顺序绑定的参数列表
83    pub params: Vec<Value>,
84}
85
86impl BuiltQuery {
87    /// 取出 SQL 与参数两部分
88    pub fn into_parts(self) -> (String, Vec<Value>) {
89        (self.sql, self.params)
90    }
91}
92
93/// 参数化 WHERE 条件(内部表示)
94///
95/// 相比 `wheres: Vec<String>` 的原始字符串拼接,本结构分离了 SQL 模板(含 `?`)
96/// 与参数值,从根本上避免 SQL 注入。
97///
98/// # 方言感知引用(v1.0.1 修复)
99///
100/// `column` 存储未引用的列名,在 `build_with_params` 时通过 `dialect.quote()`
101/// 按目标方言引用(PostgreSQL 双引号、MySQL 反引号),避免调用阶段提前用
102/// 反引号硬编码导致 PostgreSQL 方言测试失败。
103#[derive(Debug, Clone)]
104enum ParamWhere {
105    /// `AND <expr>` 条件
106    And {
107        /// 未引用的列名(空字符串表示平凡表达式如 `1 = 0`)
108        column: String,
109        /// 运算符 + 占位符部分(如 ` = ?`、` IN (?, ?)`、`1 = 0`)
110        op: String,
111        /// 按出现顺序绑定的参数值
112        values: Vec<Value>,
113    },
114    /// `OR <expr>` 条件
115    Or {
116        column: String,
117        op: String,
118        values: Vec<Value>,
119    },
120}
121
122/// 用反引号包裹标识符并转义内部反引号(MySQL 标准:` → ``)
123///
124/// # 安全性(门禁 9 修复)
125///
126/// 不转义的反引号包裹允许恶意标识符通过 ` 逃逸注入。本函数将标识符内
127/// 的反引号加倍(MySQL 标准转义),确保拼接后的 SQL 不会被恶意标识符突破。
128///
129/// 支持带点号的限定标识符: `u.id` → `u`.`id`
130fn quote_ident(s: &str) -> String {
131    s.split('.')
132        .map(|part| format!("`{}`", part.replace('`', "``")))
133        .collect::<Vec<_>>()
134        .join(".")
135}
136
137/// 按目标方言引用列名,支持带点号的限定标识符
138///
139/// 与 [`sz_orm_core::Dialect::quote`] 的区别:本函数会先按 `.` 拆分限定标识符
140/// (如 `t.id`),对每段分别应用方言引用后再用 `.` 连接,生成 `t`.`id`
141/// (MySQL)或 `"t"."id"`(PostgreSQL/SQLite)。直接使用 `dialect.quote()` 会
142/// 把整个 `t.id` 当作单个标识符引用,导致 `t`.`id` 形态的测试断言失败。
143///
144/// # 安全性
145///
146/// 每段标识符由 `dialect.quote()` 负责转义内部引号(MySQL `` ` `` → ` `` ` ``,
147/// PostgreSQL `"` → `""`),防止标识符逃逸注入。
148fn quote_column_dialect(dialect: &dyn sz_orm_core::Dialect, column: &str) -> String {
149    column
150        .split('.')
151        .map(|part| dialect.quote(part))
152        .collect::<Vec<_>>()
153        .join(".")
154}
155
156/// 校验 WHERE 条件字符串,拒绝明显的 SQL 注入模式
157///
158/// v0.2.2 修复 C-6:公开 `where_clause(condition: &str)` 接受任意字符串,存在 SQL 注入风险。
159/// 本函数检测高危模式(分号+SQL 关键字、行注释、块注释),拒绝明显恶意输入。
160///
161/// # 检测模式
162///
163/// - `;` 后跟 SQL 关键字(DROP/DELETE/UPDATE/INSERT/ALTER/TRUNCATE/EXEC/CREATE/GRANT/REVOKE)
164/// - `--` 行注释序列
165/// - `/*` 块注释起始
166/// - `*/` 块注释结束
167///
168/// # 注意
169///
170/// 此校验是基础防线,不能替代参数化查询。复杂 WHERE 条件应使用参数化 API。
171fn check_where_injection(condition: &str) {
172    let upper = condition.to_uppercase();
173    const SQL_KEYWORDS: &[&str] = &[
174        "DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "TRUNCATE", "EXEC", "CREATE", "GRANT",
175        "REVOKE",
176    ];
177    for kw in SQL_KEYWORDS {
178        let pattern1 = format!(";{}", kw);
179        let pattern2 = format!("; {}", kw);
180        if upper.contains(&pattern1) || upper.contains(&pattern2) {
181            panic!(
182                "SQL injection detected in where_clause: semicolon followed by {} keyword: {:?}",
183                kw, condition
184            );
185        }
186    }
187    if condition.contains("--") {
188        panic!(
189            "SQL injection detected in where_clause: line comment '--' not allowed: {:?}",
190            condition
191        );
192    }
193    if condition.contains("/*") || condition.contains("*/") {
194        panic!(
195            "SQL injection detected in where_clause: block comment '/*' or '*/' not allowed: {:?}",
196            condition
197        );
198    }
199}
200
201/// 查询构造器入口
202///
203/// # 渐进 Deprecation 说明(v3.5.0)
204///
205/// 自 v3.5.0 起,`sz-orm-query-builder` 进入渐进 deprecation 阶段。
206/// 请评估 [docs/query-builder-guide.md](../../../docs/query-builder-guide.md)
207/// 选择合适方案:
208///
209/// - **标准 CRUD**:推荐迁移到 `sz_orm_core::QueryBuilder<M>`(编译期类型安全)
210/// - **复杂 SQL(UNION/CTE/窗口函数)**:可继续使用本 crate(暂不删除)
211///
212/// 本 crate 在 v3.5.0 **不立即删除**,API 完全兼容,仅添加 `#[deprecated]` 警告。
213/// 计划在 v4.0.0 评估是否合并到 `sz-orm-core` 或保留独立。
214#[deprecated(
215    since = "3.5.0",
216    note = "v3.5.0: 请评估 docs/query-builder-guide.md 选择合适方案。标准 CRUD 推荐迁移到 sz_orm_core::QueryBuilder<M>"
217)]
218pub struct Query;
219
220#[allow(deprecated)]
221impl Query {
222    /// 创建 SELECT 查询
223    #[deprecated(
224        since = "3.5.0",
225        note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::select()。详见 docs/query-builder-guide.md"
226    )]
227    pub fn select() -> SelectQuery {
228        SelectQuery::new()
229    }
230
231    /// 创建 INSERT 查询
232    #[deprecated(
233        since = "3.5.0",
234        note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::insert()。详见 docs/query-builder-guide.md"
235    )]
236    pub fn insert() -> InsertQuery {
237        InsertQuery::new()
238    }
239
240    /// 创建 UPDATE 查询
241    #[deprecated(
242        since = "3.5.0",
243        note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::update()。详见 docs/query-builder-guide.md"
244    )]
245    pub fn update() -> UpdateQuery {
246        UpdateQuery::new()
247    }
248
249    /// 创建 DELETE 查询
250    #[deprecated(
251        since = "3.5.0",
252        note = "v3.5.0: 推荐迁移到 sz_orm_core::QueryBuilder<M>::delete()。详见 docs/query-builder-guide.md"
253    )]
254    pub fn delete() -> DeleteQuery {
255        DeleteQuery::new()
256    }
257}
258
259/// 参数化 JOIN ON 条件(P2 修复 #68:JOIN 注入风险)
260///
261/// 与原始字符串 `joins: Vec<String>` 相比,本结构分离了 SQL 模板与参数值,
262/// 防止用户输入通过 `format!("u.id = {}", user_input)` 注入到 ON 条件中。
263#[derive(Debug, Clone)]
264enum JoinOn {
265    /// 原始字符串(向后兼容 `inner_join(table, on_str)`)
266    Raw(String),
267    /// 列对列等值连接:`left_column = right_column`(无参数,纯标识符,已转义)
268    ColumnEq {
269        left_column: String,
270        right_column: String,
271    },
272    /// 参数化条件:`left_column op ?` with Value
273    Param {
274        left_column: String,
275        op: String,
276        values: Vec<Value>,
277    },
278}
279
280/// JOIN 子句(P2 修复 #68)
281#[derive(Debug, Clone)]
282struct JoinClause {
283    /// JOIN 类型关键字(INNER JOIN / LEFT JOIN / RIGHT JOIN)
284    join_type: &'static str,
285    /// 表名部分(已通过 `quote_join_table` 转义)
286    table: String,
287    /// ON 条件列表(支持多个条件,AND 连接)
288    on: Vec<JoinOn>,
289}
290
291/// SELECT 查询构造器
292#[derive(Debug, Clone, Default)]
293pub struct SelectQuery {
294    columns: Vec<String>,
295    from_table: Option<String>,
296    /// FROM 子查询:`(子查询 SQL, 别名)`。与 `from_table` 互斥,后调用者覆盖前者。
297    from_subquery: Option<(String, String)>,
298    /// 结构化 JOIN 子句(统一存储原始字符串与参数化 ON 条件,P2 修复 #68)
299    ///
300    /// 旧的 `inner_join(table, on_str)` API 通过 `JoinOn::Raw(on_str)` 入栈,
301    /// 与新的 `inner_join_on` / `inner_join_param` 共用同一渲染路径,
302    /// 避免双轨数据结构带来的渲染顺序与死代码问题。
303    join_clauses: Vec<JoinClause>,
304    wheres: Vec<String>,
305    /// 参数化 WHERE 条件(P0 修复:与 `wheres` 互不干扰,渲染时先 `wheres` 后 `param_wheres`)
306    param_wheres: Vec<ParamWhere>,
307    order_by: Vec<String>,
308    group_by: Vec<String>,
309    having: Vec<String>,
310    limit: Option<u64>,
311    offset: Option<u64>,
312    distinct: bool,
313    /// CTE(Common Table Expression)子句列表:(名称, 子查询 SQL, 是否递归)
314    ctes: Vec<(String, String, bool)>,
315    /// 窗口函数列:原始表达式(如 `ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC)`)
316    window_columns: Vec<String>,
317    /// FOR UPDATE 锁提示
318    for_update: bool,
319    /// FOR UPDATE 的列限定(NOWAIT / SKIP LOCKED 等)
320    for_update_options: Option<String>,
321}
322
323impl SelectQuery {
324    /// 创建空的 SELECT 查询
325    pub fn new() -> Self {
326        Self::default()
327    }
328
329    /// 设置 DISTINCT
330    pub fn distinct(mut self) -> Self {
331        self.distinct = true;
332        self
333    }
334
335    /// 添加列
336    pub fn column(mut self, name: &str) -> Self {
337        self.columns.push(name.to_string());
338        self
339    }
340
341    /// 添加多个列
342    pub fn columns(mut self, names: &[&str]) -> Self {
343        for n in names {
344            self.columns.push(n.to_string());
345        }
346        self
347    }
348
349    /// 添加 `*` 列
350    pub fn all_columns(self) -> Self {
351        self.column("*")
352    }
353
354    /// 设置 FROM 表
355    pub fn from(mut self, table: &str) -> Self {
356        self.from_table = Some(table.to_string());
357        // 与 from_subquery 互斥,后调用者覆盖前者
358        self.from_subquery = None;
359        self
360    }
361
362    /// 设置 FROM 子查询:`FROM (<subquery_sql>) AS <alias>`
363    ///
364    /// 与 [`from`](Self::from) 互斥,后调用者覆盖前者。
365    /// 子查询 SQL 由调用方负责构造(可由另一个 `SelectQuery::build` 生成),
366    /// 别名经 `dialect.quote()` 转义,防止标识符逃逸。
367    ///
368    /// # 示例
369    ///
370    /// ```
371    /// use sz_orm_core::DbType;
372    /// use sz_orm_query_builder::Query;
373    ///
374    /// let inner = Query::select()
375    ///     .column("id")
376    ///     .column("amount")
377    ///     .from("orders")
378    ///     .build(DbType::MySQL);
379    /// let sql = Query::select()
380    ///     .column("id")
381    ///     .from_subquery(&inner, "t")
382    ///     .build(DbType::MySQL);
383    /// assert!(sql.contains("FROM (SELECT `id`, `amount` FROM `orders`) AS `t`"));
384    /// ```
385    pub fn from_subquery(mut self, subquery_sql: &str, alias: &str) -> Self {
386        self.from_subquery = Some((subquery_sql.to_string(), alias.to_string()));
387        // 与 from_table 互斥,后调用者覆盖前者
388        self.from_table = None;
389        self
390    }
391
392    /// 添加 INNER JOIN
393    ///
394    /// # 安全性(门禁 9 修复)
395    ///
396    /// 表名经 `quote_ident()` 转义。`on` 条件为表达式,调用方应确保不使用恶意输入构造。
397    pub fn inner_join(mut self, table: &str, on: &str) -> Self {
398        self.join_clauses.push(JoinClause {
399            join_type: "INNER JOIN",
400            table: Self::quote_join_table(table),
401            on: vec![JoinOn::Raw(on.to_string())],
402        });
403        self
404    }
405
406    /// 添加 LEFT JOIN
407    ///
408    /// # 安全性(门禁 9 修复)
409    ///
410    /// 同 `inner_join`,表名经 `quote_ident()` 转义。
411    pub fn left_join(mut self, table: &str, on: &str) -> Self {
412        self.join_clauses.push(JoinClause {
413            join_type: "LEFT JOIN",
414            table: Self::quote_join_table(table),
415            on: vec![JoinOn::Raw(on.to_string())],
416        });
417        self
418    }
419
420    /// 添加 RIGHT JOIN
421    ///
422    /// # 安全性(门禁 9 修复)
423    ///
424    /// 同 `inner_join`,表名经 `quote_ident()` 转义。
425    pub fn right_join(mut self, table: &str, on: &str) -> Self {
426        self.join_clauses.push(JoinClause {
427            join_type: "RIGHT JOIN",
428            table: Self::quote_join_table(table),
429            on: vec![JoinOn::Raw(on.to_string())],
430        });
431        self
432    }
433
434    // ========================================================================
435    // 参数化 JOIN(P2 修复 #68:JOIN 注入风险)
436    // ========================================================================
437    //
438    // 以下方法分离 JOIN ON 条件的 SQL 模板与参数值,从根源上杜绝 SQL 注入。
439    // 调用方应优先使用这些方法替代 `inner_join(table, &format!(...))`,
440    // 尤其是当 ON 条件值来自不可信输入时。
441    //
442    // 使用示例:
443    // ```
444    // use sz_orm_core::{DbType, Value};
445    // use sz_orm_query_builder::Query;
446    //
447    // // 列对列等值连接(最常见场景,无参数)
448    // let sql = Query::select()
449    //     .column("u.id")
450    //     .from("users u")
451    //     .inner_join_on("orders o", "u.id", "o.user_id")
452    //     .build(DbType::MySQL);
453    // // INNER JOIN `orders` o ON `u`.`id` = `o`.`user_id`
454    //
455    // // 参数化 ON 条件(用户输入作为参数绑定)
456    // let built = Query::select()
457    //     .column("u.id")
458    //     .from("users u")
459    //     .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
460    //     .build_with_params(DbType::MySQL);
461    // // built.sql: "INNER JOIN `orders` o ON `o`.`status` = ?"
462    // // built.params: [String("paid")]
463    // ```
464
465    /// 添加 INNER JOIN,ON 条件为列对列等值连接(`left_col = right_col`)
466    ///
467    /// # 安全性
468    ///
469    /// 列名经 `quote_column_dialect` 按方言转义,防止标识符逃逸。
470    /// 无参数值,纯标识符连接,最常见且最安全的 JOIN 形式。
471    pub fn inner_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
472        self.join_clauses.push(JoinClause {
473            join_type: "INNER JOIN",
474            table: Self::quote_join_table(table),
475            on: vec![JoinOn::ColumnEq {
476                left_column: left_col.to_string(),
477                right_column: right_col.to_string(),
478            }],
479        });
480        self
481    }
482
483    /// 添加 LEFT JOIN,ON 条件为列对列等值连接
484    pub fn left_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
485        self.join_clauses.push(JoinClause {
486            join_type: "LEFT JOIN",
487            table: Self::quote_join_table(table),
488            on: vec![JoinOn::ColumnEq {
489                left_column: left_col.to_string(),
490                right_column: right_col.to_string(),
491            }],
492        });
493        self
494    }
495
496    /// 添加 RIGHT JOIN,ON 条件为列对列等值连接
497    pub fn right_join_on(mut self, table: &str, left_col: &str, right_col: &str) -> Self {
498        self.join_clauses.push(JoinClause {
499            join_type: "RIGHT JOIN",
500            table: Self::quote_join_table(table),
501            on: vec![JoinOn::ColumnEq {
502                left_column: left_col.to_string(),
503                right_column: right_col.to_string(),
504            }],
505        });
506        self
507    }
508
509    /// 添加 INNER JOIN,ON 条件为参数化表达式(`left_col op ?`)
510    ///
511    /// # 参数
512    ///
513    /// - `table`: JOIN 的表名(支持别名 `orders o`)
514    /// - `left_col`: 左侧列名(已转义)
515    /// - `op_expr`: 运算符 + 占位符部分(如 ` = ?`、` > ?`、` IN (?, ?)`)
516    /// - `value`: 单个参数值
517    pub fn inner_join_param(
518        mut self,
519        table: &str,
520        left_col: &str,
521        op_expr: &str,
522        value: Value,
523    ) -> Self {
524        self.join_clauses.push(JoinClause {
525            join_type: "INNER JOIN",
526            table: Self::quote_join_table(table),
527            on: vec![JoinOn::Param {
528                left_column: left_col.to_string(),
529                op: op_expr.to_string(),
530                values: vec![value],
531            }],
532        });
533        self
534    }
535
536    /// 添加 LEFT JOIN,ON 条件为参数化表达式
537    pub fn left_join_param(
538        mut self,
539        table: &str,
540        left_col: &str,
541        op_expr: &str,
542        value: Value,
543    ) -> Self {
544        self.join_clauses.push(JoinClause {
545            join_type: "LEFT JOIN",
546            table: Self::quote_join_table(table),
547            on: vec![JoinOn::Param {
548                left_column: left_col.to_string(),
549                op: op_expr.to_string(),
550                values: vec![value],
551            }],
552        });
553        self
554    }
555
556    /// 添加 RIGHT JOIN,ON 条件为参数化表达式
557    pub fn right_join_param(
558        mut self,
559        table: &str,
560        left_col: &str,
561        op_expr: &str,
562        value: Value,
563    ) -> Self {
564        self.join_clauses.push(JoinClause {
565            join_type: "RIGHT JOIN",
566            table: Self::quote_join_table(table),
567            on: vec![JoinOn::Param {
568                left_column: left_col.to_string(),
569                op: op_expr.to_string(),
570                values: vec![value],
571            }],
572        });
573        self
574    }
575
576    /// 渲染 JOIN 子句(含参数化 ON 条件)到 SQL 字符串
577    ///
578    /// 统一渲染 `join_clauses`:包含原始字符串 ON 条件(`JoinOn::Raw`,向后兼容)
579    /// 与参数化 ON 条件(`ColumnEq` / `Param`)。参数化 JOIN 的参数会追加到 `params` 中。
580    fn render_joins(&self, dialect: &dyn sz_orm_core::Dialect, params: &mut Vec<Value>) -> String {
581        let mut sql = String::new();
582        for clause in &self.join_clauses {
583            sql.push(' ');
584            sql.push_str(clause.join_type);
585            sql.push(' ');
586            sql.push_str(&clause.table);
587            sql.push_str(" ON ");
588            for (i, on) in clause.on.iter().enumerate() {
589                if i > 0 {
590                    sql.push_str(" AND ");
591                }
592                match on {
593                    JoinOn::Raw(raw) => sql.push_str(raw),
594                    JoinOn::ColumnEq {
595                        left_column,
596                        right_column,
597                    } => {
598                        sql.push_str(&quote_column_dialect(dialect, left_column));
599                        sql.push_str(" = ");
600                        sql.push_str(&quote_column_dialect(dialect, right_column));
601                    }
602                    JoinOn::Param {
603                        left_column,
604                        op,
605                        values,
606                    } => {
607                        sql.push_str(&quote_column_dialect(dialect, left_column));
608                        sql.push_str(op);
609                        params.extend(values.iter().cloned());
610                    }
611                }
612            }
613        }
614        sql
615    }
616
617    /// 对 JOIN 表名部分进行转义(支持别名:`orders o` → `` `orders` o ``)
618    fn quote_join_table(table: &str) -> String {
619        if let Some((tbl, alias)) = table.rsplit_once(' ') {
620            if alias.to_uppercase() == "AS" {
621                // `orders AS o`
622                format!("{} AS {}", quote_ident(tbl), alias)
623            } else {
624                // `orders o`
625                format!("{} {}", quote_ident(tbl), alias)
626            }
627        } else {
628            quote_ident(table)
629        }
630    }
631
632    /// 添加 WHERE 条件(AND 连接)
633    ///
634    /// # 安全性(v0.2.2 修复 C-6)
635    ///
636    /// 调用 `check_where_injection` 检测高危模式(分号+SQL 关键字、行注释、块注释)。
637    /// 复杂 WHERE 条件应使用参数化查询 API,避免直接拼接字符串。
638    pub fn where_clause(mut self, condition: &str) -> Self {
639        check_where_injection(condition);
640        self.wheres.push(condition.to_string());
641        self
642    }
643
644    // ========================================================================
645    // 参数化 WHERE 条件(P0 修复:SQL 注入防护)
646    // ========================================================================
647    //
648    // 以下方法分离 SQL 模板(含 `?` 占位符)与参数值,从根源上杜绝 SQL 注入。
649    // 调用方应优先使用这些方法替代 `where_clause(&str)`,尤其是当 WHERE 条件值
650    // 来自不可信输入时。
651    //
652    // 使用示例:
653    // ```
654    // use sz_orm_core::DbType;
655    // use sz_orm_query_builder::Query;
656    //
657    // let built = Query::select()
658    //     .column("id")
659    //     .from("users")
660    //     .where_eq("age", Value::I32(18))           // age > 18
661    //     .where_eq("status", Value::String("active".into()))  // AND status = ?
662    //     .or_where_eq("role", Value::String("admin".into()))  // OR role = ?
663    //     .build_with_params(DbType::MySQL);
664    // // built.sql: "SELECT `id` FROM `users` WHERE `age` = ? AND `status` = ? OR `role` = ?"
665    // // built.params: [I32(18), String("active"), String("admin")]
666    // ```
667
668    /// 添加 `column = ?` AND 条件
669    pub fn where_eq(mut self, column: &str, value: Value) -> Self {
670        self.param_wheres.push(ParamWhere::And {
671            column: column.to_string(),
672            op: " = ?".to_string(),
673            values: vec![value],
674        });
675        self
676    }
677
678    /// 添加 `column <> ?` AND 条件
679    pub fn where_ne(mut self, column: &str, value: Value) -> Self {
680        self.param_wheres.push(ParamWhere::And {
681            column: column.to_string(),
682            op: " <> ?".to_string(),
683            values: vec![value],
684        });
685        self
686    }
687
688    /// 添加 `column > ?` AND 条件
689    pub fn where_gt(mut self, column: &str, value: Value) -> Self {
690        self.param_wheres.push(ParamWhere::And {
691            column: column.to_string(),
692            op: " > ?".to_string(),
693            values: vec![value],
694        });
695        self
696    }
697
698    /// 添加 `column >= ?` AND 条件
699    pub fn where_ge(mut self, column: &str, value: Value) -> Self {
700        self.param_wheres.push(ParamWhere::And {
701            column: column.to_string(),
702            op: " >= ?".to_string(),
703            values: vec![value],
704        });
705        self
706    }
707
708    /// 添加 `column < ?` AND 条件
709    pub fn where_lt(mut self, column: &str, value: Value) -> Self {
710        self.param_wheres.push(ParamWhere::And {
711            column: column.to_string(),
712            op: " < ?".to_string(),
713            values: vec![value],
714        });
715        self
716    }
717
718    /// 添加 `column <= ?` AND 条件
719    pub fn where_le(mut self, column: &str, value: Value) -> Self {
720        self.param_wheres.push(ParamWhere::And {
721            column: column.to_string(),
722            op: " <= ?".to_string(),
723            values: vec![value],
724        });
725        self
726    }
727
728    /// 添加 `column LIKE ?` AND 条件
729    pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
730        self.param_wheres.push(ParamWhere::And {
731            column: column.to_string(),
732            op: " LIKE ?".to_string(),
733            values: vec![pattern],
734        });
735        self
736    }
737
738    /// 添加 `column IN (?, ?, ...)` AND 条件
739    ///
740    /// 空列表生成 `1 = 0`(恒假),避免生成非法的 `IN ()`。
741    pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
742        let (column, op) = if values.is_empty() {
743            (String::new(), "1 = 0".to_string())
744        } else {
745            let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
746            (
747                column.to_string(),
748                format!(" IN ({})", placeholders.join(", ")),
749            )
750        };
751        self.param_wheres
752            .push(ParamWhere::And { column, op, values });
753        self
754    }
755
756    /// 添加 `column NOT IN (?, ?, ...)` AND 条件
757    ///
758    /// 空列表生成 `1 = 1`(恒真),避免生成非法的 `NOT IN ()`。
759    pub fn where_not_in(mut self, column: &str, values: Vec<Value>) -> Self {
760        let (column, op) = if values.is_empty() {
761            (String::new(), "1 = 1".to_string())
762        } else {
763            let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
764            (
765                column.to_string(),
766                format!(" NOT IN ({})", placeholders.join(", ")),
767            )
768        };
769        self.param_wheres
770            .push(ParamWhere::And { column, op, values });
771        self
772    }
773
774    /// 添加 `column BETWEEN ? AND ?` AND 条件
775    pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
776        self.param_wheres.push(ParamWhere::And {
777            column: column.to_string(),
778            op: " BETWEEN ? AND ?".to_string(),
779            values: vec![low, high],
780        });
781        self
782    }
783
784    /// 添加 `column IS NULL` AND 条件
785    pub fn where_null(mut self, column: &str) -> Self {
786        self.param_wheres.push(ParamWhere::And {
787            column: column.to_string(),
788            op: " IS NULL".to_string(),
789            values: vec![],
790        });
791        self
792    }
793
794    /// 添加 `column IS NOT NULL` AND 条件
795    pub fn where_not_null(mut self, column: &str) -> Self {
796        self.param_wheres.push(ParamWhere::And {
797            column: column.to_string(),
798            op: " IS NOT NULL".to_string(),
799            values: vec![],
800        });
801        self
802    }
803
804    /// 添加 `column = ?` OR 条件
805    pub fn or_where_eq(mut self, column: &str, value: Value) -> Self {
806        self.param_wheres.push(ParamWhere::Or {
807            column: column.to_string(),
808            op: " = ?".to_string(),
809            values: vec![value],
810        });
811        self
812    }
813
814    /// 添加 `column <> ?` OR 条件
815    pub fn or_where_ne(mut self, column: &str, value: Value) -> Self {
816        self.param_wheres.push(ParamWhere::Or {
817            column: column.to_string(),
818            op: " <> ?".to_string(),
819            values: vec![value],
820        });
821        self
822    }
823
824    /// 添加 `column > ?` OR 条件
825    pub fn or_where_gt(mut self, column: &str, value: Value) -> Self {
826        self.param_wheres.push(ParamWhere::Or {
827            column: column.to_string(),
828            op: " > ?".to_string(),
829            values: vec![value],
830        });
831        self
832    }
833
834    /// 添加 `column >= ?` OR 条件
835    pub fn or_where_ge(mut self, column: &str, value: Value) -> Self {
836        self.param_wheres.push(ParamWhere::Or {
837            column: column.to_string(),
838            op: " >= ?".to_string(),
839            values: vec![value],
840        });
841        self
842    }
843
844    /// 添加 `column < ?` OR 条件
845    pub fn or_where_lt(mut self, column: &str, value: Value) -> Self {
846        self.param_wheres.push(ParamWhere::Or {
847            column: column.to_string(),
848            op: " < ?".to_string(),
849            values: vec![value],
850        });
851        self
852    }
853
854    /// 添加 `column <= ?` OR 条件
855    pub fn or_where_le(mut self, column: &str, value: Value) -> Self {
856        self.param_wheres.push(ParamWhere::Or {
857            column: column.to_string(),
858            op: " <= ?".to_string(),
859            values: vec![value],
860        });
861        self
862    }
863
864    /// 添加 `column LIKE ?` OR 条件
865    pub fn or_where_like(mut self, column: &str, pattern: Value) -> Self {
866        self.param_wheres.push(ParamWhere::Or {
867            column: column.to_string(),
868            op: " LIKE ?".to_string(),
869            values: vec![pattern],
870        });
871        self
872    }
873
874    /// 添加 `column IN (?, ?, ...)` OR 条件
875    pub fn or_where_in(mut self, column: &str, values: Vec<Value>) -> Self {
876        let (column, op) = if values.is_empty() {
877            (String::new(), "1 = 0".to_string())
878        } else {
879            let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
880            (
881                column.to_string(),
882                format!(" IN ({})", placeholders.join(", ")),
883            )
884        };
885        self.param_wheres
886            .push(ParamWhere::Or { column, op, values });
887        self
888    }
889
890    /// 添加 `column BETWEEN ? AND ?` OR 条件
891    pub fn or_where_between(mut self, column: &str, low: Value, high: Value) -> Self {
892        self.param_wheres.push(ParamWhere::Or {
893            column: column.to_string(),
894            op: " BETWEEN ? AND ?".to_string(),
895            values: vec![low, high],
896        });
897        self
898    }
899
900    /// 添加 `column IS NULL` OR 条件
901    pub fn or_where_null(mut self, column: &str) -> Self {
902        self.param_wheres.push(ParamWhere::Or {
903            column: column.to_string(),
904            op: " IS NULL".to_string(),
905            values: vec![],
906        });
907        self
908    }
909
910    /// 添加 `column IS NOT NULL` OR 条件
911    pub fn or_where_not_null(mut self, column: &str) -> Self {
912        self.param_wheres.push(ParamWhere::Or {
913            column: column.to_string(),
914            op: " IS NOT NULL".to_string(),
915            values: vec![],
916        });
917        self
918    }
919
920    /// 添加 GROUP BY
921    pub fn group_by(mut self, column: &str) -> Self {
922        self.group_by.push(column.to_string());
923        self
924    }
925
926    /// 添加 HAVING
927    pub fn having(mut self, condition: &str) -> Self {
928        self.having.push(condition.to_string());
929        self
930    }
931
932    /// 添加 ORDER BY
933    ///
934    /// # 参数
935    ///
936    /// - `column`: 列名
937    /// - `asc`: true=ASC, false=DESC
938    pub fn order_by(mut self, column: &str, asc: bool) -> Self {
939        let dir = if asc { "ASC" } else { "DESC" };
940        self.order_by.push(format!("{} {}", column, dir));
941        self
942    }
943
944    /// 设置 LIMIT
945    pub fn limit(mut self, n: u64) -> Self {
946        self.limit = Some(n);
947        self
948    }
949
950    /// 设置 OFFSET
951    pub fn offset(mut self, n: u64) -> Self {
952        self.offset = Some(n);
953        self
954    }
955
956    /// 生成分页(同时设置 LIMIT 和 OFFSET)
957    ///
958    /// # 参数
959    ///
960    /// - `page`: 页码(从 1 开始)
961    /// - `size`: 每页大小
962    pub fn paginate(self, page: u64, size: u64) -> Self {
963        let offset = (page.saturating_sub(1)) * size;
964        self.limit(size).offset(offset)
965    }
966
967    /// 添加 CTE(Common Table Expression / WITH 子句)。
968    ///
969    /// 生成形如 `WITH name AS (subquery) SELECT ...` 的 SQL。
970    ///
971    /// # 参数
972    ///
973    /// - `name`: CTE 名称
974    /// - `subquery`: 子查询 SQL(完整的 SELECT 语句)
975    pub fn with_cte(mut self, name: &str, subquery: &str) -> Self {
976        self.ctes
977            .push((name.to_string(), subquery.to_string(), false));
978        self
979    }
980
981    /// 添加递归 CTE(`WITH RECURSIVE name AS (...) SELECT ...`)。
982    ///
983    /// # 参数
984    ///
985    /// - `name`: CTE 名称
986    /// - `subquery`: 递归子查询 SQL
987    pub fn with_recursive_cte(mut self, name: &str, subquery: &str) -> Self {
988        self.ctes
989            .push((name.to_string(), subquery.to_string(), true));
990        self
991    }
992
993    /// 添加窗口函数列(作为 SELECT 列表的原始表达式)。
994    ///
995    /// 调用方负责构造完整的窗口函数表达式,例如:
996    /// - `ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC)`
997    /// - `RANK() OVER (ORDER BY score DESC)`
998    /// - `SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at)`
999    ///
1000    /// # 参数
1001    ///
1002    /// - `expr`: 完整的窗口函数表达式
1003    pub fn window_function(mut self, expr: &str) -> Self {
1004        self.window_columns.push(expr.to_string());
1005        self
1006    }
1007
1008    /// 添加 `ROW_NUMBER()` 窗口函数列。
1009    ///
1010    /// # 参数
1011    ///
1012    /// - `partition_by`: PARTITION BY 列(可为空)
1013    /// - `order_by`: ORDER BY 列(如 `salary DESC`)
1014    /// - `alias`: 结果列别名(如 `row_num`)
1015    pub fn row_number(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1016        let partition_clause = if partition_by.is_empty() {
1017            String::new()
1018        } else {
1019            format!("PARTITION BY {} ", partition_by)
1020        };
1021        let expr = format!(
1022            "ROW_NUMBER() OVER ({}ORDER BY {}) AS {}",
1023            partition_clause, order_by, alias
1024        );
1025        self.window_function(&expr)
1026    }
1027
1028    /// 添加 `RANK()` 窗口函数列。
1029    ///
1030    /// # 参数
1031    ///
1032    /// - `partition_by`: PARTITION BY 列(可为空)
1033    /// - `order_by`: ORDER BY 列
1034    /// - `alias`: 结果列别名
1035    pub fn rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1036        let partition_clause = if partition_by.is_empty() {
1037            String::new()
1038        } else {
1039            format!("PARTITION BY {} ", partition_by)
1040        };
1041        let expr = format!(
1042            "RANK() OVER ({}ORDER BY {}) AS {}",
1043            partition_clause, order_by, alias
1044        );
1045        self.window_function(&expr)
1046    }
1047
1048    /// 添加 `DENSE_RANK()` 窗口函数列。
1049    ///
1050    /// # 参数
1051    ///
1052    /// - `partition_by`: PARTITION BY 列(可为空)
1053    /// - `order_by`: ORDER BY 列
1054    /// - `alias`: 结果列别名
1055    pub fn dense_rank(self, partition_by: &str, order_by: &str, alias: &str) -> Self {
1056        let partition_clause = if partition_by.is_empty() {
1057            String::new()
1058        } else {
1059            format!("PARTITION BY {} ", partition_by)
1060        };
1061        let expr = format!(
1062            "DENSE_RANK() OVER ({}ORDER BY {}) AS {}",
1063            partition_clause, order_by, alias
1064        );
1065        self.window_function(&expr)
1066    }
1067
1068    /// 设置 FOR UPDATE 行锁。
1069    ///
1070    /// 在生成的 SQL 末尾追加 `FOR UPDATE`,用于悲观锁。
1071    pub fn for_update(mut self) -> Self {
1072        self.for_update = true;
1073        self.for_update_options = None;
1074        self
1075    }
1076
1077    /// 设置 FOR UPDATE 并附带选项(如 `NOWAIT`、`SKIP LOCKED`)。
1078    ///
1079    /// # 参数
1080    ///
1081    /// - `options`: 选项字符串,如 `"NOWAIT"` 或 `"SKIP LOCKED"`
1082    pub fn for_update_with_options(mut self, options: &str) -> Self {
1083        self.for_update = true;
1084        self.for_update_options = Some(options.to_string());
1085        self
1086    }
1087
1088    /// 将当前查询与另一个查询进行 UNION 集合运算。
1089    ///
1090    /// 返回一个 [`SetQuery`],可通过 `build()` 生成最终 SQL。
1091    pub fn union(self, other: SelectQuery) -> SetQuery {
1092        SetQuery::new(self, SetOperator::Union, other)
1093    }
1094
1095    /// 将当前查询与另一个查询进行 UNION ALL 集合运算。
1096    pub fn union_all(self, other: SelectQuery) -> SetQuery {
1097        SetQuery::new(self, SetOperator::UnionAll, other)
1098    }
1099
1100    /// 将当前查询与另一个查询进行 INTERSECT 集合运算。
1101    pub fn intersect(self, other: SelectQuery) -> SetQuery {
1102        SetQuery::new(self, SetOperator::Intersect, other)
1103    }
1104
1105    /// 将当前查询与另一个查询进行 EXCEPT 集合运算。
1106    pub fn except(self, other: SelectQuery) -> SetQuery {
1107        SetQuery::new(self, SetOperator::Except, other)
1108    }
1109
1110    /// 生成 SQL
1111    ///
1112    /// # 参数
1113    ///
1114    /// - `db_type`: 数据库类型,用于选择方言
1115    pub fn build(self, db_type: DbType) -> String {
1116        let dialect = match sz_orm_core::get_dialect(db_type) {
1117            Ok(d) => d,
1118            Err(_) => return String::new(),
1119        };
1120
1121        let mut sql = String::new();
1122
1123        // CTE(WITH 子句)
1124        if !self.ctes.is_empty() {
1125            let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1126            if has_recursive {
1127                sql.push_str("WITH RECURSIVE ");
1128            } else {
1129                sql.push_str("WITH ");
1130            }
1131            let cte_strs: Vec<String> = self
1132                .ctes
1133                .iter()
1134                .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1135                .collect();
1136            sql.push_str(&cte_strs.join(", "));
1137            sql.push(' ');
1138        }
1139
1140        sql.push_str("SELECT ");
1141
1142        if self.distinct {
1143            sql.push_str("DISTINCT ");
1144        }
1145
1146        // 合并普通列与窗口函数列
1147        let mut all_columns: Vec<String> = self
1148            .columns
1149            .iter()
1150            .map(|c| {
1151                if c == "*" {
1152                    c.clone()
1153                } else {
1154                    dialect.quote(c)
1155                }
1156            })
1157            .collect();
1158        all_columns.extend(self.window_columns.iter().cloned());
1159
1160        if all_columns.is_empty() {
1161            sql.push('*');
1162        } else {
1163            sql.push_str(&all_columns.join(", "));
1164        }
1165
1166        if let Some(ref table) = self.from_table {
1167            sql.push_str(" FROM ");
1168            sql.push_str(&dialect.quote(table));
1169        } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1170            // FROM 子查询:`FROM (<subquery>) AS <alias>`
1171            sql.push_str(" FROM (");
1172            sql.push_str(subquery);
1173            sql.push_str(") AS ");
1174            sql.push_str(&dialect.quote(alias));
1175        }
1176
1177        // 渲染 JOIN(含参数化 ON 条件,P2 修复 #68)
1178        // build() 不收集参数(无参数化查询),参数化 JOIN 应通过 build_with_params() 调用
1179        let mut unused_params = Vec::new();
1180        sql.push_str(&self.render_joins(&*dialect, &mut unused_params));
1181
1182        if !self.wheres.is_empty() {
1183            sql.push_str(" WHERE ");
1184            // 第一个条件不加 AND/OR 前缀
1185            sql.push_str(&self.wheres[0]);
1186            for w in &self.wheres[1..] {
1187                if w.starts_with("OR ") {
1188                    sql.push(' ');
1189                    sql.push_str(w);
1190                } else {
1191                    sql.push_str(" AND ");
1192                    sql.push_str(w);
1193                }
1194            }
1195        }
1196
1197        if !self.group_by.is_empty() {
1198            sql.push_str(" GROUP BY ");
1199            sql.push_str(
1200                &self
1201                    .group_by
1202                    .iter()
1203                    .map(|c| quote_ident(c))
1204                    .collect::<Vec<_>>()
1205                    .join(", "),
1206            );
1207        }
1208
1209        if !self.having.is_empty() {
1210            sql.push_str(" HAVING ");
1211            sql.push_str(&self.having.join(" AND "));
1212        }
1213
1214        if !self.order_by.is_empty() {
1215            sql.push_str(" ORDER BY ");
1216            sql.push_str(
1217                &self
1218                    .order_by
1219                    .iter()
1220                    .map(|s| {
1221                        // 格式为 "column ASC" 或 "column DESC"
1222                        if let Some((col, dir)) = s.rsplit_once(' ') {
1223                            format!("{} {}", quote_ident(col), dir)
1224                        } else {
1225                            quote_ident(s)
1226                        }
1227                    })
1228                    .collect::<Vec<_>>()
1229                    .join(", "),
1230            );
1231        }
1232
1233        if let Some(limit) = self.limit {
1234            sql.push_str(&format!(" LIMIT {}", limit));
1235        }
1236        if let Some(offset) = self.offset {
1237            sql.push_str(&format!(" OFFSET {}", offset));
1238        }
1239
1240        // FOR UPDATE 行锁
1241        if self.for_update {
1242            sql.push_str(" FOR UPDATE");
1243            if let Some(ref opts) = self.for_update_options {
1244                sql.push(' ');
1245                sql.push_str(opts);
1246            }
1247        }
1248
1249        sql
1250    }
1251
1252    /// 生成带参数的 SQL(参数化查询,P0 修复:SQL 注入防护)
1253    ///
1254    /// 返回 [`BuiltQuery`],包含带 `?` 占位符的 SQL 与按序绑定的参数列表。
1255    /// 与 [`build`](Self::build) 的区别:
1256    /// - WHERE 条件可来自 `where_eq`/`where_in`/`where_between` 等参数化 API
1257    /// - 用户输入作为参数绑定,而非字符串拼接到 SQL 中
1258    ///
1259    /// # 混合使用规则
1260    ///
1261    /// 当同时使用原始 `where_clause(&str)` 与参数化 `where_eq(column, value)` 时:
1262    /// - 原始条件先渲染(无参数)
1263    /// - 参数化条件后渲染(按顺序收集参数)
1264    /// - 两者均按调用顺序保持 AND/OR 连接语义
1265    ///
1266    /// # 示例
1267    ///
1268    /// ```
1269    /// use sz_orm_core::{DbType, Value};
1270    /// use sz_orm_query_builder::Query;
1271    ///
1272    /// let built = Query::select()
1273    ///     .column("id")
1274    ///     .from("users")
1275    ///     .where_eq("age", Value::I32(18))
1276    ///     .or_where_eq("role", Value::String("admin".into()))
1277    ///     .build_with_params(DbType::MySQL);
1278    /// assert!(built.sql.contains("WHERE `age` = ? OR `role` = ?"));
1279    /// assert_eq!(built.params.len(), 2);
1280    /// ```
1281    pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
1282        let dialect = match sz_orm_core::get_dialect(db_type) {
1283            Ok(d) => d,
1284            Err(_) => return BuiltQuery::default(),
1285        };
1286
1287        let mut sql = String::new();
1288        let mut params: Vec<Value> = Vec::new();
1289
1290        // CTE(WITH 子句)
1291        if !self.ctes.is_empty() {
1292            let has_recursive = self.ctes.iter().any(|(_, _, r)| *r);
1293            if has_recursive {
1294                sql.push_str("WITH RECURSIVE ");
1295            } else {
1296                sql.push_str("WITH ");
1297            }
1298            let cte_strs: Vec<String> = self
1299                .ctes
1300                .iter()
1301                .map(|(name, subquery, _)| format!("{} AS ({})", name, subquery))
1302                .collect();
1303            sql.push_str(&cte_strs.join(", "));
1304            sql.push(' ');
1305        }
1306
1307        sql.push_str("SELECT ");
1308
1309        if self.distinct {
1310            sql.push_str("DISTINCT ");
1311        }
1312
1313        let mut all_columns: Vec<String> = self
1314            .columns
1315            .iter()
1316            .map(|c| {
1317                if c == "*" {
1318                    c.clone()
1319                } else {
1320                    dialect.quote(c)
1321                }
1322            })
1323            .collect();
1324        all_columns.extend(self.window_columns.iter().cloned());
1325
1326        if all_columns.is_empty() {
1327            sql.push('*');
1328        } else {
1329            sql.push_str(&all_columns.join(", "));
1330        }
1331
1332        if let Some(ref table) = self.from_table {
1333            sql.push_str(" FROM ");
1334            sql.push_str(&dialect.quote(table));
1335        } else if let Some((ref subquery, ref alias)) = self.from_subquery {
1336            // FROM 子查询:`FROM (<subquery>) AS <alias>`
1337            sql.push_str(" FROM (");
1338            sql.push_str(subquery);
1339            sql.push_str(") AS ");
1340            sql.push_str(&dialect.quote(alias));
1341        }
1342
1343        // 渲染 JOIN(含参数化 ON 条件,P2 修复 #68)
1344        // 参数化 JOIN 的参数追加到 params 列表
1345        sql.push_str(&self.render_joins(&*dialect, &mut params));
1346
1347        // WHERE:原始 wheres(无参数)+ 参数化 param_wheres(带 ? 占位符)
1348        let has_raw = !self.wheres.is_empty();
1349        let has_param = !self.param_wheres.is_empty();
1350        if has_raw || has_param {
1351            sql.push_str(" WHERE ");
1352            let mut first = true;
1353            // 原始条件先渲染
1354            for w in &self.wheres {
1355                if first {
1356                    sql.push_str(w);
1357                    first = false;
1358                } else if w.starts_with("OR ") {
1359                    sql.push(' ');
1360                    sql.push_str(w);
1361                } else {
1362                    sql.push_str(" AND ");
1363                    sql.push_str(w);
1364                }
1365            }
1366            // 参数化条件后渲染
1367            for pw in &self.param_wheres {
1368                let (conjunction, column, op, vals) = match pw {
1369                    ParamWhere::And { column, op, values } => ("AND", column, op, values),
1370                    ParamWhere::Or { column, op, values } => ("OR", column, op, values),
1371                };
1372                // 按目标方言引用列名(PostgreSQL 双引号、MySQL 反引号)
1373                let expr = if column.is_empty() {
1374                    op.clone()
1375                } else {
1376                    format!("{}{}", quote_column_dialect(&*dialect, column), op)
1377                };
1378                if first {
1379                    sql.push_str(&expr);
1380                    first = false;
1381                } else {
1382                    sql.push(' ');
1383                    sql.push_str(conjunction);
1384                    sql.push(' ');
1385                    sql.push_str(&expr);
1386                }
1387                params.extend(vals.iter().cloned());
1388            }
1389        }
1390
1391        if !self.group_by.is_empty() {
1392            sql.push_str(" GROUP BY ");
1393            sql.push_str(
1394                &self
1395                    .group_by
1396                    .iter()
1397                    .map(|c| quote_ident(c))
1398                    .collect::<Vec<_>>()
1399                    .join(", "),
1400            );
1401        }
1402
1403        if !self.having.is_empty() {
1404            sql.push_str(" HAVING ");
1405            sql.push_str(&self.having.join(" AND "));
1406        }
1407
1408        if !self.order_by.is_empty() {
1409            sql.push_str(" ORDER BY ");
1410            sql.push_str(
1411                &self
1412                    .order_by
1413                    .iter()
1414                    .map(|s| {
1415                        if let Some((col, dir)) = s.rsplit_once(' ') {
1416                            format!("{} {}", quote_ident(col), dir)
1417                        } else {
1418                            quote_ident(s)
1419                        }
1420                    })
1421                    .collect::<Vec<_>>()
1422                    .join(", "),
1423            );
1424        }
1425
1426        if let Some(limit) = self.limit {
1427            sql.push_str(&format!(" LIMIT {}", limit));
1428        }
1429        if let Some(offset) = self.offset {
1430            sql.push_str(&format!(" OFFSET {}", offset));
1431        }
1432
1433        if self.for_update {
1434            sql.push_str(" FOR UPDATE");
1435            if let Some(ref opts) = self.for_update_options {
1436                sql.push(' ');
1437                sql.push_str(opts);
1438            }
1439        }
1440
1441        BuiltQuery { sql, params }
1442    }
1443}
1444
1445// ============================================================================
1446// 深度扩展:集合运算(UNION / INTERSECT / EXCEPT)
1447// ============================================================================
1448
1449/// SQL 集合运算符类型。
1450#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1451pub enum SetOperator {
1452    /// `UNION`:合并去重
1453    Union,
1454    /// `UNION ALL`:合并不去重
1455    UnionAll,
1456    /// `INTERSECT`:交集
1457    Intersect,
1458    /// `EXCEPT`:差集(MySQL 8.0+ 称 `EXCEPT`,部分方言为 `MINUS`)
1459    Except,
1460}
1461
1462impl SetOperator {
1463    /// 返回运算符对应的 SQL 关键字。
1464    pub fn as_sql(&self) -> &'static str {
1465        match self {
1466            SetOperator::Union => "UNION",
1467            SetOperator::UnionAll => "UNION ALL",
1468            SetOperator::Intersect => "INTERSECT",
1469            SetOperator::Except => "EXCEPT",
1470        }
1471    }
1472}
1473
1474/// 集合运算查询,支持链式追加多个 SELECT 并以 UNION/INTERSECT/EXCEPT 连接。
1475///
1476/// # 示例
1477///
1478/// ```ignore
1479/// use sz_orm_core::DbType;
1480/// use sz_orm_query_builder::Query;
1481///
1482/// let q1 = Query::select().column("id").from("active_users");
1483/// let q2 = Query::select().column("id").from("pending_users");
1484/// let sql = q1.union(q2).build(DbType::MySQL);
1485/// // SELECT `id` FROM `active_users` UNION SELECT `id` FROM `pending_users`
1486/// ```
1487#[derive(Debug, Clone)]
1488pub struct SetQuery {
1489    /// 第一个 SELECT 查询
1490    first: SelectQuery,
1491    /// 后续的 (运算符, 查询) 对
1492    rest: Vec<(SetOperator, SelectQuery)>,
1493    /// 全局 ORDER BY(作用于整个集合运算结果)
1494    order_by: Vec<String>,
1495    /// 全局 LIMIT
1496    limit: Option<u64>,
1497    /// 全局 OFFSET
1498    offset: Option<u64>,
1499}
1500
1501impl SetQuery {
1502    /// 创建一个集合运算查询。
1503    pub fn new(first: SelectQuery, op: SetOperator, second: SelectQuery) -> Self {
1504        Self {
1505            first,
1506            rest: vec![(op, second)],
1507            order_by: Vec::new(),
1508            limit: None,
1509            offset: None,
1510        }
1511    }
1512
1513    /// 追加 UNION 查询。
1514    pub fn union(mut self, other: SelectQuery) -> Self {
1515        self.rest.push((SetOperator::Union, other));
1516        self
1517    }
1518
1519    /// 追加 UNION ALL 查询。
1520    pub fn union_all(mut self, other: SelectQuery) -> Self {
1521        self.rest.push((SetOperator::UnionAll, other));
1522        self
1523    }
1524
1525    /// 追加 INTERSECT 查询。
1526    pub fn intersect(mut self, other: SelectQuery) -> Self {
1527        self.rest.push((SetOperator::Intersect, other));
1528        self
1529    }
1530
1531    /// 追加 EXCEPT 查询。
1532    pub fn except(mut self, other: SelectQuery) -> Self {
1533        self.rest.push((SetOperator::Except, other));
1534        self
1535    }
1536
1537    /// 设置全局 ORDER BY(作用于整个集合运算结果)。
1538    pub fn order_by(mut self, column: &str, asc: bool) -> Self {
1539        let dir = if asc { "ASC" } else { "DESC" };
1540        self.order_by.push(format!("{} {}", column, dir));
1541        self
1542    }
1543
1544    /// 设置全局 LIMIT。
1545    pub fn limit(mut self, n: u64) -> Self {
1546        self.limit = Some(n);
1547        self
1548    }
1549
1550    /// 设置全局 OFFSET。
1551    pub fn offset(mut self, n: u64) -> Self {
1552        self.offset = Some(n);
1553        self
1554    }
1555
1556    /// 生成 SQL。
1557    ///
1558    /// 将所有子查询用对应的集合运算符连接,并在末尾追加全局 ORDER BY / LIMIT / OFFSET。
1559    pub fn build(self, db_type: DbType) -> String {
1560        let mut sql = self.first.build(db_type);
1561        for (op, query) in &self.rest {
1562            sql.push(' ');
1563            sql.push_str(op.as_sql());
1564            sql.push(' ');
1565            sql.push_str(&query.clone().build(db_type));
1566        }
1567        if !self.order_by.is_empty() {
1568            sql.push_str(" ORDER BY ");
1569            sql.push_str(
1570                &self
1571                    .order_by
1572                    .iter()
1573                    .map(|s| {
1574                        if let Some((col, dir)) = s.rsplit_once(' ') {
1575                            format!("{} {}", quote_ident(col), dir)
1576                        } else {
1577                            quote_ident(s)
1578                        }
1579                    })
1580                    .collect::<Vec<_>>()
1581                    .join(", "),
1582            );
1583        }
1584        if let Some(limit) = self.limit {
1585            sql.push_str(&format!(" LIMIT {}", limit));
1586        }
1587        if let Some(offset) = self.offset {
1588            sql.push_str(&format!(" OFFSET {}", offset));
1589        }
1590        sql
1591    }
1592}
1593
1594/// Upsert(插入或更新)策略
1595///
1596/// 支持三种主流数据库的冲突处理语法:
1597/// - PostgreSQL/SQLite:`ON CONFLICT ... DO NOTHING` / `DO UPDATE`
1598/// - MySQL/MariaDB/TiDB:`ON DUPLICATE KEY UPDATE` / `REPLACE INTO`
1599///
1600/// # 方言兼容性
1601///
1602/// | 策略 | MySQL | PostgreSQL | SQLite |
1603/// |------|-------|------------|--------|
1604/// | `OnConflictDoNothing` | ✗ | ✓ | ✓ |
1605/// | `OnConflictDoUpdate` | ✗ | ✓ | ✓ |
1606/// | `OnDuplicateKeyUpdate` | ✓ | ✗ | ✗ |
1607/// | `Replace` | ✓ | ✗ | ✓(`INSERT OR REPLACE`)|
1608///
1609/// 不兼容的组合会在 `build_with_dialect` 中跳过 upsert 子句(不报错,由调用方确保方言匹配)。
1610#[derive(Debug, Clone, Default)]
1611pub enum UpsertStrategy {
1612    /// 不使用 upsert(默认)
1613    #[default]
1614    None,
1615    /// PostgreSQL/SQLite:`ON CONFLICT (cols) DO NOTHING`
1616    OnConflictDoNothing(Vec<String>),
1617    /// PostgreSQL/SQLite:`ON CONFLICT (cols) DO UPDATE SET col = expr, ...`
1618    /// `(conflict_cols, update_assignments)`
1619    OnConflictDoUpdate(Vec<String>, Vec<(String, String)>),
1620    /// MySQL:`ON DUPLICATE KEY UPDATE col = expr, ...`
1621    OnDuplicateKeyUpdate(Vec<(String, String)>),
1622    /// MySQL:`REPLACE INTO`(先删除冲突行再插入)
1623    Replace,
1624}
1625
1626/// 判断 DbType 是否为 MySQL 兼容方言
1627fn is_mysql_family(db_type: DbType) -> bool {
1628    matches!(
1629        db_type,
1630        DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase | DbType::PolarDB
1631    )
1632}
1633
1634/// 判断 DbType 是否为 PostgreSQL 兼容方言
1635fn is_pg_family(db_type: DbType) -> bool {
1636    matches!(
1637        db_type,
1638        DbType::PostgreSQL | DbType::Kingbase | DbType::GaussDB | DbType::PolarDB
1639    ) || db_type == DbType::Sqlite
1640}
1641
1642/// 渲染 upsert 子句(用于 build_with_dialect)
1643///
1644/// 根据方言返回对应的 upsert SQL 片段;不兼容的组合返回 `None`(跳过)。
1645fn render_upsert_clause(strategy: &UpsertStrategy, db_type: DbType) -> Option<String> {
1646    match strategy {
1647        UpsertStrategy::None => None,
1648        UpsertStrategy::OnConflictDoNothing(cols) => {
1649            if is_pg_family(db_type) {
1650                let cols_str = cols
1651                    .iter()
1652                    .map(|c| quote_ident(c))
1653                    .collect::<Vec<_>>()
1654                    .join(", ");
1655                Some(format!("ON CONFLICT ({}) DO NOTHING", cols_str))
1656            } else {
1657                None
1658            }
1659        }
1660        UpsertStrategy::OnConflictDoUpdate(cols, assignments) => {
1661            if is_pg_family(db_type) {
1662                let cols_str = cols
1663                    .iter()
1664                    .map(|c| quote_ident(c))
1665                    .collect::<Vec<_>>()
1666                    .join(", ");
1667                let sets = assignments
1668                    .iter()
1669                    .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1670                    .collect::<Vec<_>>()
1671                    .join(", ");
1672                Some(format!("ON CONFLICT ({}) DO UPDATE SET {}", cols_str, sets))
1673            } else {
1674                None
1675            }
1676        }
1677        UpsertStrategy::OnDuplicateKeyUpdate(assignments) => {
1678            if is_mysql_family(db_type) {
1679                let sets = assignments
1680                    .iter()
1681                    .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1682                    .collect::<Vec<_>>()
1683                    .join(", ");
1684                Some(format!("ON DUPLICATE KEY UPDATE {}", sets))
1685            } else {
1686                None
1687            }
1688        }
1689        UpsertStrategy::Replace => None, // REPLACE 在 build 阶段处理 INSERT 关键字替换
1690    }
1691}
1692
1693/// 渲染 RETURNING 子句(用于 build_with_dialect)
1694///
1695/// RETURNING 是 PostgreSQL 和 SQLite 3.35+ 的语法,MySQL 不支持。
1696/// - MySQL 家族:返回 `None`(跳过)
1697/// - PostgreSQL/SQLite:返回 `RETURNING col1, col2, ...`
1698/// - 列为 `*` 时不加引号,其余列名经 `dialect.quote()` 转义
1699fn render_returning_clause(columns: &Option<Vec<String>>, db_type: DbType) -> Option<String> {
1700    let cols = columns.as_ref()?;
1701    if cols.is_empty() {
1702        return None;
1703    }
1704    // MySQL 不支持 RETURNING
1705    if is_mysql_family(db_type) {
1706        return None;
1707    }
1708    let dialect = sz_orm_core::get_dialect(db_type).ok()?;
1709    let quoted: Vec<String> = cols
1710        .iter()
1711        .map(|c| {
1712            if c == "*" {
1713                c.clone()
1714            } else {
1715                dialect.quote(c)
1716            }
1717        })
1718        .collect();
1719    Some(format!("RETURNING {}", quoted.join(", ")))
1720}
1721
1722/// INSERT 查询构造器
1723#[derive(Debug, Clone, Default)]
1724pub struct InsertQuery {
1725    table: Option<String>,
1726    columns: Vec<String>,
1727    values: Vec<String>,
1728    /// Upsert 策略(ON CONFLICT / ON DUPLICATE KEY UPDATE / REPLACE)
1729    upsert: UpsertStrategy,
1730    /// RETURNING 子句列列表(PostgreSQL/SQLite 支持,MySQL 不支持)
1731    returning: Option<Vec<String>>,
1732}
1733
1734impl InsertQuery {
1735    /// 创建空的 INSERT 查询
1736    pub fn new() -> Self {
1737        Self::default()
1738    }
1739
1740    /// 设置目标表
1741    pub fn into_table(mut self, table: &str) -> Self {
1742        self.table = Some(table.to_string());
1743        self
1744    }
1745
1746    /// 添加列值对(值应为已转义的 SQL 字面量)
1747    pub fn value(mut self, column: &str, value: &str) -> Self {
1748        self.columns.push(column.to_string());
1749        self.values.push(value.to_string());
1750        self
1751    }
1752
1753    /// 批量添加列值对
1754    pub fn values(mut self, pairs: &[(&str, &str)]) -> Self {
1755        for (c, v) in pairs {
1756            self.columns.push(c.to_string());
1757            self.values.push(v.to_string());
1758        }
1759        self
1760    }
1761
1762    // ========================================================================
1763    // Upsert 策略(ON CONFLICT / ON DUPLICATE KEY UPDATE / REPLACE)
1764    // ========================================================================
1765
1766    /// PostgreSQL/SQLite:`ON CONFLICT (cols) DO NOTHING`
1767    ///
1768    /// 冲突时忽略插入。适用于 PostgreSQL、SQLite 方言。
1769    /// MySQL 方言下此设置被忽略(MySQL 不支持 ON CONFLICT 语法)。
1770    ///
1771    /// # 示例
1772    ///
1773    /// ```
1774    /// use sz_orm_core::DbType;
1775    /// use sz_orm_query_builder::Query;
1776    ///
1777    /// let sql = Query::insert()
1778    ///     .into_table("users")
1779    ///     .value("id", "1")
1780    ///     .value("name", "'Alice'")
1781    ///     .on_conflict_do_nothing(&["id"])
1782    ///     .build_with_dialect(DbType::PostgreSQL);
1783    /// // INSERT INTO "users" ("id", "name") VALUES (1, 'Alice') ON CONFLICT ("id") DO NOTHING
1784    /// ```
1785    pub fn on_conflict_do_nothing(mut self, conflict_cols: &[&str]) -> Self {
1786        self.upsert = UpsertStrategy::OnConflictDoNothing(
1787            conflict_cols.iter().map(|s| s.to_string()).collect(),
1788        );
1789        self
1790    }
1791
1792    /// PostgreSQL/SQLite:`ON CONFLICT (cols) DO UPDATE SET col = expr, ...`
1793    ///
1794    /// 冲突时更新指定列。`assignments` 为 `(列名, 表达式)` 对。
1795    /// 表达式中可使用 `EXCLUDED.col` 引用待插入的值(PG/SQLite 标准)。
1796    ///
1797    /// # 示例
1798    ///
1799    /// ```
1800    /// use sz_orm_core::DbType;
1801    /// use sz_orm_query_builder::Query;
1802    ///
1803    /// let sql = Query::insert()
1804    ///     .into_table("users")
1805    ///     .value("id", "1")
1806    ///     .value("name", "'Alice'")
1807    ///     .value("count", "1")
1808    ///     .on_conflict_do_update(
1809    ///         &["id"],
1810    ///         &[("name", "EXCLUDED.name"), ("count", "users.count + 1")],
1811    ///     )
1812    ///     .build_with_dialect(DbType::PostgreSQL);
1813    /// // INSERT INTO "users" (...) VALUES (...) ON CONFLICT ("id") DO UPDATE SET
1814    /// //   "name" = EXCLUDED.name, "count" = users.count + 1
1815    /// ```
1816    pub fn on_conflict_do_update(
1817        mut self,
1818        conflict_cols: &[&str],
1819        assignments: &[(&str, &str)],
1820    ) -> Self {
1821        self.upsert = UpsertStrategy::OnConflictDoUpdate(
1822            conflict_cols.iter().map(|s| s.to_string()).collect(),
1823            assignments
1824                .iter()
1825                .map(|(c, v)| (c.to_string(), v.to_string()))
1826                .collect(),
1827        );
1828        self
1829    }
1830
1831    /// MySQL:`ON DUPLICATE KEY UPDATE col = expr, ...`
1832    ///
1833    /// 主键/唯一键冲突时更新指定列。`assignments` 为 `(列名, 表达式)` 对。
1834    /// 表达式中可使用 `VALUES(col)` 引用待插入的值(MySQL 语法)。
1835    ///
1836    /// # 示例
1837    ///
1838    /// ```
1839    /// use sz_orm_core::DbType;
1840    /// use sz_orm_query_builder::Query;
1841    ///
1842    /// let sql = Query::insert()
1843    ///     .into_table("users")
1844    ///     .value("id", "1")
1845    ///     .value("name", "'Alice'")
1846    ///     .value("count", "1")
1847    ///     .on_duplicate_key_update(&[("name", "VALUES(name)"), ("count", "count + 1")])
1848    ///     .build_with_dialect(DbType::MySQL);
1849    /// // INSERT INTO `users` (`id`, `name`, `count`) VALUES (1, 'Alice', 1)
1850    /// //   ON DUPLICATE KEY UPDATE `name` = VALUES(name), `count` = count + 1
1851    /// ```
1852    pub fn on_duplicate_key_update(mut self, assignments: &[(&str, &str)]) -> Self {
1853        self.upsert = UpsertStrategy::OnDuplicateKeyUpdate(
1854            assignments
1855                .iter()
1856                .map(|(c, v)| (c.to_string(), v.to_string()))
1857                .collect(),
1858        );
1859        self
1860    }
1861
1862    /// MySQL:使用 `REPLACE INTO` 代替 `INSERT INTO`
1863    ///
1864    /// 主键/唯一键冲突时先删除旧行再插入新行。
1865    /// 适用于 MySQL/MariaDB/TiDB 方言。
1866    pub fn replace(mut self) -> Self {
1867        self.upsert = UpsertStrategy::Replace;
1868        self
1869    }
1870
1871    /// 设置 RETURNING 子句(PostgreSQL/SQLite 3.35+ 支持)
1872    ///
1873    /// 在 INSERT 后返回指定列的值,常用于获取自增主键或默认值。
1874    /// MySQL 不支持 RETURNING,在 `build()`(MySQL 风格)中会被忽略;
1875    /// 在 `build_with_dialect()` 中仅 PostgreSQL/SQLite 方言渲染。
1876    ///
1877    /// # 示例
1878    ///
1879    /// ```
1880    /// use sz_orm_core::DbType;
1881    /// use sz_orm_query_builder::Query;
1882    ///
1883    /// let sql = Query::insert()
1884    ///     .into_table("users")
1885    ///     .value("name", "'Alice'")
1886    ///     .returning(&["id", "created_at"])
1887    ///     .build_with_dialect(DbType::PostgreSQL);
1888    /// // INSERT INTO "users" ("name") VALUES ('Alice') RETURNING "id", "created_at"
1889    /// ```
1890    pub fn returning(mut self, columns: &[&str]) -> Self {
1891        self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
1892        self
1893    }
1894
1895    /// 设置 RETURNING *(返回所有列)
1896    pub fn returning_all(mut self) -> Self {
1897        self.returning = Some(vec!["*".to_string()]);
1898        self
1899    }
1900
1901    /// 构建 INSERT SQL(无方言,硬编码反引号,MySQL 风格)
1902    ///
1903    /// # 安全性(门禁 9 修复)
1904    ///
1905    /// 标识符经 `quote_ident()` 转义后包裹反引号,防止含 `` ` `` 的恶意标识符逃逸。
1906    ///
1907    /// # Upsert 行为
1908    ///
1909    /// - `Replace`:生成 `REPLACE INTO` 代替 `INSERT INTO`
1910    /// - `OnDuplicateKeyUpdate`:追加 `ON DUPLICATE KEY UPDATE` 子句
1911    /// - `OnConflictDoNothing`/`OnConflictDoUpdate`:跳过(MySQL 不支持)
1912    pub fn build(self) -> String {
1913        let table = self.table.unwrap_or_default();
1914        if table.is_empty() || self.columns.is_empty() {
1915            return String::new();
1916        }
1917
1918        let cols: Vec<String> = self.columns.iter().map(|c| quote_ident(c)).collect();
1919        let vals: Vec<String> = self.values.iter().map(|v| v.to_string()).collect();
1920
1921        // 根据是否 REPLACE 策略选择关键字
1922        let verb = match &self.upsert {
1923            UpsertStrategy::Replace => "REPLACE INTO",
1924            _ => "INSERT INTO",
1925        };
1926        let mut sql = format!(
1927            "{} {} ({}) VALUES ({})",
1928            verb,
1929            quote_ident(&table),
1930            cols.join(", "),
1931            vals.join(", ")
1932        );
1933
1934        // MySQL 风格下仅支持 ON DUPLICATE KEY UPDATE
1935        if let UpsertStrategy::OnDuplicateKeyUpdate(assignments) = &self.upsert {
1936            let sets = assignments
1937                .iter()
1938                .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
1939                .collect::<Vec<_>>()
1940                .join(", ");
1941            sql.push_str(&format!(" ON DUPLICATE KEY UPDATE {}", sets));
1942        }
1943
1944        sql
1945    }
1946
1947    /// 按指定方言生成 SQL
1948    ///
1949    /// # Upsert 方言兼容性
1950    ///
1951    /// - MySQL 家族:支持 `OnDuplicateKeyUpdate`、`Replace`
1952    /// - PostgreSQL/SQLite:支持 `OnConflictDoNothing`、`OnConflictDoUpdate`
1953    /// - 不兼容的组合会跳过 upsert 子句(不报错)
1954    pub fn build_with_dialect(self, db_type: DbType) -> String {
1955        let dialect = match sz_orm_core::get_dialect(db_type) {
1956            Ok(d) => d,
1957            Err(_) => return String::new(),
1958        };
1959
1960        let table = self.table.unwrap_or_default();
1961        if table.is_empty() || self.columns.is_empty() {
1962            return String::new();
1963        }
1964
1965        let cols: Vec<String> = self.columns.iter().map(|c| dialect.quote(c)).collect();
1966
1967        // 根据是否 REPLACE 策略和方言选择关键字
1968        let verb = match (&self.upsert, db_type) {
1969            (UpsertStrategy::Replace, dt) if is_mysql_family(dt) => "REPLACE INTO",
1970            (UpsertStrategy::Replace, DbType::Sqlite) => "INSERT OR REPLACE INTO",
1971            _ => "INSERT INTO",
1972        };
1973        let mut sql = format!(
1974            "{} {} ({}) VALUES ({})",
1975            verb,
1976            dialect.quote(&table),
1977            cols.join(", "),
1978            self.values.join(", ")
1979        );
1980
1981        // 追加 upsert 子句(根据方言)
1982        if let Some(clause) = render_upsert_clause(&self.upsert, db_type) {
1983            sql.push(' ');
1984            sql.push_str(&clause);
1985        }
1986
1987        // 追加 RETURNING 子句(PostgreSQL/SQLite 支持,MySQL 跳过)
1988        if let Some(clause) = render_returning_clause(&self.returning, db_type) {
1989            sql.push(' ');
1990            sql.push_str(&clause);
1991        }
1992
1993        sql
1994    }
1995}
1996
1997/// UPDATE 查询构造器
1998#[derive(Debug, Clone, Default)]
1999pub struct UpdateQuery {
2000    table: Option<String>,
2001    sets: Vec<(String, String)>,
2002    wheres: Vec<String>,
2003    /// 参数化 WHERE 条件(P0 修复:SQL 注入防护)
2004    param_wheres: Vec<ParamWhere>,
2005    /// RETURNING 子句列列表(PostgreSQL/SQLite 支持,MySQL 不支持)
2006    returning: Option<Vec<String>>,
2007}
2008
2009impl UpdateQuery {
2010    /// 创建空的 UPDATE 查询
2011    pub fn new() -> Self {
2012        Self::default()
2013    }
2014
2015    /// 设置目标表
2016    pub fn table(mut self, table: &str) -> Self {
2017        self.table = Some(table.to_string());
2018        self
2019    }
2020
2021    /// 添加 SET 赋值(值应为已转义的 SQL 字面量)
2022    pub fn set(mut self, column: &str, value: &str) -> Self {
2023        self.sets.push((column.to_string(), value.to_string()));
2024        self
2025    }
2026
2027    /// 批量添加 SET 赋值
2028    pub fn sets(mut self, pairs: &[(&str, &str)]) -> Self {
2029        for (c, v) in pairs {
2030            self.sets.push((c.to_string(), v.to_string()));
2031        }
2032        self
2033    }
2034
2035    /// 添加 WHERE 条件
2036    ///
2037    /// # 安全性(v0.2.2 修复 C-6)
2038    ///
2039    /// 调用 `check_where_injection` 检测高危模式。
2040    pub fn where_clause(mut self, condition: &str) -> Self {
2041        check_where_injection(condition);
2042        self.wheres.push(condition.to_string());
2043        self
2044    }
2045
2046    /// 添加 `column = ?` AND 条件(P0 修复:参数化绑定)
2047    pub fn where_eq(mut self, column: &str, value: Value) -> Self {
2048        self.param_wheres.push(ParamWhere::And {
2049            column: column.to_string(),
2050            op: " = ?".to_string(),
2051            values: vec![value],
2052        });
2053        self
2054    }
2055
2056    /// 添加 `column <> ?` AND 条件
2057    pub fn where_ne(mut self, column: &str, value: Value) -> Self {
2058        self.param_wheres.push(ParamWhere::And {
2059            column: column.to_string(),
2060            op: " <> ?".to_string(),
2061            values: vec![value],
2062        });
2063        self
2064    }
2065
2066    /// 添加 `column > ?` AND 条件
2067    pub fn where_gt(mut self, column: &str, value: Value) -> Self {
2068        self.param_wheres.push(ParamWhere::And {
2069            column: column.to_string(),
2070            op: " > ?".to_string(),
2071            values: vec![value],
2072        });
2073        self
2074    }
2075
2076    /// 添加 `column >= ?` AND 条件
2077    pub fn where_ge(mut self, column: &str, value: Value) -> Self {
2078        self.param_wheres.push(ParamWhere::And {
2079            column: column.to_string(),
2080            op: " >= ?".to_string(),
2081            values: vec![value],
2082        });
2083        self
2084    }
2085
2086    /// 添加 `column < ?` AND 条件
2087    pub fn where_lt(mut self, column: &str, value: Value) -> Self {
2088        self.param_wheres.push(ParamWhere::And {
2089            column: column.to_string(),
2090            op: " < ?".to_string(),
2091            values: vec![value],
2092        });
2093        self
2094    }
2095
2096    /// 添加 `column <= ?` AND 条件
2097    pub fn where_le(mut self, column: &str, value: Value) -> Self {
2098        self.param_wheres.push(ParamWhere::And {
2099            column: column.to_string(),
2100            op: " <= ?".to_string(),
2101            values: vec![value],
2102        });
2103        self
2104    }
2105
2106    /// 添加 `column LIKE ?` AND 条件
2107    pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2108        self.param_wheres.push(ParamWhere::And {
2109            column: column.to_string(),
2110            op: " LIKE ?".to_string(),
2111            values: vec![pattern],
2112        });
2113        self
2114    }
2115
2116    /// 添加 `column IN (?, ?, ...)` AND 条件
2117    pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2118        let (column, op) = if values.is_empty() {
2119            (String::new(), "1 = 0".to_string())
2120        } else {
2121            let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2122            (
2123                column.to_string(),
2124                format!(" IN ({})", placeholders.join(", ")),
2125            )
2126        };
2127        self.param_wheres
2128            .push(ParamWhere::And { column, op, values });
2129        self
2130    }
2131
2132    /// 添加 `column BETWEEN ? AND ?` AND 条件
2133    pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2134        self.param_wheres.push(ParamWhere::And {
2135            column: column.to_string(),
2136            op: " BETWEEN ? AND ?".to_string(),
2137            values: vec![low, high],
2138        });
2139        self
2140    }
2141
2142    /// 添加 `column IS NULL` AND 条件
2143    pub fn where_null(mut self, column: &str) -> Self {
2144        self.param_wheres.push(ParamWhere::And {
2145            column: column.to_string(),
2146            op: " IS NULL".to_string(),
2147            values: vec![],
2148        });
2149        self
2150    }
2151
2152    /// 添加 `column IS NOT NULL` AND 条件
2153    pub fn where_not_null(mut self, column: &str) -> Self {
2154        self.param_wheres.push(ParamWhere::And {
2155            column: column.to_string(),
2156            op: " IS NOT NULL".to_string(),
2157            values: vec![],
2158        });
2159        self
2160    }
2161
2162    /// 设置 RETURNING 子句(PostgreSQL/SQLite 3.35+ 支持)
2163    ///
2164    /// 在 UPDATE 后返回指定列的新值。MySQL 不支持 RETURNING,
2165    /// 在 `build()`(MySQL 风格)和 `build_with_dialect()` 的 MySQL 方言下被忽略。
2166    pub fn returning(mut self, columns: &[&str]) -> Self {
2167        self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2168        self
2169    }
2170
2171    /// 设置 RETURNING *(返回所有列)
2172    pub fn returning_all(mut self) -> Self {
2173        self.returning = Some(vec!["*".to_string()]);
2174        self
2175    }
2176
2177    /// 生成 SQL
2178    ///
2179    /// # 安全性(门禁 9 修复)
2180    ///
2181    /// 表名和列名经 `quote_ident()` 转义后包裹反引号,防止含 `` ` `` 的恶意标识符逃逸。
2182    pub fn build(self) -> String {
2183        let table = self.table.unwrap_or_default();
2184        if table.is_empty() || self.sets.is_empty() {
2185            return String::new();
2186        }
2187
2188        let set_str: Vec<String> = self
2189            .sets
2190            .iter()
2191            .map(|(c, v)| format!("{} = {}", quote_ident(c), v))
2192            .collect();
2193
2194        let mut sql = format!("UPDATE {} SET {}", quote_ident(&table), set_str.join(", "));
2195
2196        if !self.wheres.is_empty() {
2197            sql.push_str(" WHERE ");
2198            sql.push_str(&self.wheres.join(" AND "));
2199        }
2200
2201        sql
2202    }
2203
2204    /// 按指定方言生成 SQL
2205    pub fn build_with_dialect(self, db_type: DbType) -> String {
2206        let dialect = match sz_orm_core::get_dialect(db_type) {
2207            Ok(d) => d,
2208            Err(_) => return String::new(),
2209        };
2210
2211        let table = self.table.unwrap_or_default();
2212        if table.is_empty() || self.sets.is_empty() {
2213            return String::new();
2214        }
2215
2216        let set_str: Vec<String> = self
2217            .sets
2218            .iter()
2219            .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2220            .collect();
2221
2222        let mut sql = format!(
2223            "UPDATE {} SET {}",
2224            dialect.quote(&table),
2225            set_str.join(", ")
2226        );
2227
2228        if !self.wheres.is_empty() {
2229            sql.push_str(" WHERE ");
2230            sql.push_str(&self.wheres.join(" AND "));
2231        }
2232
2233        // 追加 RETURNING 子句(PostgreSQL/SQLite 支持,MySQL 跳过)
2234        if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2235            sql.push(' ');
2236            sql.push_str(&clause);
2237        }
2238
2239        sql
2240    }
2241
2242    /// 生成带参数的 SQL(参数化查询,P0 修复:SQL 注入防护)
2243    ///
2244    /// WHERE 条件可来自 `where_eq`/`where_in`/`where_between` 等参数化 API,
2245    /// 用户输入作为参数绑定,而非字符串拼接。
2246    /// 注意:SET 值仍为原始字符串,如需参数化 SET 请使用 ORM 的 `save()` 接口。
2247    pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2248        let dialect = match sz_orm_core::get_dialect(db_type) {
2249            Ok(d) => d,
2250            Err(_) => return BuiltQuery::default(),
2251        };
2252
2253        let table = self.table.unwrap_or_default();
2254        if table.is_empty() || self.sets.is_empty() {
2255            return BuiltQuery::default();
2256        }
2257
2258        let set_str: Vec<String> = self
2259            .sets
2260            .iter()
2261            .map(|(c, v)| format!("{} = {}", dialect.quote(c), v))
2262            .collect();
2263
2264        let mut sql = format!(
2265            "UPDATE {} SET {}",
2266            dialect.quote(&table),
2267            set_str.join(", ")
2268        );
2269        let mut params: Vec<Value> = Vec::new();
2270
2271        let has_raw = !self.wheres.is_empty();
2272        let has_param = !self.param_wheres.is_empty();
2273        if has_raw || has_param {
2274            sql.push_str(" WHERE ");
2275            let mut first = true;
2276            for w in &self.wheres {
2277                if first {
2278                    sql.push_str(w);
2279                    first = false;
2280                } else {
2281                    sql.push_str(" AND ");
2282                    sql.push_str(w);
2283                }
2284            }
2285            for pw in &self.param_wheres {
2286                let (conjunction, column, op, vals) = match pw {
2287                    ParamWhere::And { column, op, values } => ("AND", column, op, values),
2288                    ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2289                };
2290                // 按目标方言引用列名(PostgreSQL 双引号、MySQL 反引号)
2291                let expr = if column.is_empty() {
2292                    op.clone()
2293                } else {
2294                    format!("{}{}", quote_column_dialect(&*dialect, column), op)
2295                };
2296                if first {
2297                    sql.push_str(&expr);
2298                    first = false;
2299                } else {
2300                    sql.push(' ');
2301                    sql.push_str(conjunction);
2302                    sql.push(' ');
2303                    sql.push_str(&expr);
2304                }
2305                params.extend(vals.iter().cloned());
2306            }
2307        }
2308
2309        // 追加 RETURNING 子句(PostgreSQL/SQLite 支持,MySQL 跳过)
2310        if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2311            sql.push(' ');
2312            sql.push_str(&clause);
2313        }
2314
2315        BuiltQuery { sql, params }
2316    }
2317}
2318
2319/// DELETE 查询构造器
2320#[derive(Debug, Clone, Default)]
2321pub struct DeleteQuery {
2322    table: Option<String>,
2323    wheres: Vec<String>,
2324    /// 参数化 WHERE 条件(P0 修复:SQL 注入防护)
2325    param_wheres: Vec<ParamWhere>,
2326    /// RETURNING 子句列列表(PostgreSQL/SQLite 支持,MySQL 不支持)
2327    returning: Option<Vec<String>>,
2328}
2329
2330impl DeleteQuery {
2331    /// 创建空的 DELETE 查询
2332    pub fn new() -> Self {
2333        Self::default()
2334    }
2335
2336    /// 设置目标表
2337    pub fn from_table(mut self, table: &str) -> Self {
2338        self.table = Some(table.to_string());
2339        self
2340    }
2341
2342    /// 添加 WHERE 条件
2343    ///
2344    /// # 安全性(v0.2.2 修复 C-6)
2345    ///
2346    /// 调用 `check_where_injection` 检测高危模式。
2347    pub fn where_clause(mut self, condition: &str) -> Self {
2348        check_where_injection(condition);
2349        self.wheres.push(condition.to_string());
2350        self
2351    }
2352
2353    /// 添加 `column = ?` AND 条件(P0 修复:参数化绑定)
2354    pub fn where_eq(mut self, column: &str, value: Value) -> Self {
2355        self.param_wheres.push(ParamWhere::And {
2356            column: column.to_string(),
2357            op: " = ?".to_string(),
2358            values: vec![value],
2359        });
2360        self
2361    }
2362
2363    /// 添加 `column <> ?` AND 条件
2364    pub fn where_ne(mut self, column: &str, value: Value) -> Self {
2365        self.param_wheres.push(ParamWhere::And {
2366            column: column.to_string(),
2367            op: " <> ?".to_string(),
2368            values: vec![value],
2369        });
2370        self
2371    }
2372
2373    /// 添加 `column > ?` AND 条件
2374    pub fn where_gt(mut self, column: &str, value: Value) -> Self {
2375        self.param_wheres.push(ParamWhere::And {
2376            column: column.to_string(),
2377            op: " > ?".to_string(),
2378            values: vec![value],
2379        });
2380        self
2381    }
2382
2383    /// 添加 `column >= ?` AND 条件
2384    pub fn where_ge(mut self, column: &str, value: Value) -> Self {
2385        self.param_wheres.push(ParamWhere::And {
2386            column: column.to_string(),
2387            op: " >= ?".to_string(),
2388            values: vec![value],
2389        });
2390        self
2391    }
2392
2393    /// 添加 `column < ?` AND 条件
2394    pub fn where_lt(mut self, column: &str, value: Value) -> Self {
2395        self.param_wheres.push(ParamWhere::And {
2396            column: column.to_string(),
2397            op: " < ?".to_string(),
2398            values: vec![value],
2399        });
2400        self
2401    }
2402
2403    /// 添加 `column <= ?` AND 条件
2404    pub fn where_le(mut self, column: &str, value: Value) -> Self {
2405        self.param_wheres.push(ParamWhere::And {
2406            column: column.to_string(),
2407            op: " <= ?".to_string(),
2408            values: vec![value],
2409        });
2410        self
2411    }
2412
2413    /// 添加 `column LIKE ?` AND 条件
2414    pub fn where_like(mut self, column: &str, pattern: Value) -> Self {
2415        self.param_wheres.push(ParamWhere::And {
2416            column: column.to_string(),
2417            op: " LIKE ?".to_string(),
2418            values: vec![pattern],
2419        });
2420        self
2421    }
2422
2423    /// 添加 `column IN (?, ?, ...)` AND 条件
2424    pub fn where_in(mut self, column: &str, values: Vec<Value>) -> Self {
2425        let (column, op) = if values.is_empty() {
2426            (String::new(), "1 = 0".to_string())
2427        } else {
2428            let placeholders: Vec<&str> = std::iter::repeat_n("?", values.len()).collect();
2429            (
2430                column.to_string(),
2431                format!(" IN ({})", placeholders.join(", ")),
2432            )
2433        };
2434        self.param_wheres
2435            .push(ParamWhere::And { column, op, values });
2436        self
2437    }
2438
2439    /// 添加 `column BETWEEN ? AND ?` AND 条件
2440    pub fn where_between(mut self, column: &str, low: Value, high: Value) -> Self {
2441        self.param_wheres.push(ParamWhere::And {
2442            column: column.to_string(),
2443            op: " BETWEEN ? AND ?".to_string(),
2444            values: vec![low, high],
2445        });
2446        self
2447    }
2448
2449    /// 添加 `column IS NULL` AND 条件
2450    pub fn where_null(mut self, column: &str) -> Self {
2451        self.param_wheres.push(ParamWhere::And {
2452            column: column.to_string(),
2453            op: " IS NULL".to_string(),
2454            values: vec![],
2455        });
2456        self
2457    }
2458
2459    /// 添加 `column IS NOT NULL` AND 条件
2460    pub fn where_not_null(mut self, column: &str) -> Self {
2461        self.param_wheres.push(ParamWhere::And {
2462            column: column.to_string(),
2463            op: " IS NOT NULL".to_string(),
2464            values: vec![],
2465        });
2466        self
2467    }
2468
2469    /// 设置 RETURNING 子句(PostgreSQL/SQLite 3.35+ 支持)
2470    ///
2471    /// 在 DELETE 后返回被删除行的指定列值。MySQL 不支持 RETURNING,
2472    /// 在 `build()`(MySQL 风格)和 `build_with_dialect()` 的 MySQL 方言下被忽略。
2473    pub fn returning(mut self, columns: &[&str]) -> Self {
2474        self.returning = Some(columns.iter().map(|s| s.to_string()).collect());
2475        self
2476    }
2477
2478    /// 设置 RETURNING *(返回所有列)
2479    pub fn returning_all(mut self) -> Self {
2480        self.returning = Some(vec!["*".to_string()]);
2481        self
2482    }
2483
2484    /// 生成 SQL
2485    ///
2486    /// # 安全性(门禁 9 修复)
2487    ///
2488    /// 表名经 `quote_ident()` 转义后包裹反引号,防止含 `` ` `` 的恶意表名逃逸。
2489    pub fn build(self) -> String {
2490        let table = self.table.unwrap_or_default();
2491        if table.is_empty() {
2492            return String::new();
2493        }
2494
2495        let mut sql = format!("DELETE FROM {}", quote_ident(&table));
2496
2497        if !self.wheres.is_empty() {
2498            sql.push_str(" WHERE ");
2499            sql.push_str(&self.wheres.join(" AND "));
2500        }
2501
2502        sql
2503    }
2504
2505    /// 按指定方言生成 SQL
2506    pub fn build_with_dialect(self, db_type: DbType) -> String {
2507        let dialect = match sz_orm_core::get_dialect(db_type) {
2508            Ok(d) => d,
2509            Err(_) => return String::new(),
2510        };
2511
2512        let table = self.table.unwrap_or_default();
2513        if table.is_empty() {
2514            return String::new();
2515        }
2516
2517        let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2518
2519        if !self.wheres.is_empty() {
2520            sql.push_str(" WHERE ");
2521            sql.push_str(&self.wheres.join(" AND "));
2522        }
2523
2524        // 追加 RETURNING 子句(PostgreSQL/SQLite 支持,MySQL 跳过)
2525        if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2526            sql.push(' ');
2527            sql.push_str(&clause);
2528        }
2529
2530        sql
2531    }
2532
2533    /// 生成带参数的 SQL(参数化查询,P0 修复:SQL 注入防护)
2534    ///
2535    /// WHERE 条件可来自 `where_eq`/`where_in`/`where_between` 等参数化 API,
2536    /// 用户输入作为参数绑定,而非字符串拼接。
2537    pub fn build_with_params(self, db_type: DbType) -> BuiltQuery {
2538        let dialect = match sz_orm_core::get_dialect(db_type) {
2539            Ok(d) => d,
2540            Err(_) => return BuiltQuery::default(),
2541        };
2542
2543        let table = self.table.unwrap_or_default();
2544        if table.is_empty() {
2545            return BuiltQuery::default();
2546        }
2547
2548        let mut sql = format!("DELETE FROM {}", dialect.quote(&table));
2549        let mut params: Vec<Value> = Vec::new();
2550
2551        let has_raw = !self.wheres.is_empty();
2552        let has_param = !self.param_wheres.is_empty();
2553        if has_raw || has_param {
2554            sql.push_str(" WHERE ");
2555            let mut first = true;
2556            for w in &self.wheres {
2557                if first {
2558                    sql.push_str(w);
2559                    first = false;
2560                } else {
2561                    sql.push_str(" AND ");
2562                    sql.push_str(w);
2563                }
2564            }
2565            for pw in &self.param_wheres {
2566                let (conjunction, column, op, vals) = match pw {
2567                    ParamWhere::And { column, op, values } => ("AND", column, op, values),
2568                    ParamWhere::Or { column, op, values } => ("OR", column, op, values),
2569                };
2570                // 按目标方言引用列名(PostgreSQL 双引号、MySQL 反引号)
2571                let expr = if column.is_empty() {
2572                    op.clone()
2573                } else {
2574                    format!("{}{}", quote_column_dialect(&*dialect, column), op)
2575                };
2576                if first {
2577                    sql.push_str(&expr);
2578                    first = false;
2579                } else {
2580                    sql.push(' ');
2581                    sql.push_str(conjunction);
2582                    sql.push(' ');
2583                    sql.push_str(&expr);
2584                }
2585                params.extend(vals.iter().cloned());
2586            }
2587        }
2588
2589        // 追加 RETURNING 子句(PostgreSQL/SQLite 支持,MySQL 跳过)
2590        if let Some(clause) = render_returning_clause(&self.returning, db_type) {
2591            sql.push(' ');
2592            sql.push_str(&clause);
2593        }
2594
2595        BuiltQuery { sql, params }
2596    }
2597}
2598
2599#[cfg(test)]
2600#[allow(deprecated)]
2601mod tests {
2602    use super::*;
2603
2604    // ---- Query::select 测试 ----
2605
2606    #[test]
2607    fn test_select_basic() {
2608        let sql = Query::select()
2609            .column("id")
2610            .column("name")
2611            .from("users")
2612            .build(DbType::MySQL);
2613        assert!(sql.starts_with("SELECT "));
2614        assert!(sql.contains("`id`"));
2615        assert!(sql.contains("`name`"));
2616        assert!(sql.contains("FROM `users`"));
2617    }
2618
2619    #[test]
2620    fn test_select_star() {
2621        let sql = Query::select()
2622            .all_columns()
2623            .from("users")
2624            .build(DbType::MySQL);
2625        assert!(sql.contains("SELECT *"));
2626        assert!(sql.contains("FROM `users`"));
2627    }
2628
2629    #[test]
2630    fn test_select_distinct() {
2631        let sql = Query::select()
2632            .distinct()
2633            .column("name")
2634            .from("users")
2635            .build(DbType::MySQL);
2636        assert!(sql.contains("SELECT DISTINCT"));
2637    }
2638
2639    #[test]
2640    fn test_select_with_where() {
2641        let sql = Query::select()
2642            .column("id")
2643            .from("users")
2644            .where_clause("age > 18")
2645            .where_clause("status = 'active'")
2646            .build(DbType::MySQL);
2647        assert!(sql.contains("WHERE age > 18 AND status = 'active'"));
2648    }
2649
2650    #[test]
2651
2652    fn test_select_with_inner_join() {
2653        let sql = Query::select()
2654            .column("u.id")
2655            .from("users u")
2656            .inner_join("orders o", "u.id = o.user_id")
2657            .build(DbType::MySQL);
2658        assert!(sql.contains("INNER JOIN `orders` o ON u.id = o.user_id"));
2659    }
2660
2661    #[test]
2662    fn test_select_with_left_join() {
2663        let sql = Query::select()
2664            .column("u.id")
2665            .from("users u")
2666            .left_join("profiles p", "u.id = p.user_id")
2667            .build(DbType::MySQL);
2668        assert!(sql.contains("LEFT JOIN `profiles` p ON u.id = p.user_id"));
2669    }
2670
2671    #[test]
2672    fn test_select_with_order_by() {
2673        let sql = Query::select()
2674            .column("id")
2675            .from("users")
2676            .order_by("created_at", true)
2677            .order_by("id", false)
2678            .build(DbType::MySQL);
2679        assert!(sql.contains("ORDER BY `created_at` ASC, `id` DESC"));
2680    }
2681
2682    #[test]
2683    fn test_select_with_limit_offset() {
2684        let sql = Query::select()
2685            .column("id")
2686            .from("users")
2687            .limit(10)
2688            .offset(20)
2689            .build(DbType::MySQL);
2690        assert!(sql.contains("LIMIT 10"));
2691        assert!(sql.contains("OFFSET 20"));
2692    }
2693
2694    #[test]
2695    fn test_select_paginate() {
2696        let sql = Query::select()
2697            .column("id")
2698            .from("users")
2699            .paginate(3, 20)
2700            .build(DbType::MySQL);
2701        // page 3, size 20 -> offset = (3-1)*20 = 40
2702        assert!(sql.contains("LIMIT 20"));
2703        assert!(sql.contains("OFFSET 40"));
2704    }
2705
2706    #[test]
2707    fn test_select_with_group_by_having() {
2708        let sql = Query::select()
2709            .column("status")
2710            .from("users")
2711            .group_by("status")
2712            .having("COUNT(*) > 5")
2713            .build(DbType::MySQL);
2714        assert!(sql.contains("GROUP BY `status`"));
2715        assert!(sql.contains("HAVING COUNT(*) > 5"));
2716    }
2717
2718    #[test]
2719    fn test_select_postgres_dialect() {
2720        let sql = Query::select()
2721            .column("id")
2722            .from("users")
2723            .build(DbType::PostgreSQL);
2724        assert!(sql.contains("\"id\""));
2725        assert!(sql.contains("FROM \"users\""));
2726    }
2727
2728    #[test]
2729    fn test_select_sqlite_dialect() {
2730        let sql = Query::select()
2731            .column("id")
2732            .from("users")
2733            .build(DbType::Sqlite);
2734        assert!(sql.contains("\"id\""));
2735    }
2736
2737    #[test]
2738    fn test_select_multiple_joins() {
2739        let sql = Query::select()
2740            .column("u.id")
2741            .from("users u")
2742            .inner_join("orders o", "u.id = o.user_id")
2743            .left_join("profiles p", "u.id = p.user_id")
2744            .build(DbType::MySQL);
2745        assert!(sql.contains("INNER JOIN `orders` o"));
2746        assert!(sql.contains("LEFT JOIN `profiles` p"));
2747    }
2748
2749    #[test]
2750    fn test_select_columns_multiple() {
2751        let sql = Query::select()
2752            .columns(&["id", "name", "email"])
2753            .from("users")
2754            .build(DbType::MySQL);
2755        assert!(sql.contains("`id`, `name`, `email`"));
2756    }
2757
2758    #[test]
2759    fn test_select_no_columns_defaults_star() {
2760        let sql = Query::select().from("users").build(DbType::MySQL);
2761        assert!(sql.contains("SELECT *"));
2762    }
2763
2764    // ---- Query::insert 测试 ----
2765
2766    #[test]
2767    fn test_insert_basic() {
2768        let sql = Query::insert()
2769            .into_table("users")
2770            .value("name", "'Alice'")
2771            .value("age", "30")
2772            .build();
2773        assert!(sql.starts_with("INSERT INTO `users`"));
2774        assert!(sql.contains("`name`, `age`"));
2775        assert!(sql.contains("'Alice', 30"));
2776    }
2777
2778    #[test]
2779    fn test_insert_values_batch() {
2780        let sql = Query::insert()
2781            .into_table("users")
2782            .values(&[("name", "'Bob'"), ("age", "25"), ("email", "'bob@x.com'")])
2783            .build();
2784        assert!(sql.contains("`name`, `age`, `email`"));
2785        assert!(sql.contains("'Bob', 25, 'bob@x.com'"));
2786    }
2787
2788    #[test]
2789    fn test_insert_empty_returns_empty() {
2790        let sql = Query::insert().into_table("users").build();
2791        assert_eq!(sql, "");
2792    }
2793
2794    #[test]
2795    fn test_insert_with_dialect() {
2796        let sql = Query::insert()
2797            .into_table("users")
2798            .value("name", "'Alice'")
2799            .build_with_dialect(DbType::PostgreSQL);
2800        assert!(sql.contains("\"name\""));
2801        assert!(sql.contains("\"users\""));
2802    }
2803
2804    // ---- Query::update 测试 ----
2805
2806    #[test]
2807    fn test_update_basic() {
2808        let sql = Query::update()
2809            .table("users")
2810            .set("name", "'Bob'")
2811            .where_clause("id = 1")
2812            .build();
2813        assert!(sql.starts_with("UPDATE `users` SET"));
2814        assert!(sql.contains("`name` = 'Bob'"));
2815        assert!(sql.contains("WHERE id = 1"));
2816    }
2817
2818    #[test]
2819    fn test_update_multiple_sets() {
2820        let sql = Query::update()
2821            .table("users")
2822            .sets(&[("name", "'Bob'"), ("age", "30")])
2823            .where_clause("id = 1")
2824            .build();
2825        assert!(sql.contains("`name` = 'Bob', `age` = 30"));
2826    }
2827
2828    #[test]
2829    fn test_update_no_where() {
2830        let sql = Query::update()
2831            .table("users")
2832            .set("status", "'active'")
2833            .build();
2834        assert!(sql.contains("UPDATE `users` SET `status` = 'active'"));
2835        assert!(!sql.contains("WHERE"));
2836    }
2837
2838    #[test]
2839    fn test_update_empty_returns_empty() {
2840        let sql = Query::update().table("users").build();
2841        assert_eq!(sql, "");
2842    }
2843
2844    #[test]
2845    fn test_update_with_dialect() {
2846        let sql = Query::update()
2847            .table("users")
2848            .set("name", "'Bob'")
2849            .build_with_dialect(DbType::PostgreSQL);
2850        assert!(sql.contains("\"users\""));
2851        assert!(sql.contains("\"name\""));
2852    }
2853
2854    // ---- Query::delete 测试 ----
2855
2856    #[test]
2857    fn test_delete_basic() {
2858        let sql = Query::delete()
2859            .from_table("users")
2860            .where_clause("id = 1")
2861            .build();
2862        assert!(sql.starts_with("DELETE FROM `users`"));
2863        assert!(sql.contains("WHERE id = 1"));
2864    }
2865
2866    #[test]
2867    fn test_delete_no_where() {
2868        let sql = Query::delete().from_table("users").build();
2869        assert!(sql.contains("DELETE FROM `users`"));
2870        assert!(!sql.contains("WHERE"));
2871    }
2872
2873    #[test]
2874    fn test_delete_multiple_wheres() {
2875        let sql = Query::delete()
2876            .from_table("users")
2877            .where_clause("id > 100")
2878            .where_clause("status = 'inactive'")
2879            .build();
2880        assert!(sql.contains("WHERE id > 100 AND status = 'inactive'"));
2881    }
2882
2883    #[test]
2884    fn test_delete_empty_returns_empty() {
2885        let sql = Query::delete().build();
2886        assert_eq!(sql, "");
2887    }
2888
2889    #[test]
2890    fn test_delete_with_dialect() {
2891        let sql = Query::delete()
2892            .from_table("users")
2893            .where_clause("id = 1")
2894            .build_with_dialect(DbType::PostgreSQL);
2895        assert!(sql.contains("\"users\""));
2896    }
2897
2898    // ---- 完整流程测试 ----
2899
2900    #[test]
2901    fn test_full_crud_flow() {
2902        // CREATE (用 INSERT 模拟)
2903        let insert = Query::insert()
2904            .into_table("users")
2905            .value("name", "'Alice'")
2906            .value("age", "30")
2907            .build();
2908        assert!(insert.contains("INSERT INTO"));
2909
2910        // READ
2911        let select = Query::select()
2912            .column("id")
2913            .column("name")
2914            .from("users")
2915            .where_clause("age > 18")
2916            .order_by("id", true)
2917            .limit(10)
2918            .build(DbType::MySQL);
2919        assert!(select.contains("SELECT"));
2920        assert!(select.contains("FROM"));
2921        assert!(select.contains("WHERE"));
2922        assert!(select.contains("ORDER BY"));
2923        assert!(select.contains("LIMIT"));
2924
2925        // UPDATE
2926        let update = Query::update()
2927            .table("users")
2928            .set("name", "'Bob'")
2929            .where_clause("id = 1")
2930            .build();
2931        assert!(update.contains("UPDATE"));
2932        assert!(update.contains("SET"));
2933        assert!(update.contains("WHERE"));
2934
2935        // DELETE
2936        let delete = Query::delete()
2937            .from_table("users")
2938            .where_clause("id = 1")
2939            .build();
2940        assert!(delete.contains("DELETE FROM"));
2941    }
2942
2943    #[test]
2944    fn test_complex_select_query() {
2945        let sql = Query::select()
2946            .distinct()
2947            .columns(&["u.id", "u.name", "o.total"])
2948            .from("users u")
2949            .inner_join("orders o", "u.id = o.user_id")
2950            .where_clause("u.status = 'active'")
2951            .where_clause("o.total > 100")
2952            .group_by("u.id")
2953            .having("SUM(o.total) > 1000")
2954            .order_by("u.id", true)
2955            .limit(20)
2956            .offset(40)
2957            .build(DbType::MySQL);
2958
2959        assert!(sql.contains("SELECT DISTINCT"));
2960        assert!(sql.contains("INNER JOIN `orders` o"));
2961        assert!(sql.contains("WHERE u.status = 'active' AND o.total > 100"));
2962        assert!(sql.contains("GROUP BY"));
2963        assert!(sql.contains("HAVING SUM(o.total) > 1000"));
2964        assert!(sql.contains("ORDER BY `u`.`id` ASC"));
2965        assert!(sql.contains("LIMIT 20"));
2966        assert!(sql.contains("OFFSET 40"));
2967    }
2968
2969    // ---- v0.2.2 修复 C-6:SQL 注入测试 ----
2970
2971    #[test]
2972    #[should_panic(expected = "SQL injection detected")]
2973    fn test_select_where_rejects_semicolon_drop() {
2974        let _ = Query::select()
2975            .column("id")
2976            .from("users")
2977            .where_clause("1=1; DROP TABLE users")
2978            .build(DbType::MySQL);
2979    }
2980
2981    #[test]
2982    #[should_panic(expected = "SQL injection detected")]
2983    fn test_select_where_rejects_semicolon_space_drop() {
2984        let _ = Query::select()
2985            .column("id")
2986            .from("users")
2987            .where_clause("1=1; DROP TABLE users")
2988            .build(DbType::MySQL);
2989    }
2990
2991    #[test]
2992    #[should_panic(expected = "SQL injection detected")]
2993    fn test_select_where_rejects_line_comment() {
2994        let _ = Query::select()
2995            .column("id")
2996            .from("users")
2997            .where_clause("id = 1 -- DROP TABLE users")
2998            .build(DbType::MySQL);
2999    }
3000
3001    #[test]
3002    #[should_panic(expected = "SQL injection detected")]
3003    fn test_select_where_rejects_block_comment() {
3004        let _ = Query::select()
3005            .column("id")
3006            .from("users")
3007            .where_clause("id = 1 /* comment */ OR 1=1")
3008            .build(DbType::MySQL);
3009    }
3010
3011    #[test]
3012    #[should_panic(expected = "SQL injection detected")]
3013    fn test_update_where_rejects_delete() {
3014        let _ = Query::update()
3015            .table("users")
3016            .set("name", "'x'")
3017            .where_clause("1=1; DELETE FROM users")
3018            .build();
3019    }
3020
3021    #[test]
3022    #[should_panic(expected = "SQL injection detected")]
3023    fn test_update_where_rejects_line_comment() {
3024        let _ = Query::update()
3025            .table("users")
3026            .set("name", "'x'")
3027            .where_clause("id = 1 -- bypass")
3028            .build();
3029    }
3030
3031    #[test]
3032    #[should_panic(expected = "SQL injection detected")]
3033    fn test_delete_where_rejects_drop() {
3034        let _ = Query::delete()
3035            .from_table("users")
3036            .where_clause("1=1; DROP TABLE users")
3037            .build();
3038    }
3039
3040    #[test]
3041    #[should_panic(expected = "SQL injection detected")]
3042    fn test_delete_where_rejects_block_comment() {
3043        let _ = Query::delete()
3044            .from_table("users")
3045            .where_clause("id = 1 /* */ OR 1=1")
3046            .build();
3047    }
3048
3049    #[test]
3050    #[should_panic(expected = "SQL injection detected")]
3051    fn test_delete_where_rejects_line_comment() {
3052        let _ = Query::delete()
3053            .from_table("users")
3054            .where_clause("id = 1--")
3055            .build();
3056    }
3057
3058    #[test]
3059    fn test_safe_where_clauses_pass() {
3060        // 这些是合法的 WHERE 条件,不应触发 panic——同时验证生成 SQL 包含预期的 WHERE 子句
3061        let sql_str = Query::select()
3062            .column("id")
3063            .from("users")
3064            .where_clause("age > 18")
3065            .where_clause("name = 'Alice;Bob'") // 分号在字符串字面量中
3066            .where_clause("id IN (1, 2, 3)")
3067            .where_clause("created_at > '2026-01-01'")
3068            .build(DbType::MySQL);
3069        assert!(!sql_str.is_empty(), "SELECT SQL 不应为空");
3070        assert!(sql_str.contains("age > 18"), "SELECT 应包含 age > 18 条件");
3071        assert!(
3072            sql_str.contains("name = 'Alice;Bob'"),
3073            "SELECT 应包含 name 条件(含分号字面量)"
3074        );
3075        assert!(sql_str.contains("id IN (1, 2, 3)"), "SELECT 应包含 IN 子句");
3076        assert!(
3077            sql_str.contains("created_at > '2026-01-01'"),
3078            "SELECT 应包含日期条件"
3079        );
3080
3081        let sql_str = Query::update()
3082            .table("users")
3083            .set("name", "'x'")
3084            .where_clause("id = 1")
3085            .build();
3086        assert!(!sql_str.is_empty(), "UPDATE SQL 不应为空");
3087        assert!(sql_str.contains("UPDATE"), "应为 UPDATE 语句");
3088        assert!(sql_str.contains("WHERE"), "UPDATE 应包含 WHERE 子句");
3089        assert!(sql_str.contains("id = 1"), "UPDATE WHERE 应包含 id = 1");
3090
3091        let sql_str = Query::delete()
3092            .from_table("users")
3093            .where_clause("id = 1")
3094            .build();
3095        assert!(!sql_str.is_empty(), "DELETE SQL 不应为空");
3096        assert!(sql_str.contains("DELETE"), "应为 DELETE 语句");
3097        assert!(sql_str.contains("WHERE"), "DELETE 应包含 WHERE 子句");
3098        assert!(sql_str.contains("id = 1"), "DELETE WHERE 应包含 id = 1");
3099    }
3100
3101    // ---- 变异测试专项补防测试(杀死存活的变异体) ----
3102    // 目标:覆盖 `||` → `&&` 变异(`check_where_injection` 和 `build_with_dialect` 守卫条件)
3103
3104    #[test]
3105    #[should_panic(expected = "SQL injection detected")]
3106    fn test_mutant_block_comment_open_only() {
3107        // 杀死:`contains("/*") || contains("*/")` → `&&` 变异
3108        // 仅含 `/*` 无 `*/`,`||` 应 panic,`&&` 不会 panic
3109        let _ = Query::select()
3110            .column("id")
3111            .from("users")
3112            .where_clause("id = 1 /* OR 1=1")
3113            .build(DbType::MySQL);
3114    }
3115
3116    #[test]
3117    #[should_panic(expected = "SQL injection detected")]
3118    fn test_mutant_block_comment_close_only() {
3119        // 仅含 `*/` 无 `/*`
3120        let _ = Query::select()
3121            .column("id")
3122            .from("users")
3123            .where_clause("id = 1 */")
3124            .build(DbType::MySQL);
3125    }
3126
3127    #[test]
3128    fn test_mutant_insert_dialect_table_no_columns_returns_empty() {
3129        // 杀死 `||` → `&&` 变异:有 table 无 columns → `||` 返回空,`&&` 不返回空
3130        let sql = Query::insert()
3131            .into_table("users")
3132            .build_with_dialect(DbType::MySQL);
3133        assert_eq!(sql, "", "有表无列时应返回空字符串");
3134    }
3135
3136    #[test]
3137    fn test_mutant_update_dialect_table_no_sets_returns_empty() {
3138        // 杀死 `||` → `&&` 变异
3139        let sql = Query::update()
3140            .table("users")
3141            .build_with_dialect(DbType::MySQL);
3142        assert_eq!(sql, "", "有表无 SET 时应返回空字符串");
3143    }
3144
3145    #[test]
3146    fn test_mutant_update_dialect_no_table_with_sets_returns_empty() {
3147        // 杀死 `delete !` 变异:无表有 SET → 应返回空
3148        let sql = Query::update()
3149            .set("name", "'x'")
3150            .build_with_dialect(DbType::MySQL);
3151        assert_eq!(sql, "", "无表有 SET 时应返回空字符串");
3152    }
3153
3154    #[test]
3155    fn test_mutant_delete_dialect_no_table_returns_empty() {
3156        // 杀死 `delete !` 变异
3157        let sql = Query::delete()
3158            .where_clause("id = 1")
3159            .build_with_dialect(DbType::MySQL);
3160        assert_eq!(sql, "", "无表时应返回空字符串");
3161    }
3162
3163    #[test]
3164    fn test_mutant_select_right_join() {
3165        // 杀死 `right_join -> Self` → `Default::default()` 变异
3166        let sql = Query::select()
3167            .column("u.id")
3168            .from("users u")
3169            .right_join("orders o", "u.id = o.user_id")
3170            .build(DbType::MySQL);
3171        assert!(sql.contains("RIGHT JOIN `orders` o ON u.id = o.user_id"));
3172    }
3173
3174    #[test]
3175    fn test_mutant_all_columns_with_extra() {
3176        // 杀死 `all_columns -> Self` → `Default::default()` 变异
3177        // 原始:`SELECT *, `extra` FROM`;变异体(default):`SELECT `extra` FROM`
3178        let sql = Query::select()
3179            .all_columns()
3180            .column("extra")
3181            .from("users")
3182            .build(DbType::MySQL);
3183        assert!(
3184            sql.contains("SELECT *, `extra` FROM `users`"),
3185            "all_columns + column 应在 SELECT 列表中同时包含 * 和 extra,实际: {sql}"
3186        );
3187    }
3188
3189    #[test]
3190    fn test_mutant_update_dialect_no_where_no_where_clause() {
3191        // 杀死 `delete !` 在 build_with_dialect WHERE 守卫
3192        // 原始:`if !self.wheres.is_empty()`,变异体:`if self.wheres.is_empty()`
3193        let sql = Query::update()
3194            .table("users")
3195            .set("name", "'x'")
3196            .build_with_dialect(DbType::MySQL);
3197        assert!(
3198            !sql.contains("WHERE"),
3199            "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3200        );
3201    }
3202
3203    #[test]
3204    fn test_mutant_delete_dialect_no_where_no_where_clause() {
3205        // 杀死 `delete !` 在 DeleteQuery::build_with_dialect WHERE 守卫
3206        let sql = Query::delete()
3207            .from_table("users")
3208            .build_with_dialect(DbType::MySQL);
3209        assert!(
3210            !sql.contains("WHERE"),
3211            "无 WHERE 条件时不应包含 WHERE 关键字,实际: {sql}"
3212        );
3213    }
3214
3215    // ==================== 深度扩展:CTE / 窗口函数 / 集合运算 / FOR UPDATE 测试 ====================
3216
3217    // ---- CTE 测试 ----
3218
3219    #[test]
3220    fn test_cte_single_with_clause() {
3221        let sql = Query::select()
3222            .column("id")
3223            .column("name")
3224            .from("active_users")
3225            .with_cte(
3226                "active_users",
3227                "SELECT * FROM users WHERE status = 'active'",
3228            )
3229            .build(DbType::MySQL);
3230        assert!(sql.starts_with("WITH active_users AS ("));
3231        assert!(sql.contains("SELECT * FROM users WHERE status = 'active'"));
3232        assert!(sql.contains("SELECT `id`, `name` FROM `active_users`"));
3233    }
3234
3235    #[test]
3236    fn test_cte_multiple_with_clauses() {
3237        let sql = Query::select()
3238            .column("id")
3239            .from("combined")
3240            .with_cte("a", "SELECT id FROM table_a")
3241            .with_cte("b", "SELECT id FROM table_b")
3242            .with_cte("combined", "SELECT id FROM a UNION SELECT id FROM b")
3243            .build(DbType::MySQL);
3244        assert!(sql.starts_with(
3245            "WITH a AS (SELECT id FROM table_a), b AS (SELECT id FROM table_b), combined AS ("
3246        ));
3247    }
3248
3249    #[test]
3250    fn test_cte_recursive_with_clause() {
3251        let sql = Query::select()
3252            .column("id")
3253            .column("parent_id")
3254            .from("tree")
3255            .with_recursive_cte("tree", "SELECT id, parent_id FROM nodes WHERE id = 1")
3256            .build(DbType::MySQL);
3257        assert!(sql.starts_with("WITH RECURSIVE tree AS ("));
3258    }
3259
3260    #[test]
3261    fn test_cte_no_cte_no_with_prefix() {
3262        let sql = Query::select()
3263            .column("id")
3264            .from("users")
3265            .build(DbType::MySQL);
3266        assert!(!sql.contains("WITH"));
3267        assert!(sql.starts_with("SELECT"));
3268    }
3269
3270    // ---- 窗口函数测试 ----
3271
3272    #[test]
3273    fn test_window_function_raw_expr() {
3274        let sql = Query::select()
3275            .column("id")
3276            .column("salary")
3277            .from("employees")
3278            .window_function("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn")
3279            .build(DbType::MySQL);
3280        assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3281    }
3282
3283    #[test]
3284    fn test_row_number_helper_with_partition() {
3285        let sql = Query::select()
3286            .column("name")
3287            .column("dept")
3288            .from("employees")
3289            .row_number("dept", "salary DESC", "row_num")
3290            .build(DbType::MySQL);
3291        assert!(
3292            sql.contains("ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS row_num")
3293        );
3294    }
3295
3296    #[test]
3297    fn test_row_number_helper_without_partition() {
3298        let sql = Query::select()
3299            .column("name")
3300            .from("employees")
3301            .row_number("", "salary DESC", "rn")
3302            .build(DbType::MySQL);
3303        assert!(sql.contains("ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn"));
3304        assert!(!sql.contains("PARTITION BY"));
3305    }
3306
3307    #[test]
3308    fn test_rank_helper() {
3309        let sql = Query::select()
3310            .column("name")
3311            .from("scores")
3312            .rank("", "score DESC", "rank_num")
3313            .build(DbType::MySQL);
3314        assert!(sql.contains("RANK() OVER (ORDER BY score DESC) AS rank_num"));
3315    }
3316
3317    #[test]
3318    fn test_dense_rank_helper_with_partition() {
3319        let sql = Query::select()
3320            .column("name")
3321            .from("scores")
3322            .dense_rank("class", "score DESC", "dr")
3323            .build(DbType::MySQL);
3324        assert!(sql.contains("DENSE_RANK() OVER (PARTITION BY class ORDER BY score DESC) AS dr"));
3325    }
3326
3327    #[test]
3328    fn test_multiple_window_functions() {
3329        let sql = Query::select()
3330            .column("name")
3331            .column("salary")
3332            .from("employees")
3333            .row_number("dept", "salary DESC", "rn")
3334            .rank("dept", "salary DESC", "rk")
3335            .dense_rank("dept", "salary DESC", "dr")
3336            .build(DbType::MySQL);
3337        assert!(sql.contains("ROW_NUMBER()"));
3338        assert!(sql.contains("RANK()"));
3339        assert!(sql.contains("DENSE_RANK()"));
3340    }
3341
3342    #[test]
3343    fn test_window_function_with_cte_combined() {
3344        let sql = Query::select()
3345            .column("name")
3346            .from("ranked")
3347            .with_cte(
3348                "ranked",
3349                "SELECT name, ROW_NUMBER() OVER (ORDER BY salary) AS rn FROM employees",
3350            )
3351            .where_clause("rn <= 10")
3352            .build(DbType::MySQL);
3353        assert!(sql.starts_with("WITH ranked AS ("));
3354        assert!(sql.contains("FROM `ranked`"));
3355        assert!(sql.contains("WHERE rn <= 10"));
3356    }
3357
3358    // ---- FOR UPDATE 测试 ----
3359
3360    #[test]
3361    fn test_for_update_basic() {
3362        let sql = Query::select()
3363            .column("id")
3364            .column("balance")
3365            .from("accounts")
3366            .where_clause("id = 1")
3367            .for_update()
3368            .build(DbType::MySQL);
3369        assert!(sql.ends_with(" FOR UPDATE"));
3370        assert!(sql.contains("WHERE id = 1"));
3371    }
3372
3373    #[test]
3374    fn test_for_update_with_nowait() {
3375        let sql = Query::select()
3376            .column("id")
3377            .from("accounts")
3378            .where_clause("id = 1")
3379            .for_update_with_options("NOWAIT")
3380            .build(DbType::MySQL);
3381        assert!(sql.ends_with(" FOR UPDATE NOWAIT"));
3382    }
3383
3384    #[test]
3385    fn test_for_update_with_skip_locked() {
3386        let sql = Query::select()
3387            .column("id")
3388            .from("accounts")
3389            .where_clause("id = 1")
3390            .for_update_with_options("SKIP LOCKED")
3391            .build(DbType::MySQL);
3392        assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3393    }
3394
3395    #[test]
3396    fn test_for_update_with_limit_and_order() {
3397        let sql = Query::select()
3398            .column("id")
3399            .from("jobs")
3400            .order_by("priority", false)
3401            .limit(1)
3402            .for_update_with_options("SKIP LOCKED")
3403            .build(DbType::MySQL);
3404        assert!(sql.contains("ORDER BY `priority` DESC"));
3405        assert!(sql.contains("LIMIT 1"));
3406        assert!(sql.ends_with(" FOR UPDATE SKIP LOCKED"));
3407    }
3408
3409    #[test]
3410    fn test_no_for_update_by_default() {
3411        let sql = Query::select()
3412            .column("id")
3413            .from("users")
3414            .build(DbType::MySQL);
3415        assert!(!sql.contains("FOR UPDATE"));
3416    }
3417
3418    // ---- 集合运算(UNION / INTERSECT / EXCEPT)测试 ----
3419
3420    #[test]
3421    fn test_set_operator_as_sql() {
3422        assert_eq!(SetOperator::Union.as_sql(), "UNION");
3423        assert_eq!(SetOperator::UnionAll.as_sql(), "UNION ALL");
3424        assert_eq!(SetOperator::Intersect.as_sql(), "INTERSECT");
3425        assert_eq!(SetOperator::Except.as_sql(), "EXCEPT");
3426    }
3427
3428    #[test]
3429    fn test_union_basic() {
3430        let q1 = Query::select().column("id").from("active_users");
3431        let q2 = Query::select().column("id").from("pending_users");
3432        let sql = q1.union(q2).build(DbType::MySQL);
3433        assert!(sql.contains("SELECT `id` FROM `active_users`"));
3434        assert!(sql.contains(" UNION "));
3435        assert!(sql.contains("SELECT `id` FROM `pending_users`"));
3436    }
3437
3438    #[test]
3439    fn test_union_all_basic() {
3440        let q1 = Query::select().column("id").from("table_a");
3441        let q2 = Query::select().column("id").from("table_b");
3442        let sql = q1.union_all(q2).build(DbType::MySQL);
3443        assert!(sql.contains(" UNION ALL "));
3444    }
3445
3446    #[test]
3447    fn test_intersect_basic() {
3448        let q1 = Query::select().column("id").from("table_a");
3449        let q2 = Query::select().column("id").from("table_b");
3450        let sql = q1.intersect(q2).build(DbType::MySQL);
3451        assert!(sql.contains(" INTERSECT "));
3452    }
3453
3454    #[test]
3455    fn test_except_basic() {
3456        let q1 = Query::select().column("id").from("table_a");
3457        let q2 = Query::select().column("id").from("table_b");
3458        let sql = q1.except(q2).build(DbType::MySQL);
3459        assert!(sql.contains(" EXCEPT "));
3460    }
3461
3462    #[test]
3463    fn test_union_chained_multiple() {
3464        let q1 = Query::select().column("id").from("t1");
3465        let q2 = Query::select().column("id").from("t2");
3466        let q3 = Query::select().column("id").from("t3");
3467        let sql = q1.union(q2).union(q3).build(DbType::MySQL);
3468        assert_eq!(sql.matches("UNION").count(), 2);
3469    }
3470
3471    #[test]
3472    fn test_union_mixed_operators() {
3473        let q1 = Query::select().column("id").from("t1");
3474        let q2 = Query::select().column("id").from("t2");
3475        let q3 = Query::select().column("id").from("t3");
3476        let sql = q1.union(q2).intersect(q3).build(DbType::MySQL);
3477        assert!(sql.contains(" UNION "));
3478        assert!(sql.contains(" INTERSECT "));
3479    }
3480
3481    #[test]
3482    fn test_union_with_order_by_limit() {
3483        let q1 = Query::select().column("id").from("t1");
3484        let q2 = Query::select().column("id").from("t2");
3485        let sql = q1
3486            .union(q2)
3487            .order_by("id", true)
3488            .limit(10)
3489            .offset(5)
3490            .build(DbType::MySQL);
3491        assert!(sql.contains("ORDER BY `id` ASC"));
3492        assert!(sql.contains("LIMIT 10"));
3493        assert!(sql.contains("OFFSET 5"));
3494    }
3495
3496    #[test]
3497    fn test_union_postgres_dialect() {
3498        let q1 = Query::select().column("id").from("t1");
3499        let q2 = Query::select().column("id").from("t2");
3500        let sql = q1.union(q2).build(DbType::PostgreSQL);
3501        assert!(sql.contains("\"id\""));
3502        assert!(sql.contains(" UNION "));
3503    }
3504
3505    #[test]
3506    fn test_union_with_where_clauses() {
3507        let q1 = Query::select()
3508            .column("id")
3509            .from("active_users")
3510            .where_clause("age > 18");
3511        let q2 = Query::select()
3512            .column("id")
3513            .from("pending_users")
3514            .where_clause("age > 18");
3515        let sql = q1.union(q2).build(DbType::MySQL);
3516        assert!(sql.contains("WHERE age > 18"));
3517        assert!(sql.contains(" UNION "));
3518    }
3519
3520    // ---- 综合场景测试 ----
3521
3522    #[test]
3523    fn test_cte_window_for_update_combined() {
3524        // 复杂查询:CTE + 窗口函数 + FOR UPDATE
3525        let sql = Query::select()
3526            .column("id")
3527            .column("salary")
3528            .from("ranked_salaries")
3529            .with_cte(
3530                "ranked_salaries",
3531                "SELECT id, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn FROM employees",
3532            )
3533            .where_clause("rn = 1")
3534            .for_update()
3535            .build(DbType::MySQL);
3536        assert!(sql.starts_with("WITH ranked_salaries AS ("));
3537        assert!(sql.contains("FOR UPDATE"));
3538        assert!(sql.contains("WHERE rn = 1"));
3539    }
3540
3541    #[test]
3542    fn test_complex_window_aggregation() {
3543        // 运行总和 + 排名
3544        let sql = Query::select()
3545            .column("user_id")
3546            .column("amount")
3547            .from("transactions")
3548            .window_function(
3549                "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total",
3550            )
3551            .rank("user_id", "created_at", "tx_rank")
3552            .build(DbType::MySQL);
3553        assert!(sql.contains(
3554            "SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total"
3555        ));
3556        assert!(sql.contains("RANK() OVER (PARTITION BY user_id ORDER BY created_at) AS tx_rank"));
3557    }
3558
3559    // ---- 参数化 WHERE 测试(P0 修复:SQL 注入防护) ----
3560
3561    #[test]
3562    fn test_select_where_eq_params() {
3563        use sz_orm_core::Value;
3564        let built = Query::select()
3565            .column("id")
3566            .from("users")
3567            .where_eq("age", Value::I32(18))
3568            .build_with_params(DbType::MySQL);
3569        assert!(built.sql.contains("WHERE `age` = ?"));
3570        assert_eq!(built.params.len(), 1);
3571        assert_eq!(built.params[0], Value::I32(18));
3572    }
3573
3574    #[test]
3575    fn test_select_multiple_where_params() {
3576        use sz_orm_core::Value;
3577        let built = Query::select()
3578            .column("id")
3579            .from("users")
3580            .where_eq("age", Value::I32(18))
3581            .where_eq("status", Value::String("active".to_string()))
3582            .build_with_params(DbType::MySQL);
3583        assert!(built.sql.contains("WHERE `age` = ? AND `status` = ?"));
3584        assert_eq!(built.params.len(), 2);
3585    }
3586
3587    #[test]
3588    fn test_select_or_where_eq_params() {
3589        use sz_orm_core::Value;
3590        let built = Query::select()
3591            .column("id")
3592            .from("users")
3593            .where_eq("age", Value::I32(18))
3594            .or_where_eq("role", Value::String("admin".to_string()))
3595            .build_with_params(DbType::MySQL);
3596        assert!(built.sql.contains("WHERE `age` = ? OR `role` = ?"));
3597        assert_eq!(built.params.len(), 2);
3598    }
3599
3600    #[test]
3601    fn test_select_where_in_params() {
3602        use sz_orm_core::Value;
3603        let built = Query::select()
3604            .column("id")
3605            .from("users")
3606            .where_in("id", vec![Value::I32(1), Value::I32(2), Value::I32(3)])
3607            .build_with_params(DbType::MySQL);
3608        assert!(built.sql.contains("WHERE `id` IN (?, ?, ?)"));
3609        assert_eq!(built.params.len(), 3);
3610    }
3611
3612    #[test]
3613    fn test_select_where_in_empty() {
3614        let built = Query::select()
3615            .column("id")
3616            .from("users")
3617            .where_in("id", vec![])
3618            .build_with_params(DbType::MySQL);
3619        assert!(built.sql.contains("WHERE 1 = 0"));
3620        assert_eq!(built.params.len(), 0);
3621    }
3622
3623    #[test]
3624    fn test_select_where_between_params() {
3625        use sz_orm_core::Value;
3626        let built = Query::select()
3627            .column("id")
3628            .from("users")
3629            .where_between("age", Value::I32(18), Value::I32(65))
3630            .build_with_params(DbType::MySQL);
3631        assert!(built.sql.contains("WHERE `age` BETWEEN ? AND ?"));
3632        assert_eq!(built.params.len(), 2);
3633    }
3634
3635    #[test]
3636    fn test_select_where_null_params() {
3637        let built = Query::select()
3638            .column("id")
3639            .from("users")
3640            .where_null("deleted_at")
3641            .build_with_params(DbType::MySQL);
3642        assert!(built.sql.contains("WHERE `deleted_at` IS NULL"));
3643        assert_eq!(built.params.len(), 0);
3644    }
3645
3646    #[test]
3647    fn test_select_where_not_null_params() {
3648        let built = Query::select()
3649            .column("id")
3650            .from("users")
3651            .where_not_null("email")
3652            .build_with_params(DbType::MySQL);
3653        assert!(built.sql.contains("WHERE `email` IS NOT NULL"));
3654    }
3655
3656    #[test]
3657    fn test_select_mixed_raw_and_param_where() {
3658        use sz_orm_core::Value;
3659        let built = Query::select()
3660            .column("id")
3661            .from("users")
3662            .where_clause("age > 18")
3663            .where_eq("status", Value::String("active".to_string()))
3664            .build_with_params(DbType::MySQL);
3665        assert!(built.sql.contains("WHERE age > 18 AND `status` = ?"));
3666        assert_eq!(built.params.len(), 1);
3667    }
3668
3669    #[test]
3670    fn test_select_param_where_with_order_limit() {
3671        use sz_orm_core::Value;
3672        let built = Query::select()
3673            .column("id")
3674            .from("users")
3675            .where_eq("age", Value::I32(18))
3676            .order_by("id", true)
3677            .limit(10)
3678            .build_with_params(DbType::MySQL);
3679        assert!(built.sql.contains("WHERE `age` = ?"));
3680        assert!(built.sql.contains("ORDER BY `id` ASC"));
3681        assert!(built.sql.contains("LIMIT 10"));
3682    }
3683
3684    #[test]
3685    fn test_select_param_where_postgres_dialect() {
3686        use sz_orm_core::Value;
3687        let built = Query::select()
3688            .column("id")
3689            .from("users")
3690            .where_eq("age", Value::I32(18))
3691            .build_with_params(DbType::PostgreSQL);
3692        assert!(built.sql.contains("WHERE \"age\" = ?"));
3693    }
3694
3695    #[test]
3696    fn test_select_param_where_injection_safe() {
3697        // 参数化查询下,恶意输入作为参数绑定,不会改变 SQL 结构
3698        use sz_orm_core::Value;
3699        let malicious = "'; DROP TABLE users; --".to_string();
3700        let built = Query::select()
3701            .column("id")
3702            .from("users")
3703            .where_eq("name", Value::String(malicious.clone()))
3704            .build_with_params(DbType::MySQL);
3705        // SQL 结构不含恶意输入(仅含 ? 占位符)
3706        assert!(!built.sql.contains("DROP TABLE"));
3707        assert!(!built.sql.contains(";"));
3708        // 恶意输入完整保留在参数中(由驱动层转义)
3709        assert_eq!(built.params.len(), 1);
3710        assert_eq!(built.params[0], Value::String(malicious));
3711    }
3712
3713    #[test]
3714    fn test_update_where_eq_params() {
3715        use sz_orm_core::Value;
3716        let built = Query::update()
3717            .table("users")
3718            .set("name", "'Bob'")
3719            .where_eq("id", Value::I64(1))
3720            .build_with_params(DbType::MySQL);
3721        assert!(built.sql.contains("UPDATE `users` SET"));
3722        assert!(built.sql.contains("WHERE `id` = ?"));
3723        assert_eq!(built.params.len(), 1);
3724    }
3725
3726    #[test]
3727    fn test_update_where_in_params() {
3728        use sz_orm_core::Value;
3729        let built = Query::update()
3730            .table("users")
3731            .set("status", "'inactive'")
3732            .where_in("id", vec![Value::I64(1), Value::I64(2)])
3733            .build_with_params(DbType::MySQL);
3734        assert!(built.sql.contains("WHERE `id` IN (?, ?)"));
3735        assert_eq!(built.params.len(), 2);
3736    }
3737
3738    #[test]
3739    fn test_delete_where_eq_params() {
3740        use sz_orm_core::Value;
3741        let built = Query::delete()
3742            .from_table("users")
3743            .where_eq("id", Value::I64(1))
3744            .build_with_params(DbType::MySQL);
3745        assert!(built.sql.contains("DELETE FROM `users`"));
3746        assert!(built.sql.contains("WHERE `id` = ?"));
3747        assert_eq!(built.params.len(), 1);
3748    }
3749
3750    #[test]
3751    fn test_delete_where_between_params() {
3752        use sz_orm_core::Value;
3753        let built = Query::delete()
3754            .from_table("logs")
3755            .where_between(
3756                "created_at",
3757                Value::String("2020-01-01".to_string()),
3758                Value::String("2020-12-31".to_string()),
3759            )
3760            .build_with_params(DbType::MySQL);
3761        assert!(built.sql.contains("WHERE `created_at` BETWEEN ? AND ?"));
3762        assert_eq!(built.params.len(), 2);
3763    }
3764
3765    // ---- 任务4:FROM 子查询支持测试 ----
3766
3767    #[test]
3768    fn test_from_subquery_basic() {
3769        let inner = Query::select()
3770            .column("id")
3771            .column("amount")
3772            .from("orders")
3773            .build(DbType::MySQL);
3774        let sql = Query::select()
3775            .column("id")
3776            .from_subquery(&inner, "t")
3777            .build(DbType::MySQL);
3778        assert!(
3779            sql.contains("FROM (SELECT `id`, `amount` FROM `orders`) AS `t`"),
3780            "FROM 子查询应渲染为 `FROM (subquery) AS alias`,实际: {sql}"
3781        );
3782    }
3783
3784    #[test]
3785    fn test_from_subquery_postgres_dialect() {
3786        let inner = Query::select()
3787            .column("id")
3788            .from("orders")
3789            .build(DbType::PostgreSQL);
3790        let sql = Query::select()
3791            .column("id")
3792            .from_subquery(&inner, "t")
3793            .build(DbType::PostgreSQL);
3794        assert!(
3795            sql.contains("FROM (SELECT \"id\" FROM \"orders\") AS \"t\""),
3796            "PG 方言下别名应使用双引号,实际: {sql}"
3797        );
3798    }
3799
3800    #[test]
3801    fn test_from_subquery_with_where_and_order() {
3802        let inner = Query::select()
3803            .column("id")
3804            .column("amount")
3805            .from("orders")
3806            .where_clause("amount > 100")
3807            .build(DbType::MySQL);
3808        let sql = Query::select()
3809            .column("id")
3810            .column("amount")
3811            .from_subquery(&inner, "t")
3812            .where_clause("t.amount > 200")
3813            .order_by("id", true)
3814            .build(DbType::MySQL);
3815        assert!(
3816            sql.contains("FROM (SELECT `id`, `amount` FROM `orders` WHERE amount > 100) AS `t`")
3817        );
3818        assert!(sql.contains("WHERE t.amount > 200"));
3819        assert!(sql.contains("ORDER BY `id` ASC"));
3820    }
3821
3822    #[test]
3823    fn test_from_subquery_with_params() {
3824        use sz_orm_core::Value;
3825        let inner = Query::select()
3826            .column("id")
3827            .from("orders")
3828            .where_eq("amount", Value::I32(100))
3829            .build_with_params(DbType::MySQL);
3830        let built = Query::select()
3831            .column("id")
3832            .from_subquery(&inner.sql, "t")
3833            .where_eq("t.id", Value::I64(1))
3834            .build_with_params(DbType::MySQL);
3835        // 外层参数 + 内层参数(外层参数在 WHERE 之后,但内层 SQL 已固化为字符串)
3836        assert!(built
3837            .sql
3838            .contains("FROM (SELECT `id` FROM `orders` WHERE `amount` = ?) AS `t`"));
3839        assert!(built.sql.contains("WHERE `t`.`id` = ?"));
3840        // 外层只绑定 1 个参数(内层 SQL 已是字符串)
3841        assert_eq!(built.params.len(), 1);
3842    }
3843
3844    #[test]
3845    fn test_from_subquery_overrides_from_table() {
3846        // 后调用者覆盖前者:from() 后 from_subquery()
3847        let sql = Query::select()
3848            .column("id")
3849            .from("users")
3850            .from_subquery("SELECT id FROM orders", "t")
3851            .build(DbType::MySQL);
3852        assert!(sql.contains("FROM (SELECT id FROM orders) AS `t`"));
3853        assert!(!sql.contains("FROM `users`"));
3854    }
3855
3856    #[test]
3857    fn test_from_table_overrides_from_subquery() {
3858        // 后调用者覆盖前者:from_subquery() 后 from()
3859        let sql = Query::select()
3860            .column("id")
3861            .from_subquery("SELECT id FROM orders", "t")
3862            .from("users")
3863            .build(DbType::MySQL);
3864        assert!(sql.contains("FROM `users`"));
3865        assert!(!sql.contains("FROM ("));
3866    }
3867
3868    #[test]
3869    fn test_from_subquery_no_from_when_neither_set() {
3870        let sql = Query::select().column("id").build(DbType::MySQL);
3871        assert!(!sql.contains("FROM"));
3872    }
3873
3874    // ---- 任务5:RETURNING 子句支持测试 ----
3875
3876    #[test]
3877    fn test_insert_returning_postgres() {
3878        let sql = Query::insert()
3879            .into_table("users")
3880            .value("name", "'Alice'")
3881            .returning(&["id", "created_at"])
3882            .build_with_dialect(DbType::PostgreSQL);
3883        assert!(
3884            sql.contains("RETURNING \"id\", \"created_at\""),
3885            "PG 方言应渲染 RETURNING,实际: {sql}"
3886        );
3887    }
3888
3889    #[test]
3890    fn test_insert_returning_sqlite() {
3891        let sql = Query::insert()
3892            .into_table("users")
3893            .value("name", "'Alice'")
3894            .returning(&["id"])
3895            .build_with_dialect(DbType::Sqlite);
3896        assert!(
3897            sql.contains("RETURNING \"id\""),
3898            "SQLite 方言应渲染 RETURNING,实际: {sql}"
3899        );
3900    }
3901
3902    #[test]
3903    fn test_insert_returning_all() {
3904        let sql = Query::insert()
3905            .into_table("users")
3906            .value("name", "'Alice'")
3907            .returning_all()
3908            .build_with_dialect(DbType::PostgreSQL);
3909        assert!(
3910            sql.contains("RETURNING *"),
3911            "returning_all 应渲染 `RETURNING *`,实际: {sql}"
3912        );
3913    }
3914
3915    #[test]
3916    fn test_insert_returning_mysql_skipped() {
3917        // MySQL 不支持 RETURNING,应跳过
3918        let sql = Query::insert()
3919            .into_table("users")
3920            .value("name", "'Alice'")
3921            .returning(&["id"])
3922            .build_with_dialect(DbType::MySQL);
3923        assert!(
3924            !sql.contains("RETURNING"),
3925            "MySQL 方言应跳过 RETURNING,实际: {sql}"
3926        );
3927    }
3928
3929    #[test]
3930    fn test_insert_returning_with_upsert_postgres() {
3931        // ON CONFLICT + RETURNING 组合(PostgreSQL 经典用法)
3932        let sql = Query::insert()
3933            .into_table("users")
3934            .value("id", "1")
3935            .value("name", "'Alice'")
3936            .on_conflict_do_update(&["id"], &[("name", "EXCLUDED.name")])
3937            .returning(&["id", "name"])
3938            .build_with_dialect(DbType::PostgreSQL);
3939        assert!(sql.contains("ON CONFLICT"));
3940        assert!(sql.contains("RETURNING"));
3941    }
3942
3943    #[test]
3944    fn test_insert_returning_build_mysql_style_skipped() {
3945        // build() 是 MySQL 风格硬编码,不应渲染 RETURNING
3946        let sql = Query::insert()
3947            .into_table("users")
3948            .value("name", "'Alice'")
3949            .returning(&["id"])
3950            .build();
3951        assert!(!sql.contains("RETURNING"));
3952    }
3953
3954    #[test]
3955    fn test_update_returning_postgres() {
3956        let sql = Query::update()
3957            .table("users")
3958            .set("status", "'active'")
3959            .where_clause("id = 1")
3960            .returning(&["id", "status"])
3961            .build_with_dialect(DbType::PostgreSQL);
3962        assert!(sql.contains("RETURNING \"id\", \"status\""));
3963        assert!(sql.contains("WHERE id = 1"));
3964    }
3965
3966    #[test]
3967    fn test_update_returning_sqlite() {
3968        let sql = Query::update()
3969            .table("users")
3970            .set("status", "'active'")
3971            .returning(&["id"])
3972            .build_with_dialect(DbType::Sqlite);
3973        assert!(sql.contains("RETURNING \"id\""));
3974    }
3975
3976    #[test]
3977    fn test_update_returning_mysql_skipped() {
3978        let sql = Query::update()
3979            .table("users")
3980            .set("status", "'active'")
3981            .returning(&["id"])
3982            .build_with_dialect(DbType::MySQL);
3983        assert!(!sql.contains("RETURNING"));
3984    }
3985
3986    #[test]
3987    fn test_update_returning_with_params() {
3988        use sz_orm_core::Value;
3989        let built = Query::update()
3990            .table("users")
3991            .set("status", "'active'")
3992            .where_eq("id", Value::I64(1))
3993            .returning(&["id", "status"])
3994            .build_with_params(DbType::PostgreSQL);
3995        assert!(built.sql.contains("WHERE \"id\" = ?"));
3996        assert!(built.sql.contains("RETURNING \"id\", \"status\""));
3997        assert_eq!(built.params.len(), 1);
3998    }
3999
4000    #[test]
4001    fn test_delete_returning_postgres() {
4002        let sql = Query::delete()
4003            .from_table("users")
4004            .where_clause("id = 1")
4005            .returning(&["id", "name"])
4006            .build_with_dialect(DbType::PostgreSQL);
4007        assert!(sql.contains("RETURNING \"id\", \"name\""));
4008        assert!(sql.contains("WHERE id = 1"));
4009    }
4010
4011    #[test]
4012    fn test_delete_returning_sqlite() {
4013        let sql = Query::delete()
4014            .from_table("users")
4015            .where_clause("id = 1")
4016            .returning(&["id"])
4017            .build_with_dialect(DbType::Sqlite);
4018        assert!(sql.contains("RETURNING \"id\""));
4019    }
4020
4021    #[test]
4022    fn test_delete_returning_mysql_skipped() {
4023        let sql = Query::delete()
4024            .from_table("users")
4025            .where_clause("id = 1")
4026            .returning(&["id"])
4027            .build_with_dialect(DbType::MySQL);
4028        assert!(!sql.contains("RETURNING"));
4029    }
4030
4031    #[test]
4032    fn test_delete_returning_with_params() {
4033        use sz_orm_core::Value;
4034        let built = Query::delete()
4035            .from_table("users")
4036            .where_eq("id", Value::I64(1))
4037            .returning(&["id", "name"])
4038            .build_with_params(DbType::PostgreSQL);
4039        assert!(built.sql.contains("WHERE \"id\" = ?"));
4040        assert!(built.sql.contains("RETURNING \"id\", \"name\""));
4041        assert_eq!(built.params.len(), 1);
4042    }
4043
4044    #[test]
4045    fn test_returning_star_not_quoted() {
4046        // `*` 在 RETURNING 中不应被引号包裹
4047        let sql = Query::insert()
4048            .into_table("users")
4049            .value("name", "'Alice'")
4050            .returning(&["*"])
4051            .build_with_dialect(DbType::PostgreSQL);
4052        assert!(sql.contains("RETURNING *"));
4053        assert!(!sql.contains("RETURNING \"*\""));
4054    }
4055
4056    // ---- 参数化 JOIN 测试(P2 修复 #68:JOIN 注入风险) ----
4057
4058    #[test]
4059    fn test_inner_join_on_column_eq() {
4060        let sql = Query::select()
4061            .column("u.id")
4062            .from("users u")
4063            .inner_join_on("orders o", "u.id", "o.user_id")
4064            .build(DbType::MySQL);
4065        assert!(
4066            sql.contains("INNER JOIN `orders` o ON `u`.`id` = `o`.`user_id`"),
4067            "列对列等值连接应渲染转义标识符,实际: {sql}"
4068        );
4069    }
4070
4071    #[test]
4072    fn test_left_join_on_column_eq() {
4073        let sql = Query::select()
4074            .column("u.id")
4075            .from("users u")
4076            .left_join_on("profiles p", "u.id", "p.user_id")
4077            .build(DbType::MySQL);
4078        assert!(sql.contains("LEFT JOIN `profiles` p ON `u`.`id` = `p`.`user_id`"));
4079    }
4080
4081    #[test]
4082    fn test_right_join_on_column_eq() {
4083        let sql = Query::select()
4084            .column("u.id")
4085            .from("users u")
4086            .right_join_on("orders o", "u.id", "o.user_id")
4087            .build(DbType::MySQL);
4088        assert!(sql.contains("RIGHT JOIN `orders` o ON `u`.`id` = `o`.`user_id`"));
4089    }
4090
4091    #[test]
4092    fn test_inner_join_on_postgres_dialect() {
4093        let sql = Query::select()
4094            .column("u.id")
4095            .from("users u")
4096            .inner_join_on("orders o", "u.id", "o.user_id")
4097            .build(DbType::PostgreSQL);
4098        // 注:quote_join_table 仍使用反引号包裹表名(向后兼容),
4099        // 但 ON 条件的列名按 PG 方言用双引号引用
4100        assert!(
4101            sql.contains("INNER JOIN `orders` o ON \"u\".\"id\" = \"o\".\"user_id\""),
4102            "PG 方言下 ON 条件列名应使用双引号引用,实际: {sql}"
4103        );
4104    }
4105
4106    #[test]
4107    fn test_inner_join_param_binds_value() {
4108        use sz_orm_core::Value;
4109        let built = Query::select()
4110            .column("u.id")
4111            .from("users u")
4112            .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4113            .build_with_params(DbType::MySQL);
4114        assert!(
4115            built
4116                .sql
4117                .contains("INNER JOIN `orders` o ON `o`.`status` = ?"),
4118            "参数化 JOIN 应渲染 ? 占位符,实际: {}",
4119            built.sql
4120        );
4121        assert_eq!(built.params.len(), 1);
4122        assert_eq!(built.params[0], Value::String("paid".to_string()));
4123    }
4124
4125    #[test]
4126    fn test_left_join_param_binds_value() {
4127        use sz_orm_core::Value;
4128        let built = Query::select()
4129            .column("u.id")
4130            .from("users u")
4131            .left_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4132            .build_with_params(DbType::MySQL);
4133        assert!(built
4134            .sql
4135            .contains("LEFT JOIN `orders` o ON `o`.`status` = ?"));
4136        assert_eq!(built.params.len(), 1);
4137    }
4138
4139    #[test]
4140    fn test_right_join_param_binds_value() {
4141        use sz_orm_core::Value;
4142        let built = Query::select()
4143            .column("u.id")
4144            .from("users u")
4145            .right_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4146            .build_with_params(DbType::MySQL);
4147        assert!(built
4148            .sql
4149            .contains("RIGHT JOIN `orders` o ON `o`.`status` = ?"));
4150        assert_eq!(built.params.len(), 1);
4151    }
4152
4153    #[test]
4154    fn test_param_join_injection_safe() {
4155        // 参数化 JOIN 下,恶意输入作为参数绑定,不会改变 SQL 结构
4156        use sz_orm_core::Value;
4157        let malicious = "'; DROP TABLE orders; --".to_string();
4158        let built = Query::select()
4159            .column("u.id")
4160            .from("users u")
4161            .inner_join_param(
4162                "orders o",
4163                "o.status",
4164                " = ?",
4165                Value::String(malicious.clone()),
4166            )
4167            .build_with_params(DbType::MySQL);
4168        // SQL 结构不含恶意输入(仅含 ? 占位符)
4169        assert!(!built.sql.contains("DROP TABLE"));
4170        assert!(!built.sql.contains(";"));
4171        // 恶意输入完整保留在参数中(由驱动层转义)
4172        assert_eq!(built.params.len(), 1);
4173        assert_eq!(built.params[0], Value::String(malicious));
4174    }
4175
4176    #[test]
4177    fn test_mixed_raw_and_param_join() {
4178        // 混合使用原始 inner_join 和参数化 inner_join_param
4179        use sz_orm_core::Value;
4180        let built = Query::select()
4181            .column("u.id")
4182            .from("users u")
4183            .inner_join("orders o", "u.id = o.user_id")
4184            .inner_join_param(
4185                "payments p",
4186                "p.status",
4187                " = ?",
4188                Value::String("paid".into()),
4189            )
4190            .build_with_params(DbType::MySQL);
4191        assert!(built
4192            .sql
4193            .contains("INNER JOIN `orders` o ON u.id = o.user_id"));
4194        assert!(built
4195            .sql
4196            .contains("INNER JOIN `payments` p ON `p`.`status` = ?"));
4197        assert_eq!(built.params.len(), 1);
4198    }
4199
4200    #[test]
4201    fn test_param_join_with_where_params_combined() {
4202        // 参数化 JOIN + 参数化 WHERE 联合使用
4203        use sz_orm_core::Value;
4204        let built = Query::select()
4205            .column("u.id")
4206            .from("users u")
4207            .inner_join_param("orders o", "o.status", " = ?", Value::String("paid".into()))
4208            .where_eq("u.age", Value::I32(18))
4209            .build_with_params(DbType::MySQL);
4210        assert!(built
4211            .sql
4212            .contains("INNER JOIN `orders` o ON `o`.`status` = ?"));
4213        assert!(built.sql.contains("WHERE `u`.`age` = ?"));
4214        // JOIN 参数在前,WHERE 参数在后
4215        assert_eq!(built.params.len(), 2);
4216        assert_eq!(built.params[0], Value::String("paid".to_string()));
4217        assert_eq!(built.params[1], Value::I32(18));
4218    }
4219}