Skip to main content

sz_orm_query_builder/
lib.rs

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