1pub use crate::db_type::DbType;
6use crate::error::DbError;
7use std::fmt;
8
9pub const MAX_IDENTIFIER_LEN: usize = 63;
19
20pub trait Dialect: Send + Sync {
24 fn clone_box(&self) -> Box<dyn Dialect>;
26
27 fn db_type(&self) -> DbType;
29
30 fn quote(&self, identifier: &str) -> String;
32
33 fn quote_checked(&self, identifier: &str) -> Result<String, DbError> {
44 crate::sql_safety::validate_identifier(identifier, "identifier")?;
45 Ok(self.quote(identifier))
46 }
47
48 fn escape_string(&self, s: &str) -> String;
50
51 fn supports_returning(&self) -> bool;
53
54 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String;
56
57 fn json_type(&self) -> &'static str;
59
60 fn json_extract(&self, column: &str, path: &str) -> String;
62
63 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String;
65
66 fn bool_to_int(&self, expr: &str) -> String;
68
69 fn concat(&self, parts: &[&str]) -> String;
71
72 fn supports_if_exists(&self) -> bool;
74
75 fn supports_if_not_exists(&self) -> bool;
77
78 fn auto_increment_keyword(&self) -> &'static str;
80
81 fn last_insert_id_sql(&self) -> Option<&'static str>;
87
88 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String;
90
91 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String;
93
94 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
99 if if_exists && self.supports_if_exists() {
100 format!("DROP TABLE IF EXISTS {}", self.quote(table))
101 } else {
102 format!("DROP TABLE {}", self.quote(table))
103 }
104 }
105
106 fn build_upsert_on_conflict(
121 &self,
122 conflict_columns: &[&str],
123 update_columns: &[&str],
124 all_columns: &[String],
125 ) -> Option<String> {
126 let _ = (conflict_columns, update_columns, all_columns);
127 None
128 }
129
130 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
139 let _ = lock_type;
140 None
141 }
142
143 fn supports_lock_for_update(&self) -> bool {
148 true
149 }
150
151 fn supports_lock_shared(&self) -> bool {
156 true
157 }
158
159 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
166 format!("INSERT OR IGNORE INTO {}", self.quote(table))
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub enum LockType {
179 ForUpdate,
181 Shared,
183}
184
185#[derive(Debug, Clone)]
187pub struct ColumnDef {
188 pub name: String,
189 pub sql_type: String,
190 pub nullable: bool,
191 pub default: Option<String>,
192 pub auto_increment: bool,
193 pub primary_key: bool,
194}
195
196#[derive(Debug, Clone)]
198pub enum TableChange {
199 AddColumn(ColumnDef),
200 DropColumn(String),
201 ModifyColumn(ColumnDef),
202 AddIndex(String, Vec<String>),
203 DropIndex(String),
204 AddForeignKey {
205 columns: Vec<String>,
206 reference_table: String,
207 reference_columns: Vec<String>,
208 },
209}
210
211#[derive(Debug, Clone)]
213pub struct MySqlDialect;
214
215impl Dialect for MySqlDialect {
216 fn clone_box(&self) -> Box<dyn Dialect> {
217 Box::new(MySqlDialect)
218 }
219
220 fn db_type(&self) -> DbType {
221 DbType::MySQL
222 }
223
224 fn quote(&self, identifier: &str) -> String {
225 format!("`{}`", identifier.replace('`', "``"))
226 }
227
228 fn escape_string(&self, s: &str) -> String {
229 let mut escaped = String::with_capacity(s.len() * 2);
230 for c in s.chars() {
231 match c {
232 '\\' => escaped.push_str("\\\\"),
233 '\'' => escaped.push_str("\\'"),
234 '\0' => escaped.push_str("\\0"),
235 '\n' => escaped.push_str("\\n"),
236 '\r' => escaped.push_str("\\r"),
237 '\t' => escaped.push_str("\\t"),
238 '\x1a' => escaped.push_str("\\Z"),
239 _ => escaped.push(c),
240 }
241 }
242 escaped
243 }
244
245 fn supports_returning(&self) -> bool {
246 false
247 }
248
249 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
250 let offset = page.saturating_sub(1).saturating_mul(limit);
258 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
259 }
260
261 fn json_type(&self) -> &'static str {
262 "JSON"
263 }
264
265 fn json_extract(&self, column: &str, path: &str) -> String {
266 let normalized = if path.starts_with('$') {
268 path.to_string()
269 } else {
270 format!("$.{}", path)
271 };
272 format!(
273 "JSON_EXTRACT({}, '{}')",
274 column,
275 self.escape_string(&normalized)
276 )
277 }
278
279 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
280 let cols = columns.join(", ");
281 let escaped = self.escape_string(keyword);
282 format!(
283 "MATCH({}) AGAINST('{}' IN NATURAL LANGUAGE MODE)",
284 cols, escaped
285 )
286 }
287
288 fn bool_to_int(&self, expr: &str) -> String {
289 format!("IF({}, 1, 0)", expr)
291 }
292
293 fn concat(&self, parts: &[&str]) -> String {
294 if parts.is_empty() {
295 return "NULL".to_string();
296 }
297 let concat_parts: Vec<String> = parts
298 .iter()
299 .map(|p| format!("CAST({} AS CHAR)", p))
300 .collect();
301 format!("CONCAT({})", concat_parts.join(", "))
302 }
303
304 fn supports_if_exists(&self) -> bool {
305 true
306 }
307
308 fn supports_if_not_exists(&self) -> bool {
309 true
310 }
311
312 fn auto_increment_keyword(&self) -> &'static str {
313 "AUTO_INCREMENT"
314 }
315
316 fn last_insert_id_sql(&self) -> Option<&'static str> {
317 Some("LAST_INSERT_ID()")
318 }
319
320 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
321 let cols: Vec<String> = columns
322 .iter()
323 .map(|col| {
324 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
325 if !col.nullable {
326 sql.push_str(" NOT NULL");
327 }
328 if let Some(default) = &col.default {
329 sql.push_str(&format!(" DEFAULT {}", default));
330 }
331 if col.auto_increment {
332 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
333 }
334 if col.primary_key {
335 sql.push_str(" PRIMARY KEY");
336 }
337 sql
338 })
339 .collect();
340
341 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
342 }
343
344 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
345 let stmts: Vec<String> = changes.iter().map(|change| {
346 match change {
347 TableChange::AddColumn(col) => {
348 let mut sql = format!("ALTER TABLE {} ADD {}", self.quote(table), self.quote(&col.name));
349 sql.push_str(&format!(" {}", col.sql_type));
350 if !col.nullable {
351 sql.push_str(" NOT NULL");
352 }
353 if let Some(default) = &col.default {
354 sql.push_str(&format!(" DEFAULT {}", default));
355 }
356 sql
357 }
358 TableChange::DropColumn(name) => {
359 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
360 }
361 TableChange::ModifyColumn(col) => {
362 let mut sql = format!("ALTER TABLE {} MODIFY COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
364 if !col.nullable {
365 sql.push_str(" NOT NULL");
366 }
367 if let Some(default) = &col.default {
368 sql.push_str(&format!(" DEFAULT {}", default));
369 }
370 sql
371 }
372 TableChange::AddIndex(name, cols) => {
373 format!("ALTER TABLE {} ADD INDEX {} ({})", self.quote(table), name, cols.join(", "))
374 }
375 TableChange::DropIndex(name) => {
376 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
377 }
378 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
379 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
380 self.quote(table),
381 table,
382 columns.join("_"),
383 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
384 self.quote(reference_table),
385 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
386 }
387 }
388 }).collect();
389
390 stmts.join("; ")
391 }
392
393 fn build_upsert_on_conflict(
398 &self,
399 _conflict_columns: &[&str],
400 update_columns: &[&str],
401 all_columns: &[String],
402 ) -> Option<String> {
403 let cols_to_update: Vec<String> = if update_columns.is_empty() {
405 all_columns.iter().map(|c| self.quote(c)).collect()
406 } else {
407 update_columns.iter().map(|c| self.quote(c)).collect()
408 };
409 if cols_to_update.is_empty() {
410 return None;
411 }
412 let set_clauses: Vec<String> = cols_to_update
413 .iter()
414 .map(|col| format!("{}=VALUES({})", col, col))
415 .collect();
416 Some(format!(
417 "ON DUPLICATE KEY UPDATE {}",
418 set_clauses.join(", ")
419 ))
420 }
421
422 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
423 match lock_type {
424 LockType::ForUpdate => Some("FOR UPDATE".to_string()),
425 LockType::Shared => Some("LOCK IN SHARE MODE".to_string()),
426 }
427 }
428
429 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
430 format!("INSERT IGNORE INTO {}", self.quote(table))
431 }
432}
433
434#[derive(Debug, Clone)]
436pub struct PostgreSqlDialect;
437
438impl Dialect for PostgreSqlDialect {
439 fn clone_box(&self) -> Box<dyn Dialect> {
440 Box::new(PostgreSqlDialect)
441 }
442
443 fn db_type(&self) -> DbType {
444 DbType::PostgreSQL
445 }
446
447 fn quote(&self, identifier: &str) -> String {
448 format!("\"{}\"", identifier.replace('"', "\"\""))
449 }
450
451 fn escape_string(&self, s: &str) -> String {
452 let mut escaped = String::with_capacity(s.len() * 2);
455 for c in s.chars() {
456 match c {
457 '\'' => escaped.push_str("''"),
458 _ => escaped.push(c),
459 }
460 }
461 escaped
462 }
463
464 fn supports_returning(&self) -> bool {
465 true
466 }
467
468 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
469 let offset = page.saturating_sub(1).saturating_mul(limit);
470 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
471 }
472
473 fn json_type(&self) -> &'static str {
474 "JSONB"
475 }
476
477 fn json_extract(&self, column: &str, path: &str) -> String {
478 let normalized = path.trim_start_matches("$.");
483 let parts: Vec<&str> = normalized.split('.').filter(|s| !s.is_empty()).collect();
484 let path_lit = parts
485 .iter()
486 .map(|p| {
487 let needs_quoting = p.chars().any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\'));
489 if needs_quoting {
490 let escaped = p.replace('\\', "\\\\").replace('"', "\\\"");
491 format!("\"{}\"", escaped)
492 } else {
493 p.to_string()
494 }
495 })
496 .collect::<Vec<_>>()
497 .join(",");
498 let path_lit_escaped = path_lit.replace('\'', "''");
500 format!("{}#>>'{{{}}}'", column, path_lit_escaped)
501 }
502
503 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
504 let cols = columns
505 .iter()
506 .map(|c| format!("{}::text", c))
507 .collect::<Vec<_>>()
508 .join(" || ' ' || ");
509 let escaped = self.escape_string(keyword);
510 format!("to_tsvector({}) @@ to_tsquery('{}')", cols, escaped)
511 }
512
513 fn bool_to_int(&self, expr: &str) -> String {
514 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
515 }
516
517 fn concat(&self, parts: &[&str]) -> String {
518 if parts.is_empty() {
519 return "NULL".to_string();
520 }
521 format!("CONCAT({})", parts.join(", "))
522 }
523
524 fn supports_if_exists(&self) -> bool {
525 true
526 }
527
528 fn supports_if_not_exists(&self) -> bool {
529 true
530 }
531
532 fn auto_increment_keyword(&self) -> &'static str {
533 "GENERATED BY DEFAULT AS IDENTITY"
534 }
535
536 fn last_insert_id_sql(&self) -> Option<&'static str> {
537 Some("lastval()")
538 }
539
540 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
541 let cols: Vec<String> = columns
542 .iter()
543 .map(|col| {
544 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
545 if !col.nullable {
546 sql.push_str(" NOT NULL");
547 }
548 if let Some(default) = &col.default {
549 sql.push_str(&format!(" DEFAULT {}", default));
550 }
551 if col.primary_key {
552 sql.push_str(" PRIMARY KEY");
553 }
554 sql
555 })
556 .collect();
557
558 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
559 }
560
561 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
562 let stmts: Vec<String> = changes.iter().map(|change| {
563 match change {
564 TableChange::AddColumn(col) => {
565 let mut sql = format!("ALTER TABLE {} ADD COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
566 if !col.nullable {
567 sql.push_str(" NOT NULL");
568 }
569 if let Some(default) = &col.default {
570 sql.push_str(&format!(" DEFAULT {}", default));
571 }
572 sql
573 }
574 TableChange::DropColumn(name) => {
575 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
576 }
577 TableChange::ModifyColumn(col) => {
578 let mut sql = format!("ALTER TABLE {} ALTER COLUMN {} TYPE {}", self.quote(table), self.quote(&col.name), col.sql_type);
580 if !col.nullable {
581 sql.push_str(&format!(", ALTER COLUMN {} SET NOT NULL", self.quote(&col.name)));
582 }
583 if let Some(default) = &col.default {
584 sql.push_str(&format!(", ALTER COLUMN {} SET DEFAULT {}", self.quote(&col.name), default));
585 }
586 sql
587 }
588 TableChange::AddIndex(name, cols) => {
589 format!("CREATE INDEX {} ON {} ({})", name, self.quote(table), cols.join(", "))
590 }
591 TableChange::DropIndex(name) => {
592 format!("DROP INDEX {}", name)
593 }
594 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
595 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
596 self.quote(table),
597 table,
598 columns.join("_"),
599 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
600 self.quote(reference_table),
601 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
602 }
603 }
604 }).collect();
605
606 stmts.join("; ")
607 }
608
609 fn build_upsert_on_conflict(
613 &self,
614 conflict_columns: &[&str],
615 update_columns: &[&str],
616 all_columns: &[String],
617 ) -> Option<String> {
618 if conflict_columns.is_empty() {
619 return None;
620 }
621 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
622 let conflict_set: std::collections::HashSet<&str> =
624 conflict_columns.iter().copied().collect();
625 let cols_to_update: Vec<String> = if update_columns.is_empty() {
626 all_columns
627 .iter()
628 .filter(|c| !conflict_set.contains(c.as_str()))
629 .map(|c| self.quote(c))
630 .collect()
631 } else {
632 update_columns.iter().map(|c| self.quote(c)).collect()
633 };
634 if cols_to_update.is_empty() {
635 return Some(format!(
637 "ON CONFLICT ({}) DO NOTHING",
638 conflict_cols.join(", ")
639 ));
640 }
641 let set_clauses: Vec<String> = cols_to_update
642 .iter()
643 .map(|col| format!("{}=EXCLUDED.{}", col, col))
644 .collect();
645 Some(format!(
646 "ON CONFLICT ({}) DO UPDATE SET {}",
647 conflict_cols.join(", "),
648 set_clauses.join(", ")
649 ))
650 }
651
652 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
653 match lock_type {
654 LockType::ForUpdate => Some("FOR UPDATE".to_string()),
655 LockType::Shared => Some("FOR SHARE".to_string()),
656 }
657 }
658}
659
660#[derive(Debug, Clone)]
662pub struct SqliteDialect;
663
664impl Dialect for SqliteDialect {
665 fn clone_box(&self) -> Box<dyn Dialect> {
666 Box::new(SqliteDialect)
667 }
668
669 fn db_type(&self) -> DbType {
670 DbType::Sqlite
671 }
672
673 fn quote(&self, identifier: &str) -> String {
674 format!("\"{}\"", identifier.replace('"', "\"\""))
675 }
676
677 fn escape_string(&self, s: &str) -> String {
678 let mut escaped = String::with_capacity(s.len() * 2);
679 for c in s.chars() {
680 match c {
681 '\'' => escaped.push_str("''"),
682 _ => escaped.push(c),
683 }
684 }
685 escaped
686 }
687
688 fn supports_returning(&self) -> bool {
689 true
690 }
691
692 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
693 let offset = page.saturating_sub(1).saturating_mul(limit);
694 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
695 }
696
697 fn json_type(&self) -> &'static str {
698 "TEXT"
699 }
700
701 fn json_extract(&self, column: &str, path: &str) -> String {
702 let normalized = if path.starts_with('$') {
704 path.to_string()
705 } else {
706 format!("$.{}", path)
707 };
708 format!(
709 "json_extract({}, '{}')",
710 column,
711 self.escape_string(&normalized)
712 )
713 }
714
715 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
716 if columns.is_empty() {
720 return "0".to_string();
721 }
722 let escaped = self.escape_string(keyword);
723 columns
724 .iter()
725 .map(|c| format!("{} LIKE '%{}%'", c.trim(), escaped))
726 .collect::<Vec<_>>()
727 .join(" OR ")
728 }
729
730 fn bool_to_int(&self, expr: &str) -> String {
731 expr.to_string()
732 }
733
734 fn concat(&self, parts: &[&str]) -> String {
735 if parts.is_empty() {
736 return "NULL".to_string();
737 }
738 let coalesced: Vec<String> = parts
741 .iter()
742 .map(|p| format!("COALESCE({}, '')", p))
743 .collect();
744 coalesced.join(" || ")
745 }
746
747 fn supports_if_exists(&self) -> bool {
748 true
749 }
750
751 fn supports_if_not_exists(&self) -> bool {
752 true
753 }
754
755 fn auto_increment_keyword(&self) -> &'static str {
756 "AUTOINCREMENT"
757 }
758
759 fn last_insert_id_sql(&self) -> Option<&'static str> {
760 Some("last_insert_rowid()")
761 }
762
763 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
764 let cols: Vec<String> = columns
765 .iter()
766 .map(|col| {
767 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
768 if !col.nullable {
769 sql.push_str(" NOT NULL");
770 }
771 if let Some(default) = &col.default {
772 sql.push_str(&format!(" DEFAULT {}", default));
773 }
774 if col.auto_increment {
775 sql.push_str(" PRIMARY KEY AUTOINCREMENT");
776 } else if col.primary_key {
777 sql.push_str(" PRIMARY KEY");
778 }
779 sql
780 })
781 .collect();
782
783 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
784 }
785
786 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
787 let stmts: Vec<String> = changes
790 .iter()
791 .map(|change| {
792 match change {
793 TableChange::AddColumn(col) => {
794 let mut sql = format!(
795 "ALTER TABLE {} ADD COLUMN {} {}",
796 self.quote(table),
797 self.quote(&col.name),
798 col.sql_type
799 );
800 if !col.nullable {
801 sql.push_str(" NOT NULL");
802 }
803 if let Some(default) = &col.default {
804 sql.push_str(&format!(" DEFAULT {}", default));
805 }
806 sql
807 }
808 TableChange::DropColumn(name) => {
809 format!(
811 "ALTER TABLE {} DROP COLUMN {}",
812 self.quote(table),
813 self.quote(name)
814 )
815 }
816 TableChange::ModifyColumn(col) => {
817 format!(
820 "-- SQLite 不支持 MODIFY COLUMN({} {}),需重建表",
821 col.name, col.sql_type
822 )
823 }
824 TableChange::AddIndex(name, cols) => {
825 format!(
826 "CREATE INDEX {} ON {} ({})",
827 name,
828 self.quote(table),
829 cols.join(", ")
830 )
831 }
832 TableChange::DropIndex(name) => {
833 format!("DROP INDEX {}", name)
834 }
835 TableChange::AddForeignKey {
836 columns,
837 reference_table,
838 reference_columns: _,
839 } => {
840 format!(
842 "-- SQLite 不支持 ADD FOREIGN KEY({} -> {}),需重建表",
843 columns.join(","),
844 reference_table
845 )
846 }
847 }
848 })
849 .collect();
850
851 stmts.join("; ")
852 }
853
854 fn build_upsert_on_conflict(
858 &self,
859 conflict_columns: &[&str],
860 update_columns: &[&str],
861 all_columns: &[String],
862 ) -> Option<String> {
863 if conflict_columns.is_empty() {
864 return None;
865 }
866 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
867 let conflict_set: std::collections::HashSet<&str> =
868 conflict_columns.iter().copied().collect();
869 let cols_to_update: Vec<String> = if update_columns.is_empty() {
870 all_columns
871 .iter()
872 .filter(|c| !conflict_set.contains(c.as_str()))
873 .map(|c| self.quote(c))
874 .collect()
875 } else {
876 update_columns.iter().map(|c| self.quote(c)).collect()
877 };
878 if cols_to_update.is_empty() {
879 return Some(format!(
880 "ON CONFLICT ({}) DO NOTHING",
881 conflict_cols.join(", ")
882 ));
883 }
884 let set_clauses: Vec<String> = cols_to_update
885 .iter()
886 .map(|col| format!("{}=EXCLUDED.{}", col, col))
887 .collect();
888 Some(format!(
889 "ON CONFLICT ({}) DO UPDATE SET {}",
890 conflict_cols.join(", "),
891 set_clauses.join(", ")
892 ))
893 }
894
895 fn supports_lock_for_update(&self) -> bool {
896 false
897 }
898
899 fn supports_lock_shared(&self) -> bool {
900 false
901 }
902}
903
904fn map_to_oracle_type(sql_type: &str) -> String {
913 let upper = sql_type.to_uppercase();
914 let trimmed = upper.trim();
915
916 if trimmed.starts_with("BIGINT") {
917 sql_type.replacen("BIGINT", "NUMBER(19)", 1)
918 } else if trimmed.starts_with("VARCHAR2") {
919 sql_type.to_string()
920 } else if trimmed.starts_with("VARCHAR") {
921 sql_type.replacen("VARCHAR", "VARCHAR2", 1)
922 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
923 "CLOB".to_string()
924 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
925 "NUMBER(1)".to_string()
926 } else if trimmed == "INTEGER" {
927 "NUMBER(10)".to_string()
928 } else if trimmed.starts_with("INT") {
929 sql_type.replacen("INT", "NUMBER(10)", 1)
930 } else {
931 sql_type.to_string()
932 }
933}
934
935#[derive(Debug, Clone)]
937pub struct OracleDialect;
938
939impl Dialect for OracleDialect {
940 fn clone_box(&self) -> Box<dyn Dialect> {
941 Box::new(OracleDialect)
942 }
943
944 fn db_type(&self) -> DbType {
945 DbType::Oracle
946 }
947
948 fn quote(&self, identifier: &str) -> String {
949 format!("\"{}\"", identifier.replace('"', "\"\""))
951 }
952
953 fn escape_string(&self, s: &str) -> String {
954 let mut escaped = String::with_capacity(s.len() * 2);
956 for c in s.chars() {
957 match c {
958 '\'' => escaped.push_str("''"),
959 _ => escaped.push(c),
960 }
961 }
962 escaped
963 }
964
965 fn supports_returning(&self) -> bool {
966 true
968 }
969
970 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
971 let offset = page.saturating_sub(1).saturating_mul(limit);
974 format!(
975 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
976 sql, offset, limit
977 )
978 }
979
980 fn json_type(&self) -> &'static str {
981 "JSON"
983 }
984
985 fn json_extract(&self, column: &str, path: &str) -> String {
986 let normalized = if path.starts_with('$') {
988 path.to_string()
989 } else {
990 format!("$.{}", path)
991 };
992 format!(
993 "JSON_VALUE({}, '{}')",
994 column,
995 self.escape_string(&normalized)
996 )
997 }
998
999 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1000 if columns.is_empty() {
1003 return "0".to_string();
1004 }
1005 let escaped = self.escape_string(keyword);
1006 let parts: Vec<String> = columns
1007 .iter()
1008 .map(|c| format!("CONTAINS({}, '{}', 1) > 0", c, escaped))
1009 .collect();
1010 parts.join(" OR ")
1011 }
1012
1013 fn bool_to_int(&self, expr: &str) -> String {
1014 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1016 }
1017
1018 fn concat(&self, parts: &[&str]) -> String {
1019 if parts.is_empty() {
1021 return "NULL".to_string();
1022 }
1023 parts.join(" || ")
1024 }
1025
1026 fn supports_if_exists(&self) -> bool {
1027 true
1029 }
1030
1031 fn supports_if_not_exists(&self) -> bool {
1032 true
1034 }
1035
1036 fn auto_increment_keyword(&self) -> &'static str {
1037 "GENERATED BY DEFAULT AS IDENTITY"
1039 }
1040
1041 fn last_insert_id_sql(&self) -> Option<&'static str> {
1042 None
1047 }
1048
1049 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1050 let cols: Vec<String> = columns
1051 .iter()
1052 .map(|col| {
1053 let oracle_type = map_to_oracle_type(&col.sql_type);
1054 let mut sql = format!("{} {}", self.quote(&col.name), oracle_type);
1055 if !col.nullable && !col.auto_increment {
1057 sql.push_str(" NOT NULL");
1058 }
1059 if let Some(default) = &col.default {
1060 sql.push_str(&format!(" DEFAULT {}", default));
1061 }
1062 if col.auto_increment {
1063 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1064 }
1065 if col.primary_key {
1066 sql.push_str(" PRIMARY KEY");
1067 }
1068 sql
1069 })
1070 .collect();
1071
1072 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1073 }
1074
1075 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1076 let stmts: Vec<String> = changes
1077 .iter()
1078 .map(|change| match change {
1079 TableChange::AddColumn(col) => {
1080 let oracle_type = map_to_oracle_type(&col.sql_type);
1081 let mut sql = format!(
1082 "ALTER TABLE {} ADD {} {}",
1083 self.quote(table),
1084 self.quote(&col.name),
1085 oracle_type
1086 );
1087 if !col.nullable {
1088 sql.push_str(" NOT NULL");
1089 }
1090 if let Some(default) = &col.default {
1091 sql.push_str(&format!(" DEFAULT {}", default));
1092 }
1093 sql
1094 }
1095 TableChange::DropColumn(name) => {
1096 format!(
1097 "ALTER TABLE {} DROP COLUMN {}",
1098 self.quote(table),
1099 self.quote(name)
1100 )
1101 }
1102 TableChange::ModifyColumn(col) => {
1103 let oracle_type = map_to_oracle_type(&col.sql_type);
1105 let mut sql = format!(
1106 "ALTER TABLE {} MODIFY {} {}",
1107 self.quote(table),
1108 self.quote(&col.name),
1109 oracle_type
1110 );
1111 if !col.nullable {
1112 sql.push_str(" NOT NULL");
1113 }
1114 if let Some(default) = &col.default {
1115 sql.push_str(&format!(" DEFAULT {}", default));
1116 }
1117 sql
1118 }
1119 TableChange::AddIndex(name, cols) => {
1120 format!(
1121 "CREATE INDEX {} ON {} ({})",
1122 name,
1123 self.quote(table),
1124 cols.join(", ")
1125 )
1126 }
1127 TableChange::DropIndex(name) => {
1128 format!("DROP INDEX {}", name)
1129 }
1130 TableChange::AddForeignKey {
1131 columns,
1132 reference_table,
1133 reference_columns,
1134 } => {
1135 format!(
1136 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1137 self.quote(table),
1138 table,
1139 columns.join("_"),
1140 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1141 self.quote(reference_table),
1142 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1143 )
1144 }
1145 })
1146 .collect();
1147
1148 stmts.join("; ")
1149 }
1150}
1151
1152fn map_to_sqlserver_type(sql_type: &str) -> String {
1161 let upper = sql_type.to_uppercase();
1162 let trimmed = upper.trim();
1163
1164 if trimmed.starts_with("BIGINT") {
1165 sql_type.to_string()
1166 } else if matches!(trimmed, "INT" | "INTEGER") {
1167 "INT".to_string()
1168 } else if trimmed.starts_with("NVARCHAR") {
1169 sql_type.to_string()
1170 } else if trimmed.starts_with("VARCHAR") {
1171 sql_type.replacen("VARCHAR", "NVARCHAR", 1)
1172 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1173 "NVARCHAR(MAX)".to_string()
1174 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1175 "BIT".to_string()
1176 } else {
1177 sql_type.to_string()
1178 }
1179}
1180
1181#[derive(Debug, Clone)]
1183pub struct SqlServerDialect;
1184
1185impl Dialect for SqlServerDialect {
1186 fn clone_box(&self) -> Box<dyn Dialect> {
1187 Box::new(SqlServerDialect)
1188 }
1189
1190 fn db_type(&self) -> DbType {
1191 DbType::SqlServer
1192 }
1193
1194 fn quote(&self, identifier: &str) -> String {
1195 format!("[{}]", identifier.replace(']', "]]"))
1197 }
1198
1199 fn escape_string(&self, s: &str) -> String {
1200 let mut escaped = String::with_capacity(s.len() * 2);
1202 for c in s.chars() {
1203 match c {
1204 '\'' => escaped.push_str("''"),
1205 _ => escaped.push(c),
1206 }
1207 }
1208 escaped
1209 }
1210
1211 fn supports_returning(&self) -> bool {
1212 true
1214 }
1215
1216 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1217 let offset = page.saturating_sub(1).saturating_mul(limit);
1219 format!(
1220 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1221 sql, offset, limit
1222 )
1223 }
1224
1225 fn json_type(&self) -> &'static str {
1226 "NVARCHAR(MAX)"
1228 }
1229
1230 fn json_extract(&self, column: &str, path: &str) -> String {
1231 let normalized = if path.starts_with('$') {
1233 path.to_string()
1234 } else {
1235 format!("$.{}", path)
1236 };
1237 format!(
1238 "JSON_VALUE({}, '{}')",
1239 column,
1240 self.escape_string(&normalized)
1241 )
1242 }
1243
1244 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1245 if columns.is_empty() {
1247 return "0".to_string();
1248 }
1249 let escaped = self.escape_string(keyword);
1250 let cols = columns.join(", ");
1251 format!("CONTAINS({}, '{}')", cols, escaped)
1252 }
1253
1254 fn bool_to_int(&self, expr: &str) -> String {
1255 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1257 }
1258
1259 fn concat(&self, parts: &[&str]) -> String {
1260 if parts.is_empty() {
1261 return "NULL".to_string();
1262 }
1263 format!("CONCAT({})", parts.join(", "))
1264 }
1265
1266 fn supports_if_exists(&self) -> bool {
1267 true
1269 }
1270
1271 fn supports_if_not_exists(&self) -> bool {
1272 true
1274 }
1275
1276 fn auto_increment_keyword(&self) -> &'static str {
1277 "IDENTITY(1,1)"
1279 }
1280
1281 fn last_insert_id_sql(&self) -> Option<&'static str> {
1282 Some("SCOPE_IDENTITY()")
1284 }
1285
1286 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1287 let cols: Vec<String> = columns
1288 .iter()
1289 .map(|col| {
1290 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1291 let mut sql = format!("{} {}", self.quote(&col.name), sqlserver_type);
1292 if !col.nullable {
1293 sql.push_str(" NOT NULL");
1294 }
1295 if let Some(default) = &col.default {
1296 sql.push_str(&format!(" DEFAULT {}", default));
1297 }
1298 if col.auto_increment {
1299 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1300 }
1301 if col.primary_key {
1302 sql.push_str(" PRIMARY KEY");
1303 }
1304 sql
1305 })
1306 .collect();
1307
1308 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1309 }
1310
1311 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1312 let stmts: Vec<String> = changes
1313 .iter()
1314 .map(|change| match change {
1315 TableChange::AddColumn(col) => {
1316 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1317 let mut sql = format!(
1318 "ALTER TABLE {} ADD {} {}",
1319 self.quote(table),
1320 self.quote(&col.name),
1321 sqlserver_type
1322 );
1323 if !col.nullable {
1324 sql.push_str(" NOT NULL");
1325 }
1326 if let Some(default) = &col.default {
1327 sql.push_str(&format!(" DEFAULT {}", default));
1328 }
1329 sql
1330 }
1331 TableChange::DropColumn(name) => {
1332 format!(
1333 "ALTER TABLE {} DROP COLUMN {}",
1334 self.quote(table),
1335 self.quote(name)
1336 )
1337 }
1338 TableChange::ModifyColumn(col) => {
1339 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1341 let mut sql = format!(
1342 "ALTER TABLE {} ALTER COLUMN {} {}",
1343 self.quote(table),
1344 self.quote(&col.name),
1345 sqlserver_type
1346 );
1347 if !col.nullable {
1348 sql.push_str(" NOT NULL");
1349 }
1350 if let Some(default) = &col.default {
1351 sql.push_str(&format!(" DEFAULT {}", default));
1352 }
1353 sql
1354 }
1355 TableChange::AddIndex(name, cols) => {
1356 format!(
1357 "CREATE INDEX {} ON {} ({})",
1358 name,
1359 self.quote(table),
1360 cols.join(", ")
1361 )
1362 }
1363 TableChange::DropIndex(name) => {
1364 format!("DROP INDEX {} ON {}", name, self.quote(table))
1366 }
1367 TableChange::AddForeignKey {
1368 columns,
1369 reference_table,
1370 reference_columns,
1371 } => {
1372 format!(
1373 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1374 self.quote(table),
1375 table,
1376 columns.join("_"),
1377 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1378 self.quote(reference_table),
1379 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1380 )
1381 }
1382 })
1383 .collect();
1384
1385 stmts.join("; ")
1386 }
1387}
1388
1389macro_rules! delegate_dialect_to {
1406 ($wrapper:ident, $base:ident, $db_type:expr) => {
1407 #[derive(Debug, Clone)]
1409 pub struct $wrapper;
1410
1411 impl Dialect for $wrapper {
1412 fn clone_box(&self) -> Box<dyn Dialect> {
1413 Box::new($wrapper)
1414 }
1415
1416 fn db_type(&self) -> DbType {
1417 $db_type
1418 }
1419 fn quote(&self, identifier: &str) -> String {
1420 $base.quote(identifier)
1421 }
1422 fn escape_string(&self, s: &str) -> String {
1423 $base.escape_string(s)
1424 }
1425 fn supports_returning(&self) -> bool {
1426 $base.supports_returning()
1427 }
1428 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1429 $base.build_pagination(sql, page, limit)
1430 }
1431 fn json_type(&self) -> &'static str {
1432 $base.json_type()
1433 }
1434 fn json_extract(&self, column: &str, path: &str) -> String {
1435 $base.json_extract(column, path)
1436 }
1437 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1438 $base.full_text_search(columns, keyword)
1439 }
1440 fn bool_to_int(&self, expr: &str) -> String {
1441 $base.bool_to_int(expr)
1442 }
1443 fn concat(&self, parts: &[&str]) -> String {
1444 $base.concat(parts)
1445 }
1446 fn supports_if_exists(&self) -> bool {
1447 $base.supports_if_exists()
1448 }
1449 fn supports_if_not_exists(&self) -> bool {
1450 $base.supports_if_not_exists()
1451 }
1452 fn auto_increment_keyword(&self) -> &'static str {
1453 $base.auto_increment_keyword()
1454 }
1455 fn last_insert_id_sql(&self) -> Option<&'static str> {
1456 $base.last_insert_id_sql()
1457 }
1458 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1459 $base.build_create_table(table, columns)
1460 }
1461 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1462 $base.build_alter_table(table, changes)
1463 }
1464 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1465 $base.build_drop_table(table, if_exists)
1466 }
1467 }
1468 };
1469}
1470
1471delegate_dialect_to!(MariaDbDialect, MySqlDialect, DbType::MariaDB);
1473
1474delegate_dialect_to!(TiDbDialect, MySqlDialect, DbType::TiDB);
1476
1477delegate_dialect_to!(KingbaseDialect, PostgreSqlDialect, DbType::Kingbase);
1479
1480delegate_dialect_to!(PolarDbDialect, PostgreSqlDialect, DbType::PolarDB);
1482
1483delegate_dialect_to!(GaussDbDialect, PostgreSqlDialect, DbType::GaussDB);
1485
1486delegate_dialect_to!(DamengDialect, OracleDialect, DbType::Dameng);
1488
1489delegate_dialect_to!(SybaseDialect, SqlServerDialect, DbType::Sybase);
1491
1492delegate_dialect_to!(GBaseDialect, SqlServerDialect, DbType::GBase);
1494
1495#[derive(Debug, Clone)]
1509pub struct ClickHouseDialect;
1510
1511impl Dialect for ClickHouseDialect {
1512 fn clone_box(&self) -> Box<dyn Dialect> {
1513 Box::new(ClickHouseDialect)
1514 }
1515
1516 fn db_type(&self) -> DbType {
1517 DbType::ClickHouse
1518 }
1519
1520 fn quote(&self, identifier: &str) -> String {
1521 format!("`{}`", identifier.replace('`', "``"))
1523 }
1524
1525 fn escape_string(&self, s: &str) -> String {
1526 let mut escaped = String::with_capacity(s.len() * 2);
1528 for c in s.chars() {
1529 match c {
1530 '\'' => escaped.push_str("\\'"),
1531 '\\' => escaped.push_str("\\\\"),
1532 '\n' => escaped.push_str("\\n"),
1533 '\r' => escaped.push_str("\\r"),
1534 '\t' => escaped.push_str("\\t"),
1535 _ => escaped.push(c),
1536 }
1537 }
1538 escaped
1539 }
1540
1541 fn supports_returning(&self) -> bool {
1542 false
1544 }
1545
1546 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1547 let offset = page.saturating_sub(1).saturating_mul(limit);
1549 format!("{} LIMIT {}, {}", sql, offset, limit)
1550 }
1551
1552 fn json_type(&self) -> &'static str {
1553 "String"
1555 }
1556
1557 fn json_extract(&self, column: &str, path: &str) -> String {
1558 let normalized = if path.starts_with('$') {
1560 path.to_string()
1561 } else {
1562 format!("$.{}", path)
1563 };
1564 format!(
1565 "JSONExtractString({}, '{}')",
1566 column,
1567 self.escape_string(&normalized)
1568 )
1569 }
1570
1571 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1572 if columns.is_empty() {
1574 return "0".to_string();
1575 }
1576 let escaped = self.escape_string(keyword);
1577 let parts: Vec<String> = columns
1578 .iter()
1579 .map(|c| format!("position({}, '{}') > 0", c, escaped))
1580 .collect();
1581 parts.join(" OR ")
1582 }
1583
1584 fn bool_to_int(&self, expr: &str) -> String {
1585 format!("toUInt8({})", expr)
1587 }
1588
1589 fn concat(&self, parts: &[&str]) -> String {
1590 if parts.is_empty() {
1592 return "''".to_string();
1593 }
1594 format!("concat({})", parts.join(", "))
1595 }
1596
1597 fn supports_if_exists(&self) -> bool {
1598 true
1599 }
1600
1601 fn supports_if_not_exists(&self) -> bool {
1602 true
1603 }
1604
1605 fn auto_increment_keyword(&self) -> &'static str {
1606 ""
1608 }
1609
1610 fn last_insert_id_sql(&self) -> Option<&'static str> {
1611 None
1613 }
1614
1615 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1616 let cols: Vec<String> = columns
1617 .iter()
1618 .map(|col| {
1619 let ch_type = map_to_clickhouse_type(&col.sql_type);
1620 let mut sql = format!("{} {}", self.quote(&col.name), ch_type);
1621 if let Some(default) = &col.default {
1622 sql.push_str(&format!(" DEFAULT {}", default));
1623 }
1624 if col.primary_key {
1625 sql.push_str(" PRIMARY KEY");
1626 }
1627 sql
1628 })
1629 .collect();
1630
1631 format!(
1633 "CREATE TABLE {} ({}) ENGINE = MergeTree()",
1634 self.quote(table),
1635 cols.join(", ")
1636 )
1637 }
1638
1639 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1640 let stmts: Vec<String> = changes
1641 .iter()
1642 .map(|change| match change {
1643 TableChange::AddColumn(col) => {
1644 let ch_type = map_to_clickhouse_type(&col.sql_type);
1645 format!(
1646 "ALTER TABLE {} ADD COLUMN {} {}",
1647 self.quote(table),
1648 self.quote(&col.name),
1649 ch_type
1650 )
1651 }
1652 TableChange::DropColumn(name) => {
1653 format!(
1654 "ALTER TABLE {} DROP COLUMN {}",
1655 self.quote(table),
1656 self.quote(name)
1657 )
1658 }
1659 TableChange::ModifyColumn(col) => {
1660 let ch_type = map_to_clickhouse_type(&col.sql_type);
1661 format!(
1662 "ALTER TABLE {} MODIFY COLUMN {} {}",
1663 self.quote(table),
1664 self.quote(&col.name),
1665 ch_type
1666 )
1667 }
1668 TableChange::AddIndex(name, cols) => {
1669 format!(
1670 "ALTER TABLE {} ADD INDEX {} ({})",
1671 self.quote(table),
1672 name,
1673 cols.join(", ")
1674 )
1675 }
1676 TableChange::DropIndex(name) => {
1677 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
1678 }
1679 TableChange::AddForeignKey { .. } => {
1680 String::new()
1682 }
1683 })
1684 .filter(|s| !s.is_empty())
1685 .collect();
1686
1687 stmts.join("; ")
1688 }
1689}
1690
1691fn map_to_clickhouse_type(sql_type: &str) -> String {
1701 let upper = sql_type.to_uppercase();
1702 let trimmed = upper.trim();
1703
1704 if trimmed.starts_with("BIGINT") {
1705 "Int64".to_string()
1706 } else if matches!(trimmed, "INT" | "INTEGER") {
1707 "Int32".to_string()
1708 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
1709 "Int16".to_string()
1710 } else if trimmed.starts_with("VARCHAR")
1711 || trimmed.starts_with("CHAR")
1712 || matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT")
1713 {
1714 "String".to_string()
1715 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1716 "UInt8".to_string()
1717 } else if matches!(trimmed, "FLOAT" | "REAL") {
1718 "Float32".to_string()
1719 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
1720 "Float64".to_string()
1721 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
1722 "DateTime".to_string()
1723 } else if matches!(trimmed, "DATE") {
1724 "Date".to_string()
1725 } else if trimmed.starts_with("DECIMAL") || trimmed.starts_with("NUMERIC") {
1726 "Decimal(38, 4)".to_string()
1727 } else {
1728 sql_type.to_string()
1729 }
1730}
1731
1732#[derive(Debug, Clone)]
1747pub struct DuckDBDialect;
1748
1749impl Dialect for DuckDBDialect {
1750 fn clone_box(&self) -> Box<dyn Dialect> {
1751 Box::new(DuckDBDialect)
1752 }
1753
1754 fn db_type(&self) -> DbType {
1755 DbType::DuckDB
1756 }
1757
1758 fn quote(&self, identifier: &str) -> String {
1759 format!("\"{}\"", identifier.replace('"', "\"\""))
1761 }
1762
1763 fn escape_string(&self, s: &str) -> String {
1764 let mut escaped = String::with_capacity(s.len() * 2);
1766 for c in s.chars() {
1767 match c {
1768 '\'' => escaped.push_str("''"),
1769 _ => escaped.push(c),
1770 }
1771 }
1772 escaped
1773 }
1774
1775 fn supports_returning(&self) -> bool {
1776 false
1778 }
1779
1780 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1781 let offset = page.saturating_sub(1).saturating_mul(limit);
1783 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
1784 }
1785
1786 fn json_type(&self) -> &'static str {
1787 "JSON"
1789 }
1790
1791 fn json_extract(&self, column: &str, path: &str) -> String {
1792 let normalized = if path.starts_with('$') {
1794 path[2..].to_string()
1795 } else {
1796 path.to_string()
1797 };
1798 format!("{} -> '{}'", column, normalized)
1799 }
1800
1801 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1802 if columns.is_empty() {
1804 return "0".to_string();
1805 }
1806 let escaped = self.escape_string(keyword);
1807 let parts: Vec<String> = columns
1808 .iter()
1809 .map(|c| format!("{} LIKE '%{}%'", c, escaped))
1810 .collect();
1811 parts.join(" OR ")
1812 }
1813
1814 fn bool_to_int(&self, expr: &str) -> String {
1815 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1817 }
1818
1819 fn concat(&self, parts: &[&str]) -> String {
1820 if parts.is_empty() {
1822 return "''".to_string();
1823 }
1824 parts.join(" || ")
1825 }
1826
1827 fn supports_if_exists(&self) -> bool {
1828 true
1829 }
1830
1831 fn supports_if_not_exists(&self) -> bool {
1832 true
1833 }
1834
1835 fn auto_increment_keyword(&self) -> &'static str {
1836 ""
1838 }
1839
1840 fn last_insert_id_sql(&self) -> Option<&'static str> {
1841 None
1843 }
1844
1845 fn supports_lock_for_update(&self) -> bool {
1846 false
1848 }
1849
1850 fn supports_lock_shared(&self) -> bool {
1851 false
1853 }
1854
1855 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
1856 format!("INSERT OR IGNORE INTO {}", self.quote(table))
1858 }
1859
1860 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1861 let cols: Vec<String> = columns
1862 .iter()
1863 .map(|col| {
1864 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
1865 if col.auto_increment {
1866 sql = format!("{} BIGINT PRIMARY KEY", self.quote(&col.name));
1868 }
1869 if let Some(default) = &col.default {
1870 sql.push_str(&format!(" DEFAULT {}", default));
1871 }
1872 if col.primary_key && !col.auto_increment {
1873 sql.push_str(" PRIMARY KEY");
1874 }
1875 sql
1876 })
1877 .collect();
1878
1879 format!(
1880 "CREATE TABLE {} ({})",
1881 self.quote(table),
1882 cols.join(", ")
1883 )
1884 }
1885
1886 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1887 let stmts: Vec<String> = changes
1888 .iter()
1889 .map(|change| match change {
1890 TableChange::AddColumn(col) => {
1891 let mut sql = format!(
1892 "ALTER TABLE {} ADD COLUMN {} {}",
1893 self.quote(table),
1894 self.quote(&col.name),
1895 col.sql_type
1896 );
1897 if let Some(default) = &col.default {
1898 sql.push_str(&format!(" DEFAULT {}", default));
1899 }
1900 sql
1901 }
1902 TableChange::DropColumn(name) => {
1903 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
1904 }
1905 TableChange::ModifyColumn(col) => {
1906 format!(
1907 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
1908 self.quote(table),
1909 self.quote(&col.name),
1910 col.sql_type
1911 )
1912 }
1913 TableChange::AddIndex(name, _cols) => {
1914 format!("CREATE INDEX {} ON {} (id)", self.quote(name), self.quote(table))
1916 }
1917 TableChange::DropIndex(name) => {
1918 format!("DROP INDEX {}", self.quote(name))
1919 }
1920 TableChange::AddForeignKey { .. } => {
1921 String::new()
1923 }
1924 })
1925 .filter(|s| !s.is_empty())
1926 .collect();
1927
1928 stmts.join("; ")
1929 }
1930}
1931
1932#[derive(Debug, Clone)]
1946pub struct Db2Dialect;
1947
1948impl Dialect for Db2Dialect {
1949 fn clone_box(&self) -> Box<dyn Dialect> {
1950 Box::new(Db2Dialect)
1951 }
1952
1953 fn db_type(&self) -> DbType {
1954 DbType::Db2
1955 }
1956
1957 fn quote(&self, identifier: &str) -> String {
1958 format!("\"{}\"", identifier.replace('"', "\"\""))
1960 }
1961
1962 fn escape_string(&self, s: &str) -> String {
1963 let mut escaped = String::with_capacity(s.len() * 2);
1965 for c in s.chars() {
1966 match c {
1967 '\'' => escaped.push_str("''"),
1968 _ => escaped.push(c),
1969 }
1970 }
1971 escaped
1972 }
1973
1974 fn supports_returning(&self) -> bool {
1975 false
1977 }
1978
1979 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1980 let offset = page.saturating_sub(1).saturating_mul(limit);
1982 format!(
1983 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1984 sql, offset, limit
1985 )
1986 }
1987
1988 fn json_type(&self) -> &'static str {
1989 "JSON"
1991 }
1992
1993 fn json_extract(&self, column: &str, path: &str) -> String {
1994 let normalized = if path.starts_with('$') {
1996 path.to_string()
1997 } else {
1998 format!("$.{}", path)
1999 };
2000 format!(
2001 "JSON_VALUE({}, '{}')",
2002 column,
2003 self.escape_string(&normalized)
2004 )
2005 }
2006
2007 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2008 if columns.is_empty() {
2010 return "0".to_string();
2011 }
2012 let escaped = self.escape_string(keyword);
2013 let parts: Vec<String> = columns
2014 .iter()
2015 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2016 .collect();
2017 parts.join(" OR ")
2018 }
2019
2020 fn bool_to_int(&self, expr: &str) -> String {
2021 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2023 }
2024
2025 fn concat(&self, parts: &[&str]) -> String {
2026 if parts.is_empty() {
2028 return "''".to_string();
2029 }
2030 parts.join(" || ")
2031 }
2032
2033 fn supports_if_exists(&self) -> bool {
2034 false
2036 }
2037
2038 fn supports_if_not_exists(&self) -> bool {
2039 false
2041 }
2042
2043 fn auto_increment_keyword(&self) -> &'static str {
2044 "GENERATED ALWAYS AS IDENTITY"
2046 }
2047
2048 fn last_insert_id_sql(&self) -> Option<&'static str> {
2049 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
2051 }
2052
2053 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2054 let cols: Vec<String> = columns
2055 .iter()
2056 .map(|col| {
2057 let db2_type = map_to_db2_type(&col.sql_type);
2058 let mut sql = format!("{} {}", self.quote(&col.name), db2_type);
2059 if !col.nullable && !col.auto_increment {
2060 sql.push_str(" NOT NULL");
2061 }
2062 if let Some(default) = &col.default {
2063 sql.push_str(&format!(" DEFAULT {}", default));
2064 }
2065 if col.auto_increment {
2066 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2067 }
2068 if col.primary_key {
2069 sql.push_str(" PRIMARY KEY");
2070 }
2071 sql
2072 })
2073 .collect();
2074
2075 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2076 }
2077
2078 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2079 let stmts: Vec<String> = changes
2080 .iter()
2081 .map(|change| match change {
2082 TableChange::AddColumn(col) => {
2083 let db2_type = map_to_db2_type(&col.sql_type);
2084 let mut sql = format!(
2085 "ALTER TABLE {} ADD COLUMN {} {}",
2086 self.quote(table),
2087 self.quote(&col.name),
2088 db2_type
2089 );
2090 if !col.nullable {
2091 sql.push_str(" NOT NULL");
2092 }
2093 if let Some(default) = &col.default {
2094 sql.push_str(&format!(" DEFAULT {}", default));
2095 }
2096 sql
2097 }
2098 TableChange::DropColumn(name) => {
2099 format!(
2100 "ALTER TABLE {} DROP COLUMN {}",
2101 self.quote(table),
2102 self.quote(name)
2103 )
2104 }
2105 TableChange::ModifyColumn(col) => {
2106 let db2_type = map_to_db2_type(&col.sql_type);
2107 format!(
2108 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
2109 self.quote(table),
2110 self.quote(&col.name),
2111 db2_type
2112 )
2113 }
2114 TableChange::AddIndex(name, cols) => {
2115 format!(
2116 "CREATE INDEX {} ON {} ({})",
2117 name,
2118 self.quote(table),
2119 cols.join(", ")
2120 )
2121 }
2122 TableChange::DropIndex(name) => {
2123 format!("DROP INDEX {}", name)
2124 }
2125 TableChange::AddForeignKey {
2126 columns,
2127 reference_table,
2128 reference_columns,
2129 } => {
2130 format!(
2131 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
2132 self.quote(table),
2133 table,
2134 columns.join("_"),
2135 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
2136 self.quote(reference_table),
2137 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
2138 )
2139 }
2140 })
2141 .collect();
2142
2143 stmts.join("; ")
2144 }
2145
2146 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
2147 let _ = if_exists;
2149 format!("DROP TABLE {}", self.quote(table))
2150 }
2151}
2152
2153fn map_to_db2_type(sql_type: &str) -> String {
2162 let upper = sql_type.to_uppercase();
2163 let trimmed = upper.trim();
2164
2165 if trimmed.starts_with("BIGINT") {
2166 "BIGINT".to_string()
2167 } else if matches!(trimmed, "INT" | "INTEGER") {
2168 "INTEGER".to_string()
2169 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
2170 "SMALLINT".to_string()
2171 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2172 sql_type.to_string()
2173 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
2174 "CLOB(2G)".to_string()
2175 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2176 "SMALLINT".to_string()
2177 } else if matches!(trimmed, "FLOAT" | "REAL") {
2178 "REAL".to_string()
2179 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2180 "DOUBLE".to_string()
2181 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2182 "TIMESTAMP".to_string()
2183 } else if matches!(trimmed, "DATE") {
2184 "DATE".to_string()
2185 } else {
2186 sql_type.to_string()
2188 }
2189}
2190
2191pub fn get_dialect(db_type: DbType) -> Result<Box<dyn Dialect>, DbError> {
2214 match db_type {
2215 DbType::MySQL => Ok(Box::new(MySqlDialect)),
2216 DbType::PostgreSQL => Ok(Box::new(PostgreSqlDialect)),
2217 DbType::Sqlite => Ok(Box::new(SqliteDialect)),
2218 DbType::Redis => Err(DbError::Unsupported(
2219 "Redis does not support standard SQL dialect".to_string(),
2220 )),
2221 DbType::MongoDB => Err(DbError::Unsupported(
2222 "MongoDB uses different query syntax".to_string(),
2223 )),
2224 DbType::ClickHouse => Ok(Box::new(ClickHouseDialect)),
2225 DbType::Oracle => Ok(Box::new(OracleDialect)),
2226 DbType::OceanBase => Ok(Box::new(MySqlDialect)),
2227 DbType::SqlServer => Ok(Box::new(SqlServerDialect)),
2228 DbType::VectorDb => Err(DbError::Unsupported(
2229 "Vector databases have specific APIs".to_string(),
2230 )),
2231 DbType::PureJsDb => Err(DbError::Unsupported(
2232 "PureJS database uses JavaScript".to_string(),
2233 )),
2234 DbType::Dameng => Ok(Box::new(DamengDialect)),
2236 DbType::Kingbase => Ok(Box::new(KingbaseDialect)),
2237 DbType::Db2 => Ok(Box::new(Db2Dialect)),
2238 DbType::MariaDB => Ok(Box::new(MariaDbDialect)),
2239 DbType::TiDB => Ok(Box::new(TiDbDialect)),
2240 DbType::PolarDB => Ok(Box::new(PolarDbDialect)),
2241 DbType::GaussDB => Ok(Box::new(GaussDbDialect)),
2242 DbType::GBase => Ok(Box::new(GBaseDialect)),
2243 DbType::Sybase => Ok(Box::new(SybaseDialect)),
2244 DbType::DuckDB => Ok(Box::new(DuckDBDialect)),
2245 }
2246}
2247
2248impl fmt::Display for dyn Dialect {
2249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2250 write!(f, "Dialect({})", self.db_type())
2251 }
2252}
2253
2254#[cfg(test)]
2255mod tests {
2256 use super::*;
2257
2258 #[test]
2259 fn test_mysql_quote() {
2260 let dialect = MySqlDialect;
2261 assert_eq!(dialect.quote("users"), "`users`");
2262 assert_eq!(dialect.quote("user`id"), "`user``id`");
2263 }
2264
2265 #[test]
2266 fn test_mysql_escape() {
2267 let dialect = MySqlDialect;
2268 assert_eq!(dialect.escape_string("hello"), "hello");
2269 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2270 assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
2271 }
2272
2273 #[test]
2274 fn test_mysql_pagination() {
2275 let dialect = MySqlDialect;
2276 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
2277 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
2278 }
2279
2280 #[test]
2281 fn test_postgres_quote() {
2282 let dialect = PostgreSqlDialect;
2283 assert_eq!(dialect.quote("users"), "\"users\"");
2284 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2285 }
2286
2287 #[test]
2288 fn test_postgres_pagination() {
2289 let dialect = PostgreSqlDialect;
2290 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2291 assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
2292 }
2293
2294 #[test]
2295 fn test_postgres_returning() {
2296 let dialect = PostgreSqlDialect;
2297 assert!(dialect.supports_returning());
2298 }
2299
2300 #[test]
2301 fn test_sqlite_quote() {
2302 let dialect = SqliteDialect;
2303 assert_eq!(dialect.quote("users"), "\"users\"");
2304 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2305 }
2306
2307 #[test]
2308 fn test_sqlite_escape() {
2309 let dialect = SqliteDialect;
2310 assert_eq!(dialect.escape_string("hello"), "hello");
2311 assert_eq!(dialect.escape_string("it's"), "it''s");
2312 }
2313
2314 #[test]
2315 fn test_get_dialect() {
2316 let dialect = get_dialect(DbType::MySQL);
2317 assert!(dialect.is_ok());
2318
2319 let dialect = get_dialect(DbType::Redis);
2320 assert!(dialect.is_err());
2321 }
2322
2323 #[test]
2324 fn test_bool_to_int() {
2325 let mysql = MySqlDialect;
2326 assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
2327
2328 let pg = PostgreSqlDialect;
2329 assert_eq!(
2330 pg.bool_to_int("active"),
2331 "(CASE WHEN active THEN 1 ELSE 0 END)"
2332 );
2333 }
2334
2335 #[test]
2336 fn test_json_extract_with_path() {
2337 let mysql = MySqlDialect;
2338 let sql = mysql.json_extract("data", "$.user.name");
2339 assert!(sql.contains("$.user.name"));
2340 assert!(sql.contains("JSON_EXTRACT"));
2341
2342 let pg = PostgreSqlDialect;
2343 let sql = pg.json_extract("data", "user.name");
2344 assert!(sql.contains("#>>"));
2345
2346 let sqlite = SqliteDialect;
2347 let sql = sqlite.json_extract("data", "$.user.name");
2348 assert!(sql.contains("$.user.name"));
2349 assert!(sql.contains("json_extract"));
2350 }
2351
2352 #[test]
2353 fn test_sqlite_full_text_search() {
2354 let sqlite = SqliteDialect;
2355 let sql = sqlite.full_text_search(&["title", "content"], "hello");
2356 assert!(sql.contains("LIKE"));
2358 assert!(sql.contains("title LIKE '%hello%'"));
2359 assert!(sql.contains("content LIKE '%hello%'"));
2360 assert!(sql.contains(" OR "));
2361
2362 assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
2364
2365 let sql = sqlite.full_text_search(&["title"], "it's");
2367 assert!(sql.contains("title LIKE '%it''s%'"));
2368 }
2369
2370 #[test]
2371 fn test_alter_table_modify_column() {
2372 let mysql = MySqlDialect;
2373 let col = ColumnDef {
2374 name: "name".to_string(),
2375 sql_type: "VARCHAR(255)".to_string(),
2376 nullable: false,
2377 default: None,
2378 auto_increment: false,
2379 primary_key: false,
2380 };
2381 let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2382 assert!(sql.contains("MODIFY COLUMN"));
2383
2384 let pg = PostgreSqlDialect;
2385 let col = ColumnDef {
2386 name: "name".to_string(),
2387 sql_type: "VARCHAR(255)".to_string(),
2388 nullable: false,
2389 default: None,
2390 auto_increment: false,
2391 primary_key: false,
2392 };
2393 let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2394 assert!(sql.contains("ALTER COLUMN"));
2395 assert!(sql.contains("TYPE"));
2396 }
2397
2398 #[test]
2399 fn test_alter_table_add_foreign_key() {
2400 let mysql = MySqlDialect;
2401 let sql = mysql.build_alter_table(
2402 "orders",
2403 &[TableChange::AddForeignKey {
2404 columns: vec!["user_id".to_string()],
2405 reference_table: "users".to_string(),
2406 reference_columns: vec!["id".to_string()],
2407 }],
2408 );
2409 assert!(sql.contains("FOREIGN KEY"));
2410 assert!(sql.contains("REFERENCES"));
2411
2412 let sqlite = SqliteDialect;
2413 let sql = sqlite.build_alter_table(
2414 "orders",
2415 &[TableChange::AddForeignKey {
2416 columns: vec!["user_id".to_string()],
2417 reference_table: "users".to_string(),
2418 reference_columns: vec!["id".to_string()],
2419 }],
2420 );
2421 assert!(sql.starts_with("--"));
2423 }
2424
2425 #[test]
2426 fn test_sqlite_alter_table_add_column() {
2427 let sqlite = SqliteDialect;
2428 let col = ColumnDef {
2429 name: "email".to_string(),
2430 sql_type: "TEXT".to_string(),
2431 nullable: true,
2432 default: None,
2433 auto_increment: false,
2434 primary_key: false,
2435 };
2436 let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
2437 assert!(sql.contains("ADD COLUMN"));
2438 assert!(sql.contains("email"));
2439 }
2440
2441 #[test]
2444 fn test_oracle_quote_and_escape() {
2445 let dialect = OracleDialect;
2446 assert_eq!(dialect.quote("users"), "\"users\"");
2448 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2449 assert_eq!(dialect.quote("column_name"), "\"column_name\"");
2450
2451 assert_eq!(dialect.escape_string("hello"), "hello");
2453 assert_eq!(dialect.escape_string("it's"), "it''s");
2454 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2455 assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
2456 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2458 }
2459
2460 #[test]
2461 fn test_oracle_pagination() {
2462 let dialect = OracleDialect;
2463 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2465 assert_eq!(
2466 sql,
2467 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2468 );
2469 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2471 assert_eq!(
2472 sql,
2473 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2474 );
2475 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2477 assert_eq!(
2478 sql,
2479 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2480 );
2481 }
2482
2483 #[test]
2484 fn test_oracle_json_extract() {
2485 let dialect = OracleDialect;
2486 let sql = dialect.json_extract("data", "$.user.name");
2488 assert!(sql.contains("JSON_VALUE"));
2489 assert!(sql.contains("$.user.name"));
2490 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2491
2492 let sql = dialect.json_extract("data", "user.name");
2494 assert!(sql.contains("$.user.name"));
2495 assert!(sql.contains("JSON_VALUE"));
2496
2497 let sql = dialect.json_extract("data", "$.key's");
2499 assert!(sql.contains("$.key''s"));
2500 }
2501
2502 #[test]
2503 fn test_oracle_create_table() {
2504 let dialect = OracleDialect;
2505 let columns = vec![
2506 ColumnDef {
2507 name: "id".to_string(),
2508 sql_type: "BIGINT".to_string(),
2509 nullable: false,
2510 default: None,
2511 auto_increment: true,
2512 primary_key: true,
2513 },
2514 ColumnDef {
2515 name: "name".to_string(),
2516 sql_type: "VARCHAR(255)".to_string(),
2517 nullable: false,
2518 default: None,
2519 auto_increment: false,
2520 primary_key: false,
2521 },
2522 ColumnDef {
2523 name: "bio".to_string(),
2524 sql_type: "TEXT".to_string(),
2525 nullable: true,
2526 default: None,
2527 auto_increment: false,
2528 primary_key: false,
2529 },
2530 ColumnDef {
2531 name: "is_active".to_string(),
2532 sql_type: "BOOLEAN".to_string(),
2533 nullable: false,
2534 default: Some("1".to_string()),
2535 auto_increment: false,
2536 primary_key: false,
2537 },
2538 ];
2539 let sql = dialect.build_create_table("users", &columns);
2540 assert!(
2542 sql.contains("NUMBER(19)"),
2543 "BIGINT should map to NUMBER(19): {}",
2544 sql
2545 );
2546 assert!(
2547 sql.contains("VARCHAR2(255)"),
2548 "VARCHAR should map to VARCHAR2: {}",
2549 sql
2550 );
2551 assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
2552 assert!(
2553 sql.contains("NUMBER(1)"),
2554 "BOOLEAN should map to NUMBER(1): {}",
2555 sql
2556 );
2557 assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
2559 assert!(sql.contains("PRIMARY KEY"));
2560 assert!(sql.contains("NOT NULL"));
2561 assert!(sql.contains("DEFAULT 1"));
2562 assert!(sql.contains("\"users\""));
2564 assert!(sql.contains("\"id\""));
2565 }
2566
2567 #[test]
2568 fn test_oracle_bool_to_int_and_concat() {
2569 let dialect = OracleDialect;
2570 assert_eq!(
2572 dialect.bool_to_int("active"),
2573 "(CASE WHEN active THEN 1 ELSE 0 END)"
2574 );
2575 assert_eq!(
2576 dialect.bool_to_int("x > 0"),
2577 "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
2578 );
2579 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
2581 assert_eq!(
2582 dialect.concat(&["first_name", "last_name"]),
2583 "first_name || last_name"
2584 );
2585 assert_eq!(dialect.concat(&[]), "NULL");
2587 }
2588
2589 #[test]
2590 fn test_oracle_misc_dialect_methods() {
2591 let dialect = OracleDialect;
2592 assert_eq!(dialect.db_type(), DbType::Oracle);
2594 assert!(dialect.supports_returning());
2596 assert!(dialect.supports_if_exists());
2598 assert!(dialect.supports_if_not_exists());
2599 assert_eq!(
2601 dialect.auto_increment_keyword(),
2602 "GENERATED BY DEFAULT AS IDENTITY"
2603 );
2604 assert_eq!(dialect.last_insert_id_sql(), None);
2607 assert_eq!(dialect.json_type(), "JSON");
2609 }
2610
2611 #[test]
2612 fn test_oracle_get_dialect() {
2613 let dialect = get_dialect(DbType::Oracle);
2615 assert!(dialect.is_ok(), "Oracle dialect should be available");
2616 let dialect = dialect.unwrap();
2617 assert_eq!(dialect.db_type(), DbType::Oracle);
2618 assert_eq!(dialect.quote("users"), "\"users\"");
2620 assert!(dialect.supports_returning());
2622 assert_eq!(dialect.last_insert_id_sql(), None);
2624 }
2625
2626 #[test]
2627 fn test_oracle_drop_table() {
2628 let dialect = OracleDialect;
2629 let sql = dialect.build_drop_table("users", true);
2631 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
2632 let sql = dialect.build_drop_table("users", false);
2634 assert_eq!(sql, "DROP TABLE \"users\"");
2635 }
2636
2637 #[test]
2638 fn test_oracle_alter_table() {
2639 let dialect = OracleDialect;
2640 let col = ColumnDef {
2642 name: "name".to_string(),
2643 sql_type: "VARCHAR(255)".to_string(),
2644 nullable: false,
2645 default: None,
2646 auto_increment: false,
2647 primary_key: false,
2648 };
2649 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2650 assert!(sql.contains("MODIFY"));
2651 assert!(sql.contains("VARCHAR2(255)"));
2652 assert!(!sql.contains("MODIFY COLUMN")); let col = ColumnDef {
2656 name: "email".to_string(),
2657 sql_type: "VARCHAR(255)".to_string(),
2658 nullable: true,
2659 default: None,
2660 auto_increment: false,
2661 primary_key: false,
2662 };
2663 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2664 assert!(sql.contains("ADD \"email\""));
2665 assert!(sql.contains("VARCHAR2(255)"));
2666
2667 let sql =
2669 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2670 assert!(sql.contains("DROP COLUMN"));
2671 assert!(sql.contains("\"email\""));
2672 }
2673
2674 #[test]
2677 fn test_sqlite_concat_handles_null() {
2678 let sqlite = SqliteDialect;
2679 let sql = sqlite.concat(&["a", "b"]);
2682 assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
2683 let sql = sqlite.concat(&["a"]);
2685 assert_eq!(sql, "COALESCE(a, '')");
2686 assert_eq!(sqlite.concat(&[]), "NULL");
2688 }
2689
2690 #[test]
2693 fn test_sqlserver_quote_and_escape() {
2694 let dialect = SqlServerDialect;
2695 assert_eq!(dialect.quote("users"), "[users]");
2697 assert_eq!(dialect.quote("col]name"), "[col]]name]");
2698 assert_eq!(dialect.escape_string("hello"), "hello");
2700 assert_eq!(dialect.escape_string("it's"), "it''s");
2701 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2702 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2703 }
2704
2705 #[test]
2706 fn test_sqlserver_pagination() {
2707 let dialect = SqlServerDialect;
2708 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2710 assert_eq!(
2711 sql,
2712 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2713 );
2714 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2715 assert_eq!(
2716 sql,
2717 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2718 );
2719 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2721 assert_eq!(
2722 sql,
2723 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2724 );
2725 }
2726
2727 #[test]
2728 fn test_sqlserver_misc_dialect_methods() {
2729 let dialect = SqlServerDialect;
2730 assert_eq!(dialect.db_type(), DbType::SqlServer);
2731 assert!(dialect.supports_returning());
2733 assert!(dialect.supports_if_exists());
2735 assert!(dialect.supports_if_not_exists());
2736 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2738 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2740 assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
2742 }
2743
2744 #[test]
2745 fn test_sqlserver_json_extract() {
2746 let dialect = SqlServerDialect;
2747 let sql = dialect.json_extract("data", "$.user.name");
2748 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2749 let sql = dialect.json_extract("data", "user.name");
2751 assert!(sql.contains("$.user.name"));
2752 assert!(sql.contains("JSON_VALUE"));
2753 let sql = dialect.json_extract("data", "$.key's");
2755 assert!(sql.contains("$.key''s"));
2756 }
2757
2758 #[test]
2759 fn test_sqlserver_full_text_search() {
2760 let dialect = SqlServerDialect;
2761 let sql = dialect.full_text_search(&["title", "content"], "hello");
2762 assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
2763 assert_eq!(dialect.full_text_search(&[], "hello"), "0");
2765 let sql = dialect.full_text_search(&["title"], "it's");
2767 assert!(sql.contains("it''s"));
2768 }
2769
2770 #[test]
2771 fn test_sqlserver_bool_to_int_and_concat() {
2772 let dialect = SqlServerDialect;
2773 assert_eq!(
2774 dialect.bool_to_int("active"),
2775 "(CASE WHEN active THEN 1 ELSE 0 END)"
2776 );
2777 assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
2778 assert_eq!(dialect.concat(&[]), "NULL");
2779 }
2780
2781 #[test]
2782 fn test_sqlserver_create_table() {
2783 let dialect = SqlServerDialect;
2784 let columns = vec![
2785 ColumnDef {
2786 name: "id".to_string(),
2787 sql_type: "BIGINT".to_string(),
2788 nullable: false,
2789 default: None,
2790 auto_increment: true,
2791 primary_key: true,
2792 },
2793 ColumnDef {
2794 name: "name".to_string(),
2795 sql_type: "VARCHAR(255)".to_string(),
2796 nullable: false,
2797 default: None,
2798 auto_increment: false,
2799 primary_key: false,
2800 },
2801 ColumnDef {
2802 name: "bio".to_string(),
2803 sql_type: "TEXT".to_string(),
2804 nullable: true,
2805 default: None,
2806 auto_increment: false,
2807 primary_key: false,
2808 },
2809 ColumnDef {
2810 name: "is_active".to_string(),
2811 sql_type: "BOOLEAN".to_string(),
2812 nullable: false,
2813 default: Some("1".to_string()),
2814 auto_increment: false,
2815 primary_key: false,
2816 },
2817 ];
2818 let sql = dialect.build_create_table("users", &columns);
2819 assert!(sql.contains("[users]"));
2821 assert!(sql.contains("[id]"));
2822 assert!(sql.contains("IDENTITY(1,1)"));
2824 assert!(
2825 sql.contains("NVARCHAR(255)"),
2826 "VARCHAR should map to NVARCHAR: {}",
2827 sql
2828 );
2829 assert!(
2830 sql.contains("NVARCHAR(MAX)"),
2831 "TEXT should map to NVARCHAR(MAX): {}",
2832 sql
2833 );
2834 assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
2835 assert!(sql.contains("PRIMARY KEY"));
2836 assert!(sql.contains("NOT NULL"));
2837 assert!(sql.contains("DEFAULT 1"));
2838 }
2839
2840 #[test]
2841 fn test_sqlserver_drop_table() {
2842 let dialect = SqlServerDialect;
2843 assert_eq!(
2844 dialect.build_drop_table("users", true),
2845 "DROP TABLE IF EXISTS [users]"
2846 );
2847 assert_eq!(
2848 dialect.build_drop_table("users", false),
2849 "DROP TABLE [users]"
2850 );
2851 }
2852
2853 #[test]
2854 fn test_sqlserver_alter_table() {
2855 let dialect = SqlServerDialect;
2856 let col = ColumnDef {
2858 name: "name".to_string(),
2859 sql_type: "VARCHAR(255)".to_string(),
2860 nullable: false,
2861 default: None,
2862 auto_increment: false,
2863 primary_key: false,
2864 };
2865 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2866 assert!(sql.contains("ALTER COLUMN"));
2867 assert!(sql.contains("NVARCHAR(255)"));
2868 assert!(!sql.contains("MODIFY"));
2869
2870 let col = ColumnDef {
2872 name: "email".to_string(),
2873 sql_type: "VARCHAR(255)".to_string(),
2874 nullable: true,
2875 default: None,
2876 auto_increment: false,
2877 primary_key: false,
2878 };
2879 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2880 assert!(sql.contains("ADD [email]"));
2881 assert!(sql.contains("NVARCHAR(255)"));
2882
2883 let sql =
2885 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2886 assert!(sql.contains("DROP COLUMN"));
2887 assert!(sql.contains("[email]"));
2888
2889 let sql =
2891 dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
2892 assert!(sql.contains("DROP INDEX idx_name ON [users]"));
2893 }
2894
2895 #[test]
2896 fn test_sqlserver_get_dialect() {
2897 let dialect = get_dialect(DbType::SqlServer);
2899 assert!(dialect.is_ok(), "SqlServer dialect should be available");
2900 let dialect = dialect.unwrap();
2901 assert_eq!(dialect.db_type(), DbType::SqlServer);
2902 assert_eq!(dialect.quote("users"), "[users]");
2904 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2906 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2907 }
2908
2909 #[test]
2910 fn test_clickhouse_get_dialect_unsupported() {
2911 let dialect = get_dialect(DbType::ClickHouse);
2913 assert!(dialect.is_ok(), "ClickHouse should be supported");
2914 let dialect = dialect.unwrap();
2915 assert_eq!(dialect.db_type(), DbType::ClickHouse);
2916 assert_eq!(dialect.quote("users"), "`users`");
2918 assert!(!dialect.supports_returning());
2920 let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
2922 assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
2923 assert_eq!(dialect.auto_increment_keyword(), "");
2925 }
2926
2927 #[test]
2928 fn test_get_dialect_all_supported_types() {
2929 assert!(get_dialect(DbType::MySQL).is_ok());
2931 assert!(get_dialect(DbType::PostgreSQL).is_ok());
2932 assert!(get_dialect(DbType::Sqlite).is_ok());
2933 assert!(get_dialect(DbType::Oracle).is_ok());
2934 assert!(get_dialect(DbType::SqlServer).is_ok());
2935 assert!(get_dialect(DbType::OceanBase).is_ok());
2936 assert!(get_dialect(DbType::ClickHouse).is_ok());
2937 assert!(get_dialect(DbType::Dameng).is_ok());
2939 assert!(get_dialect(DbType::Kingbase).is_ok());
2940 assert!(get_dialect(DbType::Db2).is_ok());
2941 assert!(get_dialect(DbType::MariaDB).is_ok());
2942 assert!(get_dialect(DbType::TiDB).is_ok());
2943 assert!(get_dialect(DbType::PolarDB).is_ok());
2944 assert!(get_dialect(DbType::GaussDB).is_ok());
2945 assert!(get_dialect(DbType::GBase).is_ok());
2946 assert!(get_dialect(DbType::Sybase).is_ok());
2947 assert!(get_dialect(DbType::Redis).is_err());
2949 assert!(get_dialect(DbType::MongoDB).is_err());
2950 assert!(get_dialect(DbType::VectorDb).is_err());
2951 assert!(get_dialect(DbType::PureJsDb).is_err());
2952 }
2953
2954 #[test]
2957 fn test_mariadb_dialect() {
2958 let dialect = get_dialect(DbType::MariaDB).unwrap();
2959 assert_eq!(dialect.db_type(), DbType::MariaDB);
2960 assert_eq!(dialect.quote("users"), "`users`");
2962 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2963 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
2964 assert!(!dialect.supports_returning());
2966 }
2967
2968 #[test]
2969 fn test_tidb_dialect() {
2970 let dialect = get_dialect(DbType::TiDB).unwrap();
2971 assert_eq!(dialect.db_type(), DbType::TiDB);
2972 assert_eq!(dialect.quote("users"), "`users`");
2974 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2975 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
2976 }
2977
2978 #[test]
2979 fn test_dameng_dialect() {
2980 let dialect = get_dialect(DbType::Dameng).unwrap();
2981 assert_eq!(dialect.db_type(), DbType::Dameng);
2982 assert_eq!(dialect.quote("users"), "\"users\"");
2984 assert_eq!(dialect.escape_string("it's"), "it''s");
2985 assert_eq!(
2987 dialect.auto_increment_keyword(),
2988 "GENERATED BY DEFAULT AS IDENTITY"
2989 );
2990 assert!(dialect.supports_returning());
2992 }
2993
2994 #[test]
2995 fn test_kingbase_dialect() {
2996 let dialect = get_dialect(DbType::Kingbase).unwrap();
2997 assert_eq!(dialect.db_type(), DbType::Kingbase);
2998 assert_eq!(dialect.quote("users"), "\"users\"");
3000 assert_eq!(dialect.escape_string("it's"), "it''s");
3001 assert!(dialect.supports_returning());
3003 assert_eq!(
3005 dialect.auto_increment_keyword(),
3006 "GENERATED BY DEFAULT AS IDENTITY"
3007 );
3008 }
3009
3010 #[test]
3011 fn test_polardb_dialect() {
3012 let dialect = get_dialect(DbType::PolarDB).unwrap();
3013 assert_eq!(dialect.db_type(), DbType::PolarDB);
3014 assert_eq!(dialect.quote("users"), "\"users\"");
3016 assert!(dialect.supports_returning());
3017 }
3018
3019 #[test]
3020 fn test_gaussdb_dialect() {
3021 let dialect = get_dialect(DbType::GaussDB).unwrap();
3022 assert_eq!(dialect.db_type(), DbType::GaussDB);
3023 assert_eq!(dialect.quote("users"), "\"users\"");
3025 assert!(dialect.supports_returning());
3026 }
3027
3028 #[test]
3029 fn test_gbase_dialect() {
3030 let dialect = get_dialect(DbType::GBase).unwrap();
3031 assert_eq!(dialect.db_type(), DbType::GBase);
3032 assert_eq!(dialect.quote("users"), "[users]");
3034 }
3035
3036 #[test]
3037 fn test_sybase_dialect() {
3038 let dialect = get_dialect(DbType::Sybase).unwrap();
3039 assert_eq!(dialect.db_type(), DbType::Sybase);
3040 assert_eq!(dialect.quote("users"), "[users]");
3042 }
3043
3044 #[test]
3047 fn test_db2_dialect_basic() {
3048 let dialect = get_dialect(DbType::Db2).unwrap();
3049 assert_eq!(dialect.db_type(), DbType::Db2);
3050 assert_eq!(dialect.quote("users"), "\"users\"");
3052 assert_eq!(dialect.escape_string("it's"), "it''s");
3054 assert_eq!(
3056 dialect.auto_increment_keyword(),
3057 "GENERATED ALWAYS AS IDENTITY"
3058 );
3059 assert!(!dialect.supports_if_exists());
3061 assert!(!dialect.supports_if_not_exists());
3062 assert!(!dialect.supports_returning());
3064 }
3065
3066 #[test]
3067 fn test_db2_pagination() {
3068 let dialect = Db2Dialect;
3069 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3071 assert_eq!(
3072 sql,
3073 "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
3074 );
3075 }
3076
3077 #[test]
3078 fn test_db2_last_insert_id() {
3079 let dialect = Db2Dialect;
3080 assert_eq!(
3082 dialect.last_insert_id_sql(),
3083 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
3084 );
3085 }
3086
3087 #[test]
3088 fn test_db2_concat() {
3089 let dialect = Db2Dialect;
3090 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
3092 assert_eq!(dialect.concat(&[]), "''");
3093 }
3094
3095 #[test]
3096 fn test_db2_create_table() {
3097 let dialect = Db2Dialect;
3098 let cols = vec![ColumnDef {
3099 name: "id".to_string(),
3100 sql_type: "BIGINT".to_string(),
3101 nullable: false,
3102 default: None,
3103 auto_increment: true,
3104 primary_key: true,
3105 }];
3106 let sql = dialect.build_create_table("users", &cols);
3107 assert!(sql.contains("\"id\" BIGINT"));
3108 assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
3109 assert!(sql.contains("PRIMARY KEY"));
3110 }
3111
3112 #[test]
3113 fn test_db2_type_mapping() {
3114 assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
3116 assert_eq!(map_to_db2_type("INT"), "INTEGER");
3117 assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
3118 assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
3119 assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
3120 assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
3121 assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
3122 assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
3123 assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
3124 assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
3125 assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
3126 assert_eq!(map_to_db2_type("DATE"), "DATE");
3127 assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
3128 }
3129
3130 #[test]
3133 fn test_clickhouse_dialect_basic() {
3134 let dialect = get_dialect(DbType::ClickHouse).unwrap();
3135 assert_eq!(dialect.db_type(), DbType::ClickHouse);
3136 assert_eq!(dialect.quote("users"), "`users`");
3138 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3140 assert!(!dialect.supports_returning());
3142 assert_eq!(dialect.auto_increment_keyword(), "");
3144 assert!(dialect.supports_if_exists());
3146 assert!(dialect.supports_if_not_exists());
3147 }
3148
3149 #[test]
3150 fn test_clickhouse_type_mapping() {
3151 assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
3152 assert_eq!(map_to_clickhouse_type("INT"), "Int32");
3153 assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
3154 assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
3155 assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
3156 assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
3157 assert_eq!(map_to_clickhouse_type("TEXT"), "String");
3158 assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
3159 assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
3160 assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
3161 assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
3162 assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
3163 assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
3164 assert_eq!(map_to_clickhouse_type("DATE"), "Date");
3165 }
3166
3167 #[test]
3168 fn test_clickhouse_create_table() {
3169 let dialect = ClickHouseDialect;
3170 let cols = vec![ColumnDef {
3171 name: "id".to_string(),
3172 sql_type: "BIGINT".to_string(),
3173 nullable: false,
3174 default: None,
3175 auto_increment: false, primary_key: true,
3177 }];
3178 let sql = dialect.build_create_table("users", &cols);
3179 assert!(
3181 sql.contains("ENGINE = MergeTree()"),
3182 "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
3183 sql
3184 );
3185 assert!(sql.contains("`id` Int64"));
3186 assert!(sql.contains("PRIMARY KEY"));
3187 }
3188
3189 #[test]
3190 fn test_clickhouse_json_extract() {
3191 let dialect = ClickHouseDialect;
3192 let sql = dialect.json_extract("data", "$.name");
3193 assert!(
3194 sql.contains("JSONExtractString"),
3195 "ClickHouse 应使用 JSONExtractString: {}",
3196 sql
3197 );
3198 }
3199
3200 #[test]
3201 fn test_clickhouse_concat() {
3202 let dialect = ClickHouseDialect;
3203 assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
3205 assert_eq!(dialect.concat(&[]), "''");
3206 }
3207
3208 #[test]
3211 fn test_db_type_dameng_str() {
3212 assert_eq!(DbType::Dameng.as_str(), "dameng");
3213 assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
3214 assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
3215 assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
3216 assert_eq!(DbType::Dameng.default_port(), 5236);
3217 }
3218
3219 #[test]
3220 fn test_db_type_kingbase_str() {
3221 assert_eq!(DbType::Kingbase.as_str(), "kingbase");
3222 assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
3223 assert_eq!(DbType::Kingbase.default_port(), 54321);
3224 }
3225
3226 #[test]
3227 fn test_db_type_db2_str() {
3228 assert_eq!(DbType::Db2.as_str(), "db2");
3229 assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
3230 assert_eq!(DbType::Db2.default_port(), 50000);
3231 }
3232
3233 #[test]
3234 fn test_db_type_mariadb_str() {
3235 assert_eq!(DbType::MariaDB.as_str(), "mariadb");
3236 assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
3237 assert_eq!(DbType::MariaDB.default_port(), 3306);
3238 }
3239
3240 #[test]
3241 fn test_db_type_tidb_str() {
3242 assert_eq!(DbType::TiDB.as_str(), "tidb");
3243 assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
3244 assert_eq!(DbType::TiDB.default_port(), 4000);
3245 }
3246
3247 #[test]
3248 fn test_db_type_polardb_str() {
3249 assert_eq!(DbType::PolarDB.as_str(), "polardb");
3250 assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
3251 assert_eq!(DbType::PolarDB.default_port(), 5432);
3252 }
3253
3254 #[test]
3255 fn test_db_type_gaussdb_str() {
3256 assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
3257 assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
3258 assert_eq!(DbType::GaussDB.default_port(), 25308);
3259 }
3260
3261 #[test]
3262 fn test_db_type_gbase_str() {
3263 assert_eq!(DbType::GBase.as_str(), "gbase");
3264 assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
3265 assert_eq!(DbType::GBase.default_port(), 9088);
3266 }
3267
3268 #[test]
3269 fn test_db_type_sybase_str() {
3270 assert_eq!(DbType::Sybase.as_str(), "sybase");
3271 assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
3272 assert_eq!(DbType::Sybase.default_port(), 5000);
3273 }
3274
3275 #[test]
3276 fn test_db_type_family_classification() {
3277 assert!(DbType::MySQL.is_mysql_family());
3279 assert!(DbType::MariaDB.is_mysql_family());
3280 assert!(DbType::TiDB.is_mysql_family());
3281 assert!(DbType::OceanBase.is_mysql_family());
3282 assert!(!DbType::PostgreSQL.is_mysql_family());
3283
3284 assert!(DbType::PostgreSQL.is_postgres_family());
3286 assert!(DbType::Kingbase.is_postgres_family());
3287 assert!(DbType::GaussDB.is_postgres_family());
3288 assert!(!DbType::MySQL.is_postgres_family());
3289
3290 assert!(DbType::Oracle.is_oracle_family());
3292 assert!(DbType::Dameng.is_oracle_family());
3293 assert!(!DbType::MySQL.is_oracle_family());
3294 }
3295
3296 #[test]
3297 fn test_db_type_supports_stored_procedure_extended() {
3298 assert!(DbType::Dameng.supports_stored_procedure());
3300 assert!(DbType::Kingbase.supports_stored_procedure());
3301 assert!(DbType::Db2.supports_stored_procedure());
3302 assert!(DbType::MariaDB.supports_stored_procedure());
3303 assert!(DbType::TiDB.supports_stored_procedure());
3304 assert!(DbType::PolarDB.supports_stored_procedure());
3305 assert!(DbType::GaussDB.supports_stored_procedure());
3306 assert!(DbType::GBase.supports_stored_procedure());
3307 assert!(DbType::Sybase.supports_stored_procedure());
3308 }
3309
3310 #[test]
3313 fn test_l4_max_identifier_len_constant() {
3314 assert_eq!(MAX_IDENTIFIER_LEN, 63);
3316 }
3317
3318 #[test]
3319 fn test_l4_quote_checked_valid_identifier() {
3320 let dialect = MySqlDialect;
3321 assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
3322 assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
3323 let name_63 = "a".repeat(63);
3325 assert!(dialect.quote_checked(&name_63).is_ok());
3326 }
3327
3328 #[test]
3329 fn test_l4_quote_checked_rejects_too_long() {
3330 let dialect = MySqlDialect;
3331 let long_name = "a".repeat(64); let result = dialect.quote_checked(&long_name);
3333 assert!(result.is_err());
3334 match result {
3335 Err(DbError::InvalidInput(msg)) => {
3336 assert!(
3337 msg.contains("too long"),
3338 "expected 'too long' error, got: {}",
3339 msg
3340 );
3341 }
3342 _ => panic!("Expected DbError::InvalidInput"),
3343 }
3344 }
3345
3346 #[test]
3347 fn test_l4_quote_checked_rejects_empty() {
3348 let dialect = MySqlDialect;
3349 let result = dialect.quote_checked("");
3350 assert!(result.is_err());
3351 }
3352
3353 #[test]
3354 fn test_l4_quote_checked_rejects_sql_injection() {
3355 let dialect = MySqlDialect;
3356 assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
3358 assert!(dialect.quote_checked("user'name").is_err());
3360 assert!(dialect.quote_checked("user name").is_err());
3362 assert!(dialect.quote_checked("1users").is_err());
3364 assert!(dialect.quote_checked("schema.table").is_err());
3366 }
3367
3368 #[test]
3369 fn test_l4_quote_checked_postgres() {
3370 let dialect = PostgreSqlDialect;
3371 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3372 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3373 }
3374
3375 #[test]
3376 fn test_l4_quote_checked_sqlite() {
3377 let dialect = SqliteDialect;
3378 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3379 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3380 }
3381
3382 #[test]
3383 fn test_l4_quote_checked_oracle() {
3384 let dialect = OracleDialect;
3385 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3386 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3387 }
3388
3389 #[test]
3390 fn test_l4_quote_checked_sql_server() {
3391 let dialect = SqlServerDialect;
3392 assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
3393 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3394 }
3395
3396 #[test]
3399 fn test_duckdb_quote() {
3400 let dialect = DuckDBDialect;
3401 assert_eq!(dialect.quote("users"), "\"users\"");
3402 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3403 }
3404
3405 #[test]
3406 fn test_duckdb_escape() {
3407 let dialect = DuckDBDialect;
3408 assert_eq!(dialect.escape_string("hello"), "hello");
3409 assert_eq!(dialect.escape_string("it's"), "it''s");
3410 }
3411
3412 #[test]
3413 fn test_duckdb_pagination() {
3414 let dialect = DuckDBDialect;
3415 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3416 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
3417 }
3418
3419 #[test]
3420 fn test_duckdb_supports() {
3421 let dialect = DuckDBDialect;
3422 assert!(!dialect.supports_returning());
3423 assert!(!dialect.supports_lock_for_update());
3424 assert!(!dialect.supports_lock_shared());
3425 assert!(dialect.supports_if_exists());
3426 assert!(dialect.supports_if_not_exists());
3427 }
3428
3429 #[test]
3430 fn test_duckdb_insert_or_ignore() {
3431 let dialect = DuckDBDialect;
3432 let sql = dialect.build_insert_or_ignore_prefix("users");
3433 assert_eq!(sql, "INSERT OR IGNORE INTO \"users\"");
3434 }
3435
3436 #[test]
3437 fn test_duckdb_create_table() {
3438 let dialect = DuckDBDialect;
3439 let columns = vec![
3440 ColumnDef {
3441 name: "id".to_string(),
3442 sql_type: "BIGINT".to_string(),
3443 nullable: false,
3444 default: None,
3445 auto_increment: true,
3446 primary_key: true,
3447 },
3448 ColumnDef {
3449 name: "name".to_string(),
3450 sql_type: "VARCHAR(255)".to_string(),
3451 nullable: false,
3452 default: None,
3453 auto_increment: false,
3454 primary_key: false,
3455 },
3456 ];
3457 let sql = dialect.build_create_table("users", &columns);
3458 assert!(sql.contains("CREATE TABLE \"users\""));
3459 assert!(sql.contains("\"id\" BIGINT PRIMARY KEY"));
3460 assert!(sql.contains("\"name\" VARCHAR(255)"));
3461 }
3462
3463 #[test]
3464 fn test_duckdb_alter_table() {
3465 let dialect = DuckDBDialect;
3466 let changes = vec![
3467 TableChange::AddColumn(ColumnDef {
3468 name: "age".to_string(),
3469 sql_type: "INTEGER".to_string(),
3470 nullable: true,
3471 default: None,
3472 auto_increment: false,
3473 primary_key: false,
3474 }),
3475 ];
3476 let sql = dialect.build_alter_table("users", &changes);
3477 assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN \"age\" INTEGER"));
3478 }
3479
3480 #[test]
3481 fn test_duckdb_json_extract() {
3482 let dialect = DuckDBDialect;
3483 let sql = dialect.json_extract("data", "$.user.name");
3484 assert!(sql.contains("->"));
3485 assert!(sql.contains("user.name"));
3486 }
3487
3488 #[test]
3489 fn test_duckdb_concat() {
3490 let dialect = DuckDBDialect;
3491 let sql = dialect.concat(&["a", "b", "c"]);
3492 assert_eq!(sql, "a || b || c");
3493 }
3494
3495 #[test]
3496 fn test_duckdb_bool_to_int() {
3497 let dialect = DuckDBDialect;
3498 let sql = dialect.bool_to_int("active");
3499 assert!(sql.contains("CASE WHEN"));
3500 assert!(sql.contains("THEN 1"));
3501 assert!(sql.contains("ELSE 0"));
3502 }
3503
3504 #[test]
3505 fn test_get_dialect_duckdb() {
3506 let result = get_dialect(DbType::DuckDB);
3507 assert!(result.is_ok());
3508 let dialect = result.unwrap();
3509 assert_eq!(dialect.db_type(), DbType::DuckDB);
3510 }
3511}