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#[cfg(feature = "dialect-snowflake")]
1566#[derive(Debug, Clone)]
1567pub struct SnowflakeDialect;
1568
1569#[cfg(feature = "dialect-snowflake")]
1570impl Dialect for SnowflakeDialect {
1571 fn clone_box(&self) -> Box<dyn Dialect> {
1572 Box::new(SnowflakeDialect)
1573 }
1574
1575 fn db_type(&self) -> DbType {
1576 DbType::Snowflake
1577 }
1578
1579 fn quote(&self, identifier: &str) -> String {
1580 format!("\"{}\"", identifier.replace('"', "\"\""))
1582 }
1583
1584 fn escape_string(&self, s: &str) -> String {
1585 let mut escaped = String::with_capacity(s.len() * 2);
1587 for c in s.chars() {
1588 match c {
1589 '\'' => escaped.push_str("''"),
1590 '\\' => escaped.push_str("\\\\"),
1591 _ => escaped.push(c),
1592 }
1593 }
1594 escaped
1595 }
1596
1597 fn supports_returning(&self) -> bool {
1598 true
1600 }
1601
1602 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1603 let offset = page.saturating_sub(1).saturating_mul(limit);
1605 format!("{} LIMIT {}, {}", sql, offset, limit)
1606 }
1607
1608 fn json_type(&self) -> &'static str {
1609 "VARIANT"
1611 }
1612
1613 fn json_extract(&self, column: &str, path: &str) -> String {
1614 let normalized = path.trim_start_matches("$.");
1616 format!("{}:{}", column, normalized)
1617 }
1618
1619 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1620 if columns.is_empty() {
1622 return "FALSE".to_string();
1623 }
1624 let escaped = self.escape_string(keyword);
1625 let parts: Vec<String> = columns
1626 .iter()
1627 .map(|c| format!("{} ILIKE '%{}%'", c, escaped))
1628 .collect();
1629 parts.join(" OR ")
1630 }
1631
1632 fn bool_to_int(&self, expr: &str) -> String {
1633 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1634 }
1635
1636 fn concat(&self, parts: &[&str]) -> String {
1637 if parts.is_empty() {
1638 return "NULL".to_string();
1639 }
1640 format!("CONCAT({})", parts.join(", "))
1642 }
1643
1644 fn supports_if_exists(&self) -> bool {
1645 true
1646 }
1647
1648 fn supports_if_not_exists(&self) -> bool {
1649 true
1650 }
1651
1652 fn auto_increment_keyword(&self) -> &'static str {
1653 "AUTOINCREMENT"
1655 }
1656
1657 fn last_insert_id_sql(&self) -> Option<&'static str> {
1658 None
1660 }
1661
1662 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1663 let cols: Vec<String> = columns
1664 .iter()
1665 .map(|col| {
1666 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
1667 if !col.nullable {
1668 sql.push_str(" NOT NULL");
1669 }
1670 if let Some(default) = &col.default {
1671 sql.push_str(&format!(" DEFAULT {}", default));
1672 }
1673 if col.primary_key {
1674 sql.push_str(" PRIMARY KEY");
1675 }
1676 sql
1677 })
1678 .collect();
1679 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1680 }
1681
1682 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1683 let stmts: Vec<String> = changes
1684 .iter()
1685 .map(|change| match change {
1686 TableChange::AddColumn(col) => {
1687 let mut sql = format!(
1688 "ALTER TABLE {} ADD COLUMN {} {}",
1689 self.quote(table),
1690 self.quote(&col.name),
1691 col.sql_type
1692 );
1693 if !col.nullable {
1694 sql.push_str(" NOT NULL");
1695 }
1696 if let Some(default) = &col.default {
1697 sql.push_str(&format!(" DEFAULT {}", default));
1698 }
1699 sql
1700 }
1701 TableChange::DropColumn(name) => {
1702 format!(
1703 "ALTER TABLE {} DROP COLUMN {}",
1704 self.quote(table),
1705 self.quote(name)
1706 )
1707 }
1708 TableChange::ModifyColumn(col) => {
1709 format!(
1710 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
1711 self.quote(table),
1712 self.quote(&col.name),
1713 col.sql_type
1714 )
1715 }
1716 TableChange::AddIndex(name, cols) => {
1717 format!(
1718 "CREATE INDEX {} ON {} ({})",
1719 name,
1720 self.quote(table),
1721 cols.join(", ")
1722 )
1723 }
1724 TableChange::DropIndex(name) => {
1725 format!("DROP INDEX {}", name)
1726 }
1727 TableChange::AddForeignKey {
1728 columns,
1729 reference_table,
1730 reference_columns,
1731 } => {
1732 format!(
1733 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1734 self.quote(table),
1735 table,
1736 columns.join("_"),
1737 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1738 self.quote(reference_table),
1739 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1740 )
1741 }
1742 })
1743 .collect();
1744 stmts.join("; ")
1745 }
1746}
1747
1748#[cfg(feature = "dialect-snowflake")]
1750impl SnowflakeDialect {
1751 pub fn build_copy_into(&self, target: &str, source: &str) -> String {
1755 format!("COPY INTO {} FROM {}", self.quote(target), source)
1756 }
1757
1758 pub fn build_time_travel_at(&self, table: &str, timestamp: &str) -> String {
1762 format!(
1763 "SELECT * FROM {} AT(OBJECT => '{}')",
1764 self.quote(table),
1765 self.escape_string(timestamp)
1766 )
1767 }
1768
1769 pub fn build_time_travel_before(&self, table: &str, timestamp: &str) -> String {
1773 format!(
1774 "SELECT * FROM {} BEFORE(timestamp => '{}')",
1775 self.quote(table),
1776 self.escape_string(timestamp)
1777 )
1778 }
1779}
1780
1781#[cfg(feature = "dialect-redshift")]
1794delegate_dialect_to!(RedshiftDialect, PostgreSqlDialect, DbType::Redshift);
1795
1796#[cfg(feature = "dialect-redshift")]
1798impl RedshiftDialect {
1799 pub fn build_copy(&self, target: &str, source: &str, credentials: &str) -> String {
1803 format!(
1804 "COPY {} FROM '{}' CREDENTIALS '{}' FORMAT AS CSV",
1805 PostgreSqlDialect.quote(target),
1806 source,
1807 credentials
1808 )
1809 }
1810
1811 pub fn build_unload(&self, query: &str, s3_path: &str, credentials: &str) -> String {
1815 format!(
1816 "UNLOAD ('{}') TO '{}' CREDENTIALS '{}'",
1817 query, s3_path, credentials
1818 )
1819 }
1820}
1821
1822#[derive(Debug, Clone)]
1836pub struct ClickHouseDialect;
1837
1838impl Dialect for ClickHouseDialect {
1839 fn clone_box(&self) -> Box<dyn Dialect> {
1840 Box::new(ClickHouseDialect)
1841 }
1842
1843 fn db_type(&self) -> DbType {
1844 DbType::ClickHouse
1845 }
1846
1847 fn quote(&self, identifier: &str) -> String {
1848 format!("`{}`", identifier.replace('`', "``"))
1850 }
1851
1852 fn escape_string(&self, s: &str) -> String {
1853 let mut escaped = String::with_capacity(s.len() * 2);
1855 for c in s.chars() {
1856 match c {
1857 '\'' => escaped.push_str("\\'"),
1858 '\\' => escaped.push_str("\\\\"),
1859 '\n' => escaped.push_str("\\n"),
1860 '\r' => escaped.push_str("\\r"),
1861 '\t' => escaped.push_str("\\t"),
1862 _ => escaped.push(c),
1863 }
1864 }
1865 escaped
1866 }
1867
1868 fn supports_returning(&self) -> bool {
1869 false
1871 }
1872
1873 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1874 let offset = page.saturating_sub(1).saturating_mul(limit);
1876 format!("{} LIMIT {}, {}", sql, offset, limit)
1877 }
1878
1879 fn json_type(&self) -> &'static str {
1880 "String"
1882 }
1883
1884 fn json_extract(&self, column: &str, path: &str) -> String {
1885 let normalized = if path.starts_with('$') {
1887 path.to_string()
1888 } else {
1889 format!("$.{}", path)
1890 };
1891 format!(
1892 "JSONExtractString({}, '{}')",
1893 column,
1894 self.escape_string(&normalized)
1895 )
1896 }
1897
1898 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1899 if columns.is_empty() {
1901 return "0".to_string();
1902 }
1903 let escaped = self.escape_string(keyword);
1904 let parts: Vec<String> = columns
1905 .iter()
1906 .map(|c| format!("position({}, '{}') > 0", c, escaped))
1907 .collect();
1908 parts.join(" OR ")
1909 }
1910
1911 fn bool_to_int(&self, expr: &str) -> String {
1912 format!("toUInt8({})", expr)
1914 }
1915
1916 fn concat(&self, parts: &[&str]) -> String {
1917 if parts.is_empty() {
1919 return "''".to_string();
1920 }
1921 format!("concat({})", parts.join(", "))
1922 }
1923
1924 fn supports_if_exists(&self) -> bool {
1925 true
1926 }
1927
1928 fn supports_if_not_exists(&self) -> bool {
1929 true
1930 }
1931
1932 fn auto_increment_keyword(&self) -> &'static str {
1933 ""
1935 }
1936
1937 fn last_insert_id_sql(&self) -> Option<&'static str> {
1938 None
1940 }
1941
1942 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1943 let cols: Vec<String> = columns
1944 .iter()
1945 .map(|col| {
1946 let ch_type = map_to_clickhouse_type(&col.sql_type);
1947 let mut sql = format!("{} {}", self.quote(&col.name), ch_type);
1948 if let Some(default) = &col.default {
1949 sql.push_str(&format!(" DEFAULT {}", default));
1950 }
1951 if col.primary_key {
1952 sql.push_str(" PRIMARY KEY");
1953 }
1954 sql
1955 })
1956 .collect();
1957
1958 format!(
1960 "CREATE TABLE {} ({}) ENGINE = MergeTree()",
1961 self.quote(table),
1962 cols.join(", ")
1963 )
1964 }
1965
1966 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1967 let stmts: Vec<String> = changes
1968 .iter()
1969 .map(|change| match change {
1970 TableChange::AddColumn(col) => {
1971 let ch_type = map_to_clickhouse_type(&col.sql_type);
1972 format!(
1973 "ALTER TABLE {} ADD COLUMN {} {}",
1974 self.quote(table),
1975 self.quote(&col.name),
1976 ch_type
1977 )
1978 }
1979 TableChange::DropColumn(name) => {
1980 format!(
1981 "ALTER TABLE {} DROP COLUMN {}",
1982 self.quote(table),
1983 self.quote(name)
1984 )
1985 }
1986 TableChange::ModifyColumn(col) => {
1987 let ch_type = map_to_clickhouse_type(&col.sql_type);
1988 format!(
1989 "ALTER TABLE {} MODIFY COLUMN {} {}",
1990 self.quote(table),
1991 self.quote(&col.name),
1992 ch_type
1993 )
1994 }
1995 TableChange::AddIndex(name, cols) => {
1996 format!(
1997 "ALTER TABLE {} ADD INDEX {} ({})",
1998 self.quote(table),
1999 name,
2000 cols.join(", ")
2001 )
2002 }
2003 TableChange::DropIndex(name) => {
2004 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
2005 }
2006 TableChange::AddForeignKey { .. } => {
2007 String::new()
2009 }
2010 })
2011 .filter(|s| !s.is_empty())
2012 .collect();
2013
2014 stmts.join("; ")
2015 }
2016
2017 fn supports_lock_for_update(&self) -> bool {
2018 false
2020 }
2021
2022 fn supports_lock_shared(&self) -> bool {
2023 false
2025 }
2026
2027 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
2028 format!("INSERT INTO {}", self.quote(table))
2030 }
2031}
2032
2033fn map_to_clickhouse_type(sql_type: &str) -> String {
2043 let upper = sql_type.to_uppercase();
2044 let trimmed = upper.trim();
2045
2046 if trimmed.starts_with("BIGINT") {
2047 "Int64".to_string()
2048 } else if matches!(trimmed, "INT" | "INTEGER") {
2049 "Int32".to_string()
2050 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
2051 "Int16".to_string()
2052 } else if trimmed.starts_with("VARCHAR")
2053 || trimmed.starts_with("CHAR")
2054 || matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT")
2055 {
2056 "String".to_string()
2057 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2058 "UInt8".to_string()
2059 } else if matches!(trimmed, "FLOAT" | "REAL") {
2060 "Float32".to_string()
2061 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2062 "Float64".to_string()
2063 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2064 "DateTime".to_string()
2065 } else if matches!(trimmed, "DATE") {
2066 "Date".to_string()
2067 } else if trimmed.starts_with("DECIMAL") || trimmed.starts_with("NUMERIC") {
2068 "Decimal(38, 4)".to_string()
2069 } else {
2070 sql_type.to_string()
2071 }
2072}
2073
2074#[derive(Debug, Clone)]
2089pub struct DuckDBDialect;
2090
2091impl Dialect for DuckDBDialect {
2092 fn clone_box(&self) -> Box<dyn Dialect> {
2093 Box::new(DuckDBDialect)
2094 }
2095
2096 fn db_type(&self) -> DbType {
2097 DbType::DuckDB
2098 }
2099
2100 fn quote(&self, identifier: &str) -> String {
2101 format!("\"{}\"", identifier.replace('"', "\"\""))
2103 }
2104
2105 fn escape_string(&self, s: &str) -> String {
2106 let mut escaped = String::with_capacity(s.len() * 2);
2108 for c in s.chars() {
2109 match c {
2110 '\'' => escaped.push_str("''"),
2111 _ => escaped.push(c),
2112 }
2113 }
2114 escaped
2115 }
2116
2117 fn supports_returning(&self) -> bool {
2118 false
2120 }
2121
2122 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2123 let offset = page.saturating_sub(1).saturating_mul(limit);
2125 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
2126 }
2127
2128 fn json_type(&self) -> &'static str {
2129 "JSON"
2131 }
2132
2133 fn json_extract(&self, column: &str, path: &str) -> String {
2134 let normalized = if path.starts_with('$') {
2136 path[2..].to_string()
2137 } else {
2138 path.to_string()
2139 };
2140 format!("{} -> '{}'", column, normalized)
2141 }
2142
2143 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2144 if columns.is_empty() {
2146 return "0".to_string();
2147 }
2148 let escaped = self.escape_string(keyword);
2149 let parts: Vec<String> = columns
2150 .iter()
2151 .map(|c| format!("{} LIKE '%{}%'", c, escaped))
2152 .collect();
2153 parts.join(" OR ")
2154 }
2155
2156 fn bool_to_int(&self, expr: &str) -> String {
2157 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2159 }
2160
2161 fn concat(&self, parts: &[&str]) -> String {
2162 if parts.is_empty() {
2164 return "''".to_string();
2165 }
2166 parts.join(" || ")
2167 }
2168
2169 fn supports_if_exists(&self) -> bool {
2170 true
2171 }
2172
2173 fn supports_if_not_exists(&self) -> bool {
2174 true
2175 }
2176
2177 fn auto_increment_keyword(&self) -> &'static str {
2178 ""
2180 }
2181
2182 fn last_insert_id_sql(&self) -> Option<&'static str> {
2183 None
2185 }
2186
2187 fn supports_lock_for_update(&self) -> bool {
2188 false
2190 }
2191
2192 fn supports_lock_shared(&self) -> bool {
2193 false
2195 }
2196
2197 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
2198 format!("INSERT OR IGNORE INTO {}", self.quote(table))
2200 }
2201
2202 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2203 let cols: Vec<String> = columns
2204 .iter()
2205 .map(|col| {
2206 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
2207 if col.auto_increment {
2208 sql = format!("{} BIGINT PRIMARY KEY", self.quote(&col.name));
2210 }
2211 if let Some(default) = &col.default {
2212 sql.push_str(&format!(" DEFAULT {}", default));
2213 }
2214 if col.primary_key && !col.auto_increment {
2215 sql.push_str(" PRIMARY KEY");
2216 }
2217 sql
2218 })
2219 .collect();
2220
2221 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2222 }
2223
2224 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2225 let stmts: Vec<String> = changes
2226 .iter()
2227 .map(|change| match change {
2228 TableChange::AddColumn(col) => {
2229 let mut sql = format!(
2230 "ALTER TABLE {} ADD COLUMN {} {}",
2231 self.quote(table),
2232 self.quote(&col.name),
2233 col.sql_type
2234 );
2235 if let Some(default) = &col.default {
2236 sql.push_str(&format!(" DEFAULT {}", default));
2237 }
2238 sql
2239 }
2240 TableChange::DropColumn(name) => {
2241 format!(
2242 "ALTER TABLE {} DROP COLUMN {}",
2243 self.quote(table),
2244 self.quote(name)
2245 )
2246 }
2247 TableChange::ModifyColumn(col) => {
2248 format!(
2249 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
2250 self.quote(table),
2251 self.quote(&col.name),
2252 col.sql_type
2253 )
2254 }
2255 TableChange::AddIndex(name, _cols) => {
2256 format!(
2258 "CREATE INDEX {} ON {} (id)",
2259 self.quote(name),
2260 self.quote(table)
2261 )
2262 }
2263 TableChange::DropIndex(name) => {
2264 format!("DROP INDEX {}", self.quote(name))
2265 }
2266 TableChange::AddForeignKey { .. } => {
2267 String::new()
2269 }
2270 })
2271 .filter(|s| !s.is_empty())
2272 .collect();
2273
2274 stmts.join("; ")
2275 }
2276}
2277
2278#[derive(Debug, Clone)]
2292pub struct Db2Dialect;
2293
2294impl Dialect for Db2Dialect {
2295 fn clone_box(&self) -> Box<dyn Dialect> {
2296 Box::new(Db2Dialect)
2297 }
2298
2299 fn db_type(&self) -> DbType {
2300 DbType::Db2
2301 }
2302
2303 fn quote(&self, identifier: &str) -> String {
2304 format!("\"{}\"", identifier.replace('"', "\"\""))
2306 }
2307
2308 fn escape_string(&self, s: &str) -> String {
2309 let mut escaped = String::with_capacity(s.len() * 2);
2311 for c in s.chars() {
2312 match c {
2313 '\'' => escaped.push_str("''"),
2314 _ => escaped.push(c),
2315 }
2316 }
2317 escaped
2318 }
2319
2320 fn supports_returning(&self) -> bool {
2321 false
2323 }
2324
2325 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2326 let offset = page.saturating_sub(1).saturating_mul(limit);
2328 format!(
2329 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
2330 sql, offset, limit
2331 )
2332 }
2333
2334 fn json_type(&self) -> &'static str {
2335 "JSON"
2337 }
2338
2339 fn json_extract(&self, column: &str, path: &str) -> String {
2340 let normalized = if path.starts_with('$') {
2342 path.to_string()
2343 } else {
2344 format!("$.{}", path)
2345 };
2346 format!(
2347 "JSON_VALUE({}, '{}')",
2348 column,
2349 self.escape_string(&normalized)
2350 )
2351 }
2352
2353 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2354 if columns.is_empty() {
2356 return "0".to_string();
2357 }
2358 let escaped = self.escape_string(keyword);
2359 let parts: Vec<String> = columns
2360 .iter()
2361 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2362 .collect();
2363 parts.join(" OR ")
2364 }
2365
2366 fn bool_to_int(&self, expr: &str) -> String {
2367 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2369 }
2370
2371 fn concat(&self, parts: &[&str]) -> String {
2372 if parts.is_empty() {
2374 return "''".to_string();
2375 }
2376 parts.join(" || ")
2377 }
2378
2379 fn supports_if_exists(&self) -> bool {
2380 false
2382 }
2383
2384 fn supports_if_not_exists(&self) -> bool {
2385 false
2387 }
2388
2389 fn auto_increment_keyword(&self) -> &'static str {
2390 "GENERATED ALWAYS AS IDENTITY"
2392 }
2393
2394 fn last_insert_id_sql(&self) -> Option<&'static str> {
2395 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
2397 }
2398
2399 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2400 let cols: Vec<String> = columns
2401 .iter()
2402 .map(|col| {
2403 let db2_type = map_to_db2_type(&col.sql_type);
2404 let mut sql = format!("{} {}", self.quote(&col.name), db2_type);
2405 if !col.nullable && !col.auto_increment {
2406 sql.push_str(" NOT NULL");
2407 }
2408 if let Some(default) = &col.default {
2409 sql.push_str(&format!(" DEFAULT {}", default));
2410 }
2411 if col.auto_increment {
2412 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2413 }
2414 if col.primary_key {
2415 sql.push_str(" PRIMARY KEY");
2416 }
2417 sql
2418 })
2419 .collect();
2420
2421 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2422 }
2423
2424 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2425 let stmts: Vec<String> = changes
2426 .iter()
2427 .map(|change| match change {
2428 TableChange::AddColumn(col) => {
2429 let db2_type = map_to_db2_type(&col.sql_type);
2430 let mut sql = format!(
2431 "ALTER TABLE {} ADD COLUMN {} {}",
2432 self.quote(table),
2433 self.quote(&col.name),
2434 db2_type
2435 );
2436 if !col.nullable {
2437 sql.push_str(" NOT NULL");
2438 }
2439 if let Some(default) = &col.default {
2440 sql.push_str(&format!(" DEFAULT {}", default));
2441 }
2442 sql
2443 }
2444 TableChange::DropColumn(name) => {
2445 format!(
2446 "ALTER TABLE {} DROP COLUMN {}",
2447 self.quote(table),
2448 self.quote(name)
2449 )
2450 }
2451 TableChange::ModifyColumn(col) => {
2452 let db2_type = map_to_db2_type(&col.sql_type);
2453 format!(
2454 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
2455 self.quote(table),
2456 self.quote(&col.name),
2457 db2_type
2458 )
2459 }
2460 TableChange::AddIndex(name, cols) => {
2461 format!(
2462 "CREATE INDEX {} ON {} ({})",
2463 name,
2464 self.quote(table),
2465 cols.join(", ")
2466 )
2467 }
2468 TableChange::DropIndex(name) => {
2469 format!("DROP INDEX {}", name)
2470 }
2471 TableChange::AddForeignKey {
2472 columns,
2473 reference_table,
2474 reference_columns,
2475 } => {
2476 format!(
2477 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
2478 self.quote(table),
2479 table,
2480 columns.join("_"),
2481 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
2482 self.quote(reference_table),
2483 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
2484 )
2485 }
2486 })
2487 .collect();
2488
2489 stmts.join("; ")
2490 }
2491
2492 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
2493 let _ = if_exists;
2495 format!("DROP TABLE {}", self.quote(table))
2496 }
2497}
2498
2499fn map_to_db2_type(sql_type: &str) -> String {
2508 let upper = sql_type.to_uppercase();
2509 let trimmed = upper.trim();
2510
2511 if trimmed.starts_with("BIGINT") {
2512 "BIGINT".to_string()
2513 } else if matches!(trimmed, "INT" | "INTEGER") {
2514 "INTEGER".to_string()
2515 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
2516 "SMALLINT".to_string()
2517 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2518 sql_type.to_string()
2519 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
2520 "CLOB(2G)".to_string()
2521 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2522 "SMALLINT".to_string()
2523 } else if matches!(trimmed, "FLOAT" | "REAL") {
2524 "REAL".to_string()
2525 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2526 "DOUBLE".to_string()
2527 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2528 "TIMESTAMP".to_string()
2529 } else if matches!(trimmed, "DATE") {
2530 "DATE".to_string()
2531 } else {
2532 sql_type.to_string()
2534 }
2535}
2536
2537pub fn get_dialect(db_type: DbType) -> Result<Box<dyn Dialect>, DbError> {
2560 match db_type {
2561 DbType::MySQL => Ok(Box::new(MySqlDialect)),
2562 DbType::PostgreSQL => Ok(Box::new(PostgreSqlDialect)),
2563 DbType::Sqlite => Ok(Box::new(SqliteDialect)),
2564 DbType::Redis => Err(DbError::Unsupported(
2565 "Redis does not support standard SQL dialect".to_string(),
2566 )),
2567 DbType::MongoDB => Err(DbError::Unsupported(
2568 "MongoDB uses different query syntax".to_string(),
2569 )),
2570 DbType::ClickHouse => Ok(Box::new(ClickHouseDialect)),
2571 DbType::Oracle => Ok(Box::new(OracleDialect)),
2572 DbType::OceanBase => Ok(Box::new(MySqlDialect)),
2573 DbType::SqlServer => Ok(Box::new(SqlServerDialect)),
2574 DbType::VectorDb => Err(DbError::Unsupported(
2575 "Vector databases have specific APIs".to_string(),
2576 )),
2577 DbType::PureJsDb => Err(DbError::Unsupported(
2578 "PureJS database uses JavaScript".to_string(),
2579 )),
2580 DbType::Dameng => Ok(Box::new(DamengDialect)),
2582 DbType::Kingbase => Ok(Box::new(KingbaseDialect)),
2583 DbType::Db2 => Ok(Box::new(Db2Dialect)),
2584 DbType::MariaDB => Ok(Box::new(MariaDbDialect)),
2585 DbType::TiDB => Ok(Box::new(TiDbDialect)),
2586 DbType::PolarDB => Ok(Box::new(PolarDbDialect)),
2587 DbType::GaussDB => Ok(Box::new(GaussDbDialect)),
2588 DbType::GBase => Ok(Box::new(GBaseDialect)),
2589 DbType::Sybase => Ok(Box::new(SybaseDialect)),
2590 DbType::DuckDB => Ok(Box::new(DuckDBDialect)),
2591 #[cfg(feature = "dialect-cockroachdb")]
2592 DbType::CockroachDB => Ok(Box::new(CockroachDbDialect)),
2593 #[cfg(feature = "dialect-yugabytedb")]
2594 DbType::YugabyteDB => Ok(Box::new(YugabyteDbDialect)),
2595 #[cfg(feature = "dialect-snowflake")]
2596 DbType::Snowflake => Ok(Box::new(SnowflakeDialect)),
2597 #[cfg(feature = "dialect-redshift")]
2598 DbType::Redshift => Ok(Box::new(RedshiftDialect)),
2599 #[cfg(feature = "dialect-informix")]
2600 DbType::Informix => Ok(Box::new(InformixDialect)),
2601 #[cfg(feature = "dialect-saphana")]
2602 DbType::SapHana => Ok(Box::new(SapHanaDialect)),
2603 #[cfg(feature = "dialect-firebird")]
2604 DbType::Firebird => Ok(Box::new(FirebirdDialect)),
2605 }
2606}
2607
2608impl fmt::Display for dyn Dialect {
2609 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2610 write!(f, "Dialect({})", self.db_type())
2611 }
2612}
2613
2614#[cfg(feature = "dialect-informix")]
2628#[derive(Debug, Clone)]
2629pub struct InformixDialect;
2630
2631#[cfg(feature = "dialect-informix")]
2632impl Dialect for InformixDialect {
2633 fn clone_box(&self) -> Box<dyn Dialect> {
2634 Box::new(InformixDialect)
2635 }
2636
2637 fn db_type(&self) -> DbType {
2638 DbType::Informix
2639 }
2640
2641 fn quote(&self, identifier: &str) -> String {
2642 format!("\"{}\"", identifier.replace('"', "\"\""))
2643 }
2644
2645 fn escape_string(&self, s: &str) -> String {
2646 let mut escaped = String::with_capacity(s.len() * 2);
2647 for c in s.chars() {
2648 match c {
2649 '\'' => escaped.push_str("''"),
2650 _ => escaped.push(c),
2651 }
2652 }
2653 escaped
2654 }
2655
2656 fn supports_returning(&self) -> bool {
2657 false
2658 }
2659
2660 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2661 let offset = page.saturating_sub(1).saturating_mul(limit);
2662 format!("{} SKIP {} FIRST {}", sql, offset, limit)
2663 }
2664
2665 fn json_type(&self) -> &'static str {
2666 "LVARCHAR"
2667 }
2668
2669 fn json_extract(&self, column: &str, path: &str) -> String {
2670 let normalized = if path.starts_with('$') {
2671 path.to_string()
2672 } else {
2673 format!("$.{}", path)
2674 };
2675 format!(
2676 "JSONExtract({}, '{}')",
2677 column,
2678 self.escape_string(&normalized)
2679 )
2680 }
2681
2682 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2683 if columns.is_empty() {
2684 return "0".to_string();
2685 }
2686 let escaped = self.escape_string(keyword);
2687 let parts: Vec<String> = columns
2688 .iter()
2689 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2690 .collect();
2691 parts.join(" OR ")
2692 }
2693
2694 fn bool_to_int(&self, expr: &str) -> String {
2695 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2696 }
2697
2698 fn concat(&self, parts: &[&str]) -> String {
2699 if parts.is_empty() {
2700 return "''".to_string();
2701 }
2702 parts.join(" || ")
2703 }
2704
2705 fn supports_if_exists(&self) -> bool {
2706 true
2707 }
2708
2709 fn supports_if_not_exists(&self) -> bool {
2710 true
2711 }
2712
2713 fn auto_increment_keyword(&self) -> &'static str {
2714 "SERIAL"
2715 }
2716
2717 fn last_insert_id_sql(&self) -> Option<&'static str> {
2718 Some("SELECT DBINFO('sqlca.sqlerrd1') FROM systables WHERE tabid = 1")
2719 }
2720
2721 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2722 let cols: Vec<String> = columns
2723 .iter()
2724 .map(|col| {
2725 let informix_type = map_to_informix_type(&col.sql_type);
2726 let mut sql = format!("{} {}", self.quote(&col.name), informix_type);
2727 if !col.nullable && !col.auto_increment {
2728 sql.push_str(" NOT NULL");
2729 }
2730 if let Some(default) = &col.default {
2731 sql.push_str(&format!(" DEFAULT {}", default));
2732 }
2733 if col.primary_key {
2734 sql.push_str(" PRIMARY KEY");
2735 }
2736 sql
2737 })
2738 .collect();
2739
2740 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2741 }
2742
2743 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2744 let stmts: Vec<String> = changes
2745 .iter()
2746 .map(|change| match change {
2747 TableChange::AddColumn(col) => {
2748 let informix_type = map_to_informix_type(&col.sql_type);
2749 let mut sql = format!(
2750 "ALTER TABLE {} ADD COLUMN {} {}",
2751 self.quote(table),
2752 self.quote(&col.name),
2753 informix_type
2754 );
2755 if !col.nullable {
2756 sql.push_str(" NOT NULL");
2757 }
2758 if let Some(default) = &col.default {
2759 sql.push_str(&format!(" DEFAULT {}", default));
2760 }
2761 sql
2762 }
2763 TableChange::DropColumn(name) => {
2764 format!(
2765 "ALTER TABLE {} DROP COLUMN {}",
2766 self.quote(table),
2767 self.quote(name)
2768 )
2769 }
2770 TableChange::ModifyColumn(col) => {
2771 let informix_type = map_to_informix_type(&col.sql_type);
2772 format!(
2773 "ALTER TABLE {} MODIFY {} {}",
2774 self.quote(table),
2775 self.quote(&col.name),
2776 informix_type
2777 )
2778 }
2779 TableChange::AddIndex(name, cols) => {
2780 format!(
2781 "CREATE INDEX {} ON {} ({})",
2782 name,
2783 self.quote(table),
2784 cols.join(", ")
2785 )
2786 }
2787 TableChange::DropIndex(name) => {
2788 format!("DROP INDEX {}", name)
2789 }
2790 TableChange::AddForeignKey {
2791 columns,
2792 reference_table,
2793 reference_columns,
2794 } => {
2795 format!(
2796 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
2797 self.quote(table),
2798 columns
2799 .iter()
2800 .map(|c| self.quote(c))
2801 .collect::<Vec<_>>()
2802 .join(", "),
2803 self.quote(reference_table),
2804 reference_columns
2805 .iter()
2806 .map(|c| self.quote(c))
2807 .collect::<Vec<_>>()
2808 .join(", ")
2809 )
2810 }
2811 })
2812 .collect();
2813
2814 stmts.join("; ")
2815 }
2816}
2817
2818#[cfg(feature = "dialect-informix")]
2820fn map_to_informix_type(sql_type: &str) -> String {
2821 let upper = sql_type.to_uppercase();
2822 let trimmed = upper.trim();
2823
2824 if trimmed.starts_with("BIGINT") {
2825 "BIGINT".to_string()
2826 } else if matches!(trimmed, "INT" | "INTEGER") {
2827 "INTEGER".to_string()
2828 } else if matches!(trimmed, "SMALLINT") {
2829 "SMALLINT".to_string()
2830 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2831 sql_type.to_string()
2832 } else if matches!(trimmed, "TEXT" | "LVARCHAR") {
2833 "LVARCHAR".to_string()
2834 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2835 "BOOLEAN".to_string()
2836 } else if matches!(trimmed, "FLOAT" | "REAL") {
2837 "SMALLFLOAT".to_string()
2838 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2839 "FLOAT".to_string()
2840 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2841 "DATETIME YEAR TO SECOND".to_string()
2842 } else if matches!(trimmed, "DATE") {
2843 "DATE".to_string()
2844 } else if trimmed.starts_with("SERIAL") {
2845 sql_type.to_string()
2846 } else {
2847 sql_type.to_string()
2848 }
2849}
2850
2851#[cfg(feature = "dialect-saphana")]
2856#[derive(Debug, Clone)]
2857pub struct SapHanaDialect;
2858
2859#[cfg(feature = "dialect-saphana")]
2860impl Dialect for SapHanaDialect {
2861 fn clone_box(&self) -> Box<dyn Dialect> {
2862 Box::new(SapHanaDialect)
2863 }
2864
2865 fn db_type(&self) -> DbType {
2866 DbType::SapHana
2867 }
2868
2869 fn quote(&self, identifier: &str) -> String {
2870 format!("\"{}\"", identifier.replace('"', "\"\""))
2871 }
2872
2873 fn escape_string(&self, s: &str) -> String {
2874 let mut escaped = String::with_capacity(s.len() * 2);
2875 for c in s.chars() {
2876 match c {
2877 '\'' => escaped.push_str("''"),
2878 _ => escaped.push(c),
2879 }
2880 }
2881 escaped
2882 }
2883
2884 fn supports_returning(&self) -> bool {
2885 false
2886 }
2887
2888 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2889 let offset = page.saturating_sub(1).saturating_mul(limit);
2890 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
2891 }
2892
2893 fn json_type(&self) -> &'static str {
2894 "NCLOB"
2895 }
2896
2897 fn json_extract(&self, column: &str, path: &str) -> String {
2898 let normalized = if path.starts_with('$') {
2899 path.to_string()
2900 } else {
2901 format!("$.{}", path)
2902 };
2903 format!(
2904 "JSON_VALUE({}, '{}')",
2905 column,
2906 self.escape_string(&normalized)
2907 )
2908 }
2909
2910 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2911 if columns.is_empty() {
2912 return "0".to_string();
2913 }
2914 let escaped = self.escape_string(keyword);
2915 let parts: Vec<String> = columns
2916 .iter()
2917 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2918 .collect();
2919 parts.join(" OR ")
2920 }
2921
2922 fn bool_to_int(&self, expr: &str) -> String {
2923 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2924 }
2925
2926 fn concat(&self, parts: &[&str]) -> String {
2927 if parts.is_empty() {
2928 return "''".to_string();
2929 }
2930 parts.join(" || ")
2931 }
2932
2933 fn supports_if_exists(&self) -> bool {
2934 true
2935 }
2936
2937 fn supports_if_not_exists(&self) -> bool {
2938 true
2939 }
2940
2941 fn auto_increment_keyword(&self) -> &'static str {
2942 "GENERATED BY DEFAULT AS IDENTITY"
2943 }
2944
2945 fn last_insert_id_sql(&self) -> Option<&'static str> {
2946 None
2947 }
2948
2949 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2950 let cols: Vec<String> = columns
2951 .iter()
2952 .map(|col| {
2953 let hana_type = map_to_saphana_type(&col.sql_type);
2954 let mut sql = format!("{} {}", self.quote(&col.name), hana_type);
2955 if !col.nullable && !col.auto_increment {
2956 sql.push_str(" NOT NULL");
2957 }
2958 if let Some(default) = &col.default {
2959 sql.push_str(&format!(" DEFAULT {}", default));
2960 }
2961 if col.auto_increment {
2962 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2963 }
2964 if col.primary_key {
2965 sql.push_str(" PRIMARY KEY");
2966 }
2967 sql
2968 })
2969 .collect();
2970
2971 format!(
2972 "CREATE COLUMN TABLE {} ({})",
2973 self.quote(table),
2974 cols.join(", ")
2975 )
2976 }
2977
2978 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2979 let stmts: Vec<String> = changes
2980 .iter()
2981 .map(|change| match change {
2982 TableChange::AddColumn(col) => {
2983 let hana_type = map_to_saphana_type(&col.sql_type);
2984 let mut sql = format!(
2985 "ALTER TABLE {} ADD ({})",
2986 self.quote(table),
2987 format!("{} {}", self.quote(&col.name), hana_type)
2988 );
2989 if !col.nullable {
2990 sql.push_str(" NOT NULL");
2991 }
2992 if let Some(default) = &col.default {
2993 sql.push_str(&format!(" DEFAULT {}", default));
2994 }
2995 sql
2996 }
2997 TableChange::DropColumn(name) => {
2998 format!(
2999 "ALTER TABLE {} DROP ({})",
3000 self.quote(table),
3001 self.quote(name)
3002 )
3003 }
3004 TableChange::ModifyColumn(col) => {
3005 let hana_type = map_to_saphana_type(&col.sql_type);
3006 format!(
3007 "ALTER TABLE {} ALTER ({})",
3008 self.quote(table),
3009 format!("{} {}", self.quote(&col.name), hana_type)
3010 )
3011 }
3012 TableChange::AddIndex(name, cols) => {
3013 format!(
3014 "CREATE INDEX {} ON {} ({})",
3015 name,
3016 self.quote(table),
3017 cols.join(", ")
3018 )
3019 }
3020 TableChange::DropIndex(name) => {
3021 format!("DROP INDEX {}", name)
3022 }
3023 TableChange::AddForeignKey {
3024 columns,
3025 reference_table,
3026 reference_columns,
3027 } => {
3028 format!(
3029 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
3030 self.quote(table),
3031 columns
3032 .iter()
3033 .map(|c| self.quote(c))
3034 .collect::<Vec<_>>()
3035 .join(", "),
3036 self.quote(reference_table),
3037 reference_columns
3038 .iter()
3039 .map(|c| self.quote(c))
3040 .collect::<Vec<_>>()
3041 .join(", ")
3042 )
3043 }
3044 })
3045 .collect();
3046
3047 stmts.join("; ")
3048 }
3049}
3050
3051#[cfg(feature = "dialect-saphana")]
3053fn map_to_saphana_type(sql_type: &str) -> String {
3054 let upper = sql_type.to_uppercase();
3055 let trimmed = upper.trim();
3056
3057 if trimmed.starts_with("BIGINT") {
3058 "BIGINT".to_string()
3059 } else if matches!(trimmed, "INT" | "INTEGER") {
3060 "INTEGER".to_string()
3061 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
3062 "SMALLINT".to_string()
3063 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("NVARCHAR") {
3064 sql_type.to_string()
3065 } else if trimmed.starts_with("CHAR") || trimmed.starts_with("NCHAR") {
3066 sql_type.to_string()
3067 } else if matches!(trimmed, "TEXT" | "CLOB") {
3068 "NCLOB".to_string()
3069 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3070 "BOOLEAN".to_string()
3071 } else if matches!(trimmed, "FLOAT" | "REAL") {
3072 "REAL".to_string()
3073 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3074 "DOUBLE".to_string()
3075 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3076 "TIMESTAMP".to_string()
3077 } else if matches!(trimmed, "DATE") {
3078 "DATE".to_string()
3079 } else {
3080 sql_type.to_string()
3081 }
3082}
3083
3084#[cfg(feature = "dialect-firebird")]
3089#[derive(Debug, Clone)]
3090pub struct FirebirdDialect;
3091
3092#[cfg(feature = "dialect-firebird")]
3093impl Dialect for FirebirdDialect {
3094 fn clone_box(&self) -> Box<dyn Dialect> {
3095 Box::new(FirebirdDialect)
3096 }
3097
3098 fn db_type(&self) -> DbType {
3099 DbType::Firebird
3100 }
3101
3102 fn quote(&self, identifier: &str) -> String {
3103 format!("\"{}\"", identifier.replace('"', "\"\""))
3104 }
3105
3106 fn escape_string(&self, s: &str) -> String {
3107 let mut escaped = String::with_capacity(s.len() * 2);
3108 for c in s.chars() {
3109 match c {
3110 '\'' => escaped.push_str("''"),
3111 _ => escaped.push(c),
3112 }
3113 }
3114 escaped
3115 }
3116
3117 fn supports_returning(&self) -> bool {
3118 true
3119 }
3120
3121 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
3122 let offset = page.saturating_sub(1).saturating_mul(limit);
3123 let to = offset + limit;
3124 format!("{} ROWS {} TO {}", sql, offset + 1, to)
3125 }
3126
3127 fn json_type(&self) -> &'static str {
3128 "BLOB SUB_TYPE TEXT"
3129 }
3130
3131 fn json_extract(&self, column: &str, path: &str) -> String {
3132 let normalized = if path.starts_with('$') {
3133 path.to_string()
3134 } else {
3135 format!("$.{}", path)
3136 };
3137 format!(
3138 "JSON_GET({}, '{}')",
3139 column,
3140 self.escape_string(&normalized)
3141 )
3142 }
3143
3144 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
3145 if columns.is_empty() {
3146 return "0".to_string();
3147 }
3148 let escaped = self.escape_string(keyword);
3149 let parts: Vec<String> = columns
3150 .iter()
3151 .map(|c| format!("CONTAINING({}, '{}')", c, escaped))
3152 .collect();
3153 parts.join(" OR ")
3154 }
3155
3156 fn bool_to_int(&self, expr: &str) -> String {
3157 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
3158 }
3159
3160 fn concat(&self, parts: &[&str]) -> String {
3161 if parts.is_empty() {
3162 return "''".to_string();
3163 }
3164 parts.join(" || ")
3165 }
3166
3167 fn supports_if_exists(&self) -> bool {
3168 true
3169 }
3170
3171 fn supports_if_not_exists(&self) -> bool {
3172 true
3173 }
3174
3175 fn auto_increment_keyword(&self) -> &'static str {
3176 "GENERATED BY DEFAULT AS IDENTITY"
3177 }
3178
3179 fn last_insert_id_sql(&self) -> Option<&'static str> {
3180 Some("SELECT GEN_ID(SQLITE_SEQUENCE, 0) FROM RDB$DATABASE")
3181 }
3182
3183 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
3184 let cols: Vec<String> = columns
3185 .iter()
3186 .map(|col| {
3187 let fb_type = map_to_firebird_type(&col.sql_type);
3188 let mut sql = format!("{} {}", self.quote(&col.name), fb_type);
3189 if !col.nullable && !col.auto_increment {
3190 sql.push_str(" NOT NULL");
3191 }
3192 if let Some(default) = &col.default {
3193 sql.push_str(&format!(" DEFAULT {}", default));
3194 }
3195 if col.auto_increment {
3196 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
3197 }
3198 if col.primary_key {
3199 sql.push_str(" PRIMARY KEY");
3200 }
3201 sql
3202 })
3203 .collect();
3204
3205 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
3206 }
3207
3208 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
3209 let stmts: Vec<String> = changes
3210 .iter()
3211 .map(|change| match change {
3212 TableChange::AddColumn(col) => {
3213 let fb_type = map_to_firebird_type(&col.sql_type);
3214 let mut sql = format!(
3215 "ALTER TABLE {} ADD {} {}",
3216 self.quote(table),
3217 self.quote(&col.name),
3218 fb_type
3219 );
3220 if !col.nullable {
3221 sql.push_str(" NOT NULL");
3222 }
3223 if let Some(default) = &col.default {
3224 sql.push_str(&format!(" DEFAULT {}", default));
3225 }
3226 sql
3227 }
3228 TableChange::DropColumn(name) => {
3229 format!(
3230 "ALTER TABLE {} DROP {}",
3231 self.quote(table),
3232 self.quote(name)
3233 )
3234 }
3235 TableChange::ModifyColumn(col) => {
3236 let fb_type = map_to_firebird_type(&col.sql_type);
3237 format!(
3238 "ALTER TABLE {} ALTER COLUMN {} TYPE {}",
3239 self.quote(table),
3240 self.quote(&col.name),
3241 fb_type
3242 )
3243 }
3244 TableChange::AddIndex(name, cols) => {
3245 format!(
3246 "CREATE INDEX {} ON {} ({})",
3247 name,
3248 self.quote(table),
3249 cols.join(", ")
3250 )
3251 }
3252 TableChange::DropIndex(name) => {
3253 format!("DROP INDEX {}", name)
3254 }
3255 TableChange::AddForeignKey {
3256 columns,
3257 reference_table,
3258 reference_columns,
3259 } => {
3260 format!(
3261 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
3262 self.quote(table),
3263 columns
3264 .iter()
3265 .map(|c| self.quote(c))
3266 .collect::<Vec<_>>()
3267 .join(", "),
3268 self.quote(reference_table),
3269 reference_columns
3270 .iter()
3271 .map(|c| self.quote(c))
3272 .collect::<Vec<_>>()
3273 .join(", ")
3274 )
3275 }
3276 })
3277 .collect();
3278
3279 stmts.join("; ")
3280 }
3281}
3282
3283#[cfg(feature = "dialect-firebird")]
3285fn map_to_firebird_type(sql_type: &str) -> String {
3286 let upper = sql_type.to_uppercase();
3287 let trimmed = upper.trim();
3288
3289 if trimmed.starts_with("BIGINT") {
3290 "BIGINT".to_string()
3291 } else if matches!(trimmed, "INT" | "INTEGER") {
3292 "INTEGER".to_string()
3293 } else if matches!(trimmed, "SMALLINT") {
3294 "SMALLINT".to_string()
3295 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
3296 sql_type.to_string()
3297 } else if matches!(trimmed, "TEXT" | "BLOB") {
3298 "BLOB SUB_TYPE TEXT".to_string()
3299 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3300 "BOOLEAN".to_string()
3301 } else if matches!(trimmed, "FLOAT" | "REAL") {
3302 "FLOAT".to_string()
3303 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3304 "DOUBLE PRECISION".to_string()
3305 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3306 "TIMESTAMP".to_string()
3307 } else if matches!(trimmed, "DATE") {
3308 "DATE".to_string()
3309 } else {
3310 sql_type.to_string()
3311 }
3312}
3313
3314#[cfg(test)]
3315mod tests {
3316 use super::*;
3317
3318 #[test]
3319 fn test_mysql_quote() {
3320 let dialect = MySqlDialect;
3321 assert_eq!(dialect.quote("users"), "`users`");
3322 assert_eq!(dialect.quote("user`id"), "`user``id`");
3323 }
3324
3325 #[test]
3326 fn test_mysql_escape() {
3327 let dialect = MySqlDialect;
3328 assert_eq!(dialect.escape_string("hello"), "hello");
3329 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3330 assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
3331 }
3332
3333 #[test]
3334 fn test_mysql_pagination() {
3335 let dialect = MySqlDialect;
3336 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3337 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
3338 }
3339
3340 #[test]
3341 fn test_postgres_quote() {
3342 let dialect = PostgreSqlDialect;
3343 assert_eq!(dialect.quote("users"), "\"users\"");
3344 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3345 }
3346
3347 #[test]
3348 fn test_postgres_pagination() {
3349 let dialect = PostgreSqlDialect;
3350 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3351 assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
3352 }
3353
3354 #[test]
3355 fn test_postgres_returning() {
3356 let dialect = PostgreSqlDialect;
3357 assert!(dialect.supports_returning());
3358 }
3359
3360 #[test]
3361 fn test_sqlite_quote() {
3362 let dialect = SqliteDialect;
3363 assert_eq!(dialect.quote("users"), "\"users\"");
3364 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3365 }
3366
3367 #[test]
3368 fn test_sqlite_escape() {
3369 let dialect = SqliteDialect;
3370 assert_eq!(dialect.escape_string("hello"), "hello");
3371 assert_eq!(dialect.escape_string("it's"), "it''s");
3372 }
3373
3374 #[test]
3375 fn test_get_dialect() {
3376 let dialect = get_dialect(DbType::MySQL);
3377 assert!(dialect.is_ok());
3378
3379 let dialect = get_dialect(DbType::Redis);
3380 assert!(dialect.is_err());
3381 }
3382
3383 #[test]
3384 fn test_bool_to_int() {
3385 let mysql = MySqlDialect;
3386 assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
3387
3388 let pg = PostgreSqlDialect;
3389 assert_eq!(
3390 pg.bool_to_int("active"),
3391 "(CASE WHEN active THEN 1 ELSE 0 END)"
3392 );
3393 }
3394
3395 #[test]
3396 fn test_json_extract_with_path() {
3397 let mysql = MySqlDialect;
3398 let sql = mysql.json_extract("data", "$.user.name");
3399 assert!(sql.contains("$.user.name"));
3400 assert!(sql.contains("JSON_EXTRACT"));
3401
3402 let pg = PostgreSqlDialect;
3403 let sql = pg.json_extract("data", "user.name");
3404 assert!(sql.contains("#>>"));
3405
3406 let sqlite = SqliteDialect;
3407 let sql = sqlite.json_extract("data", "$.user.name");
3408 assert!(sql.contains("$.user.name"));
3409 assert!(sql.contains("json_extract"));
3410 }
3411
3412 #[test]
3413 fn test_sqlite_full_text_search() {
3414 let sqlite = SqliteDialect;
3415 let sql = sqlite.full_text_search(&["title", "content"], "hello");
3416 assert!(sql.contains("LIKE"));
3418 assert!(sql.contains("title LIKE '%hello%'"));
3419 assert!(sql.contains("content LIKE '%hello%'"));
3420 assert!(sql.contains(" OR "));
3421
3422 assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
3424
3425 let sql = sqlite.full_text_search(&["title"], "it's");
3427 assert!(sql.contains("title LIKE '%it''s%'"));
3428 }
3429
3430 #[test]
3431 fn test_alter_table_modify_column() {
3432 let mysql = MySqlDialect;
3433 let col = ColumnDef {
3434 name: "name".to_string(),
3435 sql_type: "VARCHAR(255)".to_string(),
3436 nullable: false,
3437 default: None,
3438 auto_increment: false,
3439 primary_key: false,
3440 };
3441 let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3442 assert!(sql.contains("MODIFY COLUMN"));
3443
3444 let pg = PostgreSqlDialect;
3445 let col = ColumnDef {
3446 name: "name".to_string(),
3447 sql_type: "VARCHAR(255)".to_string(),
3448 nullable: false,
3449 default: None,
3450 auto_increment: false,
3451 primary_key: false,
3452 };
3453 let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3454 assert!(sql.contains("ALTER COLUMN"));
3455 assert!(sql.contains("TYPE"));
3456 }
3457
3458 #[test]
3459 fn test_alter_table_add_foreign_key() {
3460 let mysql = MySqlDialect;
3461 let sql = mysql.build_alter_table(
3462 "orders",
3463 &[TableChange::AddForeignKey {
3464 columns: vec!["user_id".to_string()],
3465 reference_table: "users".to_string(),
3466 reference_columns: vec!["id".to_string()],
3467 }],
3468 );
3469 assert!(sql.contains("FOREIGN KEY"));
3470 assert!(sql.contains("REFERENCES"));
3471
3472 let sqlite = SqliteDialect;
3473 let sql = sqlite.build_alter_table(
3474 "orders",
3475 &[TableChange::AddForeignKey {
3476 columns: vec!["user_id".to_string()],
3477 reference_table: "users".to_string(),
3478 reference_columns: vec!["id".to_string()],
3479 }],
3480 );
3481 assert!(sql.starts_with("--"));
3483 }
3484
3485 #[test]
3486 fn test_sqlite_alter_table_add_column() {
3487 let sqlite = SqliteDialect;
3488 let col = ColumnDef {
3489 name: "email".to_string(),
3490 sql_type: "TEXT".to_string(),
3491 nullable: true,
3492 default: None,
3493 auto_increment: false,
3494 primary_key: false,
3495 };
3496 let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
3497 assert!(sql.contains("ADD COLUMN"));
3498 assert!(sql.contains("email"));
3499 }
3500
3501 #[test]
3504 fn test_oracle_quote_and_escape() {
3505 let dialect = OracleDialect;
3506 assert_eq!(dialect.quote("users"), "\"users\"");
3508 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3509 assert_eq!(dialect.quote("column_name"), "\"column_name\"");
3510
3511 assert_eq!(dialect.escape_string("hello"), "hello");
3513 assert_eq!(dialect.escape_string("it's"), "it''s");
3514 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3515 assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
3516 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3518 }
3519
3520 #[test]
3521 fn test_oracle_pagination() {
3522 let dialect = OracleDialect;
3523 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3525 assert_eq!(
3526 sql,
3527 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3528 );
3529 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3531 assert_eq!(
3532 sql,
3533 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3534 );
3535 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3537 assert_eq!(
3538 sql,
3539 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3540 );
3541 }
3542
3543 #[test]
3544 fn test_oracle_json_extract() {
3545 let dialect = OracleDialect;
3546 let sql = dialect.json_extract("data", "$.user.name");
3548 assert!(sql.contains("JSON_VALUE"));
3549 assert!(sql.contains("$.user.name"));
3550 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3551
3552 let sql = dialect.json_extract("data", "user.name");
3554 assert!(sql.contains("$.user.name"));
3555 assert!(sql.contains("JSON_VALUE"));
3556
3557 let sql = dialect.json_extract("data", "$.key's");
3559 assert!(sql.contains("$.key''s"));
3560 }
3561
3562 #[test]
3563 fn test_oracle_create_table() {
3564 let dialect = OracleDialect;
3565 let columns = vec![
3566 ColumnDef {
3567 name: "id".to_string(),
3568 sql_type: "BIGINT".to_string(),
3569 nullable: false,
3570 default: None,
3571 auto_increment: true,
3572 primary_key: true,
3573 },
3574 ColumnDef {
3575 name: "name".to_string(),
3576 sql_type: "VARCHAR(255)".to_string(),
3577 nullable: false,
3578 default: None,
3579 auto_increment: false,
3580 primary_key: false,
3581 },
3582 ColumnDef {
3583 name: "bio".to_string(),
3584 sql_type: "TEXT".to_string(),
3585 nullable: true,
3586 default: None,
3587 auto_increment: false,
3588 primary_key: false,
3589 },
3590 ColumnDef {
3591 name: "is_active".to_string(),
3592 sql_type: "BOOLEAN".to_string(),
3593 nullable: false,
3594 default: Some("1".to_string()),
3595 auto_increment: false,
3596 primary_key: false,
3597 },
3598 ];
3599 let sql = dialect.build_create_table("users", &columns);
3600 assert!(
3602 sql.contains("NUMBER(19)"),
3603 "BIGINT should map to NUMBER(19): {}",
3604 sql
3605 );
3606 assert!(
3607 sql.contains("VARCHAR2(255)"),
3608 "VARCHAR should map to VARCHAR2: {}",
3609 sql
3610 );
3611 assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
3612 assert!(
3613 sql.contains("NUMBER(1)"),
3614 "BOOLEAN should map to NUMBER(1): {}",
3615 sql
3616 );
3617 assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
3619 assert!(sql.contains("PRIMARY KEY"));
3620 assert!(sql.contains("NOT NULL"));
3621 assert!(sql.contains("DEFAULT 1"));
3622 assert!(sql.contains("\"users\""));
3624 assert!(sql.contains("\"id\""));
3625 }
3626
3627 #[test]
3628 fn test_oracle_bool_to_int_and_concat() {
3629 let dialect = OracleDialect;
3630 assert_eq!(
3632 dialect.bool_to_int("active"),
3633 "(CASE WHEN active THEN 1 ELSE 0 END)"
3634 );
3635 assert_eq!(
3636 dialect.bool_to_int("x > 0"),
3637 "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
3638 );
3639 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
3641 assert_eq!(
3642 dialect.concat(&["first_name", "last_name"]),
3643 "first_name || last_name"
3644 );
3645 assert_eq!(dialect.concat(&[]), "NULL");
3647 }
3648
3649 #[test]
3650 fn test_oracle_misc_dialect_methods() {
3651 let dialect = OracleDialect;
3652 assert_eq!(dialect.db_type(), DbType::Oracle);
3654 assert!(dialect.supports_returning());
3656 assert!(dialect.supports_if_exists());
3658 assert!(dialect.supports_if_not_exists());
3659 assert_eq!(
3661 dialect.auto_increment_keyword(),
3662 "GENERATED BY DEFAULT AS IDENTITY"
3663 );
3664 assert_eq!(dialect.last_insert_id_sql(), None);
3667 assert_eq!(dialect.json_type(), "JSON");
3669 }
3670
3671 #[test]
3672 fn test_oracle_get_dialect() {
3673 let dialect = get_dialect(DbType::Oracle);
3675 assert!(dialect.is_ok(), "Oracle dialect should be available");
3676 let dialect = dialect.unwrap();
3677 assert_eq!(dialect.db_type(), DbType::Oracle);
3678 assert_eq!(dialect.quote("users"), "\"users\"");
3680 assert!(dialect.supports_returning());
3682 assert_eq!(dialect.last_insert_id_sql(), None);
3684 }
3685
3686 #[test]
3687 fn test_oracle_drop_table() {
3688 let dialect = OracleDialect;
3689 let sql = dialect.build_drop_table("users", true);
3691 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
3692 let sql = dialect.build_drop_table("users", false);
3694 assert_eq!(sql, "DROP TABLE \"users\"");
3695 }
3696
3697 #[test]
3698 fn test_oracle_alter_table() {
3699 let dialect = OracleDialect;
3700 let col = ColumnDef {
3702 name: "name".to_string(),
3703 sql_type: "VARCHAR(255)".to_string(),
3704 nullable: false,
3705 default: None,
3706 auto_increment: false,
3707 primary_key: false,
3708 };
3709 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3710 assert!(sql.contains("MODIFY"));
3711 assert!(sql.contains("VARCHAR2(255)"));
3712 assert!(!sql.contains("MODIFY COLUMN")); let col = ColumnDef {
3716 name: "email".to_string(),
3717 sql_type: "VARCHAR(255)".to_string(),
3718 nullable: true,
3719 default: None,
3720 auto_increment: false,
3721 primary_key: false,
3722 };
3723 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
3724 assert!(sql.contains("ADD \"email\""));
3725 assert!(sql.contains("VARCHAR2(255)"));
3726
3727 let sql =
3729 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
3730 assert!(sql.contains("DROP COLUMN"));
3731 assert!(sql.contains("\"email\""));
3732 }
3733
3734 #[test]
3737 fn test_sqlite_concat_handles_null() {
3738 let sqlite = SqliteDialect;
3739 let sql = sqlite.concat(&["a", "b"]);
3742 assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
3743 let sql = sqlite.concat(&["a"]);
3745 assert_eq!(sql, "COALESCE(a, '')");
3746 assert_eq!(sqlite.concat(&[]), "NULL");
3748 }
3749
3750 #[test]
3753 fn test_sqlserver_quote_and_escape() {
3754 let dialect = SqlServerDialect;
3755 assert_eq!(dialect.quote("users"), "[users]");
3757 assert_eq!(dialect.quote("col]name"), "[col]]name]");
3758 assert_eq!(dialect.escape_string("hello"), "hello");
3760 assert_eq!(dialect.escape_string("it's"), "it''s");
3761 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3762 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3763 }
3764
3765 #[test]
3766 fn test_sqlserver_pagination() {
3767 let dialect = SqlServerDialect;
3768 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3770 assert_eq!(
3771 sql,
3772 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3773 );
3774 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3775 assert_eq!(
3776 sql,
3777 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3778 );
3779 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3781 assert_eq!(
3782 sql,
3783 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3784 );
3785 }
3786
3787 #[test]
3788 fn test_sqlserver_misc_dialect_methods() {
3789 let dialect = SqlServerDialect;
3790 assert_eq!(dialect.db_type(), DbType::SqlServer);
3791 assert!(dialect.supports_returning());
3793 assert!(dialect.supports_if_exists());
3795 assert!(dialect.supports_if_not_exists());
3796 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
3798 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
3800 assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
3802 }
3803
3804 #[test]
3805 fn test_sqlserver_insert_or_ignore_fallback_to_plain_insert() {
3806 let dialect = SqlServerDialect;
3807 let sql = dialect.build_insert_or_ignore_prefix("users");
3808 assert_eq!(sql, "INSERT INTO [users]");
3810 assert!(!sql.contains("OR IGNORE"));
3811 }
3812
3813 #[test]
3814 fn test_sqlserver_json_extract() {
3815 let dialect = SqlServerDialect;
3816 let sql = dialect.json_extract("data", "$.user.name");
3817 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3818 let sql = dialect.json_extract("data", "user.name");
3820 assert!(sql.contains("$.user.name"));
3821 assert!(sql.contains("JSON_VALUE"));
3822 let sql = dialect.json_extract("data", "$.key's");
3824 assert!(sql.contains("$.key''s"));
3825 }
3826
3827 #[test]
3828 fn test_sqlserver_full_text_search() {
3829 let dialect = SqlServerDialect;
3830 let sql = dialect.full_text_search(&["title", "content"], "hello");
3831 assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
3832 assert_eq!(dialect.full_text_search(&[], "hello"), "0");
3834 let sql = dialect.full_text_search(&["title"], "it's");
3836 assert!(sql.contains("it''s"));
3837 }
3838
3839 #[test]
3840 fn test_sqlserver_bool_to_int_and_concat() {
3841 let dialect = SqlServerDialect;
3842 assert_eq!(
3843 dialect.bool_to_int("active"),
3844 "(CASE WHEN active THEN 1 ELSE 0 END)"
3845 );
3846 assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
3847 assert_eq!(dialect.concat(&[]), "NULL");
3848 }
3849
3850 #[test]
3851 fn test_sqlserver_create_table() {
3852 let dialect = SqlServerDialect;
3853 let columns = vec![
3854 ColumnDef {
3855 name: "id".to_string(),
3856 sql_type: "BIGINT".to_string(),
3857 nullable: false,
3858 default: None,
3859 auto_increment: true,
3860 primary_key: true,
3861 },
3862 ColumnDef {
3863 name: "name".to_string(),
3864 sql_type: "VARCHAR(255)".to_string(),
3865 nullable: false,
3866 default: None,
3867 auto_increment: false,
3868 primary_key: false,
3869 },
3870 ColumnDef {
3871 name: "bio".to_string(),
3872 sql_type: "TEXT".to_string(),
3873 nullable: true,
3874 default: None,
3875 auto_increment: false,
3876 primary_key: false,
3877 },
3878 ColumnDef {
3879 name: "is_active".to_string(),
3880 sql_type: "BOOLEAN".to_string(),
3881 nullable: false,
3882 default: Some("1".to_string()),
3883 auto_increment: false,
3884 primary_key: false,
3885 },
3886 ];
3887 let sql = dialect.build_create_table("users", &columns);
3888 assert!(sql.contains("[users]"));
3890 assert!(sql.contains("[id]"));
3891 assert!(sql.contains("IDENTITY(1,1)"));
3893 assert!(
3894 sql.contains("NVARCHAR(255)"),
3895 "VARCHAR should map to NVARCHAR: {}",
3896 sql
3897 );
3898 assert!(
3899 sql.contains("NVARCHAR(MAX)"),
3900 "TEXT should map to NVARCHAR(MAX): {}",
3901 sql
3902 );
3903 assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
3904 assert!(sql.contains("PRIMARY KEY"));
3905 assert!(sql.contains("NOT NULL"));
3906 assert!(sql.contains("DEFAULT 1"));
3907 }
3908
3909 #[test]
3910 fn test_sqlserver_drop_table() {
3911 let dialect = SqlServerDialect;
3912 assert_eq!(
3913 dialect.build_drop_table("users", true),
3914 "DROP TABLE IF EXISTS [users]"
3915 );
3916 assert_eq!(
3917 dialect.build_drop_table("users", false),
3918 "DROP TABLE [users]"
3919 );
3920 }
3921
3922 #[test]
3923 fn test_sqlserver_alter_table() {
3924 let dialect = SqlServerDialect;
3925 let col = ColumnDef {
3927 name: "name".to_string(),
3928 sql_type: "VARCHAR(255)".to_string(),
3929 nullable: false,
3930 default: None,
3931 auto_increment: false,
3932 primary_key: false,
3933 };
3934 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3935 assert!(sql.contains("ALTER COLUMN"));
3936 assert!(sql.contains("NVARCHAR(255)"));
3937 assert!(!sql.contains("MODIFY"));
3938
3939 let col = ColumnDef {
3941 name: "email".to_string(),
3942 sql_type: "VARCHAR(255)".to_string(),
3943 nullable: true,
3944 default: None,
3945 auto_increment: false,
3946 primary_key: false,
3947 };
3948 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
3949 assert!(sql.contains("ADD [email]"));
3950 assert!(sql.contains("NVARCHAR(255)"));
3951
3952 let sql =
3954 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
3955 assert!(sql.contains("DROP COLUMN"));
3956 assert!(sql.contains("[email]"));
3957
3958 let sql =
3960 dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
3961 assert!(sql.contains("DROP INDEX idx_name ON [users]"));
3962 }
3963
3964 #[test]
3965 fn test_sqlserver_get_dialect() {
3966 let dialect = get_dialect(DbType::SqlServer);
3968 assert!(dialect.is_ok(), "SqlServer dialect should be available");
3969 let dialect = dialect.unwrap();
3970 assert_eq!(dialect.db_type(), DbType::SqlServer);
3971 assert_eq!(dialect.quote("users"), "[users]");
3973 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
3975 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
3976 }
3977
3978 #[test]
3979 fn test_clickhouse_get_dialect_unsupported() {
3980 let dialect = get_dialect(DbType::ClickHouse);
3982 assert!(dialect.is_ok(), "ClickHouse should be supported");
3983 let dialect = dialect.unwrap();
3984 assert_eq!(dialect.db_type(), DbType::ClickHouse);
3985 assert_eq!(dialect.quote("users"), "`users`");
3987 assert!(!dialect.supports_returning());
3989 let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
3991 assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
3992 assert_eq!(dialect.auto_increment_keyword(), "");
3994 }
3995
3996 #[test]
3997 fn test_get_dialect_all_supported_types() {
3998 assert!(get_dialect(DbType::MySQL).is_ok());
4000 assert!(get_dialect(DbType::PostgreSQL).is_ok());
4001 assert!(get_dialect(DbType::Sqlite).is_ok());
4002 assert!(get_dialect(DbType::Oracle).is_ok());
4003 assert!(get_dialect(DbType::SqlServer).is_ok());
4004 assert!(get_dialect(DbType::OceanBase).is_ok());
4005 assert!(get_dialect(DbType::ClickHouse).is_ok());
4006 assert!(get_dialect(DbType::Dameng).is_ok());
4008 assert!(get_dialect(DbType::Kingbase).is_ok());
4009 assert!(get_dialect(DbType::Db2).is_ok());
4010 assert!(get_dialect(DbType::MariaDB).is_ok());
4011 assert!(get_dialect(DbType::TiDB).is_ok());
4012 assert!(get_dialect(DbType::PolarDB).is_ok());
4013 assert!(get_dialect(DbType::GaussDB).is_ok());
4014 assert!(get_dialect(DbType::GBase).is_ok());
4015 assert!(get_dialect(DbType::Sybase).is_ok());
4016 assert!(get_dialect(DbType::Redis).is_err());
4018 assert!(get_dialect(DbType::MongoDB).is_err());
4019 assert!(get_dialect(DbType::VectorDb).is_err());
4020 assert!(get_dialect(DbType::PureJsDb).is_err());
4021 }
4022
4023 #[test]
4026 fn test_mariadb_dialect() {
4027 let dialect = get_dialect(DbType::MariaDB).unwrap();
4028 assert_eq!(dialect.db_type(), DbType::MariaDB);
4029 assert_eq!(dialect.quote("users"), "`users`");
4031 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4032 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4033 assert!(!dialect.supports_returning());
4035 }
4036
4037 #[test]
4038 fn test_tidb_dialect() {
4039 let dialect = get_dialect(DbType::TiDB).unwrap();
4040 assert_eq!(dialect.db_type(), DbType::TiDB);
4041 assert_eq!(dialect.quote("users"), "`users`");
4043 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4044 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4045 }
4046
4047 #[test]
4048 fn test_dameng_dialect() {
4049 let dialect = get_dialect(DbType::Dameng).unwrap();
4050 assert_eq!(dialect.db_type(), DbType::Dameng);
4051 assert_eq!(dialect.quote("users"), "\"users\"");
4053 assert_eq!(dialect.escape_string("it's"), "it''s");
4054 assert_eq!(
4056 dialect.auto_increment_keyword(),
4057 "GENERATED BY DEFAULT AS IDENTITY"
4058 );
4059 assert!(dialect.supports_returning());
4061 }
4062
4063 #[test]
4064 fn test_kingbase_dialect() {
4065 let dialect = get_dialect(DbType::Kingbase).unwrap();
4066 assert_eq!(dialect.db_type(), DbType::Kingbase);
4067 assert_eq!(dialect.quote("users"), "\"users\"");
4069 assert_eq!(dialect.escape_string("it's"), "it''s");
4070 assert!(dialect.supports_returning());
4072 assert_eq!(
4074 dialect.auto_increment_keyword(),
4075 "GENERATED BY DEFAULT AS IDENTITY"
4076 );
4077 }
4078
4079 #[test]
4080 fn test_polardb_dialect() {
4081 let dialect = get_dialect(DbType::PolarDB).unwrap();
4082 assert_eq!(dialect.db_type(), DbType::PolarDB);
4083 assert_eq!(dialect.quote("users"), "\"users\"");
4085 assert!(dialect.supports_returning());
4086 }
4087
4088 #[test]
4089 fn test_gaussdb_dialect() {
4090 let dialect = get_dialect(DbType::GaussDB).unwrap();
4091 assert_eq!(dialect.db_type(), DbType::GaussDB);
4092 assert_eq!(dialect.quote("users"), "\"users\"");
4094 assert!(dialect.supports_returning());
4095 }
4096
4097 #[test]
4098 fn test_gbase_dialect() {
4099 let dialect = get_dialect(DbType::GBase).unwrap();
4100 assert_eq!(dialect.db_type(), DbType::GBase);
4101 assert_eq!(dialect.quote("users"), "[users]");
4103 }
4104
4105 #[test]
4106 fn test_sybase_dialect() {
4107 let dialect = get_dialect(DbType::Sybase).unwrap();
4108 assert_eq!(dialect.db_type(), DbType::Sybase);
4109 assert_eq!(dialect.quote("users"), "[users]");
4111 }
4112
4113 #[test]
4116 fn test_db2_dialect_basic() {
4117 let dialect = get_dialect(DbType::Db2).unwrap();
4118 assert_eq!(dialect.db_type(), DbType::Db2);
4119 assert_eq!(dialect.quote("users"), "\"users\"");
4121 assert_eq!(dialect.escape_string("it's"), "it''s");
4123 assert_eq!(
4125 dialect.auto_increment_keyword(),
4126 "GENERATED ALWAYS AS IDENTITY"
4127 );
4128 assert!(!dialect.supports_if_exists());
4130 assert!(!dialect.supports_if_not_exists());
4131 assert!(!dialect.supports_returning());
4133 }
4134
4135 #[test]
4136 fn test_db2_pagination() {
4137 let dialect = Db2Dialect;
4138 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4140 assert_eq!(
4141 sql,
4142 "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
4143 );
4144 }
4145
4146 #[test]
4147 fn test_db2_last_insert_id() {
4148 let dialect = Db2Dialect;
4149 assert_eq!(
4151 dialect.last_insert_id_sql(),
4152 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
4153 );
4154 }
4155
4156 #[test]
4157 fn test_db2_concat() {
4158 let dialect = Db2Dialect;
4159 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
4161 assert_eq!(dialect.concat(&[]), "''");
4162 }
4163
4164 #[test]
4165 fn test_db2_create_table() {
4166 let dialect = Db2Dialect;
4167 let cols = vec![ColumnDef {
4168 name: "id".to_string(),
4169 sql_type: "BIGINT".to_string(),
4170 nullable: false,
4171 default: None,
4172 auto_increment: true,
4173 primary_key: true,
4174 }];
4175 let sql = dialect.build_create_table("users", &cols);
4176 assert!(sql.contains("\"id\" BIGINT"));
4177 assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
4178 assert!(sql.contains("PRIMARY KEY"));
4179 }
4180
4181 #[test]
4182 fn test_db2_type_mapping() {
4183 assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
4185 assert_eq!(map_to_db2_type("INT"), "INTEGER");
4186 assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
4187 assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
4188 assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
4189 assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
4190 assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
4191 assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
4192 assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
4193 assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
4194 assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
4195 assert_eq!(map_to_db2_type("DATE"), "DATE");
4196 assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
4197 }
4198
4199 #[test]
4202 fn test_clickhouse_dialect_basic() {
4203 let dialect = get_dialect(DbType::ClickHouse).unwrap();
4204 assert_eq!(dialect.db_type(), DbType::ClickHouse);
4205 assert_eq!(dialect.quote("users"), "`users`");
4207 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4209 assert!(!dialect.supports_returning());
4211 assert_eq!(dialect.auto_increment_keyword(), "");
4213 assert!(dialect.supports_if_exists());
4215 assert!(dialect.supports_if_not_exists());
4216 }
4217
4218 #[test]
4219 fn test_clickhouse_type_mapping() {
4220 assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
4221 assert_eq!(map_to_clickhouse_type("INT"), "Int32");
4222 assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
4223 assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
4224 assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
4225 assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
4226 assert_eq!(map_to_clickhouse_type("TEXT"), "String");
4227 assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
4228 assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
4229 assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
4230 assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
4231 assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
4232 assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
4233 assert_eq!(map_to_clickhouse_type("DATE"), "Date");
4234 }
4235
4236 #[test]
4237 fn test_clickhouse_create_table() {
4238 let dialect = ClickHouseDialect;
4239 let cols = vec![ColumnDef {
4240 name: "id".to_string(),
4241 sql_type: "BIGINT".to_string(),
4242 nullable: false,
4243 default: None,
4244 auto_increment: false, primary_key: true,
4246 }];
4247 let sql = dialect.build_create_table("users", &cols);
4248 assert!(
4250 sql.contains("ENGINE = MergeTree()"),
4251 "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
4252 sql
4253 );
4254 assert!(sql.contains("`id` Int64"));
4255 assert!(sql.contains("PRIMARY KEY"));
4256 }
4257
4258 #[test]
4259 fn test_clickhouse_json_extract() {
4260 let dialect = ClickHouseDialect;
4261 let sql = dialect.json_extract("data", "$.name");
4262 assert!(
4263 sql.contains("JSONExtractString"),
4264 "ClickHouse 应使用 JSONExtractString: {}",
4265 sql
4266 );
4267 }
4268
4269 #[test]
4270 fn test_clickhouse_concat() {
4271 let dialect = ClickHouseDialect;
4272 assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
4274 assert_eq!(dialect.concat(&[]), "''");
4275 }
4276
4277 #[test]
4280 fn test_db_type_dameng_str() {
4281 assert_eq!(DbType::Dameng.as_str(), "dameng");
4282 assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
4283 assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
4284 assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
4285 assert_eq!(DbType::Dameng.default_port(), 5236);
4286 }
4287
4288 #[test]
4289 fn test_db_type_kingbase_str() {
4290 assert_eq!(DbType::Kingbase.as_str(), "kingbase");
4291 assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
4292 assert_eq!(DbType::Kingbase.default_port(), 54321);
4293 }
4294
4295 #[test]
4296 fn test_db_type_db2_str() {
4297 assert_eq!(DbType::Db2.as_str(), "db2");
4298 assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
4299 assert_eq!(DbType::Db2.default_port(), 50000);
4300 }
4301
4302 #[test]
4303 fn test_db_type_mariadb_str() {
4304 assert_eq!(DbType::MariaDB.as_str(), "mariadb");
4305 assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
4306 assert_eq!(DbType::MariaDB.default_port(), 3306);
4307 }
4308
4309 #[test]
4310 fn test_db_type_tidb_str() {
4311 assert_eq!(DbType::TiDB.as_str(), "tidb");
4312 assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
4313 assert_eq!(DbType::TiDB.default_port(), 4000);
4314 }
4315
4316 #[test]
4317 fn test_db_type_polardb_str() {
4318 assert_eq!(DbType::PolarDB.as_str(), "polardb");
4319 assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
4320 assert_eq!(DbType::PolarDB.default_port(), 5432);
4321 }
4322
4323 #[test]
4324 fn test_db_type_gaussdb_str() {
4325 assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
4326 assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
4327 assert_eq!(DbType::GaussDB.default_port(), 25308);
4328 }
4329
4330 #[test]
4331 fn test_db_type_gbase_str() {
4332 assert_eq!(DbType::GBase.as_str(), "gbase");
4333 assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
4334 assert_eq!(DbType::GBase.default_port(), 9088);
4335 }
4336
4337 #[test]
4338 fn test_db_type_sybase_str() {
4339 assert_eq!(DbType::Sybase.as_str(), "sybase");
4340 assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
4341 assert_eq!(DbType::Sybase.default_port(), 5000);
4342 }
4343
4344 #[test]
4345 fn test_db_type_family_classification() {
4346 assert!(DbType::MySQL.is_mysql_family());
4348 assert!(DbType::MariaDB.is_mysql_family());
4349 assert!(DbType::TiDB.is_mysql_family());
4350 assert!(DbType::OceanBase.is_mysql_family());
4351 assert!(!DbType::PostgreSQL.is_mysql_family());
4352
4353 assert!(DbType::PostgreSQL.is_postgres_family());
4355 assert!(DbType::Kingbase.is_postgres_family());
4356 assert!(DbType::GaussDB.is_postgres_family());
4357 assert!(!DbType::MySQL.is_postgres_family());
4358
4359 assert!(DbType::Oracle.is_oracle_family());
4361 assert!(DbType::Dameng.is_oracle_family());
4362 assert!(!DbType::MySQL.is_oracle_family());
4363 }
4364
4365 #[test]
4366 fn test_db_type_supports_stored_procedure_extended() {
4367 assert!(DbType::Dameng.supports_stored_procedure());
4369 assert!(DbType::Kingbase.supports_stored_procedure());
4370 assert!(DbType::Db2.supports_stored_procedure());
4371 assert!(DbType::MariaDB.supports_stored_procedure());
4372 assert!(DbType::TiDB.supports_stored_procedure());
4373 assert!(DbType::PolarDB.supports_stored_procedure());
4374 assert!(DbType::GaussDB.supports_stored_procedure());
4375 assert!(DbType::GBase.supports_stored_procedure());
4376 assert!(DbType::Sybase.supports_stored_procedure());
4377 }
4378
4379 #[test]
4382 fn test_l4_max_identifier_len_constant() {
4383 assert_eq!(MAX_IDENTIFIER_LEN, 63);
4385 }
4386
4387 #[test]
4388 fn test_l4_quote_checked_valid_identifier() {
4389 let dialect = MySqlDialect;
4390 assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
4391 assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
4392 let name_63 = "a".repeat(63);
4394 assert!(dialect.quote_checked(&name_63).is_ok());
4395 }
4396
4397 #[test]
4398 fn test_l4_quote_checked_rejects_too_long() {
4399 let dialect = MySqlDialect;
4400 let long_name = "a".repeat(64); let result = dialect.quote_checked(&long_name);
4402 assert!(result.is_err());
4403 match result {
4404 Err(DbError::InvalidInput(msg)) => {
4405 assert!(
4406 msg.contains("too long"),
4407 "expected 'too long' error, got: {}",
4408 msg
4409 );
4410 }
4411 _ => panic!("Expected DbError::InvalidInput"),
4412 }
4413 }
4414
4415 #[test]
4416 fn test_l4_quote_checked_rejects_empty() {
4417 let dialect = MySqlDialect;
4418 let result = dialect.quote_checked("");
4419 assert!(result.is_err());
4420 }
4421
4422 #[test]
4423 fn test_l4_quote_checked_rejects_sql_injection() {
4424 let dialect = MySqlDialect;
4425 assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
4427 assert!(dialect.quote_checked("user'name").is_err());
4429 assert!(dialect.quote_checked("user name").is_err());
4431 assert!(dialect.quote_checked("1users").is_err());
4433 assert!(dialect.quote_checked("schema.table").is_err());
4435 }
4436
4437 #[test]
4438 fn test_l4_quote_checked_postgres() {
4439 let dialect = PostgreSqlDialect;
4440 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4441 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4442 }
4443
4444 #[test]
4445 fn test_l4_quote_checked_sqlite() {
4446 let dialect = SqliteDialect;
4447 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4448 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4449 }
4450
4451 #[test]
4452 fn test_l4_quote_checked_oracle() {
4453 let dialect = OracleDialect;
4454 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4455 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4456 }
4457
4458 #[test]
4459 fn test_l4_quote_checked_sql_server() {
4460 let dialect = SqlServerDialect;
4461 assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
4462 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4463 }
4464
4465 #[test]
4468 fn test_duckdb_quote() {
4469 let dialect = DuckDBDialect;
4470 assert_eq!(dialect.quote("users"), "\"users\"");
4471 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
4472 }
4473
4474 #[test]
4475 fn test_duckdb_escape() {
4476 let dialect = DuckDBDialect;
4477 assert_eq!(dialect.escape_string("hello"), "hello");
4478 assert_eq!(dialect.escape_string("it's"), "it''s");
4479 }
4480
4481 #[test]
4482 fn test_duckdb_pagination() {
4483 let dialect = DuckDBDialect;
4484 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4485 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
4486 }
4487
4488 #[test]
4489 fn test_duckdb_supports() {
4490 let dialect = DuckDBDialect;
4491 assert!(!dialect.supports_returning());
4492 assert!(!dialect.supports_lock_for_update());
4493 assert!(!dialect.supports_lock_shared());
4494 assert!(dialect.supports_if_exists());
4495 assert!(dialect.supports_if_not_exists());
4496 }
4497
4498 #[test]
4499 fn test_duckdb_insert_or_ignore() {
4500 let dialect = DuckDBDialect;
4501 let sql = dialect.build_insert_or_ignore_prefix("users");
4502 assert_eq!(sql, "INSERT OR IGNORE INTO \"users\"");
4503 }
4504
4505 #[test]
4506 fn test_duckdb_create_table() {
4507 let dialect = DuckDBDialect;
4508 let columns = vec![
4509 ColumnDef {
4510 name: "id".to_string(),
4511 sql_type: "BIGINT".to_string(),
4512 nullable: false,
4513 default: None,
4514 auto_increment: true,
4515 primary_key: true,
4516 },
4517 ColumnDef {
4518 name: "name".to_string(),
4519 sql_type: "VARCHAR(255)".to_string(),
4520 nullable: false,
4521 default: None,
4522 auto_increment: false,
4523 primary_key: false,
4524 },
4525 ];
4526 let sql = dialect.build_create_table("users", &columns);
4527 assert!(sql.contains("CREATE TABLE \"users\""));
4528 assert!(sql.contains("\"id\" BIGINT PRIMARY KEY"));
4529 assert!(sql.contains("\"name\" VARCHAR(255)"));
4530 }
4531
4532 #[test]
4533 fn test_duckdb_alter_table() {
4534 let dialect = DuckDBDialect;
4535 let changes = vec![TableChange::AddColumn(ColumnDef {
4536 name: "age".to_string(),
4537 sql_type: "INTEGER".to_string(),
4538 nullable: true,
4539 default: None,
4540 auto_increment: false,
4541 primary_key: false,
4542 })];
4543 let sql = dialect.build_alter_table("users", &changes);
4544 assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN \"age\" INTEGER"));
4545 }
4546
4547 #[test]
4548 fn test_duckdb_json_extract() {
4549 let dialect = DuckDBDialect;
4550 let sql = dialect.json_extract("data", "$.user.name");
4551 assert!(sql.contains("->"));
4552 assert!(sql.contains("user.name"));
4553 }
4554
4555 #[test]
4556 fn test_duckdb_concat() {
4557 let dialect = DuckDBDialect;
4558 let sql = dialect.concat(&["a", "b", "c"]);
4559 assert_eq!(sql, "a || b || c");
4560 }
4561
4562 #[test]
4563 fn test_duckdb_bool_to_int() {
4564 let dialect = DuckDBDialect;
4565 let sql = dialect.bool_to_int("active");
4566 assert!(sql.contains("CASE WHEN"));
4567 assert!(sql.contains("THEN 1"));
4568 assert!(sql.contains("ELSE 0"));
4569 }
4570
4571 #[test]
4572 fn test_get_dialect_duckdb() {
4573 let result = get_dialect(DbType::DuckDB);
4574 assert!(result.is_ok());
4575 let dialect = result.unwrap();
4576 assert_eq!(dialect.db_type(), DbType::DuckDB);
4577 }
4578}
4579
4580#[cfg(feature = "perf-enum-dispatch")]
4589#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4590pub enum DialectKind {
4591 MySQL,
4593 PostgreSQL,
4595 SQLite,
4597 Oracle,
4599 MSSQL,
4601}
4602
4603#[cfg(feature = "perf-enum-dispatch")]
4604impl DialectKind {
4605 pub fn from_db_type(db_type: DbType) -> Option<Self> {
4607 match db_type {
4608 DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase => Some(Self::MySQL),
4609 DbType::PostgreSQL | DbType::Kingbase | DbType::PolarDB | DbType::GaussDB => {
4610 Some(Self::PostgreSQL)
4611 }
4612 DbType::Sqlite => Some(Self::SQLite),
4613 DbType::Oracle | DbType::Dameng => Some(Self::Oracle),
4614 DbType::SqlServer | DbType::Sybase | DbType::GBase => Some(Self::MSSQL),
4615 _ => None,
4616 }
4617 }
4618
4619 pub fn quote(&self, identifier: &str) -> String {
4621 match self {
4622 Self::MySQL => MySqlDialect.quote(identifier),
4623 Self::PostgreSQL => PostgreSqlDialect.quote(identifier),
4624 Self::SQLite => SqliteDialect.quote(identifier),
4625 Self::Oracle => OracleDialect.quote(identifier),
4626 Self::MSSQL => SqlServerDialect.quote(identifier),
4627 }
4628 }
4629
4630 pub fn escape_string(&self, s: &str) -> String {
4632 match self {
4633 Self::MySQL => MySqlDialect.escape_string(s),
4634 Self::PostgreSQL => PostgreSqlDialect.escape_string(s),
4635 Self::SQLite => SqliteDialect.escape_string(s),
4636 Self::Oracle => OracleDialect.escape_string(s),
4637 Self::MSSQL => SqlServerDialect.escape_string(s),
4638 }
4639 }
4640
4641 pub fn db_type(&self) -> DbType {
4643 match self {
4644 Self::MySQL => DbType::MySQL,
4645 Self::PostgreSQL => DbType::PostgreSQL,
4646 Self::SQLite => DbType::Sqlite,
4647 Self::Oracle => DbType::Oracle,
4648 Self::MSSQL => DbType::SqlServer,
4649 }
4650 }
4651
4652 pub fn to_dialect(&self) -> Box<dyn Dialect> {
4654 match self {
4655 Self::MySQL => Box::new(MySqlDialect),
4656 Self::PostgreSQL => Box::new(PostgreSqlDialect),
4657 Self::SQLite => Box::new(SqliteDialect),
4658 Self::Oracle => Box::new(OracleDialect),
4659 Self::MSSQL => Box::new(SqlServerDialect),
4660 }
4661 }
4662}
4663
4664#[cfg(all(test, feature = "perf-enum-dispatch"))]
4665mod enum_dispatch_tests {
4666 use super::*;
4667
4668 #[test]
4669 fn test_dialect_kind_from_db_type() {
4670 assert_eq!(
4671 DialectKind::from_db_type(DbType::MySQL),
4672 Some(DialectKind::MySQL)
4673 );
4674 assert_eq!(
4675 DialectKind::from_db_type(DbType::PostgreSQL),
4676 Some(DialectKind::PostgreSQL)
4677 );
4678 assert_eq!(
4679 DialectKind::from_db_type(DbType::Sqlite),
4680 Some(DialectKind::SQLite)
4681 );
4682 assert_eq!(
4683 DialectKind::from_db_type(DbType::Oracle),
4684 Some(DialectKind::Oracle)
4685 );
4686 assert_eq!(
4687 DialectKind::from_db_type(DbType::SqlServer),
4688 Some(DialectKind::MSSQL)
4689 );
4690 assert_eq!(DialectKind::from_db_type(DbType::Redis), None);
4691 }
4692
4693 #[test]
4694 fn test_dialect_kind_quote_diff() {
4695 let kinds = [
4696 DialectKind::MySQL,
4697 DialectKind::PostgreSQL,
4698 DialectKind::SQLite,
4699 DialectKind::Oracle,
4700 DialectKind::MSSQL,
4701 ];
4702 for kind in &kinds {
4703 let enum_result = kind.quote("users");
4704 let box_result = kind.to_dialect().quote("users");
4705 assert_eq!(enum_result, box_result);
4706 }
4707 }
4708
4709 #[test]
4710 fn test_dialect_kind_escape_diff() {
4711 let kinds = [
4712 DialectKind::MySQL,
4713 DialectKind::PostgreSQL,
4714 DialectKind::SQLite,
4715 DialectKind::Oracle,
4716 DialectKind::MSSQL,
4717 ];
4718 for kind in &kinds {
4719 let enum_result = kind.escape_string("it's");
4720 let box_result = kind.to_dialect().escape_string("it's");
4721 assert_eq!(enum_result, box_result);
4722 }
4723 }
4724}