Skip to main content

br_db/types/
mod.rs

1use json::{object, JsonValue};
2
3static DISABLE_FIELD: &[&str] = &[
4    "default",
5    "select",
6    "delete",
7    "insert",
8    "update",
9    "order",
10    "group",
11    "user",
12    "password",
13    "desc",
14    "index",
15    "from",
16    "host",
17    "user",
18    "read",
19    "partition",
20];
21
22/// SQL injection prevention - security critical module
23pub mod sql_safety {
24    static SQL_KEYWORDS: &[&str] = &[
25        "select", "insert", "update", "delete", "drop", "truncate", "alter", "create", "exec",
26        "execute", "union",
27    ];
28
29    static DANGEROUS_PATTERNS: &[&str] = &["--", "/*", "*/", ";", "xp_", "sp_", "0x"];
30
31    #[inline]
32    pub fn validate_table_name(name: &str) -> bool {
33        if name.is_empty() || name.len() > 128 {
34            return false;
35        }
36        let bytes = name.as_bytes();
37        let first = bytes[0];
38        if !first.is_ascii_alphabetic() && first != b'_' {
39            return false;
40        }
41        for &b in bytes {
42            if !b.is_ascii_alphanumeric() && b != b'_' {
43                return false;
44            }
45        }
46        let lower = name.to_lowercase();
47        if SQL_KEYWORDS.iter().any(|kw| lower == *kw) {
48            return false;
49        }
50        !DANGEROUS_PATTERNS.iter().any(|p| lower.contains(p))
51    }
52
53    #[inline]
54    pub fn validate_field_name(name: &str) -> bool {
55        if name.is_empty() || name.len() > 256 {
56            return false;
57        }
58        for part in name.split('.') {
59            if part.is_empty() {
60                return false;
61            }
62            let bytes = part.as_bytes();
63            let first = bytes[0];
64            if !first.is_ascii_alphabetic() && first != b'_' {
65                return false;
66            }
67            for &b in bytes {
68                if !b.is_ascii_alphanumeric() && b != b'_' {
69                    return false;
70                }
71            }
72        }
73        let lower = name.to_lowercase();
74        if SQL_KEYWORDS.iter().any(|kw| lower == *kw) {
75            return false;
76        }
77        !DANGEROUS_PATTERNS.iter().any(|p| lower.contains(p))
78    }
79
80    #[inline]
81    pub fn escape_string(value: &str) -> String {
82        value
83            .replace('\\', "\\\\")
84            .replace('\'', "''")
85            .replace('\0', "")
86            .replace('\n', "\\n")
87            .replace('\r', "\\r")
88    }
89
90    #[inline]
91    pub fn validate_compare_orator(op: &str) -> bool {
92        matches!(
93            op.to_lowercase().as_str(),
94            "=" | "!="
95                | "<>"
96                | "<"
97                | ">"
98                | "<="
99                | ">="
100                | "like"
101                | "not like"
102                | "in"
103                | "not in"
104                | "notin"
105                | "between"
106                | "is"
107                | "isnot"
108                | "is not"
109                | "set"
110                | "location"
111                | "notlike"
112                | "json_contains"
113        )
114    }
115
116    #[cfg(test)]
117    mod tests {
118        use super::*;
119
120        #[test]
121        fn test_validate_table_name_valid() {
122            assert!(validate_table_name("users"));
123            assert!(validate_table_name("user_profiles"));
124            assert!(validate_table_name("_private_table"));
125            assert!(validate_table_name("Table123"));
126            assert!(validate_table_name("batch_insert_perf"));
127            assert!(validate_table_name("my_update_log"));
128        }
129
130        #[test]
131        fn test_validate_table_name_invalid() {
132            assert!(!validate_table_name(""));
133            assert!(!validate_table_name("123table"));
134            assert!(!validate_table_name("user-name"));
135            assert!(!validate_table_name("table.name"));
136            assert!(!validate_table_name("table name"));
137        }
138
139        #[test]
140        fn test_validate_table_name_sql_keywords() {
141            assert!(!validate_table_name("select"));
142            assert!(!validate_table_name("SELECT"));
143            assert!(!validate_table_name("insert"));
144            assert!(!validate_table_name("drop"));
145            assert!(!validate_table_name("union"));
146        }
147
148        #[test]
149        fn test_validate_table_name_dangerous_patterns() {
150            assert!(!validate_table_name("table;drop"));
151            assert!(!validate_table_name("table--comment"));
152            assert!(!validate_table_name("xp_cmdshell"));
153            assert!(!validate_table_name("sp_execute"));
154        }
155
156        #[test]
157        fn test_validate_table_name_length() {
158            let long_name = "a".repeat(128);
159            assert!(validate_table_name(&long_name));
160            let too_long = "a".repeat(129);
161            assert!(!validate_table_name(&too_long));
162        }
163
164        #[test]
165        fn test_validate_field_name_valid() {
166            assert!(validate_field_name("id"));
167            assert!(validate_field_name("user_name"));
168            assert!(validate_field_name("_hidden"));
169            assert!(validate_field_name("table1.field1"));
170        }
171
172        #[test]
173        fn test_validate_field_name_invalid() {
174            assert!(!validate_field_name(""));
175            assert!(!validate_field_name("123field"));
176            assert!(!validate_field_name("field-name"));
177            assert!(!validate_field_name(".field"));
178            assert!(!validate_field_name("field."));
179        }
180
181        #[test]
182        fn test_escape_string() {
183            assert_eq!(escape_string("hello"), "hello");
184            assert_eq!(escape_string("it's"), "it''s");
185            assert_eq!(escape_string("new\nline"), "new\\nline");
186            assert_eq!(escape_string("carriage\rreturn"), "carriage\\rreturn");
187        }
188
189        #[test]
190        fn test_escape_string_sql_injection() {
191            assert_eq!(
192                escape_string("'; DROP TABLE users; --"),
193                "''; DROP TABLE users; --"
194            );
195            assert_eq!(escape_string("1' OR '1'='1"), "1'' OR ''1''=''1");
196        }
197
198        #[test]
199        fn test_validate_compare_operator_valid() {
200            assert!(validate_compare_orator("="));
201            assert!(validate_compare_orator("!="));
202            assert!(validate_compare_orator("like"));
203            assert!(validate_compare_orator("LIKE"));
204            assert!(validate_compare_orator("in"));
205            assert!(validate_compare_orator("between"));
206        }
207
208        #[test]
209        fn test_validate_compare_operator_invalid() {
210            assert!(!validate_compare_orator(""));
211            assert!(!validate_compare_orator("invalid"));
212            assert!(!validate_compare_orator("=="));
213            assert!(!validate_compare_orator("&&"));
214        }
215    }
216}
217
218#[inline]
219pub fn quote_identifier(name: &str, mode: &str) -> String {
220    match mode {
221        "mysql" | "sqlite" => format!("`{}`", name),
222        "pgsql" => format!("\"{}\"", name),
223        "mssql" => format!("[{}]", name),
224        _ => name.to_string(),
225    }
226}
227
228#[cfg(feature = "db-mssql")]
229pub mod mssql;
230#[cfg(feature = "db-mysql")]
231pub mod mysql;
232#[cfg(feature = "db-mysql")]
233pub mod mysql_transaction;
234#[cfg(feature = "db-pgsql")]
235pub mod pgsql;
236#[cfg(feature = "db-pgsql")]
237pub mod pgsql_transaction;
238#[cfg(feature = "db-sqlite")]
239pub mod sqlite;
240#[cfg(feature = "db-sqlite")]
241pub mod sqlite_transaction;
242
243pub trait DbMode {
244    /// 数据库是否存在
245    fn database_tables(&mut self) -> JsonValue;
246    /// 创建数据库
247    fn database_create(&mut self, name: &str) -> bool;
248    /// 备份
249    fn backups(&mut self, _filename: &str) -> bool {
250        false
251    }
252    fn truncate(&mut self, table: &str) -> bool;
253}
254
255#[derive(Debug, Clone)]
256pub struct TableOptions {
257    table_name: String,
258    table_title: String,
259    table_key: String,
260    table_fields: JsonValue,
261    table_unique: Vec<String>,
262    table_index: Vec<Vec<String>>,
263    table_partition: bool,
264    table_partition_columns: JsonValue,
265}
266impl TableOptions {
267    pub fn set_table_name(&mut self, name: &str) {
268        self.table_name = name.to_string()
269    }
270    pub fn set_table_title(&mut self, name: &str) {
271        self.table_title = name.to_string()
272    }
273    pub fn set_table_key(&mut self, name: &str) {
274        self.table_key = name.to_string()
275    }
276    pub fn set_table_fields(&mut self, fields: JsonValue) {
277        self.table_fields = fields;
278    }
279    pub fn set_table_unique(&mut self, unique: Vec<&str>) {
280        self.table_unique = unique.iter().map(|s| s.to_string()).collect();
281    }
282    pub fn set_table_index(&mut self, index: Vec<Vec<&str>>) {
283        self.table_index = index
284            .iter()
285            .map(|s| s.iter().map(|s| s.to_string()).collect())
286            .collect();
287    }
288    pub fn set_table_partition(&mut self, index: bool) {
289        self.table_partition = index;
290    }
291    pub fn set_table_partition_columns(&mut self, index: JsonValue) {
292        self.table_partition_columns = index;
293    }
294}
295impl Default for TableOptions {
296    fn default() -> Self {
297        Self {
298            table_name: "".to_string(),
299            table_title: "".to_string(),
300            table_key: "".to_string(),
301            table_fields: JsonValue::Null,
302            table_unique: vec![],
303            table_index: vec![],
304            table_partition: false,
305            table_partition_columns: JsonValue::Null,
306        }
307    }
308}
309pub trait Mode: DbMode {
310    fn table_create(&mut self, options: TableOptions) -> JsonValue;
311    fn table_update(&mut self, options: TableOptions) -> JsonValue;
312    /// 获取表信息
313    fn table_info(&mut self, table: &str) -> JsonValue;
314    /// 判断表是否存在
315    fn table_is_exist(&mut self, name: &str) -> bool;
316    /// 当前表
317    fn table(&mut self, name: &str) -> &mut Self;
318    /// 别名
319    fn change_table(&mut self, name: &str) -> &mut Self;
320
321    /// 主键自增
322    fn autoinc(&mut self) -> &mut Self;
323    /// 自动时间戳
324    fn timestamps(&mut self) -> &mut Self;
325    /// 返回sql语句
326    fn fetch_sql(&mut self) -> &mut Self;
327    /// 排序
328    fn order(&mut self, field: &str, by: bool) -> &mut Self;
329    /// 分组
330    fn group(&mut self, field: &str) -> &mut Self;
331    /// 消除重复记录
332    fn distinct(&mut self) -> &mut Self;
333    /// JSON 字段
334    fn json(&mut self, field: &str) -> &mut Self;
335    /// location 字段
336    fn location(&mut self, field: &str) -> &mut Self;
337    /// 显示字段
338    fn field(&mut self, field: &str) -> &mut Self;
339    /// 原始表达式字段(不加表前缀,如 "SUM(amount) as total"、"CASE WHEN ... END as status_count")
340    fn field_raw(&mut self, expr: &str) -> &mut Self;
341    /// 隐藏字段
342    fn hidden(&mut self, name: &str) -> &mut Self;
343    /// 查询
344    fn where_and(&mut self, field: &str, compare: &str, value: JsonValue) -> &mut Self;
345    fn where_or(&mut self, field: &str, compare: &str, value: JsonValue) -> &mut Self;
346    /// 原始 WHERE 条件(不加表前缀,如 "YEAR(created_at) = 2026")
347    fn where_raw(&mut self, expr: &str) -> &mut Self;
348
349    /// 子查询 WHERE field IN (sub_sql)
350    fn where_in_sub(&mut self, field: &str, sub_sql: &str) -> &mut Self;
351    /// 子查询 WHERE field NOT IN (sub_sql)
352    fn where_not_in_sub(&mut self, field: &str, sub_sql: &str) -> &mut Self;
353
354    fn where_exists(&mut self, sub_sql: &str) -> &mut Self;
355    fn where_not_exists(&mut self, sub_sql: &str) -> &mut Self;
356
357    /// 比较两个列
358    fn where_column(&mut self, field_a: &str, compare: &str, field_b: &str) -> &mut Self;
359    /// 更新指定列
360    fn update_column(&mut self, field_a: &str, compare: &str) -> &mut Self;
361    /// 分页
362    fn page(&mut self, page: i32, limit: i32) -> &mut Self;
363    /// 独立限制返回条数(不带分页偏移)
364    fn limit(&mut self, count: i32) -> &mut Self;
365    /// 查询指定列并返回数组
366    fn column(&mut self, field: &str) -> JsonValue;
367    /// 总数量
368    fn count(&mut self) -> JsonValue;
369    /// 最大值
370    fn max(&mut self, field: &str) -> JsonValue;
371    /// 最小值
372    fn min(&mut self, field: &str) -> JsonValue;
373    /// 合计
374    fn sum(&mut self, field: &str) -> JsonValue;
375    /// 平均值
376    fn avg(&mut self, field: &str) -> JsonValue;
377    /// HAVING 条件(用于 GROUP BY 后过滤,如 "COUNT(*) > 5")
378    fn having(&mut self, expr: &str) -> &mut Self;
379    /// 获取集合
380    fn select(&mut self) -> JsonValue;
381    /// 获取单记录
382    fn find(&mut self) -> JsonValue;
383    /// 获取单一列值
384    fn value(&mut self, field: &str) -> JsonValue;
385    /// 添加
386    fn insert(&mut self, data: JsonValue) -> JsonValue;
387    /// 批量添加
388    fn insert_all(&mut self, data: JsonValue) -> JsonValue;
389
390    /// 插入或更新(冲突时更新)
391    fn upsert(&mut self, data: JsonValue, conflict_fields: Vec<&str>) -> JsonValue;
392
393    /// 更新
394    fn update(&mut self, data: JsonValue) -> JsonValue;
395    /// 批量更新
396    fn update_all(&mut self, data: JsonValue) -> JsonValue;
397    /// 删除
398    fn delete(&mut self) -> JsonValue;
399
400    /// 事务开始
401    fn transaction(&mut self) -> bool;
402    /// 事务提交
403    fn commit(&mut self) -> bool;
404    /// 事务回滚
405    fn rollback(&mut self) -> bool;
406    /// sql语句执行
407    fn sql(&mut self, sql: &str) -> Result<JsonValue, String>;
408    fn sql_execute(&mut self, sql: &str) -> Result<JsonValue, String>;
409    /// 自增
410    fn inc(&mut self, field: &str, num: f64) -> &mut Self;
411    /// 自减
412    fn dec(&mut self, field: &str, num: f64) -> &mut Self;
413
414    /// 构造子查询
415    fn buildsql(&mut self) -> String;
416
417    fn join_fields(&mut self, fields: Vec<&str>) -> &mut Self;
418    /// 连结
419    /// * main_table 主表表名
420    /// * main_fields 主表字段
421    /// * right_table 关联表名
422    /// * right_fields 关联字段名
423    fn join(
424        &mut self,
425        main_table: &str,
426        main_fields: &str,
427        right_table: &str,
428        right_fields: &str,
429    ) -> &mut Self;
430
431    /// 内连接--用来取交集
432    ///
433    /// * table 链表表名
434    /// * main_fields 主表字段
435    /// * second_fields 附表关联字段
436    fn join_inner(&mut self, table: &str, main_fields: &str, second_fields: &str) -> &mut Self;
437
438    /// 右连接
439    fn join_right(
440        &mut self,
441        main_table: &str,
442        main_fields: &str,
443        right_table: &str,
444        right_fields: &str,
445    ) -> &mut Self;
446
447    /// 全连接
448    fn join_full(
449        &mut self,
450        main_table: &str,
451        main_fields: &str,
452        right_table: &str,
453        right_fields: &str,
454    ) -> &mut Self;
455
456    fn union(&mut self, sub_sql: &str) -> &mut Self;
457    fn union_all(&mut self, sub_sql: &str) -> &mut Self;
458    fn lock_for_update(&mut self) -> &mut Self;
459    fn lock_for_share(&mut self) -> &mut Self;
460}
461
462#[derive(Clone, Debug)]
463pub struct Params {
464    pub mode: String,
465    pub autoinc: bool,
466    pub table: String,
467    pub where_and: Vec<String>,
468    pub where_or: Vec<String>,
469    pub where_column: String,
470    pub update_column: Vec<String>,
471    pub inc_dec: JsonValue,
472    pub page: i32,
473    pub limit: i32,
474    pub fields: JsonValue,
475    pub top: String,
476    pub top2: String,
477    pub order: JsonValue,
478    pub group: JsonValue,
479    pub distinct: bool,
480    pub json: JsonValue,
481    pub location: JsonValue,
482    pub sql: bool,
483    pub join: Vec<String>,
484    pub join_inner: Vec<String>,
485    pub join_table: String,
486    pub having: Vec<String>,
487    /// 独立 LIMIT(不带分页偏移)
488    pub limit_only: i32,
489    pub timestamps: bool,
490    pub unions: Vec<String>,
491    pub lock_mode: String,
492}
493
494impl Params {
495    pub fn default(mode: &str) -> Self {
496        Self {
497            mode: mode.to_string(),
498            autoinc: false,
499            table: "".to_string(),
500            where_and: vec![],
501            where_or: vec![],
502            where_column: "".to_string(),
503            update_column: vec![],
504            inc_dec: object! {},
505            page: -1,
506            limit: 10,
507            fields: object! {},
508            top: String::new(),
509            top2: String::new(),
510            order: object! {},
511            group: object! {},
512            distinct: false,
513            json: object! {},
514            location: object! {},
515            sql: false,
516            join: Vec::new(),
517            join_inner: Vec::new(),
518            join_table: "".to_string(),
519            having: vec![],
520            limit_only: -1,
521            timestamps: false,
522            unions: vec![],
523            lock_mode: "".to_string(),
524        }
525    }
526    pub fn where_sql(&mut self) -> String {
527        let mut where_and_sql = vec![];
528        let mut where_or_sql = vec![];
529        let mut sql = vec![];
530
531        for item in self.where_or.iter() {
532            where_or_sql.push(item.clone());
533        }
534        if !where_or_sql.is_empty() {
535            sql.push(format!(" ( {} ) ", where_or_sql.join(" OR ")));
536        }
537
538        for item in self.where_and.iter() {
539            where_and_sql.push(item.clone());
540        }
541        if !where_and_sql.is_empty() {
542            sql.push(where_and_sql.join(" AND "));
543        }
544
545        if !self.where_column.is_empty() {
546            sql.push(self.where_column.clone());
547        }
548
549        if !sql.is_empty() {
550            return format!("WHERE {}", sql.join(" AND "));
551        }
552        "".to_string()
553    }
554    pub fn page_limit_sql(&mut self) -> String {
555        if self.page == -1 {
556            if self.limit_only > 0 {
557                return format!("LIMIT {}", self.limit_only);
558            }
559            return "".to_string();
560        }
561        match self.mode.as_str() {
562            "mysql" => {
563                format!(
564                    "LIMIT {},{}",
565                    self.page * self.limit - self.limit,
566                    self.limit
567                )
568            }
569            "sqlite" | "pgsql" => {
570                format!(
571                    "LIMIT {} OFFSET {}",
572                    self.limit,
573                    self.page * self.limit - self.limit
574                )
575            }
576            _ => "".to_string(),
577        }
578    }
579    pub fn fields(&mut self) -> String {
580        let mut fields = vec![];
581        for (_, value) in self.fields.entries() {
582            match self.mode.as_str() {
583                "mssql" => {
584                    fields.push(format!("{value}"));
585                }
586                "mysql" => {
587                    if let Some(s) = value.as_str() {
588                        if DISABLE_FIELD.contains(&s) {
589                            fields.push(format!("`{value}`"));
590                        } else {
591                            fields.push(format!("{value}"));
592                        }
593                    } else {
594                        fields.push(format!("{value}"));
595                    }
596                }
597                _ => {
598                    fields.push(format!("{value}"));
599                }
600            }
601        }
602        let fields = {
603            if fields.is_empty() {
604                "*".into()
605            } else {
606                fields.join(",")
607            }
608        };
609        match self.mode.as_str() {
610            "mysql" => fields.to_string(),
611            "sqlite" => fields.to_string(),
612            "mssql" => fields.to_string(),
613            _ => fields.to_string(),
614        }
615    }
616    pub fn top(&mut self) -> String {
617        match self.mode.as_str() {
618            "mssql" => {
619                let wheres = self.where_sql();
620                if !self.top2.is_empty() {
621                    let order = self.order();
622                    if order.is_empty() {
623                        self.top = format!(
624                            "(select ROW_NUMBER() OVER(ORDER BY rand()) as ROW,* from {} {}) as ",
625                            self.table, wheres
626                        );
627                    } else {
628                        self.top = format!(
629                            "(select ROW_NUMBER() OVER({}) as ROW,* from {} {}) as ",
630                            order, self.table, wheres
631                        );
632                    }
633                    return self.top.to_string();
634                }
635                self.top.to_string()
636            }
637            _ => "".to_string(),
638        }
639    }
640    pub fn top2(&mut self) -> String {
641        match self.mode.as_str() {
642            "mssql" => {
643                if self.where_and.is_empty() && self.where_or.is_empty() && !self.top2.is_empty() {
644                    return format!("where {}", self.top2);
645                }
646                if (!self.where_and.is_empty() || !self.where_or.is_empty())
647                    && !self.top2.is_empty()
648                {
649                    return format!("AND {}", self.top2);
650                }
651                self.top2.to_string()
652            }
653            _ => "".to_string(),
654        }
655    }
656    pub fn table(&mut self) -> String {
657        match self.mode.as_str() {
658            "mssql" => {
659                if !self.top2.is_empty() {
660                    return "t".to_string();
661                }
662                self.table.to_string()
663            }
664            _ => self.table.to_string(),
665        }
666    }
667    pub fn join(&mut self) -> String {
668        match self.mode.as_str() {
669            "mssql" => self.join.join(" "),
670            _ => self.join.join(" "),
671        }
672    }
673
674    // 当前只匹配了MySQL
675    pub fn join_inner(&mut self) -> String {
676        match self.mode.as_str() {
677            "mysql" => {
678                let mut join_inner = "".to_string();
679                for item in self.join_inner.iter() {
680                    join_inner = format!("{join_inner} {item}");
681                }
682                join_inner.to_string()
683            }
684            _ => "".to_string(),
685        }
686    }
687
688    pub fn order(&mut self) -> String {
689        let mut sql = vec![];
690        for (field, item) in self.order.entries() {
691            match self.mode.as_str() {
692                "mssql" => {
693                    if DISABLE_FIELD.contains(&field) {
694                        sql.push(format!("[{field}] {item}"));
695                    } else {
696                        sql.push(format!("{field} {item}"));
697                    }
698                }
699
700                "pgsql" => {
701                    if DISABLE_FIELD.contains(&field) {
702                        sql.push(format!("\"{field}\" {item}"));
703                    } else {
704                        sql.push(format!("{field} {item}"));
705                    }
706                }
707                _ => {
708                    if DISABLE_FIELD.contains(&field) {
709                        sql.push(format!("`{field}` {item}"));
710                    } else {
711                        sql.push(format!("{field} {item}"));
712                    }
713                }
714            }
715        }
716        if !sql.is_empty() {
717            return format!("ORDER BY {}", sql.join(","));
718        }
719        "".to_string()
720    }
721    pub fn group(&mut self) -> String {
722        let mut sql = vec![];
723        for (_, field) in self.group.entries() {
724            let field_str = field.to_string();
725            if field_str.contains(".") {
726                sql.push(format!("{}", field));
727            } else {
728                match self.mode.as_str() {
729                    "pgsql" => {
730                        if DISABLE_FIELD.contains(&&*field_str) {
731                            sql.push(format!("\"{field}\""));
732                        } else {
733                            sql.push(format!("{}.{}", self.table, field));
734                        }
735                    }
736                    "mssql" => {
737                        if DISABLE_FIELD.contains(&&*field_str) {
738                            sql.push(format!("[{field}]"));
739                        } else {
740                            sql.push(format!("{}.{}", self.table, field));
741                        }
742                    }
743                    _ => {
744                        if DISABLE_FIELD.contains(&&*field_str) {
745                            sql.push(format!("`{field}`"));
746                        } else {
747                            sql.push(format!("{}.{}", self.table, field));
748                        }
749                    }
750                }
751            }
752        }
753        if !sql.is_empty() {
754            return format!("GROUP BY {}", sql.join(","));
755        }
756        "".to_string()
757    }
758    pub fn having(&mut self) -> String {
759        if self.having.is_empty() {
760            return "".to_string();
761        }
762        format!("HAVING {}", self.having.join(" AND "))
763    }
764    pub fn distinct(&self) -> String {
765        if self.distinct {
766            "DISTINCT".to_string()
767        } else {
768            "".to_string()
769        }
770    }
771    pub fn select_sql(&mut self) -> String {
772        let base = format!(
773            "SELECT {} {} FROM {} {} {} {} {} {} {} {} {} {}",
774            self.distinct(),
775            self.fields(),
776            self.top(),
777            self.table(),
778            self.join(),
779            self.join_inner(),
780            self.where_sql(),
781            self.top2(),
782            self.group(),
783            self.having(),
784            self.order(),
785            self.page_limit_sql()
786        );
787        if self.unions.is_empty() {
788            if self.lock_mode.is_empty() {
789                base
790            } else {
791                format!("{base} {}", self.lock_mode)
792            }
793        } else {
794            let unions = self.unions.join(" ");
795            if self.lock_mode.is_empty() {
796                format!("{base} {unions}")
797            } else {
798                format!("{base} {unions} {}", self.lock_mode)
799            }
800        }
801    }
802}
803
804#[cfg(test)]
805mod params_tests {
806    use super::*;
807    use json::object;
808
809    #[test]
810    fn test_quote_identifier_mysql() {
811        assert_eq!(quote_identifier("name", "mysql"), "`name`");
812        assert_eq!(quote_identifier("user", "mysql"), "`user`");
813    }
814
815    #[test]
816    fn test_quote_identifier_sqlite() {
817        assert_eq!(quote_identifier("name", "sqlite"), "`name`");
818        assert_eq!(quote_identifier("order", "sqlite"), "`order`");
819    }
820
821    #[test]
822    fn test_quote_identifier_pgsql() {
823        assert_eq!(quote_identifier("name", "pgsql"), "\"name\"");
824        assert_eq!(quote_identifier("select", "pgsql"), "\"select\"");
825    }
826
827    #[test]
828    fn test_quote_identifier_mssql() {
829        assert_eq!(quote_identifier("name", "mssql"), "[name]");
830        assert_eq!(quote_identifier("index", "mssql"), "[index]");
831    }
832
833    #[test]
834    fn test_quote_identifier_unknown() {
835        assert_eq!(quote_identifier("name", "unknown"), "name");
836        assert_eq!(quote_identifier("field", ""), "field");
837    }
838
839    #[test]
840    fn test_table_options_default() {
841        let opts = TableOptions::default();
842        assert_eq!(opts.table_name, "");
843        assert_eq!(opts.table_title, "");
844        assert_eq!(opts.table_key, "");
845        assert!(opts.table_fields.is_null());
846        assert!(opts.table_unique.is_empty());
847        assert!(opts.table_index.is_empty());
848        assert!(!opts.table_partition);
849        assert!(opts.table_partition_columns.is_null());
850    }
851
852    #[test]
853    fn test_table_options_set_table_name() {
854        let mut opts = TableOptions::default();
855        opts.set_table_name("users");
856        assert_eq!(opts.table_name, "users");
857    }
858
859    #[test]
860    fn test_table_options_set_table_title() {
861        let mut opts = TableOptions::default();
862        opts.set_table_title("User Table");
863        assert_eq!(opts.table_title, "User Table");
864    }
865
866    #[test]
867    fn test_table_options_set_table_key() {
868        let mut opts = TableOptions::default();
869        opts.set_table_key("id");
870        assert_eq!(opts.table_key, "id");
871    }
872
873    #[test]
874    fn test_table_options_set_table_fields() {
875        let mut opts = TableOptions::default();
876        let fields = object! { "name" => "varchar", "age" => "int" };
877        opts.set_table_fields(fields);
878        assert_eq!(opts.table_fields["name"], "varchar");
879        assert_eq!(opts.table_fields["age"], "int");
880    }
881
882    #[test]
883    fn test_table_options_set_table_unique() {
884        let mut opts = TableOptions::default();
885        opts.set_table_unique(vec!["email", "username"]);
886        assert_eq!(
887            opts.table_unique,
888            vec!["email".to_string(), "username".to_string()]
889        );
890    }
891
892    #[test]
893    fn test_table_options_set_table_index() {
894        let mut opts = TableOptions::default();
895        opts.set_table_index(vec![vec!["name", "age"], vec!["email"]]);
896        assert_eq!(opts.table_index.len(), 2);
897        assert_eq!(
898            opts.table_index[0],
899            vec!["name".to_string(), "age".to_string()]
900        );
901        assert_eq!(opts.table_index[1], vec!["email".to_string()]);
902    }
903
904    #[test]
905    fn test_table_options_set_table_partition() {
906        let mut opts = TableOptions::default();
907        assert!(!opts.table_partition);
908        opts.set_table_partition(true);
909        assert!(opts.table_partition);
910    }
911
912    #[test]
913    fn test_table_options_set_table_partition_columns() {
914        let mut opts = TableOptions::default();
915        let cols = object! { "col1" => "range", "col2" => "hash" };
916        opts.set_table_partition_columns(cols);
917        assert_eq!(opts.table_partition_columns["col1"], "range");
918        assert_eq!(opts.table_partition_columns["col2"], "hash");
919    }
920
921    #[test]
922    fn test_params_default_mysql() {
923        let p = Params::default("mysql");
924        assert_eq!(p.mode, "mysql");
925        assert!(!p.autoinc);
926        assert_eq!(p.table, "");
927        assert!(p.where_and.is_empty());
928        assert!(p.where_or.is_empty());
929        assert_eq!(p.where_column, "");
930        assert!(p.update_column.is_empty());
931        assert!(p.inc_dec.is_object());
932        assert_eq!(p.page, -1);
933        assert_eq!(p.limit, 10);
934        assert!(p.fields.is_object());
935        assert_eq!(p.top, "");
936        assert_eq!(p.top2, "");
937        assert!(p.order.is_object());
938        assert!(p.group.is_object());
939        assert!(!p.distinct);
940        assert!(p.json.is_object());
941        assert!(p.location.is_object());
942        assert!(!p.sql);
943        assert!(p.join.is_empty());
944        assert!(p.join_inner.is_empty());
945        assert_eq!(p.join_table, "");
946    }
947
948    #[test]
949    fn test_params_default_pgsql() {
950        let p = Params::default("pgsql");
951        assert_eq!(p.mode, "pgsql");
952        assert_eq!(p.page, -1);
953        assert_eq!(p.limit, 10);
954    }
955
956    #[test]
957    fn test_params_default_mssql() {
958        let p = Params::default("mssql");
959        assert_eq!(p.mode, "mssql");
960    }
961
962    #[test]
963    fn test_params_default_sqlite() {
964        let p = Params::default("sqlite");
965        assert_eq!(p.mode, "sqlite");
966    }
967
968    #[test]
969    fn test_where_sql_empty() {
970        let mut p = Params::default("mysql");
971        assert_eq!(p.where_sql(), "");
972    }
973
974    #[test]
975    fn test_where_sql_only_and() {
976        let mut p = Params::default("mysql");
977        p.where_and.push("a = 1".to_string());
978        p.where_and.push("b = 2".to_string());
979        assert_eq!(p.where_sql(), "WHERE a = 1 AND b = 2");
980    }
981
982    #[test]
983    fn test_where_sql_only_or() {
984        let mut p = Params::default("mysql");
985        p.where_or.push("a = 1".to_string());
986        p.where_or.push("b = 2".to_string());
987        assert_eq!(p.where_sql(), "WHERE  ( a = 1 OR b = 2 ) ");
988    }
989
990    #[test]
991    fn test_where_sql_and_plus_or() {
992        let mut p = Params::default("mysql");
993        p.where_and.push("c = 3".to_string());
994        p.where_or.push("a = 1".to_string());
995        p.where_or.push("b = 2".to_string());
996        let result = p.where_sql();
997        assert!(result.starts_with("WHERE"));
998        assert!(result.contains("( a = 1 OR b = 2 )"));
999        assert!(result.contains("c = 3"));
1000        assert!(result.contains(" AND "));
1001    }
1002
1003    #[test]
1004    fn test_where_sql_with_where_column() {
1005        let mut p = Params::default("mysql");
1006        p.where_column = "x.id = y.id".to_string();
1007        assert_eq!(p.where_sql(), "WHERE x.id = y.id");
1008    }
1009
1010    #[test]
1011    fn test_where_sql_all_three() {
1012        let mut p = Params::default("mysql");
1013        p.where_and.push("a = 1".to_string());
1014        p.where_or.push("b = 2".to_string());
1015        p.where_or.push("c = 3".to_string());
1016        p.where_column = "x.id = y.id".to_string();
1017        let result = p.where_sql();
1018        assert!(result.starts_with("WHERE"));
1019        assert!(result.contains("( b = 2 OR c = 3 )"));
1020        assert!(result.contains("a = 1"));
1021        assert!(result.contains("x.id = y.id"));
1022    }
1023
1024    #[test]
1025    fn test_page_limit_sql_default_page_neg1() {
1026        let mut p = Params::default("mysql");
1027        assert_eq!(p.page_limit_sql(), "");
1028    }
1029
1030    #[test]
1031    fn test_page_limit_sql_mysql() {
1032        let mut p = Params::default("mysql");
1033        p.page = 1;
1034        p.limit = 10;
1035        assert_eq!(p.page_limit_sql(), "LIMIT 0,10");
1036    }
1037
1038    #[test]
1039    fn test_page_limit_sql_mysql_page2() {
1040        let mut p = Params::default("mysql");
1041        p.page = 2;
1042        p.limit = 20;
1043        assert_eq!(p.page_limit_sql(), "LIMIT 20,20");
1044    }
1045
1046    #[test]
1047    fn test_page_limit_sql_sqlite() {
1048        let mut p = Params::default("sqlite");
1049        p.page = 1;
1050        p.limit = 10;
1051        assert_eq!(p.page_limit_sql(), "LIMIT 10 OFFSET 0");
1052    }
1053
1054    #[test]
1055    fn test_page_limit_sql_sqlite_page3() {
1056        let mut p = Params::default("sqlite");
1057        p.page = 3;
1058        p.limit = 5;
1059        assert_eq!(p.page_limit_sql(), "LIMIT 5 OFFSET 10");
1060    }
1061
1062    #[test]
1063    fn test_page_limit_sql_unknown_mode() {
1064        let mut p = Params::default("unknown");
1065        p.page = 1;
1066        p.limit = 10;
1067        assert_eq!(p.page_limit_sql(), "");
1068    }
1069
1070    #[test]
1071    fn test_fields_empty() {
1072        let mut p = Params::default("mysql");
1073        assert_eq!(p.fields(), "*");
1074    }
1075
1076    #[test]
1077    fn test_fields_mysql_with_disable_field() {
1078        let mut p = Params::default("mysql");
1079        p.fields = object! { "f1" => "order", "f2" => "name" };
1080        let result = p.fields();
1081        assert!(result.contains("`order`"));
1082        assert!(result.contains("name"));
1083        assert!(!result.contains("`name`"));
1084    }
1085
1086    #[test]
1087    fn test_fields_mysql_normal() {
1088        let mut p = Params::default("mysql");
1089        p.fields = object! { "f1" => "id", "f2" => "name" };
1090        let result = p.fields();
1091        assert!(result.contains("id"));
1092        assert!(result.contains("name"));
1093    }
1094
1095    #[test]
1096    fn test_fields_mssql() {
1097        let mut p = Params::default("mssql");
1098        p.fields = object! { "f1" => "id", "f2" => "name" };
1099        let result = p.fields();
1100        assert!(result.contains("id"));
1101        assert!(result.contains("name"));
1102    }
1103
1104    #[test]
1105    fn test_fields_other_mode() {
1106        let mut p = Params::default("pgsql");
1107        p.fields = object! { "f1" => "id", "f2" => "email" };
1108        let result = p.fields();
1109        assert!(result.contains("id"));
1110        assert!(result.contains("email"));
1111    }
1112
1113    #[test]
1114    fn test_top_non_mssql() {
1115        let mut p = Params::default("mysql");
1116        assert_eq!(p.top(), "");
1117    }
1118
1119    #[test]
1120    fn test_top_mssql_without_top2() {
1121        let mut p = Params::default("mssql");
1122        p.table = "users".to_string();
1123        assert_eq!(p.top(), "");
1124    }
1125
1126    #[test]
1127    fn test_top_mssql_with_top2() {
1128        let mut p = Params::default("mssql");
1129        p.table = "users".to_string();
1130        p.top2 = "ROW BETWEEN 1 AND 10".to_string();
1131        let result = p.top();
1132        assert!(result.contains("ROW_NUMBER"));
1133        assert!(result.contains("users"));
1134        assert!(result.contains("as "));
1135    }
1136
1137    #[test]
1138    fn test_top_mssql_with_top2_and_order() {
1139        let mut p = Params::default("mssql");
1140        p.table = "users".to_string();
1141        p.top2 = "ROW BETWEEN 1 AND 10".to_string();
1142        p.order = object! { "id" => "ASC" };
1143        let result = p.top();
1144        assert!(result.contains("ROW_NUMBER"));
1145        assert!(result.contains("ORDER BY"));
1146        assert!(result.contains("id ASC"));
1147    }
1148
1149    #[test]
1150    fn test_top2_non_mssql() {
1151        let mut p = Params::default("mysql");
1152        p.top2 = "something".to_string();
1153        assert_eq!(p.top2(), "");
1154    }
1155
1156    #[test]
1157    fn test_top2_mssql_no_where_with_top2() {
1158        let mut p = Params::default("mssql");
1159        p.top2 = "ROW BETWEEN 1 AND 10".to_string();
1160        let result = p.top2();
1161        assert_eq!(result, "where ROW BETWEEN 1 AND 10");
1162    }
1163
1164    #[test]
1165    fn test_top2_mssql_with_where_and_top2() {
1166        let mut p = Params::default("mssql");
1167        p.where_and.push("a = 1".to_string());
1168        p.top2 = "ROW BETWEEN 1 AND 10".to_string();
1169        let result = p.top2();
1170        assert_eq!(result, "AND ROW BETWEEN 1 AND 10");
1171    }
1172
1173    #[test]
1174    fn test_top2_mssql_empty_top2() {
1175        let mut p = Params::default("mssql");
1176        assert_eq!(p.top2(), "");
1177    }
1178
1179    #[test]
1180    fn test_table_mssql_with_top2() {
1181        let mut p = Params::default("mssql");
1182        p.table = "users".to_string();
1183        p.top2 = "ROW BETWEEN 1 AND 10".to_string();
1184        assert_eq!(p.table(), "t");
1185    }
1186
1187    #[test]
1188    fn test_table_mssql_without_top2() {
1189        let mut p = Params::default("mssql");
1190        p.table = "users".to_string();
1191        assert_eq!(p.table(), "users");
1192    }
1193
1194    #[test]
1195    fn test_table_other_modes() {
1196        let mut p = Params::default("mysql");
1197        p.table = "orders".to_string();
1198        assert_eq!(p.table(), "orders");
1199
1200        let mut p2 = Params::default("pgsql");
1201        p2.table = "products".to_string();
1202        assert_eq!(p2.table(), "products");
1203    }
1204
1205    #[test]
1206    fn test_join_empty() {
1207        let mut p = Params::default("mysql");
1208        assert_eq!(p.join(), "");
1209    }
1210
1211    #[test]
1212    fn test_join_with_entries() {
1213        let mut p = Params::default("mysql");
1214        p.join
1215            .push("LEFT JOIN orders ON users.id = orders.user_id".to_string());
1216        p.join
1217            .push("LEFT JOIN items ON orders.id = items.order_id".to_string());
1218        let result = p.join();
1219        assert!(result.contains("LEFT JOIN orders ON users.id = orders.user_id"));
1220        assert!(result.contains("LEFT JOIN items ON orders.id = items.order_id"));
1221        assert!(result.contains(" "));
1222    }
1223
1224    #[test]
1225    fn test_join_mssql() {
1226        let mut p = Params::default("mssql");
1227        p.join.push("JOIN t2 ON t1.id = t2.fk".to_string());
1228        let result = p.join();
1229        assert_eq!(result, "JOIN t2 ON t1.id = t2.fk");
1230    }
1231
1232    #[test]
1233    fn test_join_inner_empty() {
1234        let mut p = Params::default("mysql");
1235        assert_eq!(p.join_inner(), "");
1236    }
1237
1238    #[test]
1239    fn test_join_inner_mysql() {
1240        let mut p = Params::default("mysql");
1241        p.join_inner
1242            .push("INNER JOIN orders ON users.id = orders.uid".to_string());
1243        p.join_inner
1244            .push("INNER JOIN items ON orders.id = items.oid".to_string());
1245        let result = p.join_inner();
1246        assert!(result.contains("INNER JOIN orders ON users.id = orders.uid"));
1247        assert!(result.contains("INNER JOIN items ON orders.id = items.oid"));
1248    }
1249
1250    #[test]
1251    fn test_join_inner_non_mysql() {
1252        let mut p = Params::default("pgsql");
1253        p.join_inner
1254            .push("INNER JOIN orders ON users.id = orders.uid".to_string());
1255        assert_eq!(p.join_inner(), "");
1256
1257        let mut p2 = Params::default("mssql");
1258        p2.join_inner
1259            .push("INNER JOIN orders ON users.id = orders.uid".to_string());
1260        assert_eq!(p2.join_inner(), "");
1261    }
1262
1263    #[test]
1264    fn test_order_empty() {
1265        let mut p = Params::default("mysql");
1266        assert_eq!(p.order(), "");
1267    }
1268
1269    #[test]
1270    fn test_order_with_entries() {
1271        let mut p = Params::default("mysql");
1272        p.order = object! { "id" => "ASC", "name" => "DESC" };
1273        let result = p.order();
1274        assert!(result.starts_with("ORDER BY"));
1275        assert!(result.contains("id ASC"));
1276        assert!(result.contains("name DESC"));
1277    }
1278
1279    #[test]
1280    fn test_order_mysql_disable_field() {
1281        let mut p = Params::default("mysql");
1282        p.order = object! { "order" => "ASC" };
1283        let result = p.order();
1284        assert!(result.contains("`order` ASC"));
1285    }
1286
1287    #[test]
1288    fn test_order_pgsql_disable_field() {
1289        let mut p = Params::default("pgsql");
1290        p.order = object! { "order" => "DESC" };
1291        let result = p.order();
1292        assert!(result.contains("\"order\" DESC"));
1293    }
1294
1295    #[test]
1296    fn test_order_mssql_disable_field() {
1297        let mut p = Params::default("mssql");
1298        p.order = object! { "order" => "ASC" };
1299        let result = p.order();
1300        assert!(result.contains("[order] ASC"));
1301    }
1302
1303    #[test]
1304    fn test_group_empty() {
1305        let mut p = Params::default("mysql");
1306        assert_eq!(p.group(), "");
1307    }
1308
1309    #[test]
1310    fn test_group_with_entries() {
1311        let mut p = Params::default("mysql");
1312        p.table = "users".to_string();
1313        p.group = object! { "g1" => "status" };
1314        let result = p.group();
1315        assert!(result.starts_with("GROUP BY"));
1316        assert!(result.contains("users.status"));
1317    }
1318
1319    #[test]
1320    fn test_group_disable_field() {
1321        let mut p = Params::default("mysql");
1322        p.table = "users".to_string();
1323        p.group = object! { "g1" => "order" };
1324        let result = p.group();
1325        assert!(result.contains("`order`"));
1326    }
1327
1328    #[test]
1329    fn test_group_dotted_field() {
1330        let mut p = Params::default("mysql");
1331        p.table = "users".to_string();
1332        p.group = object! { "g1" => "orders.status" };
1333        let result = p.group();
1334        assert!(result.contains("orders.status"));
1335        assert!(!result.contains("users.orders.status"));
1336    }
1337
1338    #[test]
1339    fn test_distinct_false() {
1340        let p = Params::default("mysql");
1341        assert_eq!(p.distinct(), "");
1342    }
1343
1344    #[test]
1345    fn test_distinct_true() {
1346        let mut p = Params::default("mysql");
1347        p.distinct = true;
1348        assert_eq!(p.distinct(), "DISTINCT");
1349    }
1350
1351    #[test]
1352    fn test_select_sql_basic() {
1353        let mut p = Params::default("mysql");
1354        p.table = "users".to_string();
1355        let result = p.select_sql();
1356        assert!(result.contains("SELECT"));
1357        assert!(result.contains("*"));
1358        assert!(result.contains("FROM"));
1359        assert!(result.contains("users"));
1360    }
1361
1362    #[test]
1363    fn test_select_sql_with_where_and_order() {
1364        let mut p = Params::default("mysql");
1365        p.table = "users".to_string();
1366        p.where_and.push("age > 18".to_string());
1367        p.order = object! { "name" => "ASC" };
1368        p.page = 1;
1369        p.limit = 10;
1370        let result = p.select_sql();
1371        assert!(result.contains("SELECT"));
1372        assert!(result.contains("FROM"));
1373        assert!(result.contains("users"));
1374        assert!(result.contains("WHERE age > 18"));
1375        assert!(result.contains("ORDER BY name ASC"));
1376        assert!(result.contains("LIMIT 0,10"));
1377    }
1378
1379    #[test]
1380    fn test_select_sql_with_distinct_and_group() {
1381        let mut p = Params::default("mysql");
1382        p.table = "orders".to_string();
1383        p.distinct = true;
1384        p.group = object! { "g1" => "status" };
1385        p.fields = object! { "f1" => "status", "f2" => "count" };
1386        let result = p.select_sql();
1387        assert!(result.contains("DISTINCT"));
1388        assert!(result.contains("GROUP BY"));
1389        assert!(result.contains("orders.status"));
1390    }
1391
1392    #[test]
1393    fn test_having_empty() {
1394        let mut p = Params::default("mysql");
1395        assert_eq!(p.having(), "");
1396    }
1397
1398    #[test]
1399    fn test_having_single() {
1400        let mut p = Params::default("mysql");
1401        p.having.push("COUNT(*) > 5".to_string());
1402        assert_eq!(p.having(), "HAVING COUNT(*) > 5");
1403    }
1404
1405    #[test]
1406    fn test_having_multiple() {
1407        let mut p = Params::default("mysql");
1408        p.having.push("COUNT(*) > 5".to_string());
1409        p.having.push("SUM(amount) > 100".to_string());
1410        assert_eq!(p.having(), "HAVING COUNT(*) > 5 AND SUM(amount) > 100");
1411    }
1412
1413    #[test]
1414    fn test_select_sql_with_having() {
1415        let mut p = Params::default("mysql");
1416        p.table = "orders".to_string();
1417        p.group = object! { "g1" => "status" };
1418        p.having.push("COUNT(*) > 1".to_string());
1419        let result = p.select_sql();
1420        assert!(result.contains("GROUP BY"));
1421        assert!(result.contains("HAVING COUNT(*) > 1"));
1422    }
1423
1424    #[test]
1425    fn test_field_raw_in_select_sql() {
1426        let mut p = Params::default("mysql");
1427        p.table = "orders".to_string();
1428        p.fields["raw1"] = "SUM(amount) as total".into();
1429        p.fields["raw2"] = "COUNT(*) as cnt".into();
1430        let result = p.select_sql();
1431        assert!(result.contains("SUM(amount) as total"));
1432        assert!(result.contains("COUNT(*) as cnt"));
1433        assert!(!result.contains("orders.`SUM"));
1434    }
1435
1436    #[test]
1437    fn test_where_raw_in_select_sql() {
1438        let mut p = Params::default("mysql");
1439        p.table = "orders".to_string();
1440        p.where_and.push("YEAR(created_at) = 2026".to_string());
1441        let result = p.select_sql();
1442        assert!(result.contains("WHERE YEAR(created_at) = 2026"));
1443    }
1444
1445    #[test]
1446    fn test_limit_only_default() {
1447        let mut p = Params::default("mysql");
1448        assert_eq!(p.page_limit_sql(), "");
1449    }
1450
1451    #[test]
1452    fn test_limit_only_mysql() {
1453        let mut p = Params::default("mysql");
1454        p.limit_only = 5;
1455        assert_eq!(p.page_limit_sql(), "LIMIT 5");
1456    }
1457
1458    #[test]
1459    fn test_limit_only_sqlite() {
1460        let mut p = Params::default("sqlite");
1461        p.limit_only = 10;
1462        assert_eq!(p.page_limit_sql(), "LIMIT 10");
1463    }
1464
1465    #[test]
1466    fn test_limit_only_pgsql() {
1467        let mut p = Params::default("pgsql");
1468        p.limit_only = 3;
1469        assert_eq!(p.page_limit_sql(), "LIMIT 3");
1470    }
1471
1472    #[test]
1473    fn test_page_overrides_limit_only() {
1474        let mut p = Params::default("mysql");
1475        p.limit_only = 5;
1476        p.page = 2;
1477        p.limit = 10;
1478        assert_eq!(p.page_limit_sql(), "LIMIT 10,10");
1479    }
1480
1481    #[test]
1482    fn test_limit_only_in_select_sql() {
1483        let mut p = Params::default("mysql");
1484        p.table = "users".to_string();
1485        p.limit_only = 3;
1486        let result = p.select_sql();
1487        assert!(result.contains("LIMIT 3"));
1488        assert!(!result.contains("OFFSET"));
1489    }
1490
1491    #[test]
1492    fn test_where_in_sub() {
1493        let mut p = Params::default("mysql");
1494        p.table = "users".to_string();
1495        p.where_and
1496            .push("`status` IN (SELECT status FROM active_users)".to_string());
1497        let result = p.select_sql();
1498        assert!(result.contains("WHERE `status` IN (SELECT status FROM active_users)"));
1499    }
1500
1501    #[test]
1502    fn test_where_not_in_sub() {
1503        let mut p = Params::default("pgsql");
1504        p.table = "users".to_string();
1505        p.where_and
1506            .push("\"id\" NOT IN (SELECT user_id FROM banned)".to_string());
1507        let result = p.select_sql();
1508        assert!(result.contains("WHERE \"id\" NOT IN (SELECT user_id FROM banned)"));
1509    }
1510
1511    #[test]
1512    fn test_union_single() {
1513        let mut p = Params::default("mysql");
1514        p.table = "users".to_string();
1515        p.unions.push("UNION SELECT * FROM admins".to_string());
1516        let result = p.select_sql();
1517        assert!(result.contains("UNION SELECT * FROM admins"));
1518    }
1519
1520    #[test]
1521    fn test_union_all() {
1522        let mut p = Params::default("mysql");
1523        p.table = "users".to_string();
1524        p.unions.push("UNION ALL SELECT * FROM guests".to_string());
1525        let result = p.select_sql();
1526        assert!(result.contains("UNION ALL SELECT * FROM guests"));
1527    }
1528
1529    #[test]
1530    fn test_union_multiple() {
1531        let mut p = Params::default("mysql");
1532        p.table = "users".to_string();
1533        p.unions.push("UNION SELECT * FROM admins".to_string());
1534        p.unions.push("UNION ALL SELECT * FROM guests".to_string());
1535        let result = p.select_sql();
1536        assert!(result.contains("UNION SELECT * FROM admins"));
1537        assert!(result.contains("UNION ALL SELECT * FROM guests"));
1538    }
1539
1540    #[test]
1541    fn test_no_union() {
1542        let mut p = Params::default("mysql");
1543        p.table = "users".to_string();
1544        let result = p.select_sql();
1545        assert!(!result.contains("UNION"));
1546    }
1547
1548    #[test]
1549    fn test_timestamps_default_false() {
1550        let p = Params::default("mysql");
1551        assert!(!p.timestamps);
1552    }
1553
1554    #[test]
1555    fn test_lock_for_update_in_select_sql() {
1556        let mut p = Params::default("mysql");
1557        p.table = "users".to_string();
1558        p.lock_mode = "FOR UPDATE".to_string();
1559        let result = p.select_sql();
1560        assert!(result.contains("FOR UPDATE"));
1561    }
1562
1563    #[test]
1564    fn test_lock_for_share_in_select_sql() {
1565        let mut p = Params::default("pgsql");
1566        p.table = "users".to_string();
1567        p.lock_mode = "FOR SHARE".to_string();
1568        let result = p.select_sql();
1569        assert!(result.contains("FOR SHARE"));
1570    }
1571
1572    #[test]
1573    fn test_no_lock_mode() {
1574        let mut p = Params::default("mysql");
1575        p.table = "users".to_string();
1576        let result = p.select_sql();
1577        assert!(!result.contains("FOR UPDATE"));
1578        assert!(!result.contains("FOR SHARE"));
1579    }
1580
1581    #[test]
1582    fn test_where_exists_in_select_sql() {
1583        let mut p = Params::default("mysql");
1584        p.table = "users".to_string();
1585        p.where_and
1586            .push("EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id)".to_string());
1587        let result = p.select_sql();
1588        assert!(result.contains("EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id)"));
1589    }
1590
1591    #[test]
1592    fn test_where_not_exists_in_select_sql() {
1593        let mut p = Params::default("mysql");
1594        p.table = "users".to_string();
1595        p.where_and
1596            .push("NOT EXISTS (SELECT 1 FROM banned WHERE banned.uid = users.id)".to_string());
1597        let result = p.select_sql();
1598        assert!(result.contains("NOT EXISTS (SELECT 1 FROM banned WHERE banned.uid = users.id)"));
1599    }
1600
1601    #[test]
1602    fn test_lock_with_union() {
1603        let mut p = Params::default("mysql");
1604        p.table = "users".to_string();
1605        p.unions.push("UNION SELECT * FROM admins".to_string());
1606        p.lock_mode = "FOR UPDATE".to_string();
1607        let result = p.select_sql();
1608        assert!(result.contains("UNION SELECT * FROM admins"));
1609        assert!(result.contains("FOR UPDATE"));
1610    }
1611
1612    #[test]
1613    fn test_lock_mode_default_empty() {
1614        let p = Params::default("mysql");
1615        assert!(p.lock_mode.is_empty());
1616    }
1617
1618    #[test]
1619    fn test_unions_default_empty() {
1620        let p = Params::default("mysql");
1621        assert!(p.unions.is_empty());
1622    }
1623}