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 if trimmed.starts_with("SERIAL") {
2851 sql_type.to_string()
2852 } else {
2853 sql_type.to_string()
2854 }
2855}
2856
2857#[cfg(feature = "dialect-saphana")]
2862#[derive(Debug, Clone)]
2863pub struct SapHanaDialect;
2864
2865#[cfg(feature = "dialect-saphana")]
2866impl Dialect for SapHanaDialect {
2867 fn clone_box(&self) -> Box<dyn Dialect> {
2868 Box::new(SapHanaDialect)
2869 }
2870
2871 fn db_type(&self) -> DbType {
2872 DbType::SapHana
2873 }
2874
2875 fn quote(&self, identifier: &str) -> String {
2876 format!("\"{}\"", identifier.replace('"', "\"\""))
2877 }
2878
2879 fn escape_string(&self, s: &str) -> String {
2880 let mut escaped = String::with_capacity(s.len() * 2);
2881 for c in s.chars() {
2882 match c {
2883 '\'' => escaped.push_str("''"),
2884 _ => escaped.push(c),
2885 }
2886 }
2887 escaped
2888 }
2889
2890 fn supports_returning(&self) -> bool {
2891 false
2892 }
2893
2894 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2895 let offset = page.saturating_sub(1).saturating_mul(limit);
2896 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
2897 }
2898
2899 fn json_type(&self) -> &'static str {
2900 "NCLOB"
2901 }
2902
2903 fn json_extract(&self, column: &str, path: &str) -> String {
2904 let normalized = if path.starts_with('$') {
2905 path.to_string()
2906 } else {
2907 format!("$.{}", path)
2908 };
2909 format!(
2910 "JSON_VALUE({}, '{}')",
2911 column,
2912 self.escape_string(&normalized)
2913 )
2914 }
2915
2916 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2917 if columns.is_empty() {
2918 return "0".to_string();
2919 }
2920 let escaped = self.escape_string(keyword);
2921 let parts: Vec<String> = columns
2922 .iter()
2923 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2924 .collect();
2925 parts.join(" OR ")
2926 }
2927
2928 fn bool_to_int(&self, expr: &str) -> String {
2929 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2930 }
2931
2932 fn concat(&self, parts: &[&str]) -> String {
2933 if parts.is_empty() {
2934 return "''".to_string();
2935 }
2936 parts.join(" || ")
2937 }
2938
2939 fn supports_if_exists(&self) -> bool {
2940 true
2941 }
2942
2943 fn supports_if_not_exists(&self) -> bool {
2944 true
2945 }
2946
2947 fn auto_increment_keyword(&self) -> &'static str {
2948 "GENERATED BY DEFAULT AS IDENTITY"
2949 }
2950
2951 fn last_insert_id_sql(&self) -> Option<&'static str> {
2952 None
2953 }
2954
2955 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2956 let cols: Vec<String> = columns
2957 .iter()
2958 .map(|col| {
2959 let hana_type = map_to_saphana_type(&col.sql_type);
2960 let mut sql = format!("{} {}", self.quote(&col.name), hana_type);
2961 if !col.nullable && !col.auto_increment {
2962 sql.push_str(" NOT NULL");
2963 }
2964 if let Some(default) = &col.default {
2965 sql.push_str(&format!(" DEFAULT {}", default));
2966 }
2967 if col.auto_increment {
2968 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2969 }
2970 if col.primary_key {
2971 sql.push_str(" PRIMARY KEY");
2972 }
2973 sql
2974 })
2975 .collect();
2976
2977 format!(
2978 "CREATE COLUMN TABLE {} ({})",
2979 self.quote(table),
2980 cols.join(", ")
2981 )
2982 }
2983
2984 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2985 let stmts: Vec<String> = changes
2986 .iter()
2987 .map(|change| match change {
2988 TableChange::AddColumn(col) => {
2989 let hana_type = map_to_saphana_type(&col.sql_type);
2990 let mut sql = format!(
2991 "ALTER TABLE {} ADD ({})",
2992 self.quote(table),
2993 format!("{} {}", self.quote(&col.name), hana_type)
2994 );
2995 if !col.nullable {
2996 sql.push_str(" NOT NULL");
2997 }
2998 if let Some(default) = &col.default {
2999 sql.push_str(&format!(" DEFAULT {}", default));
3000 }
3001 sql
3002 }
3003 TableChange::DropColumn(name) => {
3004 format!(
3005 "ALTER TABLE {} DROP ({})",
3006 self.quote(table),
3007 self.quote(name)
3008 )
3009 }
3010 TableChange::ModifyColumn(col) => {
3011 let hana_type = map_to_saphana_type(&col.sql_type);
3012 format!(
3013 "ALTER TABLE {} ALTER ({})",
3014 self.quote(table),
3015 format!("{} {}", self.quote(&col.name), 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") || trimmed.starts_with("NVARCHAR") {
3070 sql_type.to_string()
3071 } else if trimmed.starts_with("CHAR") || trimmed.starts_with("NCHAR") {
3072 sql_type.to_string()
3073 } else if matches!(trimmed, "TEXT" | "CLOB") {
3074 "NCLOB".to_string()
3075 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3076 "BOOLEAN".to_string()
3077 } else if matches!(trimmed, "FLOAT" | "REAL") {
3078 "REAL".to_string()
3079 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3080 "DOUBLE".to_string()
3081 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3082 "TIMESTAMP".to_string()
3083 } else if matches!(trimmed, "DATE") {
3084 "DATE".to_string()
3085 } else {
3086 sql_type.to_string()
3087 }
3088}
3089
3090#[cfg(feature = "dialect-firebird")]
3095#[derive(Debug, Clone)]
3096pub struct FirebirdDialect;
3097
3098#[cfg(feature = "dialect-firebird")]
3099impl Dialect for FirebirdDialect {
3100 fn clone_box(&self) -> Box<dyn Dialect> {
3101 Box::new(FirebirdDialect)
3102 }
3103
3104 fn db_type(&self) -> DbType {
3105 DbType::Firebird
3106 }
3107
3108 fn quote(&self, identifier: &str) -> String {
3109 format!("\"{}\"", identifier.replace('"', "\"\""))
3110 }
3111
3112 fn escape_string(&self, s: &str) -> String {
3113 let mut escaped = String::with_capacity(s.len() * 2);
3114 for c in s.chars() {
3115 match c {
3116 '\'' => escaped.push_str("''"),
3117 _ => escaped.push(c),
3118 }
3119 }
3120 escaped
3121 }
3122
3123 fn supports_returning(&self) -> bool {
3124 true
3125 }
3126
3127 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
3128 let offset = page.saturating_sub(1).saturating_mul(limit);
3129 let to = offset + limit;
3130 format!("{} ROWS {} TO {}", sql, offset + 1, to)
3131 }
3132
3133 fn json_type(&self) -> &'static str {
3134 "BLOB SUB_TYPE TEXT"
3135 }
3136
3137 fn json_extract(&self, column: &str, path: &str) -> String {
3138 let normalized = if path.starts_with('$') {
3139 path.to_string()
3140 } else {
3141 format!("$.{}", path)
3142 };
3143 format!(
3144 "JSON_GET({}, '{}')",
3145 column,
3146 self.escape_string(&normalized)
3147 )
3148 }
3149
3150 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
3151 if columns.is_empty() {
3152 return "0".to_string();
3153 }
3154 let escaped = self.escape_string(keyword);
3155 let parts: Vec<String> = columns
3156 .iter()
3157 .map(|c| format!("CONTAINING({}, '{}')", c, escaped))
3158 .collect();
3159 parts.join(" OR ")
3160 }
3161
3162 fn bool_to_int(&self, expr: &str) -> String {
3163 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
3164 }
3165
3166 fn concat(&self, parts: &[&str]) -> String {
3167 if parts.is_empty() {
3168 return "''".to_string();
3169 }
3170 parts.join(" || ")
3171 }
3172
3173 fn supports_if_exists(&self) -> bool {
3174 true
3175 }
3176
3177 fn supports_if_not_exists(&self) -> bool {
3178 true
3179 }
3180
3181 fn auto_increment_keyword(&self) -> &'static str {
3182 "GENERATED BY DEFAULT AS IDENTITY"
3183 }
3184
3185 fn last_insert_id_sql(&self) -> Option<&'static str> {
3186 Some("SELECT GEN_ID(SQLITE_SEQUENCE, 0) FROM RDB$DATABASE")
3187 }
3188
3189 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
3190 let cols: Vec<String> = columns
3191 .iter()
3192 .map(|col| {
3193 let fb_type = map_to_firebird_type(&col.sql_type);
3194 let mut sql = format!("{} {}", self.quote(&col.name), fb_type);
3195 if !col.nullable && !col.auto_increment {
3196 sql.push_str(" NOT NULL");
3197 }
3198 if let Some(default) = &col.default {
3199 sql.push_str(&format!(" DEFAULT {}", default));
3200 }
3201 if col.auto_increment {
3202 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
3203 }
3204 if col.primary_key {
3205 sql.push_str(" PRIMARY KEY");
3206 }
3207 sql
3208 })
3209 .collect();
3210
3211 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
3212 }
3213
3214 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
3215 let stmts: Vec<String> = changes
3216 .iter()
3217 .map(|change| match change {
3218 TableChange::AddColumn(col) => {
3219 let fb_type = map_to_firebird_type(&col.sql_type);
3220 let mut sql = format!(
3221 "ALTER TABLE {} ADD {} {}",
3222 self.quote(table),
3223 self.quote(&col.name),
3224 fb_type
3225 );
3226 if !col.nullable {
3227 sql.push_str(" NOT NULL");
3228 }
3229 if let Some(default) = &col.default {
3230 sql.push_str(&format!(" DEFAULT {}", default));
3231 }
3232 sql
3233 }
3234 TableChange::DropColumn(name) => {
3235 format!(
3236 "ALTER TABLE {} DROP {}",
3237 self.quote(table),
3238 self.quote(name)
3239 )
3240 }
3241 TableChange::ModifyColumn(col) => {
3242 let fb_type = map_to_firebird_type(&col.sql_type);
3243 format!(
3244 "ALTER TABLE {} ALTER COLUMN {} TYPE {}",
3245 self.quote(table),
3246 self.quote(&col.name),
3247 fb_type
3248 )
3249 }
3250 TableChange::AddIndex(name, cols) => {
3251 format!(
3252 "CREATE INDEX {} ON {} ({})",
3253 name,
3254 self.quote(table),
3255 cols.join(", ")
3256 )
3257 }
3258 TableChange::DropIndex(name) => {
3259 format!("DROP INDEX {}", name)
3260 }
3261 TableChange::AddForeignKey {
3262 columns,
3263 reference_table,
3264 reference_columns,
3265 } => {
3266 format!(
3267 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
3268 self.quote(table),
3269 columns
3270 .iter()
3271 .map(|c| self.quote(c))
3272 .collect::<Vec<_>>()
3273 .join(", "),
3274 self.quote(reference_table),
3275 reference_columns
3276 .iter()
3277 .map(|c| self.quote(c))
3278 .collect::<Vec<_>>()
3279 .join(", ")
3280 )
3281 }
3282 })
3283 .collect();
3284
3285 stmts.join("; ")
3286 }
3287}
3288
3289#[cfg(feature = "dialect-firebird")]
3291fn map_to_firebird_type(sql_type: &str) -> String {
3292 let upper = sql_type.to_uppercase();
3293 let trimmed = upper.trim();
3294
3295 if trimmed.starts_with("BIGINT") {
3296 "BIGINT".to_string()
3297 } else if matches!(trimmed, "INT" | "INTEGER") {
3298 "INTEGER".to_string()
3299 } else if matches!(trimmed, "SMALLINT") {
3300 "SMALLINT".to_string()
3301 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
3302 sql_type.to_string()
3303 } else if matches!(trimmed, "TEXT" | "BLOB") {
3304 "BLOB SUB_TYPE TEXT".to_string()
3305 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3306 "BOOLEAN".to_string()
3307 } else if matches!(trimmed, "FLOAT" | "REAL") {
3308 "FLOAT".to_string()
3309 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3310 "DOUBLE PRECISION".to_string()
3311 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3312 "TIMESTAMP".to_string()
3313 } else if matches!(trimmed, "DATE") {
3314 "DATE".to_string()
3315 } else {
3316 sql_type.to_string()
3317 }
3318}
3319
3320#[cfg(test)]
3321mod tests {
3322 use super::*;
3323
3324 #[test]
3325 fn test_mysql_quote() {
3326 let dialect = MySqlDialect;
3327 assert_eq!(dialect.quote("users"), "`users`");
3328 assert_eq!(dialect.quote("user`id"), "`user``id`");
3329 }
3330
3331 #[test]
3332 fn test_mysql_escape() {
3333 let dialect = MySqlDialect;
3334 assert_eq!(dialect.escape_string("hello"), "hello");
3335 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3336 assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
3337 }
3338
3339 #[test]
3340 fn test_mysql_pagination() {
3341 let dialect = MySqlDialect;
3342 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3343 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
3344 }
3345
3346 #[test]
3347 fn test_postgres_quote() {
3348 let dialect = PostgreSqlDialect;
3349 assert_eq!(dialect.quote("users"), "\"users\"");
3350 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3351 }
3352
3353 #[test]
3354 fn test_postgres_pagination() {
3355 let dialect = PostgreSqlDialect;
3356 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3357 assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
3358 }
3359
3360 #[test]
3361 fn test_postgres_returning() {
3362 let dialect = PostgreSqlDialect;
3363 assert!(dialect.supports_returning());
3364 }
3365
3366 #[test]
3367 fn test_sqlite_quote() {
3368 let dialect = SqliteDialect;
3369 assert_eq!(dialect.quote("users"), "\"users\"");
3370 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3371 }
3372
3373 #[test]
3374 fn test_sqlite_escape() {
3375 let dialect = SqliteDialect;
3376 assert_eq!(dialect.escape_string("hello"), "hello");
3377 assert_eq!(dialect.escape_string("it's"), "it''s");
3378 }
3379
3380 #[test]
3381 fn test_get_dialect() {
3382 let dialect = get_dialect(DbType::MySQL);
3383 assert!(dialect.is_ok());
3384
3385 let dialect = get_dialect(DbType::Redis);
3386 assert!(dialect.is_err());
3387 }
3388
3389 #[test]
3390 fn test_bool_to_int() {
3391 let mysql = MySqlDialect;
3392 assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
3393
3394 let pg = PostgreSqlDialect;
3395 assert_eq!(
3396 pg.bool_to_int("active"),
3397 "(CASE WHEN active THEN 1 ELSE 0 END)"
3398 );
3399 }
3400
3401 #[test]
3402 fn test_json_extract_with_path() {
3403 let mysql = MySqlDialect;
3404 let sql = mysql.json_extract("data", "$.user.name");
3405 assert!(sql.contains("$.user.name"));
3406 assert!(sql.contains("JSON_EXTRACT"));
3407
3408 let pg = PostgreSqlDialect;
3409 let sql = pg.json_extract("data", "user.name");
3410 assert!(sql.contains("#>>"));
3411
3412 let sqlite = SqliteDialect;
3413 let sql = sqlite.json_extract("data", "$.user.name");
3414 assert!(sql.contains("$.user.name"));
3415 assert!(sql.contains("json_extract"));
3416 }
3417
3418 #[test]
3419 fn test_sqlite_full_text_search() {
3420 let sqlite = SqliteDialect;
3421 let sql = sqlite.full_text_search(&["title", "content"], "hello");
3422 assert!(sql.contains("LIKE"));
3424 assert!(sql.contains("title LIKE '%hello%'"));
3425 assert!(sql.contains("content LIKE '%hello%'"));
3426 assert!(sql.contains(" OR "));
3427
3428 assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
3430
3431 let sql = sqlite.full_text_search(&["title"], "it's");
3433 assert!(sql.contains("title LIKE '%it''s%'"));
3434 }
3435
3436 #[test]
3437 fn test_alter_table_modify_column() {
3438 let mysql = MySqlDialect;
3439 let col = ColumnDef {
3440 name: "name".to_string(),
3441 sql_type: "VARCHAR(255)".to_string(),
3442 nullable: false,
3443 default: None,
3444 auto_increment: false,
3445 primary_key: false,
3446 };
3447 let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3448 assert!(sql.contains("MODIFY COLUMN"));
3449
3450 let pg = PostgreSqlDialect;
3451 let col = ColumnDef {
3452 name: "name".to_string(),
3453 sql_type: "VARCHAR(255)".to_string(),
3454 nullable: false,
3455 default: None,
3456 auto_increment: false,
3457 primary_key: false,
3458 };
3459 let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3460 assert!(sql.contains("ALTER COLUMN"));
3461 assert!(sql.contains("TYPE"));
3462 }
3463
3464 #[test]
3465 fn test_alter_table_add_foreign_key() {
3466 let mysql = MySqlDialect;
3467 let sql = mysql.build_alter_table(
3468 "orders",
3469 &[TableChange::AddForeignKey {
3470 columns: vec!["user_id".to_string()],
3471 reference_table: "users".to_string(),
3472 reference_columns: vec!["id".to_string()],
3473 }],
3474 );
3475 assert!(sql.contains("FOREIGN KEY"));
3476 assert!(sql.contains("REFERENCES"));
3477
3478 let sqlite = SqliteDialect;
3479 let sql = sqlite.build_alter_table(
3480 "orders",
3481 &[TableChange::AddForeignKey {
3482 columns: vec!["user_id".to_string()],
3483 reference_table: "users".to_string(),
3484 reference_columns: vec!["id".to_string()],
3485 }],
3486 );
3487 assert!(sql.starts_with("--"));
3489 }
3490
3491 #[test]
3492 fn test_sqlite_alter_table_add_column() {
3493 let sqlite = SqliteDialect;
3494 let col = ColumnDef {
3495 name: "email".to_string(),
3496 sql_type: "TEXT".to_string(),
3497 nullable: true,
3498 default: None,
3499 auto_increment: false,
3500 primary_key: false,
3501 };
3502 let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
3503 assert!(sql.contains("ADD COLUMN"));
3504 assert!(sql.contains("email"));
3505 }
3506
3507 #[test]
3510 fn test_oracle_quote_and_escape() {
3511 let dialect = OracleDialect;
3512 assert_eq!(dialect.quote("users"), "\"users\"");
3514 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3515 assert_eq!(dialect.quote("column_name"), "\"column_name\"");
3516
3517 assert_eq!(dialect.escape_string("hello"), "hello");
3519 assert_eq!(dialect.escape_string("it's"), "it''s");
3520 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3521 assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
3522 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3524 }
3525
3526 #[test]
3527 fn test_oracle_pagination() {
3528 let dialect = OracleDialect;
3529 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3531 assert_eq!(
3532 sql,
3533 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3534 );
3535 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3537 assert_eq!(
3538 sql,
3539 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3540 );
3541 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3543 assert_eq!(
3544 sql,
3545 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3546 );
3547 }
3548
3549 #[test]
3550 fn test_oracle_json_extract() {
3551 let dialect = OracleDialect;
3552 let sql = dialect.json_extract("data", "$.user.name");
3554 assert!(sql.contains("JSON_VALUE"));
3555 assert!(sql.contains("$.user.name"));
3556 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3557
3558 let sql = dialect.json_extract("data", "user.name");
3560 assert!(sql.contains("$.user.name"));
3561 assert!(sql.contains("JSON_VALUE"));
3562
3563 let sql = dialect.json_extract("data", "$.key's");
3565 assert!(sql.contains("$.key''s"));
3566 }
3567
3568 #[test]
3569 fn test_oracle_create_table() {
3570 let dialect = OracleDialect;
3571 let columns = vec![
3572 ColumnDef {
3573 name: "id".to_string(),
3574 sql_type: "BIGINT".to_string(),
3575 nullable: false,
3576 default: None,
3577 auto_increment: true,
3578 primary_key: true,
3579 },
3580 ColumnDef {
3581 name: "name".to_string(),
3582 sql_type: "VARCHAR(255)".to_string(),
3583 nullable: false,
3584 default: None,
3585 auto_increment: false,
3586 primary_key: false,
3587 },
3588 ColumnDef {
3589 name: "bio".to_string(),
3590 sql_type: "TEXT".to_string(),
3591 nullable: true,
3592 default: None,
3593 auto_increment: false,
3594 primary_key: false,
3595 },
3596 ColumnDef {
3597 name: "is_active".to_string(),
3598 sql_type: "BOOLEAN".to_string(),
3599 nullable: false,
3600 default: Some("1".to_string()),
3601 auto_increment: false,
3602 primary_key: false,
3603 },
3604 ];
3605 let sql = dialect.build_create_table("users", &columns);
3606 assert!(
3608 sql.contains("NUMBER(19)"),
3609 "BIGINT should map to NUMBER(19): {}",
3610 sql
3611 );
3612 assert!(
3613 sql.contains("VARCHAR2(255)"),
3614 "VARCHAR should map to VARCHAR2: {}",
3615 sql
3616 );
3617 assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
3618 assert!(
3619 sql.contains("NUMBER(1)"),
3620 "BOOLEAN should map to NUMBER(1): {}",
3621 sql
3622 );
3623 assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
3625 assert!(sql.contains("PRIMARY KEY"));
3626 assert!(sql.contains("NOT NULL"));
3627 assert!(sql.contains("DEFAULT 1"));
3628 assert!(sql.contains("\"users\""));
3630 assert!(sql.contains("\"id\""));
3631 }
3632
3633 #[test]
3634 fn test_oracle_bool_to_int_and_concat() {
3635 let dialect = OracleDialect;
3636 assert_eq!(
3638 dialect.bool_to_int("active"),
3639 "(CASE WHEN active THEN 1 ELSE 0 END)"
3640 );
3641 assert_eq!(
3642 dialect.bool_to_int("x > 0"),
3643 "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
3644 );
3645 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
3647 assert_eq!(
3648 dialect.concat(&["first_name", "last_name"]),
3649 "first_name || last_name"
3650 );
3651 assert_eq!(dialect.concat(&[]), "NULL");
3653 }
3654
3655 #[test]
3656 fn test_oracle_misc_dialect_methods() {
3657 let dialect = OracleDialect;
3658 assert_eq!(dialect.db_type(), DbType::Oracle);
3660 assert!(dialect.supports_returning());
3662 assert!(dialect.supports_if_exists());
3664 assert!(dialect.supports_if_not_exists());
3665 assert_eq!(
3667 dialect.auto_increment_keyword(),
3668 "GENERATED BY DEFAULT AS IDENTITY"
3669 );
3670 assert_eq!(dialect.last_insert_id_sql(), None);
3673 assert_eq!(dialect.json_type(), "JSON");
3675 }
3676
3677 #[test]
3678 fn test_oracle_get_dialect() {
3679 let dialect = get_dialect(DbType::Oracle);
3681 assert!(dialect.is_ok(), "Oracle dialect should be available");
3682 let dialect = dialect.unwrap();
3683 assert_eq!(dialect.db_type(), DbType::Oracle);
3684 assert_eq!(dialect.quote("users"), "\"users\"");
3686 assert!(dialect.supports_returning());
3688 assert_eq!(dialect.last_insert_id_sql(), None);
3690 }
3691
3692 #[test]
3693 fn test_oracle_drop_table() {
3694 let dialect = OracleDialect;
3695 let sql = dialect.build_drop_table("users", true);
3697 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
3698 let sql = dialect.build_drop_table("users", false);
3700 assert_eq!(sql, "DROP TABLE \"users\"");
3701 }
3702
3703 #[test]
3704 fn test_oracle_alter_table() {
3705 let dialect = OracleDialect;
3706 let col = ColumnDef {
3708 name: "name".to_string(),
3709 sql_type: "VARCHAR(255)".to_string(),
3710 nullable: false,
3711 default: None,
3712 auto_increment: false,
3713 primary_key: false,
3714 };
3715 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3716 assert!(sql.contains("MODIFY"));
3717 assert!(sql.contains("VARCHAR2(255)"));
3718 assert!(!sql.contains("MODIFY COLUMN")); let col = ColumnDef {
3722 name: "email".to_string(),
3723 sql_type: "VARCHAR(255)".to_string(),
3724 nullable: true,
3725 default: None,
3726 auto_increment: false,
3727 primary_key: false,
3728 };
3729 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
3730 assert!(sql.contains("ADD \"email\""));
3731 assert!(sql.contains("VARCHAR2(255)"));
3732
3733 let sql =
3735 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
3736 assert!(sql.contains("DROP COLUMN"));
3737 assert!(sql.contains("\"email\""));
3738 }
3739
3740 #[test]
3743 fn test_sqlite_concat_handles_null() {
3744 let sqlite = SqliteDialect;
3745 let sql = sqlite.concat(&["a", "b"]);
3748 assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
3749 let sql = sqlite.concat(&["a"]);
3751 assert_eq!(sql, "COALESCE(a, '')");
3752 assert_eq!(sqlite.concat(&[]), "NULL");
3754 }
3755
3756 #[test]
3759 fn test_sqlserver_quote_and_escape() {
3760 let dialect = SqlServerDialect;
3761 assert_eq!(dialect.quote("users"), "[users]");
3763 assert_eq!(dialect.quote("col]name"), "[col]]name]");
3764 assert_eq!(dialect.escape_string("hello"), "hello");
3766 assert_eq!(dialect.escape_string("it's"), "it''s");
3767 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3768 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3769 }
3770
3771 #[test]
3772 fn test_sqlserver_pagination() {
3773 let dialect = SqlServerDialect;
3774 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3776 assert_eq!(
3777 sql,
3778 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3779 );
3780 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3781 assert_eq!(
3782 sql,
3783 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3784 );
3785 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3787 assert_eq!(
3788 sql,
3789 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3790 );
3791 }
3792
3793 #[test]
3794 fn test_sqlserver_misc_dialect_methods() {
3795 let dialect = SqlServerDialect;
3796 assert_eq!(dialect.db_type(), DbType::SqlServer);
3797 assert!(dialect.supports_returning());
3799 assert!(dialect.supports_if_exists());
3801 assert!(dialect.supports_if_not_exists());
3802 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
3804 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
3806 assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
3808 }
3809
3810 #[test]
3811 fn test_sqlserver_insert_or_ignore_fallback_to_plain_insert() {
3812 let dialect = SqlServerDialect;
3813 let sql = dialect.build_insert_or_ignore_prefix("users");
3814 assert_eq!(sql, "INSERT INTO [users]");
3816 assert!(!sql.contains("OR IGNORE"));
3817 }
3818
3819 #[test]
3820 fn test_sqlserver_json_extract() {
3821 let dialect = SqlServerDialect;
3822 let sql = dialect.json_extract("data", "$.user.name");
3823 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3824 let sql = dialect.json_extract("data", "user.name");
3826 assert!(sql.contains("$.user.name"));
3827 assert!(sql.contains("JSON_VALUE"));
3828 let sql = dialect.json_extract("data", "$.key's");
3830 assert!(sql.contains("$.key''s"));
3831 }
3832
3833 #[test]
3834 fn test_sqlserver_full_text_search() {
3835 let dialect = SqlServerDialect;
3836 let sql = dialect.full_text_search(&["title", "content"], "hello");
3837 assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
3838 assert_eq!(dialect.full_text_search(&[], "hello"), "0");
3840 let sql = dialect.full_text_search(&["title"], "it's");
3842 assert!(sql.contains("it''s"));
3843 }
3844
3845 #[test]
3846 fn test_sqlserver_bool_to_int_and_concat() {
3847 let dialect = SqlServerDialect;
3848 assert_eq!(
3849 dialect.bool_to_int("active"),
3850 "(CASE WHEN active THEN 1 ELSE 0 END)"
3851 );
3852 assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
3853 assert_eq!(dialect.concat(&[]), "NULL");
3854 }
3855
3856 #[test]
3857 fn test_sqlserver_create_table() {
3858 let dialect = SqlServerDialect;
3859 let columns = vec![
3860 ColumnDef {
3861 name: "id".to_string(),
3862 sql_type: "BIGINT".to_string(),
3863 nullable: false,
3864 default: None,
3865 auto_increment: true,
3866 primary_key: true,
3867 },
3868 ColumnDef {
3869 name: "name".to_string(),
3870 sql_type: "VARCHAR(255)".to_string(),
3871 nullable: false,
3872 default: None,
3873 auto_increment: false,
3874 primary_key: false,
3875 },
3876 ColumnDef {
3877 name: "bio".to_string(),
3878 sql_type: "TEXT".to_string(),
3879 nullable: true,
3880 default: None,
3881 auto_increment: false,
3882 primary_key: false,
3883 },
3884 ColumnDef {
3885 name: "is_active".to_string(),
3886 sql_type: "BOOLEAN".to_string(),
3887 nullable: false,
3888 default: Some("1".to_string()),
3889 auto_increment: false,
3890 primary_key: false,
3891 },
3892 ];
3893 let sql = dialect.build_create_table("users", &columns);
3894 assert!(sql.contains("[users]"));
3896 assert!(sql.contains("[id]"));
3897 assert!(sql.contains("IDENTITY(1,1)"));
3899 assert!(
3900 sql.contains("NVARCHAR(255)"),
3901 "VARCHAR should map to NVARCHAR: {}",
3902 sql
3903 );
3904 assert!(
3905 sql.contains("NVARCHAR(MAX)"),
3906 "TEXT should map to NVARCHAR(MAX): {}",
3907 sql
3908 );
3909 assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
3910 assert!(sql.contains("PRIMARY KEY"));
3911 assert!(sql.contains("NOT NULL"));
3912 assert!(sql.contains("DEFAULT 1"));
3913 }
3914
3915 #[test]
3916 fn test_sqlserver_drop_table() {
3917 let dialect = SqlServerDialect;
3918 assert_eq!(
3919 dialect.build_drop_table("users", true),
3920 "DROP TABLE IF EXISTS [users]"
3921 );
3922 assert_eq!(
3923 dialect.build_drop_table("users", false),
3924 "DROP TABLE [users]"
3925 );
3926 }
3927
3928 #[test]
3929 fn test_sqlserver_alter_table() {
3930 let dialect = SqlServerDialect;
3931 let col = ColumnDef {
3933 name: "name".to_string(),
3934 sql_type: "VARCHAR(255)".to_string(),
3935 nullable: false,
3936 default: None,
3937 auto_increment: false,
3938 primary_key: false,
3939 };
3940 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3941 assert!(sql.contains("ALTER COLUMN"));
3942 assert!(sql.contains("NVARCHAR(255)"));
3943 assert!(!sql.contains("MODIFY"));
3944
3945 let col = ColumnDef {
3947 name: "email".to_string(),
3948 sql_type: "VARCHAR(255)".to_string(),
3949 nullable: true,
3950 default: None,
3951 auto_increment: false,
3952 primary_key: false,
3953 };
3954 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
3955 assert!(sql.contains("ADD [email]"));
3956 assert!(sql.contains("NVARCHAR(255)"));
3957
3958 let sql =
3960 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
3961 assert!(sql.contains("DROP COLUMN"));
3962 assert!(sql.contains("[email]"));
3963
3964 let sql =
3966 dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
3967 assert!(sql.contains("DROP INDEX idx_name ON [users]"));
3968 }
3969
3970 #[test]
3971 fn test_sqlserver_get_dialect() {
3972 let dialect = get_dialect(DbType::SqlServer);
3974 assert!(dialect.is_ok(), "SqlServer dialect should be available");
3975 let dialect = dialect.unwrap();
3976 assert_eq!(dialect.db_type(), DbType::SqlServer);
3977 assert_eq!(dialect.quote("users"), "[users]");
3979 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
3981 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
3982 }
3983
3984 #[test]
3985 fn test_clickhouse_get_dialect_unsupported() {
3986 let dialect = get_dialect(DbType::ClickHouse);
3988 assert!(dialect.is_ok(), "ClickHouse should be supported");
3989 let dialect = dialect.unwrap();
3990 assert_eq!(dialect.db_type(), DbType::ClickHouse);
3991 assert_eq!(dialect.quote("users"), "`users`");
3993 assert!(!dialect.supports_returning());
3995 let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
3997 assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
3998 assert_eq!(dialect.auto_increment_keyword(), "");
4000 }
4001
4002 #[test]
4003 fn test_get_dialect_all_supported_types() {
4004 assert!(get_dialect(DbType::MySQL).is_ok());
4006 assert!(get_dialect(DbType::PostgreSQL).is_ok());
4007 assert!(get_dialect(DbType::Sqlite).is_ok());
4008 assert!(get_dialect(DbType::Oracle).is_ok());
4009 assert!(get_dialect(DbType::SqlServer).is_ok());
4010 assert!(get_dialect(DbType::OceanBase).is_ok());
4011 assert!(get_dialect(DbType::ClickHouse).is_ok());
4012 assert!(get_dialect(DbType::Dameng).is_ok());
4014 assert!(get_dialect(DbType::Kingbase).is_ok());
4015 assert!(get_dialect(DbType::Db2).is_ok());
4016 assert!(get_dialect(DbType::MariaDB).is_ok());
4017 assert!(get_dialect(DbType::TiDB).is_ok());
4018 assert!(get_dialect(DbType::PolarDB).is_ok());
4019 assert!(get_dialect(DbType::GaussDB).is_ok());
4020 assert!(get_dialect(DbType::GBase).is_ok());
4021 assert!(get_dialect(DbType::Sybase).is_ok());
4022 assert!(get_dialect(DbType::Redis).is_err());
4024 assert!(get_dialect(DbType::MongoDB).is_err());
4025 assert!(get_dialect(DbType::VectorDb).is_err());
4026 assert!(get_dialect(DbType::PureJsDb).is_err());
4027 }
4028
4029 #[test]
4032 fn test_mariadb_dialect() {
4033 let dialect = get_dialect(DbType::MariaDB).unwrap();
4034 assert_eq!(dialect.db_type(), DbType::MariaDB);
4035 assert_eq!(dialect.quote("users"), "`users`");
4037 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4038 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4039 assert!(!dialect.supports_returning());
4041 }
4042
4043 #[test]
4044 fn test_tidb_dialect() {
4045 let dialect = get_dialect(DbType::TiDB).unwrap();
4046 assert_eq!(dialect.db_type(), DbType::TiDB);
4047 assert_eq!(dialect.quote("users"), "`users`");
4049 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4050 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4051 }
4052
4053 #[test]
4054 fn test_dameng_dialect() {
4055 let dialect = get_dialect(DbType::Dameng).unwrap();
4056 assert_eq!(dialect.db_type(), DbType::Dameng);
4057 assert_eq!(dialect.quote("users"), "\"users\"");
4059 assert_eq!(dialect.escape_string("it's"), "it''s");
4060 assert_eq!(
4062 dialect.auto_increment_keyword(),
4063 "GENERATED BY DEFAULT AS IDENTITY"
4064 );
4065 assert!(dialect.supports_returning());
4067 }
4068
4069 #[test]
4070 fn test_kingbase_dialect() {
4071 let dialect = get_dialect(DbType::Kingbase).unwrap();
4072 assert_eq!(dialect.db_type(), DbType::Kingbase);
4073 assert_eq!(dialect.quote("users"), "\"users\"");
4075 assert_eq!(dialect.escape_string("it's"), "it''s");
4076 assert!(dialect.supports_returning());
4078 assert_eq!(
4080 dialect.auto_increment_keyword(),
4081 "GENERATED BY DEFAULT AS IDENTITY"
4082 );
4083 }
4084
4085 #[test]
4086 fn test_polardb_dialect() {
4087 let dialect = get_dialect(DbType::PolarDB).unwrap();
4088 assert_eq!(dialect.db_type(), DbType::PolarDB);
4089 assert_eq!(dialect.quote("users"), "\"users\"");
4091 assert!(dialect.supports_returning());
4092 }
4093
4094 #[test]
4095 fn test_gaussdb_dialect() {
4096 let dialect = get_dialect(DbType::GaussDB).unwrap();
4097 assert_eq!(dialect.db_type(), DbType::GaussDB);
4098 assert_eq!(dialect.quote("users"), "\"users\"");
4100 assert!(dialect.supports_returning());
4101 }
4102
4103 #[test]
4104 fn test_gbase_dialect() {
4105 let dialect = get_dialect(DbType::GBase).unwrap();
4106 assert_eq!(dialect.db_type(), DbType::GBase);
4107 assert_eq!(dialect.quote("users"), "[users]");
4109 }
4110
4111 #[test]
4112 fn test_sybase_dialect() {
4113 let dialect = get_dialect(DbType::Sybase).unwrap();
4114 assert_eq!(dialect.db_type(), DbType::Sybase);
4115 assert_eq!(dialect.quote("users"), "[users]");
4117 }
4118
4119 #[test]
4122 fn test_db2_dialect_basic() {
4123 let dialect = get_dialect(DbType::Db2).unwrap();
4124 assert_eq!(dialect.db_type(), DbType::Db2);
4125 assert_eq!(dialect.quote("users"), "\"users\"");
4127 assert_eq!(dialect.escape_string("it's"), "it''s");
4129 assert_eq!(
4131 dialect.auto_increment_keyword(),
4132 "GENERATED ALWAYS AS IDENTITY"
4133 );
4134 assert!(!dialect.supports_if_exists());
4136 assert!(!dialect.supports_if_not_exists());
4137 assert!(!dialect.supports_returning());
4139 }
4140
4141 #[test]
4142 fn test_db2_pagination() {
4143 let dialect = Db2Dialect;
4144 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4146 assert_eq!(
4147 sql,
4148 "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
4149 );
4150 }
4151
4152 #[test]
4153 fn test_db2_last_insert_id() {
4154 let dialect = Db2Dialect;
4155 assert_eq!(
4157 dialect.last_insert_id_sql(),
4158 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
4159 );
4160 }
4161
4162 #[test]
4163 fn test_db2_concat() {
4164 let dialect = Db2Dialect;
4165 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
4167 assert_eq!(dialect.concat(&[]), "''");
4168 }
4169
4170 #[test]
4171 fn test_db2_create_table() {
4172 let dialect = Db2Dialect;
4173 let cols = vec![ColumnDef {
4174 name: "id".to_string(),
4175 sql_type: "BIGINT".to_string(),
4176 nullable: false,
4177 default: None,
4178 auto_increment: true,
4179 primary_key: true,
4180 }];
4181 let sql = dialect.build_create_table("users", &cols);
4182 assert!(sql.contains("\"id\" BIGINT"));
4183 assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
4184 assert!(sql.contains("PRIMARY KEY"));
4185 }
4186
4187 #[test]
4188 fn test_db2_type_mapping() {
4189 assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
4191 assert_eq!(map_to_db2_type("INT"), "INTEGER");
4192 assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
4193 assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
4194 assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
4195 assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
4196 assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
4197 assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
4198 assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
4199 assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
4200 assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
4201 assert_eq!(map_to_db2_type("DATE"), "DATE");
4202 assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
4203 }
4204
4205 #[test]
4208 fn test_clickhouse_dialect_basic() {
4209 let dialect = get_dialect(DbType::ClickHouse).unwrap();
4210 assert_eq!(dialect.db_type(), DbType::ClickHouse);
4211 assert_eq!(dialect.quote("users"), "`users`");
4213 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4215 assert!(!dialect.supports_returning());
4217 assert_eq!(dialect.auto_increment_keyword(), "");
4219 assert!(dialect.supports_if_exists());
4221 assert!(dialect.supports_if_not_exists());
4222 }
4223
4224 #[test]
4225 fn test_clickhouse_type_mapping() {
4226 assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
4227 assert_eq!(map_to_clickhouse_type("INT"), "Int32");
4228 assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
4229 assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
4230 assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
4231 assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
4232 assert_eq!(map_to_clickhouse_type("TEXT"), "String");
4233 assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
4234 assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
4235 assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
4236 assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
4237 assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
4238 assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
4239 assert_eq!(map_to_clickhouse_type("DATE"), "Date");
4240 }
4241
4242 #[test]
4243 fn test_clickhouse_create_table() {
4244 let dialect = ClickHouseDialect;
4245 let cols = vec![ColumnDef {
4246 name: "id".to_string(),
4247 sql_type: "BIGINT".to_string(),
4248 nullable: false,
4249 default: None,
4250 auto_increment: false, primary_key: true,
4252 }];
4253 let sql = dialect.build_create_table("users", &cols);
4254 assert!(
4256 sql.contains("ENGINE = MergeTree()"),
4257 "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
4258 sql
4259 );
4260 assert!(sql.contains("`id` Int64"));
4261 assert!(sql.contains("PRIMARY KEY"));
4262 }
4263
4264 #[test]
4265 fn test_clickhouse_json_extract() {
4266 let dialect = ClickHouseDialect;
4267 let sql = dialect.json_extract("data", "$.name");
4268 assert!(
4269 sql.contains("JSONExtractString"),
4270 "ClickHouse 应使用 JSONExtractString: {}",
4271 sql
4272 );
4273 }
4274
4275 #[test]
4276 fn test_clickhouse_concat() {
4277 let dialect = ClickHouseDialect;
4278 assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
4280 assert_eq!(dialect.concat(&[]), "''");
4281 }
4282
4283 #[test]
4286 fn test_db_type_dameng_str() {
4287 assert_eq!(DbType::Dameng.as_str(), "dameng");
4288 assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
4289 assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
4290 assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
4291 assert_eq!(DbType::Dameng.default_port(), 5236);
4292 }
4293
4294 #[test]
4295 fn test_db_type_kingbase_str() {
4296 assert_eq!(DbType::Kingbase.as_str(), "kingbase");
4297 assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
4298 assert_eq!(DbType::Kingbase.default_port(), 54321);
4299 }
4300
4301 #[test]
4302 fn test_db_type_db2_str() {
4303 assert_eq!(DbType::Db2.as_str(), "db2");
4304 assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
4305 assert_eq!(DbType::Db2.default_port(), 50000);
4306 }
4307
4308 #[test]
4309 fn test_db_type_mariadb_str() {
4310 assert_eq!(DbType::MariaDB.as_str(), "mariadb");
4311 assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
4312 assert_eq!(DbType::MariaDB.default_port(), 3306);
4313 }
4314
4315 #[test]
4316 fn test_db_type_tidb_str() {
4317 assert_eq!(DbType::TiDB.as_str(), "tidb");
4318 assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
4319 assert_eq!(DbType::TiDB.default_port(), 4000);
4320 }
4321
4322 #[test]
4323 fn test_db_type_polardb_str() {
4324 assert_eq!(DbType::PolarDB.as_str(), "polardb");
4325 assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
4326 assert_eq!(DbType::PolarDB.default_port(), 5432);
4327 }
4328
4329 #[test]
4330 fn test_db_type_gaussdb_str() {
4331 assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
4332 assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
4333 assert_eq!(DbType::GaussDB.default_port(), 25308);
4334 }
4335
4336 #[test]
4337 fn test_db_type_gbase_str() {
4338 assert_eq!(DbType::GBase.as_str(), "gbase");
4339 assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
4340 assert_eq!(DbType::GBase.default_port(), 9088);
4341 }
4342
4343 #[test]
4344 fn test_db_type_sybase_str() {
4345 assert_eq!(DbType::Sybase.as_str(), "sybase");
4346 assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
4347 assert_eq!(DbType::Sybase.default_port(), 5000);
4348 }
4349
4350 #[test]
4351 fn test_db_type_family_classification() {
4352 assert!(DbType::MySQL.is_mysql_family());
4354 assert!(DbType::MariaDB.is_mysql_family());
4355 assert!(DbType::TiDB.is_mysql_family());
4356 assert!(DbType::OceanBase.is_mysql_family());
4357 assert!(!DbType::PostgreSQL.is_mysql_family());
4358
4359 assert!(DbType::PostgreSQL.is_postgres_family());
4361 assert!(DbType::Kingbase.is_postgres_family());
4362 assert!(DbType::GaussDB.is_postgres_family());
4363 assert!(!DbType::MySQL.is_postgres_family());
4364
4365 assert!(DbType::Oracle.is_oracle_family());
4367 assert!(DbType::Dameng.is_oracle_family());
4368 assert!(!DbType::MySQL.is_oracle_family());
4369 }
4370
4371 #[test]
4372 fn test_db_type_supports_stored_procedure_extended() {
4373 assert!(DbType::Dameng.supports_stored_procedure());
4375 assert!(DbType::Kingbase.supports_stored_procedure());
4376 assert!(DbType::Db2.supports_stored_procedure());
4377 assert!(DbType::MariaDB.supports_stored_procedure());
4378 assert!(DbType::TiDB.supports_stored_procedure());
4379 assert!(DbType::PolarDB.supports_stored_procedure());
4380 assert!(DbType::GaussDB.supports_stored_procedure());
4381 assert!(DbType::GBase.supports_stored_procedure());
4382 assert!(DbType::Sybase.supports_stored_procedure());
4383 }
4384
4385 #[test]
4388 fn test_l4_max_identifier_len_constant() {
4389 assert_eq!(MAX_IDENTIFIER_LEN, 63);
4391 }
4392
4393 #[test]
4394 fn test_l4_quote_checked_valid_identifier() {
4395 let dialect = MySqlDialect;
4396 assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
4397 assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
4398 let name_63 = "a".repeat(63);
4400 assert!(dialect.quote_checked(&name_63).is_ok());
4401 }
4402
4403 #[test]
4404 fn test_l4_quote_checked_rejects_too_long() {
4405 let dialect = MySqlDialect;
4406 let long_name = "a".repeat(64); let result = dialect.quote_checked(&long_name);
4408 assert!(result.is_err());
4409 match result {
4410 Err(DbError::InvalidInput(msg)) => {
4411 assert!(
4412 msg.contains("too long"),
4413 "expected 'too long' error, got: {}",
4414 msg
4415 );
4416 }
4417 _ => panic!("Expected DbError::InvalidInput"),
4418 }
4419 }
4420
4421 #[test]
4422 fn test_l4_quote_checked_rejects_empty() {
4423 let dialect = MySqlDialect;
4424 let result = dialect.quote_checked("");
4425 assert!(result.is_err());
4426 }
4427
4428 #[test]
4429 fn test_l4_quote_checked_rejects_sql_injection() {
4430 let dialect = MySqlDialect;
4431 assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
4433 assert!(dialect.quote_checked("user'name").is_err());
4435 assert!(dialect.quote_checked("user name").is_err());
4437 assert!(dialect.quote_checked("1users").is_err());
4439 assert!(dialect.quote_checked("schema.table").is_err());
4441 }
4442
4443 #[test]
4444 fn test_l4_quote_checked_postgres() {
4445 let dialect = PostgreSqlDialect;
4446 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4447 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4448 }
4449
4450 #[test]
4451 fn test_l4_quote_checked_sqlite() {
4452 let dialect = SqliteDialect;
4453 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4454 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4455 }
4456
4457 #[test]
4458 fn test_l4_quote_checked_oracle() {
4459 let dialect = OracleDialect;
4460 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4461 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4462 }
4463
4464 #[test]
4465 fn test_l4_quote_checked_sql_server() {
4466 let dialect = SqlServerDialect;
4467 assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
4468 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4469 }
4470
4471 #[test]
4474 fn test_duckdb_quote() {
4475 let dialect = DuckDBDialect;
4476 assert_eq!(dialect.quote("users"), "\"users\"");
4477 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
4478 }
4479
4480 #[test]
4481 fn test_duckdb_escape() {
4482 let dialect = DuckDBDialect;
4483 assert_eq!(dialect.escape_string("hello"), "hello");
4484 assert_eq!(dialect.escape_string("it's"), "it''s");
4485 }
4486
4487 #[test]
4488 fn test_duckdb_pagination() {
4489 let dialect = DuckDBDialect;
4490 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4491 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
4492 }
4493
4494 #[test]
4495 fn test_duckdb_supports() {
4496 let dialect = DuckDBDialect;
4497 assert!(!dialect.supports_returning());
4498 assert!(!dialect.supports_lock_for_update());
4499 assert!(!dialect.supports_lock_shared());
4500 assert!(dialect.supports_if_exists());
4501 assert!(dialect.supports_if_not_exists());
4502 }
4503
4504 #[test]
4505 fn test_duckdb_insert_or_ignore() {
4506 let dialect = DuckDBDialect;
4507 let sql = dialect.build_insert_or_ignore_prefix("users");
4508 assert_eq!(sql, "INSERT OR IGNORE INTO \"users\"");
4509 }
4510
4511 #[test]
4512 fn test_duckdb_create_table() {
4513 let dialect = DuckDBDialect;
4514 let columns = vec![
4515 ColumnDef {
4516 name: "id".to_string(),
4517 sql_type: "BIGINT".to_string(),
4518 nullable: false,
4519 default: None,
4520 auto_increment: true,
4521 primary_key: true,
4522 },
4523 ColumnDef {
4524 name: "name".to_string(),
4525 sql_type: "VARCHAR(255)".to_string(),
4526 nullable: false,
4527 default: None,
4528 auto_increment: false,
4529 primary_key: false,
4530 },
4531 ];
4532 let sql = dialect.build_create_table("users", &columns);
4533 assert!(sql.contains("CREATE TABLE \"users\""));
4534 assert!(sql.contains("\"id\" BIGINT PRIMARY KEY"));
4535 assert!(sql.contains("\"name\" VARCHAR(255)"));
4536 }
4537
4538 #[test]
4539 fn test_duckdb_alter_table() {
4540 let dialect = DuckDBDialect;
4541 let changes = vec![TableChange::AddColumn(ColumnDef {
4542 name: "age".to_string(),
4543 sql_type: "INTEGER".to_string(),
4544 nullable: true,
4545 default: None,
4546 auto_increment: false,
4547 primary_key: false,
4548 })];
4549 let sql = dialect.build_alter_table("users", &changes);
4550 assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN \"age\" INTEGER"));
4551 }
4552
4553 #[test]
4554 fn test_duckdb_json_extract() {
4555 let dialect = DuckDBDialect;
4556 let sql = dialect.json_extract("data", "$.user.name");
4557 assert!(sql.contains("->"));
4558 assert!(sql.contains("user.name"));
4559 }
4560
4561 #[test]
4562 fn test_duckdb_concat() {
4563 let dialect = DuckDBDialect;
4564 let sql = dialect.concat(&["a", "b", "c"]);
4565 assert_eq!(sql, "a || b || c");
4566 }
4567
4568 #[test]
4569 fn test_duckdb_bool_to_int() {
4570 let dialect = DuckDBDialect;
4571 let sql = dialect.bool_to_int("active");
4572 assert!(sql.contains("CASE WHEN"));
4573 assert!(sql.contains("THEN 1"));
4574 assert!(sql.contains("ELSE 0"));
4575 }
4576
4577 #[test]
4578 fn test_get_dialect_duckdb() {
4579 let result = get_dialect(DbType::DuckDB);
4580 assert!(result.is_ok());
4581 let dialect = result.unwrap();
4582 assert_eq!(dialect.db_type(), DbType::DuckDB);
4583 }
4584}
4585
4586#[cfg(feature = "perf-enum-dispatch")]
4595#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4596pub enum DialectKind {
4597 MySQL,
4599 PostgreSQL,
4601 SQLite,
4603 Oracle,
4605 MSSQL,
4607}
4608
4609#[cfg(feature = "perf-enum-dispatch")]
4610impl DialectKind {
4611 pub fn from_db_type(db_type: DbType) -> Option<Self> {
4613 match db_type {
4614 DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase => Some(Self::MySQL),
4615 DbType::PostgreSQL | DbType::Kingbase | DbType::PolarDB | DbType::GaussDB => {
4616 Some(Self::PostgreSQL)
4617 }
4618 DbType::Sqlite => Some(Self::SQLite),
4619 DbType::Oracle | DbType::Dameng => Some(Self::Oracle),
4620 DbType::SqlServer | DbType::Sybase | DbType::GBase => Some(Self::MSSQL),
4621 _ => None,
4622 }
4623 }
4624
4625 pub fn quote(&self, identifier: &str) -> String {
4627 match self {
4628 Self::MySQL => MySqlDialect.quote(identifier),
4629 Self::PostgreSQL => PostgreSqlDialect.quote(identifier),
4630 Self::SQLite => SqliteDialect.quote(identifier),
4631 Self::Oracle => OracleDialect.quote(identifier),
4632 Self::MSSQL => SqlServerDialect.quote(identifier),
4633 }
4634 }
4635
4636 pub fn escape_string(&self, s: &str) -> String {
4638 match self {
4639 Self::MySQL => MySqlDialect.escape_string(s),
4640 Self::PostgreSQL => PostgreSqlDialect.escape_string(s),
4641 Self::SQLite => SqliteDialect.escape_string(s),
4642 Self::Oracle => OracleDialect.escape_string(s),
4643 Self::MSSQL => SqlServerDialect.escape_string(s),
4644 }
4645 }
4646
4647 pub fn db_type(&self) -> DbType {
4649 match self {
4650 Self::MySQL => DbType::MySQL,
4651 Self::PostgreSQL => DbType::PostgreSQL,
4652 Self::SQLite => DbType::Sqlite,
4653 Self::Oracle => DbType::Oracle,
4654 Self::MSSQL => DbType::SqlServer,
4655 }
4656 }
4657
4658 pub fn to_dialect(&self) -> Box<dyn Dialect> {
4660 match self {
4661 Self::MySQL => Box::new(MySqlDialect),
4662 Self::PostgreSQL => Box::new(PostgreSqlDialect),
4663 Self::SQLite => Box::new(SqliteDialect),
4664 Self::Oracle => Box::new(OracleDialect),
4665 Self::MSSQL => Box::new(SqlServerDialect),
4666 }
4667 }
4668}
4669
4670#[cfg(all(test, feature = "perf-enum-dispatch"))]
4671mod enum_dispatch_tests {
4672 use super::*;
4673
4674 #[test]
4675 fn test_dialect_kind_from_db_type() {
4676 assert_eq!(
4677 DialectKind::from_db_type(DbType::MySQL),
4678 Some(DialectKind::MySQL)
4679 );
4680 assert_eq!(
4681 DialectKind::from_db_type(DbType::PostgreSQL),
4682 Some(DialectKind::PostgreSQL)
4683 );
4684 assert_eq!(
4685 DialectKind::from_db_type(DbType::Sqlite),
4686 Some(DialectKind::SQLite)
4687 );
4688 assert_eq!(
4689 DialectKind::from_db_type(DbType::Oracle),
4690 Some(DialectKind::Oracle)
4691 );
4692 assert_eq!(
4693 DialectKind::from_db_type(DbType::SqlServer),
4694 Some(DialectKind::MSSQL)
4695 );
4696 assert_eq!(DialectKind::from_db_type(DbType::Redis), None);
4697 }
4698
4699 #[test]
4700 fn test_dialect_kind_quote_diff() {
4701 let kinds = [
4702 DialectKind::MySQL,
4703 DialectKind::PostgreSQL,
4704 DialectKind::SQLite,
4705 DialectKind::Oracle,
4706 DialectKind::MSSQL,
4707 ];
4708 for kind in &kinds {
4709 let enum_result = kind.quote("users");
4710 let box_result = kind.to_dialect().quote("users");
4711 assert_eq!(enum_result, box_result);
4712 }
4713 }
4714
4715 #[test]
4716 fn test_dialect_kind_escape_diff() {
4717 let kinds = [
4718 DialectKind::MySQL,
4719 DialectKind::PostgreSQL,
4720 DialectKind::SQLite,
4721 DialectKind::Oracle,
4722 DialectKind::MSSQL,
4723 ];
4724 for kind in &kinds {
4725 let enum_result = kind.escape_string("it's");
4726 let box_result = kind.to_dialect().escape_string("it's");
4727 assert_eq!(enum_result, box_result);
4728 }
4729 }
4730}