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