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