Skip to main content

br_db/types/
mod.rs

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