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")]
2634#[derive(Debug, Clone)]
2635pub struct InformixDialect;
2636
2637#[cfg(feature = "dialect-informix")]
2638impl Dialect for InformixDialect {
2639 fn clone_box(&self) -> Box<dyn Dialect> {
2640 Box::new(InformixDialect)
2641 }
2642
2643 fn db_type(&self) -> DbType {
2644 DbType::Informix
2645 }
2646
2647 fn quote(&self, identifier: &str) -> String {
2648 format!("\"{}\"", identifier.replace('"', "\"\""))
2649 }
2650
2651 fn escape_string(&self, s: &str) -> String {
2652 let mut escaped = String::with_capacity(s.len() * 2);
2653 for c in s.chars() {
2654 match c {
2655 '\'' => escaped.push_str("''"),
2656 _ => escaped.push(c),
2657 }
2658 }
2659 escaped
2660 }
2661
2662 fn supports_returning(&self) -> bool {
2663 false
2664 }
2665
2666 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2667 let offset = page.saturating_sub(1).saturating_mul(limit);
2668 format!("{} SKIP {} FIRST {}", sql, offset, limit)
2669 }
2670
2671 fn json_type(&self) -> &'static str {
2672 "LVARCHAR"
2673 }
2674
2675 fn json_extract(&self, column: &str, path: &str) -> String {
2676 let normalized = if path.starts_with('$') {
2677 path.to_string()
2678 } else {
2679 format!("$.{}", path)
2680 };
2681 format!(
2682 "JSONExtract({}, '{}')",
2683 column,
2684 self.escape_string(&normalized)
2685 )
2686 }
2687
2688 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2689 if columns.is_empty() {
2690 return "0".to_string();
2691 }
2692 let escaped = self.escape_string(keyword);
2693 let parts: Vec<String> = columns
2694 .iter()
2695 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2696 .collect();
2697 parts.join(" OR ")
2698 }
2699
2700 fn bool_to_int(&self, expr: &str) -> String {
2701 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2702 }
2703
2704 fn concat(&self, parts: &[&str]) -> String {
2705 if parts.is_empty() {
2706 return "''".to_string();
2707 }
2708 parts.join(" || ")
2709 }
2710
2711 fn supports_if_exists(&self) -> bool {
2712 true
2713 }
2714
2715 fn supports_if_not_exists(&self) -> bool {
2716 true
2717 }
2718
2719 fn auto_increment_keyword(&self) -> &'static str {
2720 "SERIAL"
2721 }
2722
2723 fn last_insert_id_sql(&self) -> Option<&'static str> {
2724 Some("SELECT DBINFO('sqlca.sqlerrd1') FROM systables WHERE tabid = 1")
2725 }
2726
2727 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2728 let cols: Vec<String> = columns
2729 .iter()
2730 .map(|col| {
2731 let informix_type = map_to_informix_type(&col.sql_type);
2732 let mut sql = format!("{} {}", self.quote(&col.name), informix_type);
2733 if !col.nullable && !col.auto_increment {
2734 sql.push_str(" NOT NULL");
2735 }
2736 if let Some(default) = &col.default {
2737 sql.push_str(&format!(" DEFAULT {}", default));
2738 }
2739 if col.primary_key {
2740 sql.push_str(" PRIMARY KEY");
2741 }
2742 sql
2743 })
2744 .collect();
2745
2746 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2747 }
2748
2749 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2750 let stmts: Vec<String> = changes
2751 .iter()
2752 .map(|change| match change {
2753 TableChange::AddColumn(col) => {
2754 let informix_type = map_to_informix_type(&col.sql_type);
2755 let mut sql = format!(
2756 "ALTER TABLE {} ADD COLUMN {} {}",
2757 self.quote(table),
2758 self.quote(&col.name),
2759 informix_type
2760 );
2761 if !col.nullable {
2762 sql.push_str(" NOT NULL");
2763 }
2764 if let Some(default) = &col.default {
2765 sql.push_str(&format!(" DEFAULT {}", default));
2766 }
2767 sql
2768 }
2769 TableChange::DropColumn(name) => {
2770 format!(
2771 "ALTER TABLE {} DROP COLUMN {}",
2772 self.quote(table),
2773 self.quote(name)
2774 )
2775 }
2776 TableChange::ModifyColumn(col) => {
2777 let informix_type = map_to_informix_type(&col.sql_type);
2778 format!(
2779 "ALTER TABLE {} MODIFY {} {}",
2780 self.quote(table),
2781 self.quote(&col.name),
2782 informix_type
2783 )
2784 }
2785 TableChange::AddIndex(name, cols) => {
2786 format!(
2787 "CREATE INDEX {} ON {} ({})",
2788 name,
2789 self.quote(table),
2790 cols.join(", ")
2791 )
2792 }
2793 TableChange::DropIndex(name) => {
2794 format!("DROP INDEX {}", name)
2795 }
2796 TableChange::AddForeignKey {
2797 columns,
2798 reference_table,
2799 reference_columns,
2800 } => {
2801 format!(
2802 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
2803 self.quote(table),
2804 columns
2805 .iter()
2806 .map(|c| self.quote(c))
2807 .collect::<Vec<_>>()
2808 .join(", "),
2809 self.quote(reference_table),
2810 reference_columns
2811 .iter()
2812 .map(|c| self.quote(c))
2813 .collect::<Vec<_>>()
2814 .join(", ")
2815 )
2816 }
2817 })
2818 .collect();
2819
2820 stmts.join("; ")
2821 }
2822}
2823
2824#[cfg(feature = "dialect-informix")]
2826fn map_to_informix_type(sql_type: &str) -> String {
2827 let upper = sql_type.to_uppercase();
2828 let trimmed = upper.trim();
2829
2830 if trimmed.starts_with("BIGINT") {
2831 "BIGINT".to_string()
2832 } else if matches!(trimmed, "INT" | "INTEGER") {
2833 "INTEGER".to_string()
2834 } else if matches!(trimmed, "SMALLINT") {
2835 "SMALLINT".to_string()
2836 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2837 sql_type.to_string()
2838 } else if matches!(trimmed, "TEXT" | "LVARCHAR") {
2839 "LVARCHAR".to_string()
2840 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2841 "BOOLEAN".to_string()
2842 } else if matches!(trimmed, "FLOAT" | "REAL") {
2843 "SMALLFLOAT".to_string()
2844 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2845 "FLOAT".to_string()
2846 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2847 "DATETIME YEAR TO SECOND".to_string()
2848 } else if matches!(trimmed, "DATE") {
2849 "DATE".to_string()
2850 } else {
2851 sql_type.to_string()
2852 }
2853}
2854
2855#[cfg(feature = "dialect-saphana")]
2860#[derive(Debug, Clone)]
2861pub struct SapHanaDialect;
2862
2863#[cfg(feature = "dialect-saphana")]
2864impl Dialect for SapHanaDialect {
2865 fn clone_box(&self) -> Box<dyn Dialect> {
2866 Box::new(SapHanaDialect)
2867 }
2868
2869 fn db_type(&self) -> DbType {
2870 DbType::SapHana
2871 }
2872
2873 fn quote(&self, identifier: &str) -> String {
2874 format!("\"{}\"", identifier.replace('"', "\"\""))
2875 }
2876
2877 fn escape_string(&self, s: &str) -> String {
2878 let mut escaped = String::with_capacity(s.len() * 2);
2879 for c in s.chars() {
2880 match c {
2881 '\'' => escaped.push_str("''"),
2882 _ => escaped.push(c),
2883 }
2884 }
2885 escaped
2886 }
2887
2888 fn supports_returning(&self) -> bool {
2889 false
2890 }
2891
2892 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2893 let offset = page.saturating_sub(1).saturating_mul(limit);
2894 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
2895 }
2896
2897 fn json_type(&self) -> &'static str {
2898 "NCLOB"
2899 }
2900
2901 fn json_extract(&self, column: &str, path: &str) -> String {
2902 let normalized = if path.starts_with('$') {
2903 path.to_string()
2904 } else {
2905 format!("$.{}", path)
2906 };
2907 format!(
2908 "JSON_VALUE({}, '{}')",
2909 column,
2910 self.escape_string(&normalized)
2911 )
2912 }
2913
2914 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2915 if columns.is_empty() {
2916 return "0".to_string();
2917 }
2918 let escaped = self.escape_string(keyword);
2919 let parts: Vec<String> = columns
2920 .iter()
2921 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2922 .collect();
2923 parts.join(" OR ")
2924 }
2925
2926 fn bool_to_int(&self, expr: &str) -> String {
2927 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2928 }
2929
2930 fn concat(&self, parts: &[&str]) -> String {
2931 if parts.is_empty() {
2932 return "''".to_string();
2933 }
2934 parts.join(" || ")
2935 }
2936
2937 fn supports_if_exists(&self) -> bool {
2938 true
2939 }
2940
2941 fn supports_if_not_exists(&self) -> bool {
2942 true
2943 }
2944
2945 fn auto_increment_keyword(&self) -> &'static str {
2946 "GENERATED BY DEFAULT AS IDENTITY"
2947 }
2948
2949 fn last_insert_id_sql(&self) -> Option<&'static str> {
2950 None
2951 }
2952
2953 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2954 let cols: Vec<String> = columns
2955 .iter()
2956 .map(|col| {
2957 let hana_type = map_to_saphana_type(&col.sql_type);
2958 let mut sql = format!("{} {}", self.quote(&col.name), hana_type);
2959 if !col.nullable && !col.auto_increment {
2960 sql.push_str(" NOT NULL");
2961 }
2962 if let Some(default) = &col.default {
2963 sql.push_str(&format!(" DEFAULT {}", default));
2964 }
2965 if col.auto_increment {
2966 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2967 }
2968 if col.primary_key {
2969 sql.push_str(" PRIMARY KEY");
2970 }
2971 sql
2972 })
2973 .collect();
2974
2975 format!(
2976 "CREATE COLUMN TABLE {} ({})",
2977 self.quote(table),
2978 cols.join(", ")
2979 )
2980 }
2981
2982 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2983 let stmts: Vec<String> = changes
2984 .iter()
2985 .map(|change| match change {
2986 TableChange::AddColumn(col) => {
2987 let hana_type = map_to_saphana_type(&col.sql_type);
2988 let mut sql = format!(
2989 "ALTER TABLE {} ADD ({} {})",
2990 self.quote(table),
2991 self.quote(&col.name),
2992 hana_type
2993 );
2994 if !col.nullable {
2995 sql.push_str(" NOT NULL");
2996 }
2997 if let Some(default) = &col.default {
2998 sql.push_str(&format!(" DEFAULT {}", default));
2999 }
3000 sql
3001 }
3002 TableChange::DropColumn(name) => {
3003 format!(
3004 "ALTER TABLE {} DROP ({})",
3005 self.quote(table),
3006 self.quote(name)
3007 )
3008 }
3009 TableChange::ModifyColumn(col) => {
3010 let hana_type = map_to_saphana_type(&col.sql_type);
3011 format!(
3012 "ALTER TABLE {} ALTER ({} {})",
3013 self.quote(table),
3014 self.quote(&col.name),
3015 hana_type
3016 )
3017 }
3018 TableChange::AddIndex(name, cols) => {
3019 format!(
3020 "CREATE INDEX {} ON {} ({})",
3021 name,
3022 self.quote(table),
3023 cols.join(", ")
3024 )
3025 }
3026 TableChange::DropIndex(name) => {
3027 format!("DROP INDEX {}", name)
3028 }
3029 TableChange::AddForeignKey {
3030 columns,
3031 reference_table,
3032 reference_columns,
3033 } => {
3034 format!(
3035 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
3036 self.quote(table),
3037 columns
3038 .iter()
3039 .map(|c| self.quote(c))
3040 .collect::<Vec<_>>()
3041 .join(", "),
3042 self.quote(reference_table),
3043 reference_columns
3044 .iter()
3045 .map(|c| self.quote(c))
3046 .collect::<Vec<_>>()
3047 .join(", ")
3048 )
3049 }
3050 })
3051 .collect();
3052
3053 stmts.join("; ")
3054 }
3055}
3056
3057#[cfg(feature = "dialect-saphana")]
3059fn map_to_saphana_type(sql_type: &str) -> String {
3060 let upper = sql_type.to_uppercase();
3061 let trimmed = upper.trim();
3062
3063 if trimmed.starts_with("BIGINT") {
3064 "BIGINT".to_string()
3065 } else if matches!(trimmed, "INT" | "INTEGER") {
3066 "INTEGER".to_string()
3067 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
3068 "SMALLINT".to_string()
3069 } else if trimmed.starts_with("VARCHAR")
3070 || trimmed.starts_with("NVARCHAR")
3071 || trimmed.starts_with("CHAR")
3072 || trimmed.starts_with("NCHAR")
3073 {
3074 sql_type.to_string()
3075 } else if matches!(trimmed, "TEXT" | "CLOB") {
3076 "NCLOB".to_string()
3077 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3078 "BOOLEAN".to_string()
3079 } else if matches!(trimmed, "FLOAT" | "REAL") {
3080 "REAL".to_string()
3081 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3082 "DOUBLE".to_string()
3083 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3084 "TIMESTAMP".to_string()
3085 } else if matches!(trimmed, "DATE") {
3086 "DATE".to_string()
3087 } else {
3088 sql_type.to_string()
3089 }
3090}
3091
3092#[cfg(feature = "dialect-firebird")]
3097#[derive(Debug, Clone)]
3098pub struct FirebirdDialect;
3099
3100#[cfg(feature = "dialect-firebird")]
3101impl Dialect for FirebirdDialect {
3102 fn clone_box(&self) -> Box<dyn Dialect> {
3103 Box::new(FirebirdDialect)
3104 }
3105
3106 fn db_type(&self) -> DbType {
3107 DbType::Firebird
3108 }
3109
3110 fn quote(&self, identifier: &str) -> String {
3111 format!("\"{}\"", identifier.replace('"', "\"\""))
3112 }
3113
3114 fn escape_string(&self, s: &str) -> String {
3115 let mut escaped = String::with_capacity(s.len() * 2);
3116 for c in s.chars() {
3117 match c {
3118 '\'' => escaped.push_str("''"),
3119 _ => escaped.push(c),
3120 }
3121 }
3122 escaped
3123 }
3124
3125 fn supports_returning(&self) -> bool {
3126 true
3127 }
3128
3129 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
3130 let offset = page.saturating_sub(1).saturating_mul(limit);
3131 let to = offset + limit;
3132 format!("{} ROWS {} TO {}", sql, offset + 1, to)
3133 }
3134
3135 fn json_type(&self) -> &'static str {
3136 "BLOB SUB_TYPE TEXT"
3137 }
3138
3139 fn json_extract(&self, column: &str, path: &str) -> String {
3140 let normalized = if path.starts_with('$') {
3141 path.to_string()
3142 } else {
3143 format!("$.{}", path)
3144 };
3145 format!(
3146 "JSON_GET({}, '{}')",
3147 column,
3148 self.escape_string(&normalized)
3149 )
3150 }
3151
3152 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
3153 if columns.is_empty() {
3154 return "0".to_string();
3155 }
3156 let escaped = self.escape_string(keyword);
3157 let parts: Vec<String> = columns
3158 .iter()
3159 .map(|c| format!("CONTAINING({}, '{}')", c, escaped))
3160 .collect();
3161 parts.join(" OR ")
3162 }
3163
3164 fn bool_to_int(&self, expr: &str) -> String {
3165 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
3166 }
3167
3168 fn concat(&self, parts: &[&str]) -> String {
3169 if parts.is_empty() {
3170 return "''".to_string();
3171 }
3172 parts.join(" || ")
3173 }
3174
3175 fn supports_if_exists(&self) -> bool {
3176 true
3177 }
3178
3179 fn supports_if_not_exists(&self) -> bool {
3180 true
3181 }
3182
3183 fn auto_increment_keyword(&self) -> &'static str {
3184 "GENERATED BY DEFAULT AS IDENTITY"
3185 }
3186
3187 fn last_insert_id_sql(&self) -> Option<&'static str> {
3188 Some("SELECT GEN_ID(SQLITE_SEQUENCE, 0) FROM RDB$DATABASE")
3189 }
3190
3191 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
3192 let cols: Vec<String> = columns
3193 .iter()
3194 .map(|col| {
3195 let fb_type = map_to_firebird_type(&col.sql_type);
3196 let mut sql = format!("{} {}", self.quote(&col.name), fb_type);
3197 if !col.nullable && !col.auto_increment {
3198 sql.push_str(" NOT NULL");
3199 }
3200 if let Some(default) = &col.default {
3201 sql.push_str(&format!(" DEFAULT {}", default));
3202 }
3203 if col.auto_increment {
3204 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
3205 }
3206 if col.primary_key {
3207 sql.push_str(" PRIMARY KEY");
3208 }
3209 sql
3210 })
3211 .collect();
3212
3213 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
3214 }
3215
3216 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
3217 let stmts: Vec<String> = changes
3218 .iter()
3219 .map(|change| match change {
3220 TableChange::AddColumn(col) => {
3221 let fb_type = map_to_firebird_type(&col.sql_type);
3222 let mut sql = format!(
3223 "ALTER TABLE {} ADD {} {}",
3224 self.quote(table),
3225 self.quote(&col.name),
3226 fb_type
3227 );
3228 if !col.nullable {
3229 sql.push_str(" NOT NULL");
3230 }
3231 if let Some(default) = &col.default {
3232 sql.push_str(&format!(" DEFAULT {}", default));
3233 }
3234 sql
3235 }
3236 TableChange::DropColumn(name) => {
3237 format!(
3238 "ALTER TABLE {} DROP {}",
3239 self.quote(table),
3240 self.quote(name)
3241 )
3242 }
3243 TableChange::ModifyColumn(col) => {
3244 let fb_type = map_to_firebird_type(&col.sql_type);
3245 format!(
3246 "ALTER TABLE {} ALTER COLUMN {} TYPE {}",
3247 self.quote(table),
3248 self.quote(&col.name),
3249 fb_type
3250 )
3251 }
3252 TableChange::AddIndex(name, cols) => {
3253 format!(
3254 "CREATE INDEX {} ON {} ({})",
3255 name,
3256 self.quote(table),
3257 cols.join(", ")
3258 )
3259 }
3260 TableChange::DropIndex(name) => {
3261 format!("DROP INDEX {}", name)
3262 }
3263 TableChange::AddForeignKey {
3264 columns,
3265 reference_table,
3266 reference_columns,
3267 } => {
3268 format!(
3269 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
3270 self.quote(table),
3271 columns
3272 .iter()
3273 .map(|c| self.quote(c))
3274 .collect::<Vec<_>>()
3275 .join(", "),
3276 self.quote(reference_table),
3277 reference_columns
3278 .iter()
3279 .map(|c| self.quote(c))
3280 .collect::<Vec<_>>()
3281 .join(", ")
3282 )
3283 }
3284 })
3285 .collect();
3286
3287 stmts.join("; ")
3288 }
3289}
3290
3291#[cfg(feature = "dialect-firebird")]
3293fn map_to_firebird_type(sql_type: &str) -> String {
3294 let upper = sql_type.to_uppercase();
3295 let trimmed = upper.trim();
3296
3297 if trimmed.starts_with("BIGINT") {
3298 "BIGINT".to_string()
3299 } else if matches!(trimmed, "INT" | "INTEGER") {
3300 "INTEGER".to_string()
3301 } else if matches!(trimmed, "SMALLINT") {
3302 "SMALLINT".to_string()
3303 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
3304 sql_type.to_string()
3305 } else if matches!(trimmed, "TEXT" | "BLOB") {
3306 "BLOB SUB_TYPE TEXT".to_string()
3307 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3308 "BOOLEAN".to_string()
3309 } else if matches!(trimmed, "FLOAT" | "REAL") {
3310 "FLOAT".to_string()
3311 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3312 "DOUBLE PRECISION".to_string()
3313 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3314 "TIMESTAMP".to_string()
3315 } else if matches!(trimmed, "DATE") {
3316 "DATE".to_string()
3317 } else {
3318 sql_type.to_string()
3319 }
3320}
3321
3322#[cfg(test)]
3323mod tests {
3324 use super::*;
3325
3326 #[test]
3327 fn test_mysql_quote() {
3328 let dialect = MySqlDialect;
3329 assert_eq!(dialect.quote("users"), "`users`");
3330 assert_eq!(dialect.quote("user`id"), "`user``id`");
3331 }
3332
3333 #[test]
3334 fn test_mysql_escape() {
3335 let dialect = MySqlDialect;
3336 assert_eq!(dialect.escape_string("hello"), "hello");
3337 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3338 assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
3339 }
3340
3341 #[test]
3342 fn test_mysql_pagination() {
3343 let dialect = MySqlDialect;
3344 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3345 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
3346 }
3347
3348 #[test]
3349 fn test_postgres_quote() {
3350 let dialect = PostgreSqlDialect;
3351 assert_eq!(dialect.quote("users"), "\"users\"");
3352 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3353 }
3354
3355 #[test]
3356 fn test_postgres_pagination() {
3357 let dialect = PostgreSqlDialect;
3358 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3359 assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
3360 }
3361
3362 #[test]
3363 fn test_postgres_returning() {
3364 let dialect = PostgreSqlDialect;
3365 assert!(dialect.supports_returning());
3366 }
3367
3368 #[test]
3369 fn test_sqlite_quote() {
3370 let dialect = SqliteDialect;
3371 assert_eq!(dialect.quote("users"), "\"users\"");
3372 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3373 }
3374
3375 #[test]
3376 fn test_sqlite_escape() {
3377 let dialect = SqliteDialect;
3378 assert_eq!(dialect.escape_string("hello"), "hello");
3379 assert_eq!(dialect.escape_string("it's"), "it''s");
3380 }
3381
3382 #[test]
3383 fn test_get_dialect() {
3384 let dialect = get_dialect(DbType::MySQL);
3385 assert!(dialect.is_ok());
3386
3387 let dialect = get_dialect(DbType::Redis);
3388 assert!(dialect.is_err());
3389 }
3390
3391 #[test]
3392 fn test_bool_to_int() {
3393 let mysql = MySqlDialect;
3394 assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
3395
3396 let pg = PostgreSqlDialect;
3397 assert_eq!(
3398 pg.bool_to_int("active"),
3399 "(CASE WHEN active THEN 1 ELSE 0 END)"
3400 );
3401 }
3402
3403 #[test]
3404 fn test_json_extract_with_path() {
3405 let mysql = MySqlDialect;
3406 let sql = mysql.json_extract("data", "$.user.name");
3407 assert!(sql.contains("$.user.name"));
3408 assert!(sql.contains("JSON_EXTRACT"));
3409
3410 let pg = PostgreSqlDialect;
3411 let sql = pg.json_extract("data", "user.name");
3412 assert!(sql.contains("#>>"));
3413
3414 let sqlite = SqliteDialect;
3415 let sql = sqlite.json_extract("data", "$.user.name");
3416 assert!(sql.contains("$.user.name"));
3417 assert!(sql.contains("json_extract"));
3418 }
3419
3420 #[test]
3421 fn test_sqlite_full_text_search() {
3422 let sqlite = SqliteDialect;
3423 let sql = sqlite.full_text_search(&["title", "content"], "hello");
3424 assert!(sql.contains("LIKE"));
3426 assert!(sql.contains("title LIKE '%hello%'"));
3427 assert!(sql.contains("content LIKE '%hello%'"));
3428 assert!(sql.contains(" OR "));
3429
3430 assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
3432
3433 let sql = sqlite.full_text_search(&["title"], "it's");
3435 assert!(sql.contains("title LIKE '%it''s%'"));
3436 }
3437
3438 #[test]
3439 fn test_alter_table_modify_column() {
3440 let mysql = MySqlDialect;
3441 let col = ColumnDef {
3442 name: "name".to_string(),
3443 sql_type: "VARCHAR(255)".to_string(),
3444 nullable: false,
3445 default: None,
3446 auto_increment: false,
3447 primary_key: false,
3448 };
3449 let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3450 assert!(sql.contains("MODIFY COLUMN"));
3451
3452 let pg = PostgreSqlDialect;
3453 let col = ColumnDef {
3454 name: "name".to_string(),
3455 sql_type: "VARCHAR(255)".to_string(),
3456 nullable: false,
3457 default: None,
3458 auto_increment: false,
3459 primary_key: false,
3460 };
3461 let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3462 assert!(sql.contains("ALTER COLUMN"));
3463 assert!(sql.contains("TYPE"));
3464 }
3465
3466 #[test]
3467 fn test_alter_table_add_foreign_key() {
3468 let mysql = MySqlDialect;
3469 let sql = mysql.build_alter_table(
3470 "orders",
3471 &[TableChange::AddForeignKey {
3472 columns: vec!["user_id".to_string()],
3473 reference_table: "users".to_string(),
3474 reference_columns: vec!["id".to_string()],
3475 }],
3476 );
3477 assert!(sql.contains("FOREIGN KEY"));
3478 assert!(sql.contains("REFERENCES"));
3479
3480 let sqlite = SqliteDialect;
3481 let sql = sqlite.build_alter_table(
3482 "orders",
3483 &[TableChange::AddForeignKey {
3484 columns: vec!["user_id".to_string()],
3485 reference_table: "users".to_string(),
3486 reference_columns: vec!["id".to_string()],
3487 }],
3488 );
3489 assert!(sql.starts_with("--"));
3491 }
3492
3493 #[test]
3494 fn test_sqlite_alter_table_add_column() {
3495 let sqlite = SqliteDialect;
3496 let col = ColumnDef {
3497 name: "email".to_string(),
3498 sql_type: "TEXT".to_string(),
3499 nullable: true,
3500 default: None,
3501 auto_increment: false,
3502 primary_key: false,
3503 };
3504 let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
3505 assert!(sql.contains("ADD COLUMN"));
3506 assert!(sql.contains("email"));
3507 }
3508
3509 #[test]
3512 fn test_oracle_quote_and_escape() {
3513 let dialect = OracleDialect;
3514 assert_eq!(dialect.quote("users"), "\"users\"");
3516 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3517 assert_eq!(dialect.quote("column_name"), "\"column_name\"");
3518
3519 assert_eq!(dialect.escape_string("hello"), "hello");
3521 assert_eq!(dialect.escape_string("it's"), "it''s");
3522 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3523 assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
3524 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3526 }
3527
3528 #[test]
3529 fn test_oracle_pagination() {
3530 let dialect = OracleDialect;
3531 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3533 assert_eq!(
3534 sql,
3535 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3536 );
3537 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3539 assert_eq!(
3540 sql,
3541 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3542 );
3543 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3545 assert_eq!(
3546 sql,
3547 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3548 );
3549 }
3550
3551 #[test]
3552 fn test_oracle_json_extract() {
3553 let dialect = OracleDialect;
3554 let sql = dialect.json_extract("data", "$.user.name");
3556 assert!(sql.contains("JSON_VALUE"));
3557 assert!(sql.contains("$.user.name"));
3558 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3559
3560 let sql = dialect.json_extract("data", "user.name");
3562 assert!(sql.contains("$.user.name"));
3563 assert!(sql.contains("JSON_VALUE"));
3564
3565 let sql = dialect.json_extract("data", "$.key's");
3567 assert!(sql.contains("$.key''s"));
3568 }
3569
3570 #[test]
3571 fn test_oracle_create_table() {
3572 let dialect = OracleDialect;
3573 let columns = vec![
3574 ColumnDef {
3575 name: "id".to_string(),
3576 sql_type: "BIGINT".to_string(),
3577 nullable: false,
3578 default: None,
3579 auto_increment: true,
3580 primary_key: true,
3581 },
3582 ColumnDef {
3583 name: "name".to_string(),
3584 sql_type: "VARCHAR(255)".to_string(),
3585 nullable: false,
3586 default: None,
3587 auto_increment: false,
3588 primary_key: false,
3589 },
3590 ColumnDef {
3591 name: "bio".to_string(),
3592 sql_type: "TEXT".to_string(),
3593 nullable: true,
3594 default: None,
3595 auto_increment: false,
3596 primary_key: false,
3597 },
3598 ColumnDef {
3599 name: "is_active".to_string(),
3600 sql_type: "BOOLEAN".to_string(),
3601 nullable: false,
3602 default: Some("1".to_string()),
3603 auto_increment: false,
3604 primary_key: false,
3605 },
3606 ];
3607 let sql = dialect.build_create_table("users", &columns);
3608 assert!(
3610 sql.contains("NUMBER(19)"),
3611 "BIGINT should map to NUMBER(19): {}",
3612 sql
3613 );
3614 assert!(
3615 sql.contains("VARCHAR2(255)"),
3616 "VARCHAR should map to VARCHAR2: {}",
3617 sql
3618 );
3619 assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
3620 assert!(
3621 sql.contains("NUMBER(1)"),
3622 "BOOLEAN should map to NUMBER(1): {}",
3623 sql
3624 );
3625 assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
3627 assert!(sql.contains("PRIMARY KEY"));
3628 assert!(sql.contains("NOT NULL"));
3629 assert!(sql.contains("DEFAULT 1"));
3630 assert!(sql.contains("\"users\""));
3632 assert!(sql.contains("\"id\""));
3633 }
3634
3635 #[test]
3636 fn test_oracle_bool_to_int_and_concat() {
3637 let dialect = OracleDialect;
3638 assert_eq!(
3640 dialect.bool_to_int("active"),
3641 "(CASE WHEN active THEN 1 ELSE 0 END)"
3642 );
3643 assert_eq!(
3644 dialect.bool_to_int("x > 0"),
3645 "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
3646 );
3647 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
3649 assert_eq!(
3650 dialect.concat(&["first_name", "last_name"]),
3651 "first_name || last_name"
3652 );
3653 assert_eq!(dialect.concat(&[]), "NULL");
3655 }
3656
3657 #[test]
3658 fn test_oracle_misc_dialect_methods() {
3659 let dialect = OracleDialect;
3660 assert_eq!(dialect.db_type(), DbType::Oracle);
3662 assert!(dialect.supports_returning());
3664 assert!(dialect.supports_if_exists());
3666 assert!(dialect.supports_if_not_exists());
3667 assert_eq!(
3669 dialect.auto_increment_keyword(),
3670 "GENERATED BY DEFAULT AS IDENTITY"
3671 );
3672 assert_eq!(dialect.last_insert_id_sql(), None);
3675 assert_eq!(dialect.json_type(), "JSON");
3677 }
3678
3679 #[test]
3680 fn test_oracle_get_dialect() {
3681 let dialect = get_dialect(DbType::Oracle);
3683 assert!(dialect.is_ok(), "Oracle dialect should be available");
3684 let dialect = dialect.unwrap();
3685 assert_eq!(dialect.db_type(), DbType::Oracle);
3686 assert_eq!(dialect.quote("users"), "\"users\"");
3688 assert!(dialect.supports_returning());
3690 assert_eq!(dialect.last_insert_id_sql(), None);
3692 }
3693
3694 #[test]
3695 fn test_oracle_drop_table() {
3696 let dialect = OracleDialect;
3697 let sql = dialect.build_drop_table("users", true);
3699 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
3700 let sql = dialect.build_drop_table("users", false);
3702 assert_eq!(sql, "DROP TABLE \"users\"");
3703 }
3704
3705 #[test]
3706 fn test_oracle_alter_table() {
3707 let dialect = OracleDialect;
3708 let col = ColumnDef {
3710 name: "name".to_string(),
3711 sql_type: "VARCHAR(255)".to_string(),
3712 nullable: false,
3713 default: None,
3714 auto_increment: false,
3715 primary_key: false,
3716 };
3717 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3718 assert!(sql.contains("MODIFY"));
3719 assert!(sql.contains("VARCHAR2(255)"));
3720 assert!(!sql.contains("MODIFY COLUMN")); let col = ColumnDef {
3724 name: "email".to_string(),
3725 sql_type: "VARCHAR(255)".to_string(),
3726 nullable: true,
3727 default: None,
3728 auto_increment: false,
3729 primary_key: false,
3730 };
3731 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
3732 assert!(sql.contains("ADD \"email\""));
3733 assert!(sql.contains("VARCHAR2(255)"));
3734
3735 let sql =
3737 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
3738 assert!(sql.contains("DROP COLUMN"));
3739 assert!(sql.contains("\"email\""));
3740 }
3741
3742 #[test]
3745 fn test_sqlite_concat_handles_null() {
3746 let sqlite = SqliteDialect;
3747 let sql = sqlite.concat(&["a", "b"]);
3750 assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
3751 let sql = sqlite.concat(&["a"]);
3753 assert_eq!(sql, "COALESCE(a, '')");
3754 assert_eq!(sqlite.concat(&[]), "NULL");
3756 }
3757
3758 #[test]
3761 fn test_sqlserver_quote_and_escape() {
3762 let dialect = SqlServerDialect;
3763 assert_eq!(dialect.quote("users"), "[users]");
3765 assert_eq!(dialect.quote("col]name"), "[col]]name]");
3766 assert_eq!(dialect.escape_string("hello"), "hello");
3768 assert_eq!(dialect.escape_string("it's"), "it''s");
3769 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3770 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3771 }
3772
3773 #[test]
3774 fn test_sqlserver_pagination() {
3775 let dialect = SqlServerDialect;
3776 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3778 assert_eq!(
3779 sql,
3780 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3781 );
3782 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3783 assert_eq!(
3784 sql,
3785 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3786 );
3787 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3789 assert_eq!(
3790 sql,
3791 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3792 );
3793 }
3794
3795 #[test]
3796 fn test_sqlserver_misc_dialect_methods() {
3797 let dialect = SqlServerDialect;
3798 assert_eq!(dialect.db_type(), DbType::SqlServer);
3799 assert!(dialect.supports_returning());
3801 assert!(dialect.supports_if_exists());
3803 assert!(dialect.supports_if_not_exists());
3804 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
3806 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
3808 assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
3810 }
3811
3812 #[test]
3813 fn test_sqlserver_insert_or_ignore_fallback_to_plain_insert() {
3814 let dialect = SqlServerDialect;
3815 let sql = dialect.build_insert_or_ignore_prefix("users");
3816 assert_eq!(sql, "INSERT INTO [users]");
3818 assert!(!sql.contains("OR IGNORE"));
3819 }
3820
3821 #[test]
3822 fn test_sqlserver_json_extract() {
3823 let dialect = SqlServerDialect;
3824 let sql = dialect.json_extract("data", "$.user.name");
3825 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3826 let sql = dialect.json_extract("data", "user.name");
3828 assert!(sql.contains("$.user.name"));
3829 assert!(sql.contains("JSON_VALUE"));
3830 let sql = dialect.json_extract("data", "$.key's");
3832 assert!(sql.contains("$.key''s"));
3833 }
3834
3835 #[test]
3836 fn test_sqlserver_full_text_search() {
3837 let dialect = SqlServerDialect;
3838 let sql = dialect.full_text_search(&["title", "content"], "hello");
3839 assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
3840 assert_eq!(dialect.full_text_search(&[], "hello"), "0");
3842 let sql = dialect.full_text_search(&["title"], "it's");
3844 assert!(sql.contains("it''s"));
3845 }
3846
3847 #[test]
3848 fn test_sqlserver_bool_to_int_and_concat() {
3849 let dialect = SqlServerDialect;
3850 assert_eq!(
3851 dialect.bool_to_int("active"),
3852 "(CASE WHEN active THEN 1 ELSE 0 END)"
3853 );
3854 assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
3855 assert_eq!(dialect.concat(&[]), "NULL");
3856 }
3857
3858 #[test]
3859 fn test_sqlserver_create_table() {
3860 let dialect = SqlServerDialect;
3861 let columns = vec![
3862 ColumnDef {
3863 name: "id".to_string(),
3864 sql_type: "BIGINT".to_string(),
3865 nullable: false,
3866 default: None,
3867 auto_increment: true,
3868 primary_key: true,
3869 },
3870 ColumnDef {
3871 name: "name".to_string(),
3872 sql_type: "VARCHAR(255)".to_string(),
3873 nullable: false,
3874 default: None,
3875 auto_increment: false,
3876 primary_key: false,
3877 },
3878 ColumnDef {
3879 name: "bio".to_string(),
3880 sql_type: "TEXT".to_string(),
3881 nullable: true,
3882 default: None,
3883 auto_increment: false,
3884 primary_key: false,
3885 },
3886 ColumnDef {
3887 name: "is_active".to_string(),
3888 sql_type: "BOOLEAN".to_string(),
3889 nullable: false,
3890 default: Some("1".to_string()),
3891 auto_increment: false,
3892 primary_key: false,
3893 },
3894 ];
3895 let sql = dialect.build_create_table("users", &columns);
3896 assert!(sql.contains("[users]"));
3898 assert!(sql.contains("[id]"));
3899 assert!(sql.contains("IDENTITY(1,1)"));
3901 assert!(
3902 sql.contains("NVARCHAR(255)"),
3903 "VARCHAR should map to NVARCHAR: {}",
3904 sql
3905 );
3906 assert!(
3907 sql.contains("NVARCHAR(MAX)"),
3908 "TEXT should map to NVARCHAR(MAX): {}",
3909 sql
3910 );
3911 assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
3912 assert!(sql.contains("PRIMARY KEY"));
3913 assert!(sql.contains("NOT NULL"));
3914 assert!(sql.contains("DEFAULT 1"));
3915 }
3916
3917 #[test]
3918 fn test_sqlserver_drop_table() {
3919 let dialect = SqlServerDialect;
3920 assert_eq!(
3921 dialect.build_drop_table("users", true),
3922 "DROP TABLE IF EXISTS [users]"
3923 );
3924 assert_eq!(
3925 dialect.build_drop_table("users", false),
3926 "DROP TABLE [users]"
3927 );
3928 }
3929
3930 #[test]
3931 fn test_sqlserver_alter_table() {
3932 let dialect = SqlServerDialect;
3933 let col = ColumnDef {
3935 name: "name".to_string(),
3936 sql_type: "VARCHAR(255)".to_string(),
3937 nullable: false,
3938 default: None,
3939 auto_increment: false,
3940 primary_key: false,
3941 };
3942 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3943 assert!(sql.contains("ALTER COLUMN"));
3944 assert!(sql.contains("NVARCHAR(255)"));
3945 assert!(!sql.contains("MODIFY"));
3946
3947 let col = ColumnDef {
3949 name: "email".to_string(),
3950 sql_type: "VARCHAR(255)".to_string(),
3951 nullable: true,
3952 default: None,
3953 auto_increment: false,
3954 primary_key: false,
3955 };
3956 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
3957 assert!(sql.contains("ADD [email]"));
3958 assert!(sql.contains("NVARCHAR(255)"));
3959
3960 let sql =
3962 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
3963 assert!(sql.contains("DROP COLUMN"));
3964 assert!(sql.contains("[email]"));
3965
3966 let sql =
3968 dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
3969 assert!(sql.contains("DROP INDEX idx_name ON [users]"));
3970 }
3971
3972 #[test]
3973 fn test_sqlserver_get_dialect() {
3974 let dialect = get_dialect(DbType::SqlServer);
3976 assert!(dialect.is_ok(), "SqlServer dialect should be available");
3977 let dialect = dialect.unwrap();
3978 assert_eq!(dialect.db_type(), DbType::SqlServer);
3979 assert_eq!(dialect.quote("users"), "[users]");
3981 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
3983 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
3984 }
3985
3986 #[test]
3987 fn test_clickhouse_get_dialect_unsupported() {
3988 let dialect = get_dialect(DbType::ClickHouse);
3990 assert!(dialect.is_ok(), "ClickHouse should be supported");
3991 let dialect = dialect.unwrap();
3992 assert_eq!(dialect.db_type(), DbType::ClickHouse);
3993 assert_eq!(dialect.quote("users"), "`users`");
3995 assert!(!dialect.supports_returning());
3997 let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
3999 assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
4000 assert_eq!(dialect.auto_increment_keyword(), "");
4002 }
4003
4004 #[test]
4005 fn test_get_dialect_all_supported_types() {
4006 assert!(get_dialect(DbType::MySQL).is_ok());
4008 assert!(get_dialect(DbType::PostgreSQL).is_ok());
4009 assert!(get_dialect(DbType::Sqlite).is_ok());
4010 assert!(get_dialect(DbType::Oracle).is_ok());
4011 assert!(get_dialect(DbType::SqlServer).is_ok());
4012 assert!(get_dialect(DbType::OceanBase).is_ok());
4013 assert!(get_dialect(DbType::ClickHouse).is_ok());
4014 assert!(get_dialect(DbType::Dameng).is_ok());
4016 assert!(get_dialect(DbType::Kingbase).is_ok());
4017 assert!(get_dialect(DbType::Db2).is_ok());
4018 assert!(get_dialect(DbType::MariaDB).is_ok());
4019 assert!(get_dialect(DbType::TiDB).is_ok());
4020 assert!(get_dialect(DbType::PolarDB).is_ok());
4021 assert!(get_dialect(DbType::GaussDB).is_ok());
4022 assert!(get_dialect(DbType::GBase).is_ok());
4023 assert!(get_dialect(DbType::Sybase).is_ok());
4024 assert!(get_dialect(DbType::Redis).is_err());
4026 assert!(get_dialect(DbType::MongoDB).is_err());
4027 assert!(get_dialect(DbType::VectorDb).is_err());
4028 assert!(get_dialect(DbType::PureJsDb).is_err());
4029 }
4030
4031 #[test]
4034 fn test_mariadb_dialect() {
4035 let dialect = get_dialect(DbType::MariaDB).unwrap();
4036 assert_eq!(dialect.db_type(), DbType::MariaDB);
4037 assert_eq!(dialect.quote("users"), "`users`");
4039 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4040 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4041 assert!(!dialect.supports_returning());
4043 }
4044
4045 #[test]
4046 fn test_tidb_dialect() {
4047 let dialect = get_dialect(DbType::TiDB).unwrap();
4048 assert_eq!(dialect.db_type(), DbType::TiDB);
4049 assert_eq!(dialect.quote("users"), "`users`");
4051 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4052 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4053 }
4054
4055 #[test]
4056 fn test_dameng_dialect() {
4057 let dialect = get_dialect(DbType::Dameng).unwrap();
4058 assert_eq!(dialect.db_type(), DbType::Dameng);
4059 assert_eq!(dialect.quote("users"), "\"users\"");
4061 assert_eq!(dialect.escape_string("it's"), "it''s");
4062 assert_eq!(
4064 dialect.auto_increment_keyword(),
4065 "GENERATED BY DEFAULT AS IDENTITY"
4066 );
4067 assert!(dialect.supports_returning());
4069 }
4070
4071 #[test]
4072 fn test_kingbase_dialect() {
4073 let dialect = get_dialect(DbType::Kingbase).unwrap();
4074 assert_eq!(dialect.db_type(), DbType::Kingbase);
4075 assert_eq!(dialect.quote("users"), "\"users\"");
4077 assert_eq!(dialect.escape_string("it's"), "it''s");
4078 assert!(dialect.supports_returning());
4080 assert_eq!(
4082 dialect.auto_increment_keyword(),
4083 "GENERATED BY DEFAULT AS IDENTITY"
4084 );
4085 }
4086
4087 #[test]
4088 fn test_polardb_dialect() {
4089 let dialect = get_dialect(DbType::PolarDB).unwrap();
4090 assert_eq!(dialect.db_type(), DbType::PolarDB);
4091 assert_eq!(dialect.quote("users"), "\"users\"");
4093 assert!(dialect.supports_returning());
4094 }
4095
4096 #[test]
4097 fn test_gaussdb_dialect() {
4098 let dialect = get_dialect(DbType::GaussDB).unwrap();
4099 assert_eq!(dialect.db_type(), DbType::GaussDB);
4100 assert_eq!(dialect.quote("users"), "\"users\"");
4102 assert!(dialect.supports_returning());
4103 }
4104
4105 #[test]
4106 fn test_gbase_dialect() {
4107 let dialect = get_dialect(DbType::GBase).unwrap();
4108 assert_eq!(dialect.db_type(), DbType::GBase);
4109 assert_eq!(dialect.quote("users"), "[users]");
4111 }
4112
4113 #[test]
4114 fn test_sybase_dialect() {
4115 let dialect = get_dialect(DbType::Sybase).unwrap();
4116 assert_eq!(dialect.db_type(), DbType::Sybase);
4117 assert_eq!(dialect.quote("users"), "[users]");
4119 }
4120
4121 #[test]
4124 fn test_db2_dialect_basic() {
4125 let dialect = get_dialect(DbType::Db2).unwrap();
4126 assert_eq!(dialect.db_type(), DbType::Db2);
4127 assert_eq!(dialect.quote("users"), "\"users\"");
4129 assert_eq!(dialect.escape_string("it's"), "it''s");
4131 assert_eq!(
4133 dialect.auto_increment_keyword(),
4134 "GENERATED ALWAYS AS IDENTITY"
4135 );
4136 assert!(!dialect.supports_if_exists());
4138 assert!(!dialect.supports_if_not_exists());
4139 assert!(!dialect.supports_returning());
4141 }
4142
4143 #[test]
4144 fn test_db2_pagination() {
4145 let dialect = Db2Dialect;
4146 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4148 assert_eq!(
4149 sql,
4150 "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
4151 );
4152 }
4153
4154 #[test]
4155 fn test_db2_last_insert_id() {
4156 let dialect = Db2Dialect;
4157 assert_eq!(
4159 dialect.last_insert_id_sql(),
4160 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
4161 );
4162 }
4163
4164 #[test]
4165 fn test_db2_concat() {
4166 let dialect = Db2Dialect;
4167 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
4169 assert_eq!(dialect.concat(&[]), "''");
4170 }
4171
4172 #[test]
4173 fn test_db2_create_table() {
4174 let dialect = Db2Dialect;
4175 let cols = vec![ColumnDef {
4176 name: "id".to_string(),
4177 sql_type: "BIGINT".to_string(),
4178 nullable: false,
4179 default: None,
4180 auto_increment: true,
4181 primary_key: true,
4182 }];
4183 let sql = dialect.build_create_table("users", &cols);
4184 assert!(sql.contains("\"id\" BIGINT"));
4185 assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
4186 assert!(sql.contains("PRIMARY KEY"));
4187 }
4188
4189 #[test]
4190 fn test_db2_type_mapping() {
4191 assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
4193 assert_eq!(map_to_db2_type("INT"), "INTEGER");
4194 assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
4195 assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
4196 assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
4197 assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
4198 assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
4199 assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
4200 assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
4201 assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
4202 assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
4203 assert_eq!(map_to_db2_type("DATE"), "DATE");
4204 assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
4205 }
4206
4207 #[test]
4210 fn test_clickhouse_dialect_basic() {
4211 let dialect = get_dialect(DbType::ClickHouse).unwrap();
4212 assert_eq!(dialect.db_type(), DbType::ClickHouse);
4213 assert_eq!(dialect.quote("users"), "`users`");
4215 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4217 assert!(!dialect.supports_returning());
4219 assert_eq!(dialect.auto_increment_keyword(), "");
4221 assert!(dialect.supports_if_exists());
4223 assert!(dialect.supports_if_not_exists());
4224 }
4225
4226 #[test]
4227 fn test_clickhouse_type_mapping() {
4228 assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
4229 assert_eq!(map_to_clickhouse_type("INT"), "Int32");
4230 assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
4231 assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
4232 assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
4233 assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
4234 assert_eq!(map_to_clickhouse_type("TEXT"), "String");
4235 assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
4236 assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
4237 assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
4238 assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
4239 assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
4240 assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
4241 assert_eq!(map_to_clickhouse_type("DATE"), "Date");
4242 }
4243
4244 #[test]
4245 fn test_clickhouse_create_table() {
4246 let dialect = ClickHouseDialect;
4247 let cols = vec![ColumnDef {
4248 name: "id".to_string(),
4249 sql_type: "BIGINT".to_string(),
4250 nullable: false,
4251 default: None,
4252 auto_increment: false, primary_key: true,
4254 }];
4255 let sql = dialect.build_create_table("users", &cols);
4256 assert!(
4258 sql.contains("ENGINE = MergeTree()"),
4259 "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
4260 sql
4261 );
4262 assert!(sql.contains("`id` Int64"));
4263 assert!(sql.contains("PRIMARY KEY"));
4264 }
4265
4266 #[test]
4267 fn test_clickhouse_json_extract() {
4268 let dialect = ClickHouseDialect;
4269 let sql = dialect.json_extract("data", "$.name");
4270 assert!(
4271 sql.contains("JSONExtractString"),
4272 "ClickHouse 应使用 JSONExtractString: {}",
4273 sql
4274 );
4275 }
4276
4277 #[test]
4278 fn test_clickhouse_concat() {
4279 let dialect = ClickHouseDialect;
4280 assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
4282 assert_eq!(dialect.concat(&[]), "''");
4283 }
4284
4285 #[test]
4288 fn test_db_type_dameng_str() {
4289 assert_eq!(DbType::Dameng.as_str(), "dameng");
4290 assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
4291 assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
4292 assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
4293 assert_eq!(DbType::Dameng.default_port(), 5236);
4294 }
4295
4296 #[test]
4297 fn test_db_type_kingbase_str() {
4298 assert_eq!(DbType::Kingbase.as_str(), "kingbase");
4299 assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
4300 assert_eq!(DbType::Kingbase.default_port(), 54321);
4301 }
4302
4303 #[test]
4304 fn test_db_type_db2_str() {
4305 assert_eq!(DbType::Db2.as_str(), "db2");
4306 assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
4307 assert_eq!(DbType::Db2.default_port(), 50000);
4308 }
4309
4310 #[test]
4311 fn test_db_type_mariadb_str() {
4312 assert_eq!(DbType::MariaDB.as_str(), "mariadb");
4313 assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
4314 assert_eq!(DbType::MariaDB.default_port(), 3306);
4315 }
4316
4317 #[test]
4318 fn test_db_type_tidb_str() {
4319 assert_eq!(DbType::TiDB.as_str(), "tidb");
4320 assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
4321 assert_eq!(DbType::TiDB.default_port(), 4000);
4322 }
4323
4324 #[test]
4325 fn test_db_type_polardb_str() {
4326 assert_eq!(DbType::PolarDB.as_str(), "polardb");
4327 assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
4328 assert_eq!(DbType::PolarDB.default_port(), 5432);
4329 }
4330
4331 #[test]
4332 fn test_db_type_gaussdb_str() {
4333 assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
4334 assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
4335 assert_eq!(DbType::GaussDB.default_port(), 25308);
4336 }
4337
4338 #[test]
4339 fn test_db_type_gbase_str() {
4340 assert_eq!(DbType::GBase.as_str(), "gbase");
4341 assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
4342 assert_eq!(DbType::GBase.default_port(), 9088);
4343 }
4344
4345 #[test]
4346 fn test_db_type_sybase_str() {
4347 assert_eq!(DbType::Sybase.as_str(), "sybase");
4348 assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
4349 assert_eq!(DbType::Sybase.default_port(), 5000);
4350 }
4351
4352 #[test]
4353 fn test_db_type_family_classification() {
4354 assert!(DbType::MySQL.is_mysql_family());
4356 assert!(DbType::MariaDB.is_mysql_family());
4357 assert!(DbType::TiDB.is_mysql_family());
4358 assert!(DbType::OceanBase.is_mysql_family());
4359 assert!(!DbType::PostgreSQL.is_mysql_family());
4360
4361 assert!(DbType::PostgreSQL.is_postgres_family());
4363 assert!(DbType::Kingbase.is_postgres_family());
4364 assert!(DbType::GaussDB.is_postgres_family());
4365 assert!(!DbType::MySQL.is_postgres_family());
4366
4367 assert!(DbType::Oracle.is_oracle_family());
4369 assert!(DbType::Dameng.is_oracle_family());
4370 assert!(!DbType::MySQL.is_oracle_family());
4371 }
4372
4373 #[test]
4374 fn test_db_type_supports_stored_procedure_extended() {
4375 assert!(DbType::Dameng.supports_stored_procedure());
4377 assert!(DbType::Kingbase.supports_stored_procedure());
4378 assert!(DbType::Db2.supports_stored_procedure());
4379 assert!(DbType::MariaDB.supports_stored_procedure());
4380 assert!(DbType::TiDB.supports_stored_procedure());
4381 assert!(DbType::PolarDB.supports_stored_procedure());
4382 assert!(DbType::GaussDB.supports_stored_procedure());
4383 assert!(DbType::GBase.supports_stored_procedure());
4384 assert!(DbType::Sybase.supports_stored_procedure());
4385 }
4386
4387 #[test]
4390 fn test_l4_max_identifier_len_constant() {
4391 assert_eq!(MAX_IDENTIFIER_LEN, 63);
4393 }
4394
4395 #[test]
4396 fn test_l4_quote_checked_valid_identifier() {
4397 let dialect = MySqlDialect;
4398 assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
4399 assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
4400 let name_63 = "a".repeat(63);
4402 assert!(dialect.quote_checked(&name_63).is_ok());
4403 }
4404
4405 #[test]
4406 fn test_l4_quote_checked_rejects_too_long() {
4407 let dialect = MySqlDialect;
4408 let long_name = "a".repeat(64); let result = dialect.quote_checked(&long_name);
4410 assert!(result.is_err());
4411 match result {
4412 Err(DbError::InvalidInput(msg)) => {
4413 assert!(
4414 msg.contains("too long"),
4415 "expected 'too long' error, got: {}",
4416 msg
4417 );
4418 }
4419 _ => panic!("Expected DbError::InvalidInput"),
4420 }
4421 }
4422
4423 #[test]
4424 fn test_l4_quote_checked_rejects_empty() {
4425 let dialect = MySqlDialect;
4426 let result = dialect.quote_checked("");
4427 assert!(result.is_err());
4428 }
4429
4430 #[test]
4431 fn test_l4_quote_checked_rejects_sql_injection() {
4432 let dialect = MySqlDialect;
4433 assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
4435 assert!(dialect.quote_checked("user'name").is_err());
4437 assert!(dialect.quote_checked("user name").is_err());
4439 assert!(dialect.quote_checked("1users").is_err());
4441 assert!(dialect.quote_checked("schema.table").is_err());
4443 }
4444
4445 #[test]
4446 fn test_l4_quote_checked_postgres() {
4447 let dialect = PostgreSqlDialect;
4448 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4449 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4450 }
4451
4452 #[test]
4453 fn test_l4_quote_checked_sqlite() {
4454 let dialect = SqliteDialect;
4455 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4456 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4457 }
4458
4459 #[test]
4460 fn test_l4_quote_checked_oracle() {
4461 let dialect = OracleDialect;
4462 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4463 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4464 }
4465
4466 #[test]
4467 fn test_l4_quote_checked_sql_server() {
4468 let dialect = SqlServerDialect;
4469 assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
4470 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4471 }
4472
4473 #[test]
4476 fn test_duckdb_quote() {
4477 let dialect = DuckDBDialect;
4478 assert_eq!(dialect.quote("users"), "\"users\"");
4479 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
4480 }
4481
4482 #[test]
4483 fn test_duckdb_escape() {
4484 let dialect = DuckDBDialect;
4485 assert_eq!(dialect.escape_string("hello"), "hello");
4486 assert_eq!(dialect.escape_string("it's"), "it''s");
4487 }
4488
4489 #[test]
4490 fn test_duckdb_pagination() {
4491 let dialect = DuckDBDialect;
4492 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4493 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
4494 }
4495
4496 #[test]
4497 fn test_duckdb_supports() {
4498 let dialect = DuckDBDialect;
4499 assert!(!dialect.supports_returning());
4500 assert!(!dialect.supports_lock_for_update());
4501 assert!(!dialect.supports_lock_shared());
4502 assert!(dialect.supports_if_exists());
4503 assert!(dialect.supports_if_not_exists());
4504 }
4505
4506 #[test]
4507 fn test_duckdb_insert_or_ignore() {
4508 let dialect = DuckDBDialect;
4509 let sql = dialect.build_insert_or_ignore_prefix("users");
4510 assert_eq!(sql, "INSERT OR IGNORE INTO \"users\"");
4511 }
4512
4513 #[test]
4514 fn test_duckdb_create_table() {
4515 let dialect = DuckDBDialect;
4516 let columns = vec![
4517 ColumnDef {
4518 name: "id".to_string(),
4519 sql_type: "BIGINT".to_string(),
4520 nullable: false,
4521 default: None,
4522 auto_increment: true,
4523 primary_key: true,
4524 },
4525 ColumnDef {
4526 name: "name".to_string(),
4527 sql_type: "VARCHAR(255)".to_string(),
4528 nullable: false,
4529 default: None,
4530 auto_increment: false,
4531 primary_key: false,
4532 },
4533 ];
4534 let sql = dialect.build_create_table("users", &columns);
4535 assert!(sql.contains("CREATE TABLE \"users\""));
4536 assert!(sql.contains("\"id\" BIGINT PRIMARY KEY"));
4537 assert!(sql.contains("\"name\" VARCHAR(255)"));
4538 }
4539
4540 #[test]
4541 fn test_duckdb_alter_table() {
4542 let dialect = DuckDBDialect;
4543 let changes = vec![TableChange::AddColumn(ColumnDef {
4544 name: "age".to_string(),
4545 sql_type: "INTEGER".to_string(),
4546 nullable: true,
4547 default: None,
4548 auto_increment: false,
4549 primary_key: false,
4550 })];
4551 let sql = dialect.build_alter_table("users", &changes);
4552 assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN \"age\" INTEGER"));
4553 }
4554
4555 #[test]
4556 fn test_duckdb_json_extract() {
4557 let dialect = DuckDBDialect;
4558 let sql = dialect.json_extract("data", "$.user.name");
4559 assert!(sql.contains("->"));
4560 assert!(sql.contains("user.name"));
4561 }
4562
4563 #[test]
4564 fn test_duckdb_concat() {
4565 let dialect = DuckDBDialect;
4566 let sql = dialect.concat(&["a", "b", "c"]);
4567 assert_eq!(sql, "a || b || c");
4568 }
4569
4570 #[test]
4571 fn test_duckdb_bool_to_int() {
4572 let dialect = DuckDBDialect;
4573 let sql = dialect.bool_to_int("active");
4574 assert!(sql.contains("CASE WHEN"));
4575 assert!(sql.contains("THEN 1"));
4576 assert!(sql.contains("ELSE 0"));
4577 }
4578
4579 #[test]
4580 fn test_get_dialect_duckdb() {
4581 let result = get_dialect(DbType::DuckDB);
4582 assert!(result.is_ok());
4583 let dialect = result.unwrap();
4584 assert_eq!(dialect.db_type(), DbType::DuckDB);
4585 }
4586}
4587
4588#[cfg(feature = "perf-enum-dispatch")]
4597#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4598pub enum DialectKind {
4599 MySQL,
4601 PostgreSQL,
4603 SQLite,
4605 Oracle,
4607 MSSQL,
4609}
4610
4611#[cfg(feature = "perf-enum-dispatch")]
4612impl DialectKind {
4613 pub fn from_db_type(db_type: DbType) -> Option<Self> {
4615 match db_type {
4616 DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase => Some(Self::MySQL),
4617 DbType::PostgreSQL | DbType::Kingbase | DbType::PolarDB | DbType::GaussDB => {
4618 Some(Self::PostgreSQL)
4619 }
4620 DbType::Sqlite => Some(Self::SQLite),
4621 DbType::Oracle | DbType::Dameng => Some(Self::Oracle),
4622 DbType::SqlServer | DbType::Sybase | DbType::GBase => Some(Self::MSSQL),
4623 _ => None,
4624 }
4625 }
4626
4627 pub fn quote(&self, identifier: &str) -> String {
4629 match self {
4630 Self::MySQL => MySqlDialect.quote(identifier),
4631 Self::PostgreSQL => PostgreSqlDialect.quote(identifier),
4632 Self::SQLite => SqliteDialect.quote(identifier),
4633 Self::Oracle => OracleDialect.quote(identifier),
4634 Self::MSSQL => SqlServerDialect.quote(identifier),
4635 }
4636 }
4637
4638 pub fn escape_string(&self, s: &str) -> String {
4640 match self {
4641 Self::MySQL => MySqlDialect.escape_string(s),
4642 Self::PostgreSQL => PostgreSqlDialect.escape_string(s),
4643 Self::SQLite => SqliteDialect.escape_string(s),
4644 Self::Oracle => OracleDialect.escape_string(s),
4645 Self::MSSQL => SqlServerDialect.escape_string(s),
4646 }
4647 }
4648
4649 pub fn db_type(&self) -> DbType {
4651 match self {
4652 Self::MySQL => DbType::MySQL,
4653 Self::PostgreSQL => DbType::PostgreSQL,
4654 Self::SQLite => DbType::Sqlite,
4655 Self::Oracle => DbType::Oracle,
4656 Self::MSSQL => DbType::SqlServer,
4657 }
4658 }
4659
4660 pub fn to_dialect(&self) -> Box<dyn Dialect> {
4662 match self {
4663 Self::MySQL => Box::new(MySqlDialect),
4664 Self::PostgreSQL => Box::new(PostgreSqlDialect),
4665 Self::SQLite => Box::new(SqliteDialect),
4666 Self::Oracle => Box::new(OracleDialect),
4667 Self::MSSQL => Box::new(SqlServerDialect),
4668 }
4669 }
4670}
4671
4672#[cfg(all(test, feature = "perf-enum-dispatch"))]
4673mod enum_dispatch_tests {
4674 use super::*;
4675
4676 #[test]
4677 fn test_dialect_kind_from_db_type() {
4678 assert_eq!(
4679 DialectKind::from_db_type(DbType::MySQL),
4680 Some(DialectKind::MySQL)
4681 );
4682 assert_eq!(
4683 DialectKind::from_db_type(DbType::PostgreSQL),
4684 Some(DialectKind::PostgreSQL)
4685 );
4686 assert_eq!(
4687 DialectKind::from_db_type(DbType::Sqlite),
4688 Some(DialectKind::SQLite)
4689 );
4690 assert_eq!(
4691 DialectKind::from_db_type(DbType::Oracle),
4692 Some(DialectKind::Oracle)
4693 );
4694 assert_eq!(
4695 DialectKind::from_db_type(DbType::SqlServer),
4696 Some(DialectKind::MSSQL)
4697 );
4698 assert_eq!(DialectKind::from_db_type(DbType::Redis), None);
4699 }
4700
4701 #[test]
4702 fn test_dialect_kind_quote_diff() {
4703 let kinds = [
4704 DialectKind::MySQL,
4705 DialectKind::PostgreSQL,
4706 DialectKind::SQLite,
4707 DialectKind::Oracle,
4708 DialectKind::MSSQL,
4709 ];
4710 for kind in &kinds {
4711 let enum_result = kind.quote("users");
4712 let box_result = kind.to_dialect().quote("users");
4713 assert_eq!(enum_result, box_result);
4714 }
4715 }
4716
4717 #[test]
4718 fn test_dialect_kind_escape_diff() {
4719 let kinds = [
4720 DialectKind::MySQL,
4721 DialectKind::PostgreSQL,
4722 DialectKind::SQLite,
4723 DialectKind::Oracle,
4724 DialectKind::MSSQL,
4725 ];
4726 for kind in &kinds {
4727 let enum_result = kind.escape_string("it's");
4728 let box_result = kind.to_dialect().escape_string("it's");
4729 assert_eq!(enum_result, box_result);
4730 }
4731 }
4732}