Skip to main content

sz_orm_query_builder/
lib.rs

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