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,
190 pub sql_type: String,
192 pub nullable: bool,
194 pub default: Option<String>,
196 pub auto_increment: bool,
198 pub primary_key: bool,
200}
201
202#[derive(Debug, Clone)]
204pub enum TableChange {
205 AddColumn(ColumnDef),
207 DropColumn(String),
209 ModifyColumn(ColumnDef),
211 AddIndex(String, Vec<String>),
213 DropIndex(String),
215 AddForeignKey {
217 columns: Vec<String>,
219 reference_table: String,
221 reference_columns: Vec<String>,
223 },
224}
225
226#[derive(Debug, Clone)]
228pub struct MySqlDialect;
229
230impl Dialect for MySqlDialect {
231 fn clone_box(&self) -> Box<dyn Dialect> {
232 Box::new(MySqlDialect)
233 }
234
235 fn db_type(&self) -> DbType {
236 DbType::MySQL
237 }
238
239 fn quote(&self, identifier: &str) -> String {
240 format!("`{}`", identifier.replace('`', "``"))
241 }
242
243 fn escape_string(&self, s: &str) -> String {
244 let mut escaped = String::with_capacity(s.len() * 2);
245 for c in s.chars() {
246 match c {
247 '\\' => escaped.push_str("\\\\"),
248 '\'' => escaped.push_str("\\'"),
249 '\0' => escaped.push_str("\\0"),
250 '\n' => escaped.push_str("\\n"),
251 '\r' => escaped.push_str("\\r"),
252 '\t' => escaped.push_str("\\t"),
253 '\x1a' => escaped.push_str("\\Z"),
254 _ => escaped.push(c),
255 }
256 }
257 escaped
258 }
259
260 fn supports_returning(&self) -> bool {
261 false
262 }
263
264 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
265 let offset = page.saturating_sub(1).saturating_mul(limit);
273 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
274 }
275
276 fn json_type(&self) -> &'static str {
277 "JSON"
278 }
279
280 fn json_extract(&self, column: &str, path: &str) -> String {
281 let normalized = if path.starts_with('$') {
283 path.to_string()
284 } else {
285 format!("$.{}", path)
286 };
287 format!(
288 "JSON_EXTRACT({}, '{}')",
289 column,
290 self.escape_string(&normalized)
291 )
292 }
293
294 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
295 let cols = columns.join(", ");
296 let escaped = self.escape_string(keyword);
297 format!(
298 "MATCH({}) AGAINST('{}' IN NATURAL LANGUAGE MODE)",
299 cols, escaped
300 )
301 }
302
303 fn bool_to_int(&self, expr: &str) -> String {
304 format!("IF({}, 1, 0)", expr)
306 }
307
308 fn concat(&self, parts: &[&str]) -> String {
309 if parts.is_empty() {
310 return "NULL".to_string();
311 }
312 let concat_parts: Vec<String> = parts
313 .iter()
314 .map(|p| format!("CAST({} AS CHAR)", p))
315 .collect();
316 format!("CONCAT({})", concat_parts.join(", "))
317 }
318
319 fn supports_if_exists(&self) -> bool {
320 true
321 }
322
323 fn supports_if_not_exists(&self) -> bool {
324 true
325 }
326
327 fn auto_increment_keyword(&self) -> &'static str {
328 "AUTO_INCREMENT"
329 }
330
331 fn last_insert_id_sql(&self) -> Option<&'static str> {
332 Some("LAST_INSERT_ID()")
333 }
334
335 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
336 let cols: Vec<String> = columns
337 .iter()
338 .map(|col| {
339 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
340 if !col.nullable {
341 sql.push_str(" NOT NULL");
342 }
343 if let Some(default) = &col.default {
344 sql.push_str(&format!(" DEFAULT {}", default));
345 }
346 if col.auto_increment {
347 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
348 }
349 if col.primary_key {
350 sql.push_str(" PRIMARY KEY");
351 }
352 sql
353 })
354 .collect();
355
356 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
357 }
358
359 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
360 let stmts: Vec<String> = changes.iter().map(|change| {
361 match change {
362 TableChange::AddColumn(col) => {
363 let mut sql = format!("ALTER TABLE {} ADD {}", self.quote(table), self.quote(&col.name));
364 sql.push_str(&format!(" {}", col.sql_type));
365 if !col.nullable {
366 sql.push_str(" NOT NULL");
367 }
368 if let Some(default) = &col.default {
369 sql.push_str(&format!(" DEFAULT {}", default));
370 }
371 sql
372 }
373 TableChange::DropColumn(name) => {
374 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
375 }
376 TableChange::ModifyColumn(col) => {
377 let mut sql = format!("ALTER TABLE {} MODIFY COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
379 if !col.nullable {
380 sql.push_str(" NOT NULL");
381 }
382 if let Some(default) = &col.default {
383 sql.push_str(&format!(" DEFAULT {}", default));
384 }
385 sql
386 }
387 TableChange::AddIndex(name, cols) => {
388 format!("ALTER TABLE {} ADD INDEX {} ({})", self.quote(table), name, cols.join(", "))
389 }
390 TableChange::DropIndex(name) => {
391 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
392 }
393 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
394 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
395 self.quote(table),
396 table,
397 columns.join("_"),
398 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
399 self.quote(reference_table),
400 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
401 }
402 }
403 }).collect();
404
405 stmts.join("; ")
406 }
407
408 fn build_upsert_on_conflict(
413 &self,
414 _conflict_columns: &[&str],
415 update_columns: &[&str],
416 all_columns: &[String],
417 ) -> Option<String> {
418 let cols_to_update: Vec<String> = if update_columns.is_empty() {
420 all_columns.iter().map(|c| self.quote(c)).collect()
421 } else {
422 update_columns.iter().map(|c| self.quote(c)).collect()
423 };
424 if cols_to_update.is_empty() {
425 return None;
426 }
427 let set_clauses: Vec<String> = cols_to_update
428 .iter()
429 .map(|col| format!("{}=VALUES({})", col, col))
430 .collect();
431 Some(format!(
432 "ON DUPLICATE KEY UPDATE {}",
433 set_clauses.join(", ")
434 ))
435 }
436
437 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
438 match lock_type {
439 LockType::ForUpdate => Some("FOR UPDATE".to_string()),
440 LockType::Shared => Some("LOCK IN SHARE MODE".to_string()),
441 }
442 }
443
444 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
445 format!("INSERT IGNORE INTO {}", self.quote(table))
446 }
447}
448
449#[derive(Debug, Clone)]
451pub struct PostgreSqlDialect;
452
453impl Dialect for PostgreSqlDialect {
454 fn clone_box(&self) -> Box<dyn Dialect> {
455 Box::new(PostgreSqlDialect)
456 }
457
458 fn db_type(&self) -> DbType {
459 DbType::PostgreSQL
460 }
461
462 fn quote(&self, identifier: &str) -> String {
463 format!("\"{}\"", identifier.replace('"', "\"\""))
464 }
465
466 fn escape_string(&self, s: &str) -> String {
467 let mut escaped = String::with_capacity(s.len() * 2);
470 for c in s.chars() {
471 match c {
472 '\'' => escaped.push_str("''"),
473 _ => escaped.push(c),
474 }
475 }
476 escaped
477 }
478
479 fn supports_returning(&self) -> bool {
480 true
481 }
482
483 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
484 let offset = page.saturating_sub(1).saturating_mul(limit);
485 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
486 }
487
488 fn json_type(&self) -> &'static str {
489 "JSONB"
490 }
491
492 fn json_extract(&self, column: &str, path: &str) -> String {
493 let normalized = path.trim_start_matches("$.");
498 let parts: Vec<&str> = normalized.split('.').filter(|s| !s.is_empty()).collect();
499 let path_lit = parts
500 .iter()
501 .map(|p| {
502 let needs_quoting = p.chars().any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\'));
504 if needs_quoting {
505 let escaped = p.replace('\\', "\\\\").replace('"', "\\\"");
506 format!("\"{}\"", escaped)
507 } else {
508 p.to_string()
509 }
510 })
511 .collect::<Vec<_>>()
512 .join(",");
513 let path_lit_escaped = path_lit.replace('\'', "''");
515 format!("{}#>>'{{{}}}'", column, path_lit_escaped)
516 }
517
518 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
519 let cols = columns
520 .iter()
521 .map(|c| format!("{}::text", c))
522 .collect::<Vec<_>>()
523 .join(" || ' ' || ");
524 let escaped = self.escape_string(keyword);
525 format!("to_tsvector({}) @@ to_tsquery('{}')", cols, escaped)
526 }
527
528 fn bool_to_int(&self, expr: &str) -> String {
529 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
530 }
531
532 fn concat(&self, parts: &[&str]) -> String {
533 if parts.is_empty() {
534 return "NULL".to_string();
535 }
536 format!("CONCAT({})", parts.join(", "))
537 }
538
539 fn supports_if_exists(&self) -> bool {
540 true
541 }
542
543 fn supports_if_not_exists(&self) -> bool {
544 true
545 }
546
547 fn auto_increment_keyword(&self) -> &'static str {
548 "GENERATED BY DEFAULT AS IDENTITY"
549 }
550
551 fn last_insert_id_sql(&self) -> Option<&'static str> {
552 Some("lastval()")
553 }
554
555 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
556 let cols: Vec<String> = columns
557 .iter()
558 .map(|col| {
559 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
560 if !col.nullable {
561 sql.push_str(" NOT NULL");
562 }
563 if let Some(default) = &col.default {
564 sql.push_str(&format!(" DEFAULT {}", default));
565 }
566 if col.primary_key {
567 sql.push_str(" PRIMARY KEY");
568 }
569 sql
570 })
571 .collect();
572
573 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
574 }
575
576 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
577 let stmts: Vec<String> = changes.iter().map(|change| {
578 match change {
579 TableChange::AddColumn(col) => {
580 let mut sql = format!("ALTER TABLE {} ADD COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
581 if !col.nullable {
582 sql.push_str(" NOT NULL");
583 }
584 if let Some(default) = &col.default {
585 sql.push_str(&format!(" DEFAULT {}", default));
586 }
587 sql
588 }
589 TableChange::DropColumn(name) => {
590 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
591 }
592 TableChange::ModifyColumn(col) => {
593 let mut sql = format!("ALTER TABLE {} ALTER COLUMN {} TYPE {}", self.quote(table), self.quote(&col.name), col.sql_type);
595 if !col.nullable {
596 sql.push_str(&format!(", ALTER COLUMN {} SET NOT NULL", self.quote(&col.name)));
597 }
598 if let Some(default) = &col.default {
599 sql.push_str(&format!(", ALTER COLUMN {} SET DEFAULT {}", self.quote(&col.name), default));
600 }
601 sql
602 }
603 TableChange::AddIndex(name, cols) => {
604 format!("CREATE INDEX {} ON {} ({})", name, self.quote(table), cols.join(", "))
605 }
606 TableChange::DropIndex(name) => {
607 format!("DROP INDEX {}", name)
608 }
609 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
610 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
611 self.quote(table),
612 table,
613 columns.join("_"),
614 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
615 self.quote(reference_table),
616 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
617 }
618 }
619 }).collect();
620
621 stmts.join("; ")
622 }
623
624 fn build_upsert_on_conflict(
628 &self,
629 conflict_columns: &[&str],
630 update_columns: &[&str],
631 all_columns: &[String],
632 ) -> Option<String> {
633 if conflict_columns.is_empty() {
634 return None;
635 }
636 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
637 let conflict_set: std::collections::HashSet<&str> =
639 conflict_columns.iter().copied().collect();
640 let cols_to_update: Vec<String> = if update_columns.is_empty() {
641 all_columns
642 .iter()
643 .filter(|c| !conflict_set.contains(c.as_str()))
644 .map(|c| self.quote(c))
645 .collect()
646 } else {
647 update_columns.iter().map(|c| self.quote(c)).collect()
648 };
649 if cols_to_update.is_empty() {
650 return Some(format!(
652 "ON CONFLICT ({}) DO NOTHING",
653 conflict_cols.join(", ")
654 ));
655 }
656 let set_clauses: Vec<String> = cols_to_update
657 .iter()
658 .map(|col| format!("{}=EXCLUDED.{}", col, col))
659 .collect();
660 Some(format!(
661 "ON CONFLICT ({}) DO UPDATE SET {}",
662 conflict_cols.join(", "),
663 set_clauses.join(", ")
664 ))
665 }
666
667 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
668 match lock_type {
669 LockType::ForUpdate => Some("FOR UPDATE".to_string()),
670 LockType::Shared => Some("FOR SHARE".to_string()),
671 }
672 }
673}
674
675#[derive(Debug, Clone)]
677pub struct SqliteDialect;
678
679impl Dialect for SqliteDialect {
680 fn clone_box(&self) -> Box<dyn Dialect> {
681 Box::new(SqliteDialect)
682 }
683
684 fn db_type(&self) -> DbType {
685 DbType::Sqlite
686 }
687
688 fn quote(&self, identifier: &str) -> String {
689 format!("\"{}\"", identifier.replace('"', "\"\""))
690 }
691
692 fn escape_string(&self, s: &str) -> String {
693 let mut escaped = String::with_capacity(s.len() * 2);
694 for c in s.chars() {
695 match c {
696 '\'' => escaped.push_str("''"),
697 _ => escaped.push(c),
698 }
699 }
700 escaped
701 }
702
703 fn supports_returning(&self) -> bool {
704 true
705 }
706
707 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
708 let offset = page.saturating_sub(1).saturating_mul(limit);
709 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
710 }
711
712 fn json_type(&self) -> &'static str {
713 "TEXT"
714 }
715
716 fn json_extract(&self, column: &str, path: &str) -> String {
717 let normalized = if path.starts_with('$') {
719 path.to_string()
720 } else {
721 format!("$.{}", path)
722 };
723 format!(
724 "json_extract({}, '{}')",
725 column,
726 self.escape_string(&normalized)
727 )
728 }
729
730 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
731 if columns.is_empty() {
735 return "0".to_string();
736 }
737 let escaped = self.escape_string(keyword);
738 columns
739 .iter()
740 .map(|c| format!("{} LIKE '%{}%'", c.trim(), escaped))
741 .collect::<Vec<_>>()
742 .join(" OR ")
743 }
744
745 fn bool_to_int(&self, expr: &str) -> String {
746 expr.to_string()
747 }
748
749 fn concat(&self, parts: &[&str]) -> String {
750 if parts.is_empty() {
751 return "NULL".to_string();
752 }
753 let coalesced: Vec<String> = parts
756 .iter()
757 .map(|p| format!("COALESCE({}, '')", p))
758 .collect();
759 coalesced.join(" || ")
760 }
761
762 fn supports_if_exists(&self) -> bool {
763 true
764 }
765
766 fn supports_if_not_exists(&self) -> bool {
767 true
768 }
769
770 fn auto_increment_keyword(&self) -> &'static str {
771 "AUTOINCREMENT"
772 }
773
774 fn last_insert_id_sql(&self) -> Option<&'static str> {
775 Some("last_insert_rowid()")
776 }
777
778 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
779 let cols: Vec<String> = columns
780 .iter()
781 .map(|col| {
782 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
783 if !col.nullable {
784 sql.push_str(" NOT NULL");
785 }
786 if let Some(default) = &col.default {
787 sql.push_str(&format!(" DEFAULT {}", default));
788 }
789 if col.auto_increment {
790 sql.push_str(" PRIMARY KEY AUTOINCREMENT");
791 } else if col.primary_key {
792 sql.push_str(" PRIMARY KEY");
793 }
794 sql
795 })
796 .collect();
797
798 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
799 }
800
801 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
802 let stmts: Vec<String> = changes
805 .iter()
806 .map(|change| {
807 match change {
808 TableChange::AddColumn(col) => {
809 let mut sql = format!(
810 "ALTER TABLE {} ADD COLUMN {} {}",
811 self.quote(table),
812 self.quote(&col.name),
813 col.sql_type
814 );
815 if !col.nullable {
816 sql.push_str(" NOT NULL");
817 }
818 if let Some(default) = &col.default {
819 sql.push_str(&format!(" DEFAULT {}", default));
820 }
821 sql
822 }
823 TableChange::DropColumn(name) => {
824 format!(
826 "ALTER TABLE {} DROP COLUMN {}",
827 self.quote(table),
828 self.quote(name)
829 )
830 }
831 TableChange::ModifyColumn(col) => {
832 format!(
835 "-- SQLite 不支持 MODIFY COLUMN({} {}),需重建表",
836 col.name, col.sql_type
837 )
838 }
839 TableChange::AddIndex(name, cols) => {
840 format!(
841 "CREATE INDEX {} ON {} ({})",
842 name,
843 self.quote(table),
844 cols.join(", ")
845 )
846 }
847 TableChange::DropIndex(name) => {
848 format!("DROP INDEX {}", name)
849 }
850 TableChange::AddForeignKey {
851 columns,
852 reference_table,
853 reference_columns: _,
854 } => {
855 format!(
857 "-- SQLite 不支持 ADD FOREIGN KEY({} -> {}),需重建表",
858 columns.join(","),
859 reference_table
860 )
861 }
862 }
863 })
864 .collect();
865
866 stmts.join("; ")
867 }
868
869 fn build_upsert_on_conflict(
873 &self,
874 conflict_columns: &[&str],
875 update_columns: &[&str],
876 all_columns: &[String],
877 ) -> Option<String> {
878 if conflict_columns.is_empty() {
879 return None;
880 }
881 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
882 let conflict_set: std::collections::HashSet<&str> =
883 conflict_columns.iter().copied().collect();
884 let cols_to_update: Vec<String> = if update_columns.is_empty() {
885 all_columns
886 .iter()
887 .filter(|c| !conflict_set.contains(c.as_str()))
888 .map(|c| self.quote(c))
889 .collect()
890 } else {
891 update_columns.iter().map(|c| self.quote(c)).collect()
892 };
893 if cols_to_update.is_empty() {
894 return Some(format!(
895 "ON CONFLICT ({}) DO NOTHING",
896 conflict_cols.join(", ")
897 ));
898 }
899 let set_clauses: Vec<String> = cols_to_update
900 .iter()
901 .map(|col| format!("{}=EXCLUDED.{}", col, col))
902 .collect();
903 Some(format!(
904 "ON CONFLICT ({}) DO UPDATE SET {}",
905 conflict_cols.join(", "),
906 set_clauses.join(", ")
907 ))
908 }
909
910 fn supports_lock_for_update(&self) -> bool {
911 false
912 }
913
914 fn supports_lock_shared(&self) -> bool {
915 false
916 }
917}
918
919fn map_to_oracle_type(sql_type: &str) -> String {
928 let upper = sql_type.to_uppercase();
929 let trimmed = upper.trim();
930
931 if trimmed.starts_with("BIGINT") {
932 sql_type.replacen("BIGINT", "NUMBER(19)", 1)
933 } else if trimmed.starts_with("VARCHAR2") {
934 sql_type.to_string()
935 } else if trimmed.starts_with("VARCHAR") {
936 sql_type.replacen("VARCHAR", "VARCHAR2", 1)
937 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
938 "CLOB".to_string()
939 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
940 "NUMBER(1)".to_string()
941 } else if trimmed == "INTEGER" {
942 "NUMBER(10)".to_string()
943 } else if trimmed.starts_with("INT") {
944 sql_type.replacen("INT", "NUMBER(10)", 1)
945 } else {
946 sql_type.to_string()
947 }
948}
949
950#[derive(Debug, Clone)]
952pub struct OracleDialect;
953
954impl Dialect for OracleDialect {
955 fn clone_box(&self) -> Box<dyn Dialect> {
956 Box::new(OracleDialect)
957 }
958
959 fn db_type(&self) -> DbType {
960 DbType::Oracle
961 }
962
963 fn quote(&self, identifier: &str) -> String {
964 format!("\"{}\"", identifier.replace('"', "\"\""))
966 }
967
968 fn escape_string(&self, s: &str) -> String {
969 let mut escaped = String::with_capacity(s.len() * 2);
971 for c in s.chars() {
972 match c {
973 '\'' => escaped.push_str("''"),
974 _ => escaped.push(c),
975 }
976 }
977 escaped
978 }
979
980 fn supports_returning(&self) -> bool {
981 true
983 }
984
985 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
986 let offset = page.saturating_sub(1).saturating_mul(limit);
989 format!(
990 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
991 sql, offset, limit
992 )
993 }
994
995 fn json_type(&self) -> &'static str {
996 "JSON"
998 }
999
1000 fn json_extract(&self, column: &str, path: &str) -> String {
1001 let normalized = if path.starts_with('$') {
1003 path.to_string()
1004 } else {
1005 format!("$.{}", path)
1006 };
1007 format!(
1008 "JSON_VALUE({}, '{}')",
1009 column,
1010 self.escape_string(&normalized)
1011 )
1012 }
1013
1014 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1015 if columns.is_empty() {
1018 return "0".to_string();
1019 }
1020 let escaped = self.escape_string(keyword);
1021 let parts: Vec<String> = columns
1022 .iter()
1023 .map(|c| format!("CONTAINS({}, '{}', 1) > 0", c, escaped))
1024 .collect();
1025 parts.join(" OR ")
1026 }
1027
1028 fn bool_to_int(&self, expr: &str) -> String {
1029 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1031 }
1032
1033 fn concat(&self, parts: &[&str]) -> String {
1034 if parts.is_empty() {
1036 return "NULL".to_string();
1037 }
1038 parts.join(" || ")
1039 }
1040
1041 fn supports_if_exists(&self) -> bool {
1042 true
1044 }
1045
1046 fn supports_if_not_exists(&self) -> bool {
1047 true
1049 }
1050
1051 fn auto_increment_keyword(&self) -> &'static str {
1052 "GENERATED BY DEFAULT AS IDENTITY"
1054 }
1055
1056 fn last_insert_id_sql(&self) -> Option<&'static str> {
1057 None
1062 }
1063
1064 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1065 let cols: Vec<String> = columns
1066 .iter()
1067 .map(|col| {
1068 let oracle_type = map_to_oracle_type(&col.sql_type);
1069 let mut sql = format!("{} {}", self.quote(&col.name), oracle_type);
1070 if !col.nullable && !col.auto_increment {
1072 sql.push_str(" NOT NULL");
1073 }
1074 if let Some(default) = &col.default {
1075 sql.push_str(&format!(" DEFAULT {}", default));
1076 }
1077 if col.auto_increment {
1078 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1079 }
1080 if col.primary_key {
1081 sql.push_str(" PRIMARY KEY");
1082 }
1083 sql
1084 })
1085 .collect();
1086
1087 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1088 }
1089
1090 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1091 let stmts: Vec<String> = changes
1092 .iter()
1093 .map(|change| match change {
1094 TableChange::AddColumn(col) => {
1095 let oracle_type = map_to_oracle_type(&col.sql_type);
1096 let mut sql = format!(
1097 "ALTER TABLE {} ADD {} {}",
1098 self.quote(table),
1099 self.quote(&col.name),
1100 oracle_type
1101 );
1102 if !col.nullable {
1103 sql.push_str(" NOT NULL");
1104 }
1105 if let Some(default) = &col.default {
1106 sql.push_str(&format!(" DEFAULT {}", default));
1107 }
1108 sql
1109 }
1110 TableChange::DropColumn(name) => {
1111 format!(
1112 "ALTER TABLE {} DROP COLUMN {}",
1113 self.quote(table),
1114 self.quote(name)
1115 )
1116 }
1117 TableChange::ModifyColumn(col) => {
1118 let oracle_type = map_to_oracle_type(&col.sql_type);
1120 let mut sql = format!(
1121 "ALTER TABLE {} MODIFY {} {}",
1122 self.quote(table),
1123 self.quote(&col.name),
1124 oracle_type
1125 );
1126 if !col.nullable {
1127 sql.push_str(" NOT NULL");
1128 }
1129 if let Some(default) = &col.default {
1130 sql.push_str(&format!(" DEFAULT {}", default));
1131 }
1132 sql
1133 }
1134 TableChange::AddIndex(name, cols) => {
1135 format!(
1136 "CREATE INDEX {} ON {} ({})",
1137 name,
1138 self.quote(table),
1139 cols.join(", ")
1140 )
1141 }
1142 TableChange::DropIndex(name) => {
1143 format!("DROP INDEX {}", name)
1144 }
1145 TableChange::AddForeignKey {
1146 columns,
1147 reference_table,
1148 reference_columns,
1149 } => {
1150 format!(
1151 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1152 self.quote(table),
1153 table,
1154 columns.join("_"),
1155 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1156 self.quote(reference_table),
1157 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1158 )
1159 }
1160 })
1161 .collect();
1162
1163 stmts.join("; ")
1164 }
1165}
1166
1167fn map_to_sqlserver_type(sql_type: &str) -> String {
1176 let upper = sql_type.to_uppercase();
1177 let trimmed = upper.trim();
1178
1179 if trimmed.starts_with("BIGINT") {
1180 sql_type.to_string()
1181 } else if matches!(trimmed, "INT" | "INTEGER") {
1182 "INT".to_string()
1183 } else if trimmed.starts_with("NVARCHAR") {
1184 sql_type.to_string()
1185 } else if trimmed.starts_with("VARCHAR") {
1186 sql_type.replacen("VARCHAR", "NVARCHAR", 1)
1187 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1188 "NVARCHAR(MAX)".to_string()
1189 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1190 "BIT".to_string()
1191 } else {
1192 sql_type.to_string()
1193 }
1194}
1195
1196#[derive(Debug, Clone)]
1198pub struct SqlServerDialect;
1199
1200impl Dialect for SqlServerDialect {
1201 fn clone_box(&self) -> Box<dyn Dialect> {
1202 Box::new(SqlServerDialect)
1203 }
1204
1205 fn db_type(&self) -> DbType {
1206 DbType::SqlServer
1207 }
1208
1209 fn quote(&self, identifier: &str) -> String {
1210 format!("[{}]", identifier.replace(']', "]]"))
1212 }
1213
1214 fn escape_string(&self, s: &str) -> String {
1215 let mut escaped = String::with_capacity(s.len() * 2);
1217 for c in s.chars() {
1218 match c {
1219 '\'' => escaped.push_str("''"),
1220 _ => escaped.push(c),
1221 }
1222 }
1223 escaped
1224 }
1225
1226 fn supports_returning(&self) -> bool {
1227 true
1229 }
1230
1231 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1232 let offset = page.saturating_sub(1).saturating_mul(limit);
1234 format!(
1235 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1236 sql, offset, limit
1237 )
1238 }
1239
1240 fn json_type(&self) -> &'static str {
1241 "NVARCHAR(MAX)"
1243 }
1244
1245 fn json_extract(&self, column: &str, path: &str) -> String {
1246 let normalized = if path.starts_with('$') {
1248 path.to_string()
1249 } else {
1250 format!("$.{}", path)
1251 };
1252 format!(
1253 "JSON_VALUE({}, '{}')",
1254 column,
1255 self.escape_string(&normalized)
1256 )
1257 }
1258
1259 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1260 if columns.is_empty() {
1262 return "0".to_string();
1263 }
1264 let escaped = self.escape_string(keyword);
1265 let cols = columns.join(", ");
1266 format!("CONTAINS({}, '{}')", cols, escaped)
1267 }
1268
1269 fn bool_to_int(&self, expr: &str) -> String {
1270 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1272 }
1273
1274 fn concat(&self, parts: &[&str]) -> String {
1275 if parts.is_empty() {
1276 return "NULL".to_string();
1277 }
1278 format!("CONCAT({})", parts.join(", "))
1279 }
1280
1281 fn supports_if_exists(&self) -> bool {
1282 true
1284 }
1285
1286 fn supports_if_not_exists(&self) -> bool {
1287 true
1289 }
1290
1291 fn auto_increment_keyword(&self) -> &'static str {
1292 "IDENTITY(1,1)"
1294 }
1295
1296 fn last_insert_id_sql(&self) -> Option<&'static str> {
1297 Some("SCOPE_IDENTITY()")
1299 }
1300
1301 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1302 let cols: Vec<String> = columns
1303 .iter()
1304 .map(|col| {
1305 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1306 let mut sql = format!("{} {}", self.quote(&col.name), sqlserver_type);
1307 if !col.nullable {
1308 sql.push_str(" NOT NULL");
1309 }
1310 if let Some(default) = &col.default {
1311 sql.push_str(&format!(" DEFAULT {}", default));
1312 }
1313 if col.auto_increment {
1314 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1315 }
1316 if col.primary_key {
1317 sql.push_str(" PRIMARY KEY");
1318 }
1319 sql
1320 })
1321 .collect();
1322
1323 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1324 }
1325
1326 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1327 let stmts: Vec<String> = changes
1328 .iter()
1329 .map(|change| match change {
1330 TableChange::AddColumn(col) => {
1331 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1332 let mut sql = format!(
1333 "ALTER TABLE {} ADD {} {}",
1334 self.quote(table),
1335 self.quote(&col.name),
1336 sqlserver_type
1337 );
1338 if !col.nullable {
1339 sql.push_str(" NOT NULL");
1340 }
1341 if let Some(default) = &col.default {
1342 sql.push_str(&format!(" DEFAULT {}", default));
1343 }
1344 sql
1345 }
1346 TableChange::DropColumn(name) => {
1347 format!(
1348 "ALTER TABLE {} DROP COLUMN {}",
1349 self.quote(table),
1350 self.quote(name)
1351 )
1352 }
1353 TableChange::ModifyColumn(col) => {
1354 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1356 let mut sql = format!(
1357 "ALTER TABLE {} ALTER COLUMN {} {}",
1358 self.quote(table),
1359 self.quote(&col.name),
1360 sqlserver_type
1361 );
1362 if !col.nullable {
1363 sql.push_str(" NOT NULL");
1364 }
1365 if let Some(default) = &col.default {
1366 sql.push_str(&format!(" DEFAULT {}", default));
1367 }
1368 sql
1369 }
1370 TableChange::AddIndex(name, cols) => {
1371 format!(
1372 "CREATE INDEX {} ON {} ({})",
1373 name,
1374 self.quote(table),
1375 cols.join(", ")
1376 )
1377 }
1378 TableChange::DropIndex(name) => {
1379 format!("DROP INDEX {} ON {}", name, self.quote(table))
1381 }
1382 TableChange::AddForeignKey {
1383 columns,
1384 reference_table,
1385 reference_columns,
1386 } => {
1387 format!(
1388 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1389 self.quote(table),
1390 table,
1391 columns.join("_"),
1392 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1393 self.quote(reference_table),
1394 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1395 )
1396 }
1397 })
1398 .collect();
1399
1400 stmts.join("; ")
1401 }
1402
1403 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
1404 format!("INSERT INTO {}", self.quote(table))
1410 }
1411}
1412
1413macro_rules! delegate_dialect_to {
1430 ($wrapper:ident, $base:ident, $db_type:expr) => {
1431 #[derive(Debug, Clone)]
1433 pub struct $wrapper;
1434
1435 impl Dialect for $wrapper {
1436 fn clone_box(&self) -> Box<dyn Dialect> {
1437 Box::new($wrapper)
1438 }
1439
1440 fn db_type(&self) -> DbType {
1441 $db_type
1442 }
1443 fn quote(&self, identifier: &str) -> String {
1444 $base.quote(identifier)
1445 }
1446 fn escape_string(&self, s: &str) -> String {
1447 $base.escape_string(s)
1448 }
1449 fn supports_returning(&self) -> bool {
1450 $base.supports_returning()
1451 }
1452 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1453 $base.build_pagination(sql, page, limit)
1454 }
1455 fn json_type(&self) -> &'static str {
1456 $base.json_type()
1457 }
1458 fn json_extract(&self, column: &str, path: &str) -> String {
1459 $base.json_extract(column, path)
1460 }
1461 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1462 $base.full_text_search(columns, keyword)
1463 }
1464 fn bool_to_int(&self, expr: &str) -> String {
1465 $base.bool_to_int(expr)
1466 }
1467 fn concat(&self, parts: &[&str]) -> String {
1468 $base.concat(parts)
1469 }
1470 fn supports_if_exists(&self) -> bool {
1471 $base.supports_if_exists()
1472 }
1473 fn supports_if_not_exists(&self) -> bool {
1474 $base.supports_if_not_exists()
1475 }
1476 fn auto_increment_keyword(&self) -> &'static str {
1477 $base.auto_increment_keyword()
1478 }
1479 fn last_insert_id_sql(&self) -> Option<&'static str> {
1480 $base.last_insert_id_sql()
1481 }
1482 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1483 $base.build_create_table(table, columns)
1484 }
1485 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1486 $base.build_alter_table(table, changes)
1487 }
1488 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1489 $base.build_drop_table(table, if_exists)
1490 }
1491 }
1492 };
1493}
1494
1495delegate_dialect_to!(MariaDbDialect, MySqlDialect, DbType::MariaDB);
1497
1498delegate_dialect_to!(TiDbDialect, MySqlDialect, DbType::TiDB);
1500
1501delegate_dialect_to!(KingbaseDialect, PostgreSqlDialect, DbType::Kingbase);
1503
1504delegate_dialect_to!(PolarDbDialect, PostgreSqlDialect, DbType::PolarDB);
1506
1507delegate_dialect_to!(GaussDbDialect, PostgreSqlDialect, DbType::GaussDB);
1509
1510delegate_dialect_to!(DamengDialect, OracleDialect, DbType::Dameng);
1512
1513delegate_dialect_to!(SybaseDialect, SqlServerDialect, DbType::Sybase);
1515
1516delegate_dialect_to!(GBaseDialect, SqlServerDialect, DbType::GBase);
1518
1519#[cfg(feature = "dialect-cockroachdb")]
1530delegate_dialect_to!(CockroachDbDialect, PostgreSqlDialect, DbType::CockroachDB);
1531
1532#[cfg(feature = "dialect-yugabytedb")]
1543delegate_dialect_to!(YugabyteDbDialect, PostgreSqlDialect, DbType::YugabyteDB);
1544
1545#[derive(Debug, Clone)]
1559pub struct ClickHouseDialect;
1560
1561impl Dialect for ClickHouseDialect {
1562 fn clone_box(&self) -> Box<dyn Dialect> {
1563 Box::new(ClickHouseDialect)
1564 }
1565
1566 fn db_type(&self) -> DbType {
1567 DbType::ClickHouse
1568 }
1569
1570 fn quote(&self, identifier: &str) -> String {
1571 format!("`{}`", identifier.replace('`', "``"))
1573 }
1574
1575 fn escape_string(&self, s: &str) -> String {
1576 let mut escaped = String::with_capacity(s.len() * 2);
1578 for c in s.chars() {
1579 match c {
1580 '\'' => escaped.push_str("\\'"),
1581 '\\' => escaped.push_str("\\\\"),
1582 '\n' => escaped.push_str("\\n"),
1583 '\r' => escaped.push_str("\\r"),
1584 '\t' => escaped.push_str("\\t"),
1585 _ => escaped.push(c),
1586 }
1587 }
1588 escaped
1589 }
1590
1591 fn supports_returning(&self) -> bool {
1592 false
1594 }
1595
1596 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1597 let offset = page.saturating_sub(1).saturating_mul(limit);
1599 format!("{} LIMIT {}, {}", sql, offset, limit)
1600 }
1601
1602 fn json_type(&self) -> &'static str {
1603 "String"
1605 }
1606
1607 fn json_extract(&self, column: &str, path: &str) -> String {
1608 let normalized = if path.starts_with('$') {
1610 path.to_string()
1611 } else {
1612 format!("$.{}", path)
1613 };
1614 format!(
1615 "JSONExtractString({}, '{}')",
1616 column,
1617 self.escape_string(&normalized)
1618 )
1619 }
1620
1621 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1622 if columns.is_empty() {
1624 return "0".to_string();
1625 }
1626 let escaped = self.escape_string(keyword);
1627 let parts: Vec<String> = columns
1628 .iter()
1629 .map(|c| format!("position({}, '{}') > 0", c, escaped))
1630 .collect();
1631 parts.join(" OR ")
1632 }
1633
1634 fn bool_to_int(&self, expr: &str) -> String {
1635 format!("toUInt8({})", expr)
1637 }
1638
1639 fn concat(&self, parts: &[&str]) -> String {
1640 if parts.is_empty() {
1642 return "''".to_string();
1643 }
1644 format!("concat({})", parts.join(", "))
1645 }
1646
1647 fn supports_if_exists(&self) -> bool {
1648 true
1649 }
1650
1651 fn supports_if_not_exists(&self) -> bool {
1652 true
1653 }
1654
1655 fn auto_increment_keyword(&self) -> &'static str {
1656 ""
1658 }
1659
1660 fn last_insert_id_sql(&self) -> Option<&'static str> {
1661 None
1663 }
1664
1665 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1666 let cols: Vec<String> = columns
1667 .iter()
1668 .map(|col| {
1669 let ch_type = map_to_clickhouse_type(&col.sql_type);
1670 let mut sql = format!("{} {}", self.quote(&col.name), ch_type);
1671 if let Some(default) = &col.default {
1672 sql.push_str(&format!(" DEFAULT {}", default));
1673 }
1674 if col.primary_key {
1675 sql.push_str(" PRIMARY KEY");
1676 }
1677 sql
1678 })
1679 .collect();
1680
1681 format!(
1683 "CREATE TABLE {} ({}) ENGINE = MergeTree()",
1684 self.quote(table),
1685 cols.join(", ")
1686 )
1687 }
1688
1689 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1690 let stmts: Vec<String> = changes
1691 .iter()
1692 .map(|change| match change {
1693 TableChange::AddColumn(col) => {
1694 let ch_type = map_to_clickhouse_type(&col.sql_type);
1695 format!(
1696 "ALTER TABLE {} ADD COLUMN {} {}",
1697 self.quote(table),
1698 self.quote(&col.name),
1699 ch_type
1700 )
1701 }
1702 TableChange::DropColumn(name) => {
1703 format!(
1704 "ALTER TABLE {} DROP COLUMN {}",
1705 self.quote(table),
1706 self.quote(name)
1707 )
1708 }
1709 TableChange::ModifyColumn(col) => {
1710 let ch_type = map_to_clickhouse_type(&col.sql_type);
1711 format!(
1712 "ALTER TABLE {} MODIFY COLUMN {} {}",
1713 self.quote(table),
1714 self.quote(&col.name),
1715 ch_type
1716 )
1717 }
1718 TableChange::AddIndex(name, cols) => {
1719 format!(
1720 "ALTER TABLE {} ADD INDEX {} ({})",
1721 self.quote(table),
1722 name,
1723 cols.join(", ")
1724 )
1725 }
1726 TableChange::DropIndex(name) => {
1727 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
1728 }
1729 TableChange::AddForeignKey { .. } => {
1730 String::new()
1732 }
1733 })
1734 .filter(|s| !s.is_empty())
1735 .collect();
1736
1737 stmts.join("; ")
1738 }
1739
1740 fn supports_lock_for_update(&self) -> bool {
1741 false
1743 }
1744
1745 fn supports_lock_shared(&self) -> bool {
1746 false
1748 }
1749
1750 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
1751 format!("INSERT INTO {}", self.quote(table))
1753 }
1754}
1755
1756fn map_to_clickhouse_type(sql_type: &str) -> String {
1766 let upper = sql_type.to_uppercase();
1767 let trimmed = upper.trim();
1768
1769 if trimmed.starts_with("BIGINT") {
1770 "Int64".to_string()
1771 } else if matches!(trimmed, "INT" | "INTEGER") {
1772 "Int32".to_string()
1773 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
1774 "Int16".to_string()
1775 } else if trimmed.starts_with("VARCHAR")
1776 || trimmed.starts_with("CHAR")
1777 || matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT")
1778 {
1779 "String".to_string()
1780 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1781 "UInt8".to_string()
1782 } else if matches!(trimmed, "FLOAT" | "REAL") {
1783 "Float32".to_string()
1784 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
1785 "Float64".to_string()
1786 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
1787 "DateTime".to_string()
1788 } else if matches!(trimmed, "DATE") {
1789 "Date".to_string()
1790 } else if trimmed.starts_with("DECIMAL") || trimmed.starts_with("NUMERIC") {
1791 "Decimal(38, 4)".to_string()
1792 } else {
1793 sql_type.to_string()
1794 }
1795}
1796
1797#[derive(Debug, Clone)]
1812pub struct DuckDBDialect;
1813
1814impl Dialect for DuckDBDialect {
1815 fn clone_box(&self) -> Box<dyn Dialect> {
1816 Box::new(DuckDBDialect)
1817 }
1818
1819 fn db_type(&self) -> DbType {
1820 DbType::DuckDB
1821 }
1822
1823 fn quote(&self, identifier: &str) -> String {
1824 format!("\"{}\"", identifier.replace('"', "\"\""))
1826 }
1827
1828 fn escape_string(&self, s: &str) -> String {
1829 let mut escaped = String::with_capacity(s.len() * 2);
1831 for c in s.chars() {
1832 match c {
1833 '\'' => escaped.push_str("''"),
1834 _ => escaped.push(c),
1835 }
1836 }
1837 escaped
1838 }
1839
1840 fn supports_returning(&self) -> bool {
1841 false
1843 }
1844
1845 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1846 let offset = page.saturating_sub(1).saturating_mul(limit);
1848 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
1849 }
1850
1851 fn json_type(&self) -> &'static str {
1852 "JSON"
1854 }
1855
1856 fn json_extract(&self, column: &str, path: &str) -> String {
1857 let normalized = if path.starts_with('$') {
1859 path[2..].to_string()
1860 } else {
1861 path.to_string()
1862 };
1863 format!("{} -> '{}'", column, normalized)
1864 }
1865
1866 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1867 if columns.is_empty() {
1869 return "0".to_string();
1870 }
1871 let escaped = self.escape_string(keyword);
1872 let parts: Vec<String> = columns
1873 .iter()
1874 .map(|c| format!("{} LIKE '%{}%'", c, escaped))
1875 .collect();
1876 parts.join(" OR ")
1877 }
1878
1879 fn bool_to_int(&self, expr: &str) -> String {
1880 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1882 }
1883
1884 fn concat(&self, parts: &[&str]) -> String {
1885 if parts.is_empty() {
1887 return "''".to_string();
1888 }
1889 parts.join(" || ")
1890 }
1891
1892 fn supports_if_exists(&self) -> bool {
1893 true
1894 }
1895
1896 fn supports_if_not_exists(&self) -> bool {
1897 true
1898 }
1899
1900 fn auto_increment_keyword(&self) -> &'static str {
1901 ""
1903 }
1904
1905 fn last_insert_id_sql(&self) -> Option<&'static str> {
1906 None
1908 }
1909
1910 fn supports_lock_for_update(&self) -> bool {
1911 false
1913 }
1914
1915 fn supports_lock_shared(&self) -> bool {
1916 false
1918 }
1919
1920 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
1921 format!("INSERT OR IGNORE INTO {}", self.quote(table))
1923 }
1924
1925 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1926 let cols: Vec<String> = columns
1927 .iter()
1928 .map(|col| {
1929 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
1930 if col.auto_increment {
1931 sql = format!("{} BIGINT PRIMARY KEY", self.quote(&col.name));
1933 }
1934 if let Some(default) = &col.default {
1935 sql.push_str(&format!(" DEFAULT {}", default));
1936 }
1937 if col.primary_key && !col.auto_increment {
1938 sql.push_str(" PRIMARY KEY");
1939 }
1940 sql
1941 })
1942 .collect();
1943
1944 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1945 }
1946
1947 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1948 let stmts: Vec<String> = changes
1949 .iter()
1950 .map(|change| match change {
1951 TableChange::AddColumn(col) => {
1952 let mut sql = format!(
1953 "ALTER TABLE {} ADD COLUMN {} {}",
1954 self.quote(table),
1955 self.quote(&col.name),
1956 col.sql_type
1957 );
1958 if let Some(default) = &col.default {
1959 sql.push_str(&format!(" DEFAULT {}", default));
1960 }
1961 sql
1962 }
1963 TableChange::DropColumn(name) => {
1964 format!(
1965 "ALTER TABLE {} DROP COLUMN {}",
1966 self.quote(table),
1967 self.quote(name)
1968 )
1969 }
1970 TableChange::ModifyColumn(col) => {
1971 format!(
1972 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
1973 self.quote(table),
1974 self.quote(&col.name),
1975 col.sql_type
1976 )
1977 }
1978 TableChange::AddIndex(name, _cols) => {
1979 format!(
1981 "CREATE INDEX {} ON {} (id)",
1982 self.quote(name),
1983 self.quote(table)
1984 )
1985 }
1986 TableChange::DropIndex(name) => {
1987 format!("DROP INDEX {}", self.quote(name))
1988 }
1989 TableChange::AddForeignKey { .. } => {
1990 String::new()
1992 }
1993 })
1994 .filter(|s| !s.is_empty())
1995 .collect();
1996
1997 stmts.join("; ")
1998 }
1999}
2000
2001#[derive(Debug, Clone)]
2015pub struct Db2Dialect;
2016
2017impl Dialect for Db2Dialect {
2018 fn clone_box(&self) -> Box<dyn Dialect> {
2019 Box::new(Db2Dialect)
2020 }
2021
2022 fn db_type(&self) -> DbType {
2023 DbType::Db2
2024 }
2025
2026 fn quote(&self, identifier: &str) -> String {
2027 format!("\"{}\"", identifier.replace('"', "\"\""))
2029 }
2030
2031 fn escape_string(&self, s: &str) -> String {
2032 let mut escaped = String::with_capacity(s.len() * 2);
2034 for c in s.chars() {
2035 match c {
2036 '\'' => escaped.push_str("''"),
2037 _ => escaped.push(c),
2038 }
2039 }
2040 escaped
2041 }
2042
2043 fn supports_returning(&self) -> bool {
2044 false
2046 }
2047
2048 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2049 let offset = page.saturating_sub(1).saturating_mul(limit);
2051 format!(
2052 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
2053 sql, offset, limit
2054 )
2055 }
2056
2057 fn json_type(&self) -> &'static str {
2058 "JSON"
2060 }
2061
2062 fn json_extract(&self, column: &str, path: &str) -> String {
2063 let normalized = if path.starts_with('$') {
2065 path.to_string()
2066 } else {
2067 format!("$.{}", path)
2068 };
2069 format!(
2070 "JSON_VALUE({}, '{}')",
2071 column,
2072 self.escape_string(&normalized)
2073 )
2074 }
2075
2076 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2077 if columns.is_empty() {
2079 return "0".to_string();
2080 }
2081 let escaped = self.escape_string(keyword);
2082 let parts: Vec<String> = columns
2083 .iter()
2084 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2085 .collect();
2086 parts.join(" OR ")
2087 }
2088
2089 fn bool_to_int(&self, expr: &str) -> String {
2090 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2092 }
2093
2094 fn concat(&self, parts: &[&str]) -> String {
2095 if parts.is_empty() {
2097 return "''".to_string();
2098 }
2099 parts.join(" || ")
2100 }
2101
2102 fn supports_if_exists(&self) -> bool {
2103 false
2105 }
2106
2107 fn supports_if_not_exists(&self) -> bool {
2108 false
2110 }
2111
2112 fn auto_increment_keyword(&self) -> &'static str {
2113 "GENERATED ALWAYS AS IDENTITY"
2115 }
2116
2117 fn last_insert_id_sql(&self) -> Option<&'static str> {
2118 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
2120 }
2121
2122 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2123 let cols: Vec<String> = columns
2124 .iter()
2125 .map(|col| {
2126 let db2_type = map_to_db2_type(&col.sql_type);
2127 let mut sql = format!("{} {}", self.quote(&col.name), db2_type);
2128 if !col.nullable && !col.auto_increment {
2129 sql.push_str(" NOT NULL");
2130 }
2131 if let Some(default) = &col.default {
2132 sql.push_str(&format!(" DEFAULT {}", default));
2133 }
2134 if col.auto_increment {
2135 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2136 }
2137 if col.primary_key {
2138 sql.push_str(" PRIMARY KEY");
2139 }
2140 sql
2141 })
2142 .collect();
2143
2144 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2145 }
2146
2147 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2148 let stmts: Vec<String> = changes
2149 .iter()
2150 .map(|change| match change {
2151 TableChange::AddColumn(col) => {
2152 let db2_type = map_to_db2_type(&col.sql_type);
2153 let mut sql = format!(
2154 "ALTER TABLE {} ADD COLUMN {} {}",
2155 self.quote(table),
2156 self.quote(&col.name),
2157 db2_type
2158 );
2159 if !col.nullable {
2160 sql.push_str(" NOT NULL");
2161 }
2162 if let Some(default) = &col.default {
2163 sql.push_str(&format!(" DEFAULT {}", default));
2164 }
2165 sql
2166 }
2167 TableChange::DropColumn(name) => {
2168 format!(
2169 "ALTER TABLE {} DROP COLUMN {}",
2170 self.quote(table),
2171 self.quote(name)
2172 )
2173 }
2174 TableChange::ModifyColumn(col) => {
2175 let db2_type = map_to_db2_type(&col.sql_type);
2176 format!(
2177 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
2178 self.quote(table),
2179 self.quote(&col.name),
2180 db2_type
2181 )
2182 }
2183 TableChange::AddIndex(name, cols) => {
2184 format!(
2185 "CREATE INDEX {} ON {} ({})",
2186 name,
2187 self.quote(table),
2188 cols.join(", ")
2189 )
2190 }
2191 TableChange::DropIndex(name) => {
2192 format!("DROP INDEX {}", name)
2193 }
2194 TableChange::AddForeignKey {
2195 columns,
2196 reference_table,
2197 reference_columns,
2198 } => {
2199 format!(
2200 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
2201 self.quote(table),
2202 table,
2203 columns.join("_"),
2204 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
2205 self.quote(reference_table),
2206 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
2207 )
2208 }
2209 })
2210 .collect();
2211
2212 stmts.join("; ")
2213 }
2214
2215 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
2216 let _ = if_exists;
2218 format!("DROP TABLE {}", self.quote(table))
2219 }
2220}
2221
2222fn map_to_db2_type(sql_type: &str) -> String {
2231 let upper = sql_type.to_uppercase();
2232 let trimmed = upper.trim();
2233
2234 if trimmed.starts_with("BIGINT") {
2235 "BIGINT".to_string()
2236 } else if matches!(trimmed, "INT" | "INTEGER") {
2237 "INTEGER".to_string()
2238 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
2239 "SMALLINT".to_string()
2240 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2241 sql_type.to_string()
2242 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
2243 "CLOB(2G)".to_string()
2244 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2245 "SMALLINT".to_string()
2246 } else if matches!(trimmed, "FLOAT" | "REAL") {
2247 "REAL".to_string()
2248 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2249 "DOUBLE".to_string()
2250 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2251 "TIMESTAMP".to_string()
2252 } else if matches!(trimmed, "DATE") {
2253 "DATE".to_string()
2254 } else {
2255 sql_type.to_string()
2257 }
2258}
2259
2260pub fn get_dialect(db_type: DbType) -> Result<Box<dyn Dialect>, DbError> {
2283 match db_type {
2284 DbType::MySQL => Ok(Box::new(MySqlDialect)),
2285 DbType::PostgreSQL => Ok(Box::new(PostgreSqlDialect)),
2286 DbType::Sqlite => Ok(Box::new(SqliteDialect)),
2287 DbType::Redis => Err(DbError::Unsupported(
2288 "Redis does not support standard SQL dialect".to_string(),
2289 )),
2290 DbType::MongoDB => Err(DbError::Unsupported(
2291 "MongoDB uses different query syntax".to_string(),
2292 )),
2293 DbType::ClickHouse => Ok(Box::new(ClickHouseDialect)),
2294 DbType::Oracle => Ok(Box::new(OracleDialect)),
2295 DbType::OceanBase => Ok(Box::new(MySqlDialect)),
2296 DbType::SqlServer => Ok(Box::new(SqlServerDialect)),
2297 DbType::VectorDb => Err(DbError::Unsupported(
2298 "Vector databases have specific APIs".to_string(),
2299 )),
2300 DbType::PureJsDb => Err(DbError::Unsupported(
2301 "PureJS database uses JavaScript".to_string(),
2302 )),
2303 DbType::Dameng => Ok(Box::new(DamengDialect)),
2305 DbType::Kingbase => Ok(Box::new(KingbaseDialect)),
2306 DbType::Db2 => Ok(Box::new(Db2Dialect)),
2307 DbType::MariaDB => Ok(Box::new(MariaDbDialect)),
2308 DbType::TiDB => Ok(Box::new(TiDbDialect)),
2309 DbType::PolarDB => Ok(Box::new(PolarDbDialect)),
2310 DbType::GaussDB => Ok(Box::new(GaussDbDialect)),
2311 DbType::GBase => Ok(Box::new(GBaseDialect)),
2312 DbType::Sybase => Ok(Box::new(SybaseDialect)),
2313 DbType::DuckDB => Ok(Box::new(DuckDBDialect)),
2314 #[cfg(feature = "dialect-cockroachdb")]
2315 DbType::CockroachDB => Ok(Box::new(CockroachDbDialect)),
2316 #[cfg(feature = "dialect-yugabytedb")]
2317 DbType::YugabyteDB => Ok(Box::new(YugabyteDbDialect)),
2318 }
2319}
2320
2321impl fmt::Display for dyn Dialect {
2322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2323 write!(f, "Dialect({})", self.db_type())
2324 }
2325}
2326
2327#[cfg(test)]
2328mod tests {
2329 use super::*;
2330
2331 #[test]
2332 fn test_mysql_quote() {
2333 let dialect = MySqlDialect;
2334 assert_eq!(dialect.quote("users"), "`users`");
2335 assert_eq!(dialect.quote("user`id"), "`user``id`");
2336 }
2337
2338 #[test]
2339 fn test_mysql_escape() {
2340 let dialect = MySqlDialect;
2341 assert_eq!(dialect.escape_string("hello"), "hello");
2342 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2343 assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
2344 }
2345
2346 #[test]
2347 fn test_mysql_pagination() {
2348 let dialect = MySqlDialect;
2349 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
2350 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
2351 }
2352
2353 #[test]
2354 fn test_postgres_quote() {
2355 let dialect = PostgreSqlDialect;
2356 assert_eq!(dialect.quote("users"), "\"users\"");
2357 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2358 }
2359
2360 #[test]
2361 fn test_postgres_pagination() {
2362 let dialect = PostgreSqlDialect;
2363 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2364 assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
2365 }
2366
2367 #[test]
2368 fn test_postgres_returning() {
2369 let dialect = PostgreSqlDialect;
2370 assert!(dialect.supports_returning());
2371 }
2372
2373 #[test]
2374 fn test_sqlite_quote() {
2375 let dialect = SqliteDialect;
2376 assert_eq!(dialect.quote("users"), "\"users\"");
2377 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2378 }
2379
2380 #[test]
2381 fn test_sqlite_escape() {
2382 let dialect = SqliteDialect;
2383 assert_eq!(dialect.escape_string("hello"), "hello");
2384 assert_eq!(dialect.escape_string("it's"), "it''s");
2385 }
2386
2387 #[test]
2388 fn test_get_dialect() {
2389 let dialect = get_dialect(DbType::MySQL);
2390 assert!(dialect.is_ok());
2391
2392 let dialect = get_dialect(DbType::Redis);
2393 assert!(dialect.is_err());
2394 }
2395
2396 #[test]
2397 fn test_bool_to_int() {
2398 let mysql = MySqlDialect;
2399 assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
2400
2401 let pg = PostgreSqlDialect;
2402 assert_eq!(
2403 pg.bool_to_int("active"),
2404 "(CASE WHEN active THEN 1 ELSE 0 END)"
2405 );
2406 }
2407
2408 #[test]
2409 fn test_json_extract_with_path() {
2410 let mysql = MySqlDialect;
2411 let sql = mysql.json_extract("data", "$.user.name");
2412 assert!(sql.contains("$.user.name"));
2413 assert!(sql.contains("JSON_EXTRACT"));
2414
2415 let pg = PostgreSqlDialect;
2416 let sql = pg.json_extract("data", "user.name");
2417 assert!(sql.contains("#>>"));
2418
2419 let sqlite = SqliteDialect;
2420 let sql = sqlite.json_extract("data", "$.user.name");
2421 assert!(sql.contains("$.user.name"));
2422 assert!(sql.contains("json_extract"));
2423 }
2424
2425 #[test]
2426 fn test_sqlite_full_text_search() {
2427 let sqlite = SqliteDialect;
2428 let sql = sqlite.full_text_search(&["title", "content"], "hello");
2429 assert!(sql.contains("LIKE"));
2431 assert!(sql.contains("title LIKE '%hello%'"));
2432 assert!(sql.contains("content LIKE '%hello%'"));
2433 assert!(sql.contains(" OR "));
2434
2435 assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
2437
2438 let sql = sqlite.full_text_search(&["title"], "it's");
2440 assert!(sql.contains("title LIKE '%it''s%'"));
2441 }
2442
2443 #[test]
2444 fn test_alter_table_modify_column() {
2445 let mysql = MySqlDialect;
2446 let col = ColumnDef {
2447 name: "name".to_string(),
2448 sql_type: "VARCHAR(255)".to_string(),
2449 nullable: false,
2450 default: None,
2451 auto_increment: false,
2452 primary_key: false,
2453 };
2454 let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2455 assert!(sql.contains("MODIFY COLUMN"));
2456
2457 let pg = PostgreSqlDialect;
2458 let col = ColumnDef {
2459 name: "name".to_string(),
2460 sql_type: "VARCHAR(255)".to_string(),
2461 nullable: false,
2462 default: None,
2463 auto_increment: false,
2464 primary_key: false,
2465 };
2466 let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2467 assert!(sql.contains("ALTER COLUMN"));
2468 assert!(sql.contains("TYPE"));
2469 }
2470
2471 #[test]
2472 fn test_alter_table_add_foreign_key() {
2473 let mysql = MySqlDialect;
2474 let sql = mysql.build_alter_table(
2475 "orders",
2476 &[TableChange::AddForeignKey {
2477 columns: vec!["user_id".to_string()],
2478 reference_table: "users".to_string(),
2479 reference_columns: vec!["id".to_string()],
2480 }],
2481 );
2482 assert!(sql.contains("FOREIGN KEY"));
2483 assert!(sql.contains("REFERENCES"));
2484
2485 let sqlite = SqliteDialect;
2486 let sql = sqlite.build_alter_table(
2487 "orders",
2488 &[TableChange::AddForeignKey {
2489 columns: vec!["user_id".to_string()],
2490 reference_table: "users".to_string(),
2491 reference_columns: vec!["id".to_string()],
2492 }],
2493 );
2494 assert!(sql.starts_with("--"));
2496 }
2497
2498 #[test]
2499 fn test_sqlite_alter_table_add_column() {
2500 let sqlite = SqliteDialect;
2501 let col = ColumnDef {
2502 name: "email".to_string(),
2503 sql_type: "TEXT".to_string(),
2504 nullable: true,
2505 default: None,
2506 auto_increment: false,
2507 primary_key: false,
2508 };
2509 let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
2510 assert!(sql.contains("ADD COLUMN"));
2511 assert!(sql.contains("email"));
2512 }
2513
2514 #[test]
2517 fn test_oracle_quote_and_escape() {
2518 let dialect = OracleDialect;
2519 assert_eq!(dialect.quote("users"), "\"users\"");
2521 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2522 assert_eq!(dialect.quote("column_name"), "\"column_name\"");
2523
2524 assert_eq!(dialect.escape_string("hello"), "hello");
2526 assert_eq!(dialect.escape_string("it's"), "it''s");
2527 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2528 assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
2529 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2531 }
2532
2533 #[test]
2534 fn test_oracle_pagination() {
2535 let dialect = OracleDialect;
2536 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2538 assert_eq!(
2539 sql,
2540 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2541 );
2542 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2544 assert_eq!(
2545 sql,
2546 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2547 );
2548 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2550 assert_eq!(
2551 sql,
2552 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2553 );
2554 }
2555
2556 #[test]
2557 fn test_oracle_json_extract() {
2558 let dialect = OracleDialect;
2559 let sql = dialect.json_extract("data", "$.user.name");
2561 assert!(sql.contains("JSON_VALUE"));
2562 assert!(sql.contains("$.user.name"));
2563 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2564
2565 let sql = dialect.json_extract("data", "user.name");
2567 assert!(sql.contains("$.user.name"));
2568 assert!(sql.contains("JSON_VALUE"));
2569
2570 let sql = dialect.json_extract("data", "$.key's");
2572 assert!(sql.contains("$.key''s"));
2573 }
2574
2575 #[test]
2576 fn test_oracle_create_table() {
2577 let dialect = OracleDialect;
2578 let columns = vec![
2579 ColumnDef {
2580 name: "id".to_string(),
2581 sql_type: "BIGINT".to_string(),
2582 nullable: false,
2583 default: None,
2584 auto_increment: true,
2585 primary_key: true,
2586 },
2587 ColumnDef {
2588 name: "name".to_string(),
2589 sql_type: "VARCHAR(255)".to_string(),
2590 nullable: false,
2591 default: None,
2592 auto_increment: false,
2593 primary_key: false,
2594 },
2595 ColumnDef {
2596 name: "bio".to_string(),
2597 sql_type: "TEXT".to_string(),
2598 nullable: true,
2599 default: None,
2600 auto_increment: false,
2601 primary_key: false,
2602 },
2603 ColumnDef {
2604 name: "is_active".to_string(),
2605 sql_type: "BOOLEAN".to_string(),
2606 nullable: false,
2607 default: Some("1".to_string()),
2608 auto_increment: false,
2609 primary_key: false,
2610 },
2611 ];
2612 let sql = dialect.build_create_table("users", &columns);
2613 assert!(
2615 sql.contains("NUMBER(19)"),
2616 "BIGINT should map to NUMBER(19): {}",
2617 sql
2618 );
2619 assert!(
2620 sql.contains("VARCHAR2(255)"),
2621 "VARCHAR should map to VARCHAR2: {}",
2622 sql
2623 );
2624 assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
2625 assert!(
2626 sql.contains("NUMBER(1)"),
2627 "BOOLEAN should map to NUMBER(1): {}",
2628 sql
2629 );
2630 assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
2632 assert!(sql.contains("PRIMARY KEY"));
2633 assert!(sql.contains("NOT NULL"));
2634 assert!(sql.contains("DEFAULT 1"));
2635 assert!(sql.contains("\"users\""));
2637 assert!(sql.contains("\"id\""));
2638 }
2639
2640 #[test]
2641 fn test_oracle_bool_to_int_and_concat() {
2642 let dialect = OracleDialect;
2643 assert_eq!(
2645 dialect.bool_to_int("active"),
2646 "(CASE WHEN active THEN 1 ELSE 0 END)"
2647 );
2648 assert_eq!(
2649 dialect.bool_to_int("x > 0"),
2650 "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
2651 );
2652 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
2654 assert_eq!(
2655 dialect.concat(&["first_name", "last_name"]),
2656 "first_name || last_name"
2657 );
2658 assert_eq!(dialect.concat(&[]), "NULL");
2660 }
2661
2662 #[test]
2663 fn test_oracle_misc_dialect_methods() {
2664 let dialect = OracleDialect;
2665 assert_eq!(dialect.db_type(), DbType::Oracle);
2667 assert!(dialect.supports_returning());
2669 assert!(dialect.supports_if_exists());
2671 assert!(dialect.supports_if_not_exists());
2672 assert_eq!(
2674 dialect.auto_increment_keyword(),
2675 "GENERATED BY DEFAULT AS IDENTITY"
2676 );
2677 assert_eq!(dialect.last_insert_id_sql(), None);
2680 assert_eq!(dialect.json_type(), "JSON");
2682 }
2683
2684 #[test]
2685 fn test_oracle_get_dialect() {
2686 let dialect = get_dialect(DbType::Oracle);
2688 assert!(dialect.is_ok(), "Oracle dialect should be available");
2689 let dialect = dialect.unwrap();
2690 assert_eq!(dialect.db_type(), DbType::Oracle);
2691 assert_eq!(dialect.quote("users"), "\"users\"");
2693 assert!(dialect.supports_returning());
2695 assert_eq!(dialect.last_insert_id_sql(), None);
2697 }
2698
2699 #[test]
2700 fn test_oracle_drop_table() {
2701 let dialect = OracleDialect;
2702 let sql = dialect.build_drop_table("users", true);
2704 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
2705 let sql = dialect.build_drop_table("users", false);
2707 assert_eq!(sql, "DROP TABLE \"users\"");
2708 }
2709
2710 #[test]
2711 fn test_oracle_alter_table() {
2712 let dialect = OracleDialect;
2713 let col = ColumnDef {
2715 name: "name".to_string(),
2716 sql_type: "VARCHAR(255)".to_string(),
2717 nullable: false,
2718 default: None,
2719 auto_increment: false,
2720 primary_key: false,
2721 };
2722 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2723 assert!(sql.contains("MODIFY"));
2724 assert!(sql.contains("VARCHAR2(255)"));
2725 assert!(!sql.contains("MODIFY COLUMN")); let col = ColumnDef {
2729 name: "email".to_string(),
2730 sql_type: "VARCHAR(255)".to_string(),
2731 nullable: true,
2732 default: None,
2733 auto_increment: false,
2734 primary_key: false,
2735 };
2736 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2737 assert!(sql.contains("ADD \"email\""));
2738 assert!(sql.contains("VARCHAR2(255)"));
2739
2740 let sql =
2742 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2743 assert!(sql.contains("DROP COLUMN"));
2744 assert!(sql.contains("\"email\""));
2745 }
2746
2747 #[test]
2750 fn test_sqlite_concat_handles_null() {
2751 let sqlite = SqliteDialect;
2752 let sql = sqlite.concat(&["a", "b"]);
2755 assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
2756 let sql = sqlite.concat(&["a"]);
2758 assert_eq!(sql, "COALESCE(a, '')");
2759 assert_eq!(sqlite.concat(&[]), "NULL");
2761 }
2762
2763 #[test]
2766 fn test_sqlserver_quote_and_escape() {
2767 let dialect = SqlServerDialect;
2768 assert_eq!(dialect.quote("users"), "[users]");
2770 assert_eq!(dialect.quote("col]name"), "[col]]name]");
2771 assert_eq!(dialect.escape_string("hello"), "hello");
2773 assert_eq!(dialect.escape_string("it's"), "it''s");
2774 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2775 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2776 }
2777
2778 #[test]
2779 fn test_sqlserver_pagination() {
2780 let dialect = SqlServerDialect;
2781 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2783 assert_eq!(
2784 sql,
2785 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2786 );
2787 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2788 assert_eq!(
2789 sql,
2790 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2791 );
2792 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2794 assert_eq!(
2795 sql,
2796 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2797 );
2798 }
2799
2800 #[test]
2801 fn test_sqlserver_misc_dialect_methods() {
2802 let dialect = SqlServerDialect;
2803 assert_eq!(dialect.db_type(), DbType::SqlServer);
2804 assert!(dialect.supports_returning());
2806 assert!(dialect.supports_if_exists());
2808 assert!(dialect.supports_if_not_exists());
2809 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2811 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2813 assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
2815 }
2816
2817 #[test]
2818 fn test_sqlserver_insert_or_ignore_fallback_to_plain_insert() {
2819 let dialect = SqlServerDialect;
2820 let sql = dialect.build_insert_or_ignore_prefix("users");
2821 assert_eq!(sql, "INSERT INTO [users]");
2823 assert!(!sql.contains("OR IGNORE"));
2824 }
2825
2826 #[test]
2827 fn test_sqlserver_json_extract() {
2828 let dialect = SqlServerDialect;
2829 let sql = dialect.json_extract("data", "$.user.name");
2830 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2831 let sql = dialect.json_extract("data", "user.name");
2833 assert!(sql.contains("$.user.name"));
2834 assert!(sql.contains("JSON_VALUE"));
2835 let sql = dialect.json_extract("data", "$.key's");
2837 assert!(sql.contains("$.key''s"));
2838 }
2839
2840 #[test]
2841 fn test_sqlserver_full_text_search() {
2842 let dialect = SqlServerDialect;
2843 let sql = dialect.full_text_search(&["title", "content"], "hello");
2844 assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
2845 assert_eq!(dialect.full_text_search(&[], "hello"), "0");
2847 let sql = dialect.full_text_search(&["title"], "it's");
2849 assert!(sql.contains("it''s"));
2850 }
2851
2852 #[test]
2853 fn test_sqlserver_bool_to_int_and_concat() {
2854 let dialect = SqlServerDialect;
2855 assert_eq!(
2856 dialect.bool_to_int("active"),
2857 "(CASE WHEN active THEN 1 ELSE 0 END)"
2858 );
2859 assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
2860 assert_eq!(dialect.concat(&[]), "NULL");
2861 }
2862
2863 #[test]
2864 fn test_sqlserver_create_table() {
2865 let dialect = SqlServerDialect;
2866 let columns = vec![
2867 ColumnDef {
2868 name: "id".to_string(),
2869 sql_type: "BIGINT".to_string(),
2870 nullable: false,
2871 default: None,
2872 auto_increment: true,
2873 primary_key: true,
2874 },
2875 ColumnDef {
2876 name: "name".to_string(),
2877 sql_type: "VARCHAR(255)".to_string(),
2878 nullable: false,
2879 default: None,
2880 auto_increment: false,
2881 primary_key: false,
2882 },
2883 ColumnDef {
2884 name: "bio".to_string(),
2885 sql_type: "TEXT".to_string(),
2886 nullable: true,
2887 default: None,
2888 auto_increment: false,
2889 primary_key: false,
2890 },
2891 ColumnDef {
2892 name: "is_active".to_string(),
2893 sql_type: "BOOLEAN".to_string(),
2894 nullable: false,
2895 default: Some("1".to_string()),
2896 auto_increment: false,
2897 primary_key: false,
2898 },
2899 ];
2900 let sql = dialect.build_create_table("users", &columns);
2901 assert!(sql.contains("[users]"));
2903 assert!(sql.contains("[id]"));
2904 assert!(sql.contains("IDENTITY(1,1)"));
2906 assert!(
2907 sql.contains("NVARCHAR(255)"),
2908 "VARCHAR should map to NVARCHAR: {}",
2909 sql
2910 );
2911 assert!(
2912 sql.contains("NVARCHAR(MAX)"),
2913 "TEXT should map to NVARCHAR(MAX): {}",
2914 sql
2915 );
2916 assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
2917 assert!(sql.contains("PRIMARY KEY"));
2918 assert!(sql.contains("NOT NULL"));
2919 assert!(sql.contains("DEFAULT 1"));
2920 }
2921
2922 #[test]
2923 fn test_sqlserver_drop_table() {
2924 let dialect = SqlServerDialect;
2925 assert_eq!(
2926 dialect.build_drop_table("users", true),
2927 "DROP TABLE IF EXISTS [users]"
2928 );
2929 assert_eq!(
2930 dialect.build_drop_table("users", false),
2931 "DROP TABLE [users]"
2932 );
2933 }
2934
2935 #[test]
2936 fn test_sqlserver_alter_table() {
2937 let dialect = SqlServerDialect;
2938 let col = ColumnDef {
2940 name: "name".to_string(),
2941 sql_type: "VARCHAR(255)".to_string(),
2942 nullable: false,
2943 default: None,
2944 auto_increment: false,
2945 primary_key: false,
2946 };
2947 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2948 assert!(sql.contains("ALTER COLUMN"));
2949 assert!(sql.contains("NVARCHAR(255)"));
2950 assert!(!sql.contains("MODIFY"));
2951
2952 let col = ColumnDef {
2954 name: "email".to_string(),
2955 sql_type: "VARCHAR(255)".to_string(),
2956 nullable: true,
2957 default: None,
2958 auto_increment: false,
2959 primary_key: false,
2960 };
2961 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2962 assert!(sql.contains("ADD [email]"));
2963 assert!(sql.contains("NVARCHAR(255)"));
2964
2965 let sql =
2967 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2968 assert!(sql.contains("DROP COLUMN"));
2969 assert!(sql.contains("[email]"));
2970
2971 let sql =
2973 dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
2974 assert!(sql.contains("DROP INDEX idx_name ON [users]"));
2975 }
2976
2977 #[test]
2978 fn test_sqlserver_get_dialect() {
2979 let dialect = get_dialect(DbType::SqlServer);
2981 assert!(dialect.is_ok(), "SqlServer dialect should be available");
2982 let dialect = dialect.unwrap();
2983 assert_eq!(dialect.db_type(), DbType::SqlServer);
2984 assert_eq!(dialect.quote("users"), "[users]");
2986 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2988 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2989 }
2990
2991 #[test]
2992 fn test_clickhouse_get_dialect_unsupported() {
2993 let dialect = get_dialect(DbType::ClickHouse);
2995 assert!(dialect.is_ok(), "ClickHouse should be supported");
2996 let dialect = dialect.unwrap();
2997 assert_eq!(dialect.db_type(), DbType::ClickHouse);
2998 assert_eq!(dialect.quote("users"), "`users`");
3000 assert!(!dialect.supports_returning());
3002 let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
3004 assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
3005 assert_eq!(dialect.auto_increment_keyword(), "");
3007 }
3008
3009 #[test]
3010 fn test_get_dialect_all_supported_types() {
3011 assert!(get_dialect(DbType::MySQL).is_ok());
3013 assert!(get_dialect(DbType::PostgreSQL).is_ok());
3014 assert!(get_dialect(DbType::Sqlite).is_ok());
3015 assert!(get_dialect(DbType::Oracle).is_ok());
3016 assert!(get_dialect(DbType::SqlServer).is_ok());
3017 assert!(get_dialect(DbType::OceanBase).is_ok());
3018 assert!(get_dialect(DbType::ClickHouse).is_ok());
3019 assert!(get_dialect(DbType::Dameng).is_ok());
3021 assert!(get_dialect(DbType::Kingbase).is_ok());
3022 assert!(get_dialect(DbType::Db2).is_ok());
3023 assert!(get_dialect(DbType::MariaDB).is_ok());
3024 assert!(get_dialect(DbType::TiDB).is_ok());
3025 assert!(get_dialect(DbType::PolarDB).is_ok());
3026 assert!(get_dialect(DbType::GaussDB).is_ok());
3027 assert!(get_dialect(DbType::GBase).is_ok());
3028 assert!(get_dialect(DbType::Sybase).is_ok());
3029 assert!(get_dialect(DbType::Redis).is_err());
3031 assert!(get_dialect(DbType::MongoDB).is_err());
3032 assert!(get_dialect(DbType::VectorDb).is_err());
3033 assert!(get_dialect(DbType::PureJsDb).is_err());
3034 }
3035
3036 #[test]
3039 fn test_mariadb_dialect() {
3040 let dialect = get_dialect(DbType::MariaDB).unwrap();
3041 assert_eq!(dialect.db_type(), DbType::MariaDB);
3042 assert_eq!(dialect.quote("users"), "`users`");
3044 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3045 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
3046 assert!(!dialect.supports_returning());
3048 }
3049
3050 #[test]
3051 fn test_tidb_dialect() {
3052 let dialect = get_dialect(DbType::TiDB).unwrap();
3053 assert_eq!(dialect.db_type(), DbType::TiDB);
3054 assert_eq!(dialect.quote("users"), "`users`");
3056 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3057 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
3058 }
3059
3060 #[test]
3061 fn test_dameng_dialect() {
3062 let dialect = get_dialect(DbType::Dameng).unwrap();
3063 assert_eq!(dialect.db_type(), DbType::Dameng);
3064 assert_eq!(dialect.quote("users"), "\"users\"");
3066 assert_eq!(dialect.escape_string("it's"), "it''s");
3067 assert_eq!(
3069 dialect.auto_increment_keyword(),
3070 "GENERATED BY DEFAULT AS IDENTITY"
3071 );
3072 assert!(dialect.supports_returning());
3074 }
3075
3076 #[test]
3077 fn test_kingbase_dialect() {
3078 let dialect = get_dialect(DbType::Kingbase).unwrap();
3079 assert_eq!(dialect.db_type(), DbType::Kingbase);
3080 assert_eq!(dialect.quote("users"), "\"users\"");
3082 assert_eq!(dialect.escape_string("it's"), "it''s");
3083 assert!(dialect.supports_returning());
3085 assert_eq!(
3087 dialect.auto_increment_keyword(),
3088 "GENERATED BY DEFAULT AS IDENTITY"
3089 );
3090 }
3091
3092 #[test]
3093 fn test_polardb_dialect() {
3094 let dialect = get_dialect(DbType::PolarDB).unwrap();
3095 assert_eq!(dialect.db_type(), DbType::PolarDB);
3096 assert_eq!(dialect.quote("users"), "\"users\"");
3098 assert!(dialect.supports_returning());
3099 }
3100
3101 #[test]
3102 fn test_gaussdb_dialect() {
3103 let dialect = get_dialect(DbType::GaussDB).unwrap();
3104 assert_eq!(dialect.db_type(), DbType::GaussDB);
3105 assert_eq!(dialect.quote("users"), "\"users\"");
3107 assert!(dialect.supports_returning());
3108 }
3109
3110 #[test]
3111 fn test_gbase_dialect() {
3112 let dialect = get_dialect(DbType::GBase).unwrap();
3113 assert_eq!(dialect.db_type(), DbType::GBase);
3114 assert_eq!(dialect.quote("users"), "[users]");
3116 }
3117
3118 #[test]
3119 fn test_sybase_dialect() {
3120 let dialect = get_dialect(DbType::Sybase).unwrap();
3121 assert_eq!(dialect.db_type(), DbType::Sybase);
3122 assert_eq!(dialect.quote("users"), "[users]");
3124 }
3125
3126 #[test]
3129 fn test_db2_dialect_basic() {
3130 let dialect = get_dialect(DbType::Db2).unwrap();
3131 assert_eq!(dialect.db_type(), DbType::Db2);
3132 assert_eq!(dialect.quote("users"), "\"users\"");
3134 assert_eq!(dialect.escape_string("it's"), "it''s");
3136 assert_eq!(
3138 dialect.auto_increment_keyword(),
3139 "GENERATED ALWAYS AS IDENTITY"
3140 );
3141 assert!(!dialect.supports_if_exists());
3143 assert!(!dialect.supports_if_not_exists());
3144 assert!(!dialect.supports_returning());
3146 }
3147
3148 #[test]
3149 fn test_db2_pagination() {
3150 let dialect = Db2Dialect;
3151 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3153 assert_eq!(
3154 sql,
3155 "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
3156 );
3157 }
3158
3159 #[test]
3160 fn test_db2_last_insert_id() {
3161 let dialect = Db2Dialect;
3162 assert_eq!(
3164 dialect.last_insert_id_sql(),
3165 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
3166 );
3167 }
3168
3169 #[test]
3170 fn test_db2_concat() {
3171 let dialect = Db2Dialect;
3172 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
3174 assert_eq!(dialect.concat(&[]), "''");
3175 }
3176
3177 #[test]
3178 fn test_db2_create_table() {
3179 let dialect = Db2Dialect;
3180 let cols = vec![ColumnDef {
3181 name: "id".to_string(),
3182 sql_type: "BIGINT".to_string(),
3183 nullable: false,
3184 default: None,
3185 auto_increment: true,
3186 primary_key: true,
3187 }];
3188 let sql = dialect.build_create_table("users", &cols);
3189 assert!(sql.contains("\"id\" BIGINT"));
3190 assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
3191 assert!(sql.contains("PRIMARY KEY"));
3192 }
3193
3194 #[test]
3195 fn test_db2_type_mapping() {
3196 assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
3198 assert_eq!(map_to_db2_type("INT"), "INTEGER");
3199 assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
3200 assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
3201 assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
3202 assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
3203 assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
3204 assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
3205 assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
3206 assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
3207 assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
3208 assert_eq!(map_to_db2_type("DATE"), "DATE");
3209 assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
3210 }
3211
3212 #[test]
3215 fn test_clickhouse_dialect_basic() {
3216 let dialect = get_dialect(DbType::ClickHouse).unwrap();
3217 assert_eq!(dialect.db_type(), DbType::ClickHouse);
3218 assert_eq!(dialect.quote("users"), "`users`");
3220 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3222 assert!(!dialect.supports_returning());
3224 assert_eq!(dialect.auto_increment_keyword(), "");
3226 assert!(dialect.supports_if_exists());
3228 assert!(dialect.supports_if_not_exists());
3229 }
3230
3231 #[test]
3232 fn test_clickhouse_type_mapping() {
3233 assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
3234 assert_eq!(map_to_clickhouse_type("INT"), "Int32");
3235 assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
3236 assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
3237 assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
3238 assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
3239 assert_eq!(map_to_clickhouse_type("TEXT"), "String");
3240 assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
3241 assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
3242 assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
3243 assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
3244 assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
3245 assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
3246 assert_eq!(map_to_clickhouse_type("DATE"), "Date");
3247 }
3248
3249 #[test]
3250 fn test_clickhouse_create_table() {
3251 let dialect = ClickHouseDialect;
3252 let cols = vec![ColumnDef {
3253 name: "id".to_string(),
3254 sql_type: "BIGINT".to_string(),
3255 nullable: false,
3256 default: None,
3257 auto_increment: false, primary_key: true,
3259 }];
3260 let sql = dialect.build_create_table("users", &cols);
3261 assert!(
3263 sql.contains("ENGINE = MergeTree()"),
3264 "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
3265 sql
3266 );
3267 assert!(sql.contains("`id` Int64"));
3268 assert!(sql.contains("PRIMARY KEY"));
3269 }
3270
3271 #[test]
3272 fn test_clickhouse_json_extract() {
3273 let dialect = ClickHouseDialect;
3274 let sql = dialect.json_extract("data", "$.name");
3275 assert!(
3276 sql.contains("JSONExtractString"),
3277 "ClickHouse 应使用 JSONExtractString: {}",
3278 sql
3279 );
3280 }
3281
3282 #[test]
3283 fn test_clickhouse_concat() {
3284 let dialect = ClickHouseDialect;
3285 assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
3287 assert_eq!(dialect.concat(&[]), "''");
3288 }
3289
3290 #[test]
3293 fn test_db_type_dameng_str() {
3294 assert_eq!(DbType::Dameng.as_str(), "dameng");
3295 assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
3296 assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
3297 assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
3298 assert_eq!(DbType::Dameng.default_port(), 5236);
3299 }
3300
3301 #[test]
3302 fn test_db_type_kingbase_str() {
3303 assert_eq!(DbType::Kingbase.as_str(), "kingbase");
3304 assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
3305 assert_eq!(DbType::Kingbase.default_port(), 54321);
3306 }
3307
3308 #[test]
3309 fn test_db_type_db2_str() {
3310 assert_eq!(DbType::Db2.as_str(), "db2");
3311 assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
3312 assert_eq!(DbType::Db2.default_port(), 50000);
3313 }
3314
3315 #[test]
3316 fn test_db_type_mariadb_str() {
3317 assert_eq!(DbType::MariaDB.as_str(), "mariadb");
3318 assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
3319 assert_eq!(DbType::MariaDB.default_port(), 3306);
3320 }
3321
3322 #[test]
3323 fn test_db_type_tidb_str() {
3324 assert_eq!(DbType::TiDB.as_str(), "tidb");
3325 assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
3326 assert_eq!(DbType::TiDB.default_port(), 4000);
3327 }
3328
3329 #[test]
3330 fn test_db_type_polardb_str() {
3331 assert_eq!(DbType::PolarDB.as_str(), "polardb");
3332 assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
3333 assert_eq!(DbType::PolarDB.default_port(), 5432);
3334 }
3335
3336 #[test]
3337 fn test_db_type_gaussdb_str() {
3338 assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
3339 assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
3340 assert_eq!(DbType::GaussDB.default_port(), 25308);
3341 }
3342
3343 #[test]
3344 fn test_db_type_gbase_str() {
3345 assert_eq!(DbType::GBase.as_str(), "gbase");
3346 assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
3347 assert_eq!(DbType::GBase.default_port(), 9088);
3348 }
3349
3350 #[test]
3351 fn test_db_type_sybase_str() {
3352 assert_eq!(DbType::Sybase.as_str(), "sybase");
3353 assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
3354 assert_eq!(DbType::Sybase.default_port(), 5000);
3355 }
3356
3357 #[test]
3358 fn test_db_type_family_classification() {
3359 assert!(DbType::MySQL.is_mysql_family());
3361 assert!(DbType::MariaDB.is_mysql_family());
3362 assert!(DbType::TiDB.is_mysql_family());
3363 assert!(DbType::OceanBase.is_mysql_family());
3364 assert!(!DbType::PostgreSQL.is_mysql_family());
3365
3366 assert!(DbType::PostgreSQL.is_postgres_family());
3368 assert!(DbType::Kingbase.is_postgres_family());
3369 assert!(DbType::GaussDB.is_postgres_family());
3370 assert!(!DbType::MySQL.is_postgres_family());
3371
3372 assert!(DbType::Oracle.is_oracle_family());
3374 assert!(DbType::Dameng.is_oracle_family());
3375 assert!(!DbType::MySQL.is_oracle_family());
3376 }
3377
3378 #[test]
3379 fn test_db_type_supports_stored_procedure_extended() {
3380 assert!(DbType::Dameng.supports_stored_procedure());
3382 assert!(DbType::Kingbase.supports_stored_procedure());
3383 assert!(DbType::Db2.supports_stored_procedure());
3384 assert!(DbType::MariaDB.supports_stored_procedure());
3385 assert!(DbType::TiDB.supports_stored_procedure());
3386 assert!(DbType::PolarDB.supports_stored_procedure());
3387 assert!(DbType::GaussDB.supports_stored_procedure());
3388 assert!(DbType::GBase.supports_stored_procedure());
3389 assert!(DbType::Sybase.supports_stored_procedure());
3390 }
3391
3392 #[test]
3395 fn test_l4_max_identifier_len_constant() {
3396 assert_eq!(MAX_IDENTIFIER_LEN, 63);
3398 }
3399
3400 #[test]
3401 fn test_l4_quote_checked_valid_identifier() {
3402 let dialect = MySqlDialect;
3403 assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
3404 assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
3405 let name_63 = "a".repeat(63);
3407 assert!(dialect.quote_checked(&name_63).is_ok());
3408 }
3409
3410 #[test]
3411 fn test_l4_quote_checked_rejects_too_long() {
3412 let dialect = MySqlDialect;
3413 let long_name = "a".repeat(64); let result = dialect.quote_checked(&long_name);
3415 assert!(result.is_err());
3416 match result {
3417 Err(DbError::InvalidInput(msg)) => {
3418 assert!(
3419 msg.contains("too long"),
3420 "expected 'too long' error, got: {}",
3421 msg
3422 );
3423 }
3424 _ => panic!("Expected DbError::InvalidInput"),
3425 }
3426 }
3427
3428 #[test]
3429 fn test_l4_quote_checked_rejects_empty() {
3430 let dialect = MySqlDialect;
3431 let result = dialect.quote_checked("");
3432 assert!(result.is_err());
3433 }
3434
3435 #[test]
3436 fn test_l4_quote_checked_rejects_sql_injection() {
3437 let dialect = MySqlDialect;
3438 assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
3440 assert!(dialect.quote_checked("user'name").is_err());
3442 assert!(dialect.quote_checked("user name").is_err());
3444 assert!(dialect.quote_checked("1users").is_err());
3446 assert!(dialect.quote_checked("schema.table").is_err());
3448 }
3449
3450 #[test]
3451 fn test_l4_quote_checked_postgres() {
3452 let dialect = PostgreSqlDialect;
3453 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3454 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3455 }
3456
3457 #[test]
3458 fn test_l4_quote_checked_sqlite() {
3459 let dialect = SqliteDialect;
3460 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3461 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3462 }
3463
3464 #[test]
3465 fn test_l4_quote_checked_oracle() {
3466 let dialect = OracleDialect;
3467 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3468 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3469 }
3470
3471 #[test]
3472 fn test_l4_quote_checked_sql_server() {
3473 let dialect = SqlServerDialect;
3474 assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
3475 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3476 }
3477
3478 #[test]
3481 fn test_duckdb_quote() {
3482 let dialect = DuckDBDialect;
3483 assert_eq!(dialect.quote("users"), "\"users\"");
3484 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3485 }
3486
3487 #[test]
3488 fn test_duckdb_escape() {
3489 let dialect = DuckDBDialect;
3490 assert_eq!(dialect.escape_string("hello"), "hello");
3491 assert_eq!(dialect.escape_string("it's"), "it''s");
3492 }
3493
3494 #[test]
3495 fn test_duckdb_pagination() {
3496 let dialect = DuckDBDialect;
3497 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3498 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
3499 }
3500
3501 #[test]
3502 fn test_duckdb_supports() {
3503 let dialect = DuckDBDialect;
3504 assert!(!dialect.supports_returning());
3505 assert!(!dialect.supports_lock_for_update());
3506 assert!(!dialect.supports_lock_shared());
3507 assert!(dialect.supports_if_exists());
3508 assert!(dialect.supports_if_not_exists());
3509 }
3510
3511 #[test]
3512 fn test_duckdb_insert_or_ignore() {
3513 let dialect = DuckDBDialect;
3514 let sql = dialect.build_insert_or_ignore_prefix("users");
3515 assert_eq!(sql, "INSERT OR IGNORE INTO \"users\"");
3516 }
3517
3518 #[test]
3519 fn test_duckdb_create_table() {
3520 let dialect = DuckDBDialect;
3521 let columns = vec![
3522 ColumnDef {
3523 name: "id".to_string(),
3524 sql_type: "BIGINT".to_string(),
3525 nullable: false,
3526 default: None,
3527 auto_increment: true,
3528 primary_key: true,
3529 },
3530 ColumnDef {
3531 name: "name".to_string(),
3532 sql_type: "VARCHAR(255)".to_string(),
3533 nullable: false,
3534 default: None,
3535 auto_increment: false,
3536 primary_key: false,
3537 },
3538 ];
3539 let sql = dialect.build_create_table("users", &columns);
3540 assert!(sql.contains("CREATE TABLE \"users\""));
3541 assert!(sql.contains("\"id\" BIGINT PRIMARY KEY"));
3542 assert!(sql.contains("\"name\" VARCHAR(255)"));
3543 }
3544
3545 #[test]
3546 fn test_duckdb_alter_table() {
3547 let dialect = DuckDBDialect;
3548 let changes = vec![TableChange::AddColumn(ColumnDef {
3549 name: "age".to_string(),
3550 sql_type: "INTEGER".to_string(),
3551 nullable: true,
3552 default: None,
3553 auto_increment: false,
3554 primary_key: false,
3555 })];
3556 let sql = dialect.build_alter_table("users", &changes);
3557 assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN \"age\" INTEGER"));
3558 }
3559
3560 #[test]
3561 fn test_duckdb_json_extract() {
3562 let dialect = DuckDBDialect;
3563 let sql = dialect.json_extract("data", "$.user.name");
3564 assert!(sql.contains("->"));
3565 assert!(sql.contains("user.name"));
3566 }
3567
3568 #[test]
3569 fn test_duckdb_concat() {
3570 let dialect = DuckDBDialect;
3571 let sql = dialect.concat(&["a", "b", "c"]);
3572 assert_eq!(sql, "a || b || c");
3573 }
3574
3575 #[test]
3576 fn test_duckdb_bool_to_int() {
3577 let dialect = DuckDBDialect;
3578 let sql = dialect.bool_to_int("active");
3579 assert!(sql.contains("CASE WHEN"));
3580 assert!(sql.contains("THEN 1"));
3581 assert!(sql.contains("ELSE 0"));
3582 }
3583
3584 #[test]
3585 fn test_get_dialect_duckdb() {
3586 let result = get_dialect(DbType::DuckDB);
3587 assert!(result.is_ok());
3588 let dialect = result.unwrap();
3589 assert_eq!(dialect.db_type(), DbType::DuckDB);
3590 }
3591}
3592
3593#[cfg(feature = "perf-enum-dispatch")]
3602#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3603pub enum DialectKind {
3604 MySQL,
3606 PostgreSQL,
3608 SQLite,
3610 Oracle,
3612 MSSQL,
3614}
3615
3616#[cfg(feature = "perf-enum-dispatch")]
3617impl DialectKind {
3618 pub fn from_db_type(db_type: DbType) -> Option<Self> {
3620 match db_type {
3621 DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase => Some(Self::MySQL),
3622 DbType::PostgreSQL | DbType::Kingbase | DbType::PolarDB | DbType::GaussDB => {
3623 Some(Self::PostgreSQL)
3624 }
3625 DbType::Sqlite => Some(Self::SQLite),
3626 DbType::Oracle | DbType::Dameng => Some(Self::Oracle),
3627 DbType::SqlServer | DbType::Sybase | DbType::GBase => Some(Self::MSSQL),
3628 _ => None,
3629 }
3630 }
3631
3632 pub fn quote(&self, identifier: &str) -> String {
3634 match self {
3635 Self::MySQL => MySqlDialect.quote(identifier),
3636 Self::PostgreSQL => PostgreSqlDialect.quote(identifier),
3637 Self::SQLite => SqliteDialect.quote(identifier),
3638 Self::Oracle => OracleDialect.quote(identifier),
3639 Self::MSSQL => SqlServerDialect.quote(identifier),
3640 }
3641 }
3642
3643 pub fn escape_string(&self, s: &str) -> String {
3645 match self {
3646 Self::MySQL => MySqlDialect.escape_string(s),
3647 Self::PostgreSQL => PostgreSqlDialect.escape_string(s),
3648 Self::SQLite => SqliteDialect.escape_string(s),
3649 Self::Oracle => OracleDialect.escape_string(s),
3650 Self::MSSQL => SqlServerDialect.escape_string(s),
3651 }
3652 }
3653
3654 pub fn db_type(&self) -> DbType {
3656 match self {
3657 Self::MySQL => DbType::MySQL,
3658 Self::PostgreSQL => DbType::PostgreSQL,
3659 Self::SQLite => DbType::Sqlite,
3660 Self::Oracle => DbType::Oracle,
3661 Self::MSSQL => DbType::SqlServer,
3662 }
3663 }
3664
3665 pub fn to_dialect(&self) -> Box<dyn Dialect> {
3667 match self {
3668 Self::MySQL => Box::new(MySqlDialect),
3669 Self::PostgreSQL => Box::new(PostgreSqlDialect),
3670 Self::SQLite => Box::new(SqliteDialect),
3671 Self::Oracle => Box::new(OracleDialect),
3672 Self::MSSQL => Box::new(SqlServerDialect),
3673 }
3674 }
3675}
3676
3677#[cfg(all(test, feature = "perf-enum-dispatch"))]
3678mod enum_dispatch_tests {
3679 use super::*;
3680
3681 #[test]
3682 fn test_dialect_kind_from_db_type() {
3683 assert_eq!(
3684 DialectKind::from_db_type(DbType::MySQL),
3685 Some(DialectKind::MySQL)
3686 );
3687 assert_eq!(
3688 DialectKind::from_db_type(DbType::PostgreSQL),
3689 Some(DialectKind::PostgreSQL)
3690 );
3691 assert_eq!(
3692 DialectKind::from_db_type(DbType::Sqlite),
3693 Some(DialectKind::SQLite)
3694 );
3695 assert_eq!(
3696 DialectKind::from_db_type(DbType::Oracle),
3697 Some(DialectKind::Oracle)
3698 );
3699 assert_eq!(
3700 DialectKind::from_db_type(DbType::SqlServer),
3701 Some(DialectKind::MSSQL)
3702 );
3703 assert_eq!(DialectKind::from_db_type(DbType::Redis), None);
3704 }
3705
3706 #[test]
3707 fn test_dialect_kind_quote_diff() {
3708 let kinds = [
3709 DialectKind::MySQL,
3710 DialectKind::PostgreSQL,
3711 DialectKind::SQLite,
3712 DialectKind::Oracle,
3713 DialectKind::MSSQL,
3714 ];
3715 for kind in &kinds {
3716 let enum_result = kind.quote("users");
3717 let box_result = kind.to_dialect().quote("users");
3718 assert_eq!(enum_result, box_result);
3719 }
3720 }
3721
3722 #[test]
3723 fn test_dialect_kind_escape_diff() {
3724 let kinds = [
3725 DialectKind::MySQL,
3726 DialectKind::PostgreSQL,
3727 DialectKind::SQLite,
3728 DialectKind::Oracle,
3729 DialectKind::MSSQL,
3730 ];
3731 for kind in &kinds {
3732 let enum_result = kind.escape_string("it's");
3733 let box_result = kind.to_dialect().escape_string("it's");
3734 assert_eq!(enum_result, box_result);
3735 }
3736 }
3737}