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_into(&self, identifier: &str, buf: &mut String) {
38 buf.push_str(&self.quote(identifier));
39 }
40
41 fn quote_checked(&self, identifier: &str) -> Result<String, DbError> {
52 crate::sql_safety::validate_identifier(identifier, "identifier")?;
53 Ok(self.quote(identifier))
54 }
55
56 fn escape_string(&self, s: &str) -> String;
58
59 fn supports_returning(&self) -> bool;
61
62 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String;
64
65 fn json_type(&self) -> &'static str;
67
68 fn json_extract(&self, column: &str, path: &str) -> String;
70
71 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String;
73
74 fn bool_to_int(&self, expr: &str) -> String;
76
77 fn concat(&self, parts: &[&str]) -> String;
79
80 fn supports_if_exists(&self) -> bool;
82
83 fn supports_if_not_exists(&self) -> bool;
85
86 fn auto_increment_keyword(&self) -> &'static str;
88
89 fn last_insert_id_sql(&self) -> Option<&'static str>;
95
96 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String;
98
99 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String;
101
102 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
107 if if_exists && self.supports_if_exists() {
108 format!("DROP TABLE IF EXISTS {}", self.quote(table))
109 } else {
110 format!("DROP TABLE {}", self.quote(table))
111 }
112 }
113
114 fn build_upsert_on_conflict(
129 &self,
130 conflict_columns: &[&str],
131 update_columns: &[&str],
132 all_columns: &[String],
133 ) -> Option<String> {
134 let _ = (conflict_columns, update_columns, all_columns);
135 None
136 }
137
138 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
147 let _ = lock_type;
148 None
149 }
150
151 fn supports_lock_for_update(&self) -> bool {
156 true
157 }
158
159 fn supports_lock_shared(&self) -> bool {
164 true
165 }
166
167 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
174 format!("INSERT OR IGNORE INTO {}", self.quote(table))
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub enum LockType {
187 ForUpdate,
189 Shared,
191}
192
193#[derive(Debug, Clone)]
195pub struct ColumnDef {
196 pub name: String,
198 pub sql_type: String,
200 pub nullable: bool,
202 pub default: Option<String>,
204 pub auto_increment: bool,
206 pub primary_key: bool,
208}
209
210#[derive(Debug, Clone)]
212pub enum TableChange {
213 AddColumn(ColumnDef),
215 DropColumn(String),
217 ModifyColumn(ColumnDef),
219 AddIndex(String, Vec<String>),
221 DropIndex(String),
223 AddForeignKey {
225 columns: Vec<String>,
227 reference_table: String,
229 reference_columns: Vec<String>,
231 },
232}
233
234#[derive(Debug, Clone)]
236pub struct MySqlDialect;
237
238impl Dialect for MySqlDialect {
239 fn clone_box(&self) -> Box<dyn Dialect> {
240 Box::new(MySqlDialect)
241 }
242
243 fn db_type(&self) -> DbType {
244 DbType::MySQL
245 }
246
247 fn quote(&self, identifier: &str) -> String {
248 format!("`{}`", identifier.replace('`', "``"))
249 }
250
251 fn quote_into(&self, identifier: &str, buf: &mut String) {
252 if identifier.contains('`') {
253 buf.push_str(&self.quote(identifier));
254 } else {
255 buf.push('`');
256 buf.push_str(identifier);
257 buf.push('`');
258 }
259 }
260
261 fn escape_string(&self, s: &str) -> String {
262 let mut escaped = String::with_capacity(s.len() * 2);
263 for c in s.chars() {
264 match c {
265 '\\' => escaped.push_str("\\\\"),
266 '\'' => escaped.push_str("\\'"),
267 '\0' => escaped.push_str("\\0"),
268 '\n' => escaped.push_str("\\n"),
269 '\r' => escaped.push_str("\\r"),
270 '\t' => escaped.push_str("\\t"),
271 '\x1a' => escaped.push_str("\\Z"),
272 _ => escaped.push(c),
273 }
274 }
275 escaped
276 }
277
278 fn supports_returning(&self) -> bool {
279 false
280 }
281
282 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
283 let offset = page.saturating_sub(1).saturating_mul(limit);
291 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
292 }
293
294 fn json_type(&self) -> &'static str {
295 "JSON"
296 }
297
298 fn json_extract(&self, column: &str, path: &str) -> String {
299 let normalized = if path.starts_with('$') {
301 path.to_string()
302 } else {
303 format!("$.{}", path)
304 };
305 format!(
306 "JSON_EXTRACT({}, '{}')",
307 column,
308 self.escape_string(&normalized)
309 )
310 }
311
312 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
313 let cols = columns.join(", ");
314 let escaped = self.escape_string(keyword);
315 format!(
316 "MATCH({}) AGAINST('{}' IN NATURAL LANGUAGE MODE)",
317 cols, escaped
318 )
319 }
320
321 fn bool_to_int(&self, expr: &str) -> String {
322 format!("IF({}, 1, 0)", expr)
324 }
325
326 fn concat(&self, parts: &[&str]) -> String {
327 if parts.is_empty() {
328 return "NULL".to_string();
329 }
330 let concat_parts: Vec<String> = parts
331 .iter()
332 .map(|p| format!("CAST({} AS CHAR)", p))
333 .collect();
334 format!("CONCAT({})", concat_parts.join(", "))
335 }
336
337 fn supports_if_exists(&self) -> bool {
338 true
339 }
340
341 fn supports_if_not_exists(&self) -> bool {
342 true
343 }
344
345 fn auto_increment_keyword(&self) -> &'static str {
346 "AUTO_INCREMENT"
347 }
348
349 fn last_insert_id_sql(&self) -> Option<&'static str> {
350 Some("LAST_INSERT_ID()")
351 }
352
353 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
354 let cols: Vec<String> = columns
355 .iter()
356 .map(|col| {
357 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
358 if !col.nullable {
359 sql.push_str(" NOT NULL");
360 }
361 if let Some(default) = &col.default {
362 sql.push_str(&format!(" DEFAULT {}", default));
363 }
364 if col.auto_increment {
365 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
366 }
367 if col.primary_key {
368 sql.push_str(" PRIMARY KEY");
369 }
370 sql
371 })
372 .collect();
373
374 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
375 }
376
377 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
378 let stmts: Vec<String> = changes.iter().map(|change| {
379 match change {
380 TableChange::AddColumn(col) => {
381 let mut sql = format!("ALTER TABLE {} ADD {}", self.quote(table), self.quote(&col.name));
382 sql.push_str(&format!(" {}", col.sql_type));
383 if !col.nullable {
384 sql.push_str(" NOT NULL");
385 }
386 if let Some(default) = &col.default {
387 sql.push_str(&format!(" DEFAULT {}", default));
388 }
389 sql
390 }
391 TableChange::DropColumn(name) => {
392 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
393 }
394 TableChange::ModifyColumn(col) => {
395 let mut sql = format!("ALTER TABLE {} MODIFY COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
397 if !col.nullable {
398 sql.push_str(" NOT NULL");
399 }
400 if let Some(default) = &col.default {
401 sql.push_str(&format!(" DEFAULT {}", default));
402 }
403 sql
404 }
405 TableChange::AddIndex(name, cols) => {
406 format!("ALTER TABLE {} ADD INDEX {} ({})", self.quote(table), name, cols.join(", "))
407 }
408 TableChange::DropIndex(name) => {
409 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
410 }
411 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
412 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
413 self.quote(table),
414 table,
415 columns.join("_"),
416 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
417 self.quote(reference_table),
418 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
419 }
420 }
421 }).collect();
422
423 stmts.join("; ")
424 }
425
426 fn build_upsert_on_conflict(
431 &self,
432 _conflict_columns: &[&str],
433 update_columns: &[&str],
434 all_columns: &[String],
435 ) -> Option<String> {
436 let cols_to_update: Vec<String> = if update_columns.is_empty() {
438 all_columns.iter().map(|c| self.quote(c)).collect()
439 } else {
440 update_columns.iter().map(|c| self.quote(c)).collect()
441 };
442 if cols_to_update.is_empty() {
443 return None;
444 }
445 let set_clauses: Vec<String> = cols_to_update
446 .iter()
447 .map(|col| format!("{}=VALUES({})", col, col))
448 .collect();
449 Some(format!(
450 "ON DUPLICATE KEY UPDATE {}",
451 set_clauses.join(", ")
452 ))
453 }
454
455 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
456 match lock_type {
457 LockType::ForUpdate => Some("FOR UPDATE".to_string()),
458 LockType::Shared => Some("LOCK IN SHARE MODE".to_string()),
459 }
460 }
461
462 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
463 format!("INSERT IGNORE INTO {}", self.quote(table))
464 }
465}
466
467#[derive(Debug, Clone)]
469pub struct PostgreSqlDialect;
470
471impl Dialect for PostgreSqlDialect {
472 fn clone_box(&self) -> Box<dyn Dialect> {
473 Box::new(PostgreSqlDialect)
474 }
475
476 fn db_type(&self) -> DbType {
477 DbType::PostgreSQL
478 }
479
480 fn quote(&self, identifier: &str) -> String {
481 format!("\"{}\"", identifier.replace('"', "\"\""))
482 }
483
484 fn quote_into(&self, identifier: &str, buf: &mut String) {
485 if identifier.contains('"') {
486 buf.push_str(&self.quote(identifier));
487 } else {
488 buf.push('"');
489 buf.push_str(identifier);
490 buf.push('"');
491 }
492 }
493
494 fn escape_string(&self, s: &str) -> String {
495 let mut escaped = String::with_capacity(s.len() * 2);
498 for c in s.chars() {
499 match c {
500 '\'' => escaped.push_str("''"),
501 _ => escaped.push(c),
502 }
503 }
504 escaped
505 }
506
507 fn supports_returning(&self) -> bool {
508 true
509 }
510
511 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
512 let offset = page.saturating_sub(1).saturating_mul(limit);
513 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
514 }
515
516 fn json_type(&self) -> &'static str {
517 "JSONB"
518 }
519
520 fn json_extract(&self, column: &str, path: &str) -> String {
521 let normalized = path.trim_start_matches("$.");
526 let parts: Vec<&str> = normalized.split('.').filter(|s| !s.is_empty()).collect();
527 let path_lit = parts
528 .iter()
529 .map(|p| {
530 let needs_quoting = p.chars().any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\'));
532 if needs_quoting {
533 let escaped = p.replace('\\', "\\\\").replace('"', "\\\"");
534 format!("\"{}\"", escaped)
535 } else {
536 p.to_string()
537 }
538 })
539 .collect::<Vec<_>>()
540 .join(",");
541 let path_lit_escaped = path_lit.replace('\'', "''");
543 format!("{}#>>'{{{}}}'", column, path_lit_escaped)
544 }
545
546 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
547 let cols = columns
548 .iter()
549 .map(|c| format!("{}::text", c))
550 .collect::<Vec<_>>()
551 .join(" || ' ' || ");
552 let escaped = self.escape_string(keyword);
553 format!("to_tsvector({}) @@ to_tsquery('{}')", cols, escaped)
554 }
555
556 fn bool_to_int(&self, expr: &str) -> String {
557 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
558 }
559
560 fn concat(&self, parts: &[&str]) -> String {
561 if parts.is_empty() {
562 return "NULL".to_string();
563 }
564 format!("CONCAT({})", parts.join(", "))
565 }
566
567 fn supports_if_exists(&self) -> bool {
568 true
569 }
570
571 fn supports_if_not_exists(&self) -> bool {
572 true
573 }
574
575 fn auto_increment_keyword(&self) -> &'static str {
576 "GENERATED BY DEFAULT AS IDENTITY"
577 }
578
579 fn last_insert_id_sql(&self) -> Option<&'static str> {
580 Some("lastval()")
581 }
582
583 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
584 let cols: Vec<String> = columns
585 .iter()
586 .map(|col| {
587 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
588 if !col.nullable {
589 sql.push_str(" NOT NULL");
590 }
591 if let Some(default) = &col.default {
592 sql.push_str(&format!(" DEFAULT {}", default));
593 }
594 if col.primary_key {
595 sql.push_str(" PRIMARY KEY");
596 }
597 sql
598 })
599 .collect();
600
601 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
602 }
603
604 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
605 let stmts: Vec<String> = changes.iter().map(|change| {
606 match change {
607 TableChange::AddColumn(col) => {
608 let mut sql = format!("ALTER TABLE {} ADD COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
609 if !col.nullable {
610 sql.push_str(" NOT NULL");
611 }
612 if let Some(default) = &col.default {
613 sql.push_str(&format!(" DEFAULT {}", default));
614 }
615 sql
616 }
617 TableChange::DropColumn(name) => {
618 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
619 }
620 TableChange::ModifyColumn(col) => {
621 let mut sql = format!("ALTER TABLE {} ALTER COLUMN {} TYPE {}", self.quote(table), self.quote(&col.name), col.sql_type);
623 if !col.nullable {
624 sql.push_str(&format!(", ALTER COLUMN {} SET NOT NULL", self.quote(&col.name)));
625 }
626 if let Some(default) = &col.default {
627 sql.push_str(&format!(", ALTER COLUMN {} SET DEFAULT {}", self.quote(&col.name), default));
628 }
629 sql
630 }
631 TableChange::AddIndex(name, cols) => {
632 format!("CREATE INDEX {} ON {} ({})", name, self.quote(table), cols.join(", "))
633 }
634 TableChange::DropIndex(name) => {
635 format!("DROP INDEX {}", name)
636 }
637 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
638 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
639 self.quote(table),
640 table,
641 columns.join("_"),
642 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
643 self.quote(reference_table),
644 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
645 }
646 }
647 }).collect();
648
649 stmts.join("; ")
650 }
651
652 fn build_upsert_on_conflict(
656 &self,
657 conflict_columns: &[&str],
658 update_columns: &[&str],
659 all_columns: &[String],
660 ) -> Option<String> {
661 if conflict_columns.is_empty() {
662 return None;
663 }
664 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
665 let conflict_set: std::collections::HashSet<&str> =
667 conflict_columns.iter().copied().collect();
668 let cols_to_update: Vec<String> = if update_columns.is_empty() {
669 all_columns
670 .iter()
671 .filter(|c| !conflict_set.contains(c.as_str()))
672 .map(|c| self.quote(c))
673 .collect()
674 } else {
675 update_columns.iter().map(|c| self.quote(c)).collect()
676 };
677 if cols_to_update.is_empty() {
678 return Some(format!(
680 "ON CONFLICT ({}) DO NOTHING",
681 conflict_cols.join(", ")
682 ));
683 }
684 let set_clauses: Vec<String> = cols_to_update
685 .iter()
686 .map(|col| format!("{}=EXCLUDED.{}", col, col))
687 .collect();
688 Some(format!(
689 "ON CONFLICT ({}) DO UPDATE SET {}",
690 conflict_cols.join(", "),
691 set_clauses.join(", ")
692 ))
693 }
694
695 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
696 match lock_type {
697 LockType::ForUpdate => Some("FOR UPDATE".to_string()),
698 LockType::Shared => Some("FOR SHARE".to_string()),
699 }
700 }
701}
702
703#[derive(Debug, Clone)]
705pub struct SqliteDialect;
706
707impl Dialect for SqliteDialect {
708 fn clone_box(&self) -> Box<dyn Dialect> {
709 Box::new(SqliteDialect)
710 }
711
712 fn db_type(&self) -> DbType {
713 DbType::Sqlite
714 }
715
716 fn quote(&self, identifier: &str) -> String {
717 format!("\"{}\"", identifier.replace('"', "\"\""))
718 }
719
720 fn quote_into(&self, identifier: &str, buf: &mut String) {
721 if identifier.contains('"') {
722 buf.push_str(&self.quote(identifier));
723 } else {
724 buf.push('"');
725 buf.push_str(identifier);
726 buf.push('"');
727 }
728 }
729
730 fn escape_string(&self, s: &str) -> String {
731 let mut escaped = String::with_capacity(s.len() * 2);
732 for c in s.chars() {
733 match c {
734 '\'' => escaped.push_str("''"),
735 _ => escaped.push(c),
736 }
737 }
738 escaped
739 }
740
741 fn supports_returning(&self) -> bool {
742 true
743 }
744
745 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
746 let offset = page.saturating_sub(1).saturating_mul(limit);
747 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
748 }
749
750 fn json_type(&self) -> &'static str {
751 "TEXT"
752 }
753
754 fn json_extract(&self, column: &str, path: &str) -> String {
755 let normalized = if path.starts_with('$') {
757 path.to_string()
758 } else {
759 format!("$.{}", path)
760 };
761 format!(
762 "json_extract({}, '{}')",
763 column,
764 self.escape_string(&normalized)
765 )
766 }
767
768 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
769 if columns.is_empty() {
773 return "0".to_string();
774 }
775 let escaped = self.escape_string(keyword);
776 columns
777 .iter()
778 .map(|c| format!("{} LIKE '%{}%'", c.trim(), escaped))
779 .collect::<Vec<_>>()
780 .join(" OR ")
781 }
782
783 fn bool_to_int(&self, expr: &str) -> String {
784 expr.to_string()
785 }
786
787 fn concat(&self, parts: &[&str]) -> String {
788 if parts.is_empty() {
789 return "NULL".to_string();
790 }
791 let coalesced: Vec<String> = parts
794 .iter()
795 .map(|p| format!("COALESCE({}, '')", p))
796 .collect();
797 coalesced.join(" || ")
798 }
799
800 fn supports_if_exists(&self) -> bool {
801 true
802 }
803
804 fn supports_if_not_exists(&self) -> bool {
805 true
806 }
807
808 fn auto_increment_keyword(&self) -> &'static str {
809 "AUTOINCREMENT"
810 }
811
812 fn last_insert_id_sql(&self) -> Option<&'static str> {
813 Some("last_insert_rowid()")
814 }
815
816 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
817 let cols: Vec<String> = columns
818 .iter()
819 .map(|col| {
820 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
821 if !col.nullable {
822 sql.push_str(" NOT NULL");
823 }
824 if let Some(default) = &col.default {
825 sql.push_str(&format!(" DEFAULT {}", default));
826 }
827 if col.auto_increment {
828 sql.push_str(" PRIMARY KEY AUTOINCREMENT");
829 } else if col.primary_key {
830 sql.push_str(" PRIMARY KEY");
831 }
832 sql
833 })
834 .collect();
835
836 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
837 }
838
839 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
840 let stmts: Vec<String> = changes
843 .iter()
844 .map(|change| {
845 match change {
846 TableChange::AddColumn(col) => {
847 let mut sql = format!(
848 "ALTER TABLE {} ADD COLUMN {} {}",
849 self.quote(table),
850 self.quote(&col.name),
851 col.sql_type
852 );
853 if !col.nullable {
854 sql.push_str(" NOT NULL");
855 }
856 if let Some(default) = &col.default {
857 sql.push_str(&format!(" DEFAULT {}", default));
858 }
859 sql
860 }
861 TableChange::DropColumn(name) => {
862 format!(
864 "ALTER TABLE {} DROP COLUMN {}",
865 self.quote(table),
866 self.quote(name)
867 )
868 }
869 TableChange::ModifyColumn(col) => {
870 format!(
873 "-- SQLite 不支持 MODIFY COLUMN({} {}),需重建表",
874 col.name, col.sql_type
875 )
876 }
877 TableChange::AddIndex(name, cols) => {
878 format!(
879 "CREATE INDEX {} ON {} ({})",
880 name,
881 self.quote(table),
882 cols.join(", ")
883 )
884 }
885 TableChange::DropIndex(name) => {
886 format!("DROP INDEX {}", name)
887 }
888 TableChange::AddForeignKey {
889 columns,
890 reference_table,
891 reference_columns: _,
892 } => {
893 format!(
895 "-- SQLite 不支持 ADD FOREIGN KEY({} -> {}),需重建表",
896 columns.join(","),
897 reference_table
898 )
899 }
900 }
901 })
902 .collect();
903
904 stmts.join("; ")
905 }
906
907 fn build_upsert_on_conflict(
911 &self,
912 conflict_columns: &[&str],
913 update_columns: &[&str],
914 all_columns: &[String],
915 ) -> Option<String> {
916 if conflict_columns.is_empty() {
917 return None;
918 }
919 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
920 let conflict_set: std::collections::HashSet<&str> =
921 conflict_columns.iter().copied().collect();
922 let cols_to_update: Vec<String> = if update_columns.is_empty() {
923 all_columns
924 .iter()
925 .filter(|c| !conflict_set.contains(c.as_str()))
926 .map(|c| self.quote(c))
927 .collect()
928 } else {
929 update_columns.iter().map(|c| self.quote(c)).collect()
930 };
931 if cols_to_update.is_empty() {
932 return Some(format!(
933 "ON CONFLICT ({}) DO NOTHING",
934 conflict_cols.join(", ")
935 ));
936 }
937 let set_clauses: Vec<String> = cols_to_update
938 .iter()
939 .map(|col| format!("{}=EXCLUDED.{}", col, col))
940 .collect();
941 Some(format!(
942 "ON CONFLICT ({}) DO UPDATE SET {}",
943 conflict_cols.join(", "),
944 set_clauses.join(", ")
945 ))
946 }
947
948 fn supports_lock_for_update(&self) -> bool {
949 false
950 }
951
952 fn supports_lock_shared(&self) -> bool {
953 false
954 }
955}
956
957fn map_to_oracle_type(sql_type: &str) -> String {
966 let upper = sql_type.to_uppercase();
967 let trimmed = upper.trim();
968
969 if trimmed.starts_with("BIGINT") {
970 sql_type.replacen("BIGINT", "NUMBER(19)", 1)
971 } else if trimmed.starts_with("VARCHAR2") {
972 sql_type.to_string()
973 } else if trimmed.starts_with("VARCHAR") {
974 sql_type.replacen("VARCHAR", "VARCHAR2", 1)
975 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
976 "CLOB".to_string()
977 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
978 "NUMBER(1)".to_string()
979 } else if trimmed == "INTEGER" {
980 "NUMBER(10)".to_string()
981 } else if trimmed.starts_with("INT") {
982 sql_type.replacen("INT", "NUMBER(10)", 1)
983 } else {
984 sql_type.to_string()
985 }
986}
987
988#[derive(Debug, Clone)]
990pub struct OracleDialect;
991
992impl Dialect for OracleDialect {
993 fn clone_box(&self) -> Box<dyn Dialect> {
994 Box::new(OracleDialect)
995 }
996
997 fn db_type(&self) -> DbType {
998 DbType::Oracle
999 }
1000
1001 fn quote(&self, identifier: &str) -> String {
1002 format!("\"{}\"", identifier.replace('"', "\"\""))
1004 }
1005
1006 fn quote_into(&self, identifier: &str, buf: &mut String) {
1007 if identifier.contains('"') {
1008 buf.push_str(&self.quote(identifier));
1009 } else {
1010 buf.push('"');
1011 buf.push_str(identifier);
1012 buf.push('"');
1013 }
1014 }
1015
1016 fn escape_string(&self, s: &str) -> String {
1017 let mut escaped = String::with_capacity(s.len() * 2);
1019 for c in s.chars() {
1020 match c {
1021 '\'' => escaped.push_str("''"),
1022 _ => escaped.push(c),
1023 }
1024 }
1025 escaped
1026 }
1027
1028 fn supports_returning(&self) -> bool {
1029 true
1031 }
1032
1033 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1034 let offset = page.saturating_sub(1).saturating_mul(limit);
1037 format!(
1038 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1039 sql, offset, limit
1040 )
1041 }
1042
1043 fn json_type(&self) -> &'static str {
1044 "JSON"
1046 }
1047
1048 fn json_extract(&self, column: &str, path: &str) -> String {
1049 let normalized = if path.starts_with('$') {
1051 path.to_string()
1052 } else {
1053 format!("$.{}", path)
1054 };
1055 format!(
1056 "JSON_VALUE({}, '{}')",
1057 column,
1058 self.escape_string(&normalized)
1059 )
1060 }
1061
1062 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1063 if columns.is_empty() {
1066 return "0".to_string();
1067 }
1068 let escaped = self.escape_string(keyword);
1069 let parts: Vec<String> = columns
1070 .iter()
1071 .map(|c| format!("CONTAINS({}, '{}', 1) > 0", c, escaped))
1072 .collect();
1073 parts.join(" OR ")
1074 }
1075
1076 fn bool_to_int(&self, expr: &str) -> String {
1077 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1079 }
1080
1081 fn concat(&self, parts: &[&str]) -> String {
1082 if parts.is_empty() {
1084 return "NULL".to_string();
1085 }
1086 parts.join(" || ")
1087 }
1088
1089 fn supports_if_exists(&self) -> bool {
1090 true
1092 }
1093
1094 fn supports_if_not_exists(&self) -> bool {
1095 true
1097 }
1098
1099 fn auto_increment_keyword(&self) -> &'static str {
1100 "GENERATED BY DEFAULT AS IDENTITY"
1102 }
1103
1104 fn last_insert_id_sql(&self) -> Option<&'static str> {
1105 None
1110 }
1111
1112 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1113 let cols: Vec<String> = columns
1114 .iter()
1115 .map(|col| {
1116 let oracle_type = map_to_oracle_type(&col.sql_type);
1117 let mut sql = format!("{} {}", self.quote(&col.name), oracle_type);
1118 if !col.nullable && !col.auto_increment {
1120 sql.push_str(" NOT NULL");
1121 }
1122 if let Some(default) = &col.default {
1123 sql.push_str(&format!(" DEFAULT {}", default));
1124 }
1125 if col.auto_increment {
1126 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1127 }
1128 if col.primary_key {
1129 sql.push_str(" PRIMARY KEY");
1130 }
1131 sql
1132 })
1133 .collect();
1134
1135 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1136 }
1137
1138 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1139 let stmts: Vec<String> = changes
1140 .iter()
1141 .map(|change| match change {
1142 TableChange::AddColumn(col) => {
1143 let oracle_type = map_to_oracle_type(&col.sql_type);
1144 let mut sql = format!(
1145 "ALTER TABLE {} ADD {} {}",
1146 self.quote(table),
1147 self.quote(&col.name),
1148 oracle_type
1149 );
1150 if !col.nullable {
1151 sql.push_str(" NOT NULL");
1152 }
1153 if let Some(default) = &col.default {
1154 sql.push_str(&format!(" DEFAULT {}", default));
1155 }
1156 sql
1157 }
1158 TableChange::DropColumn(name) => {
1159 format!(
1160 "ALTER TABLE {} DROP COLUMN {}",
1161 self.quote(table),
1162 self.quote(name)
1163 )
1164 }
1165 TableChange::ModifyColumn(col) => {
1166 let oracle_type = map_to_oracle_type(&col.sql_type);
1168 let mut sql = format!(
1169 "ALTER TABLE {} MODIFY {} {}",
1170 self.quote(table),
1171 self.quote(&col.name),
1172 oracle_type
1173 );
1174 if !col.nullable {
1175 sql.push_str(" NOT NULL");
1176 }
1177 if let Some(default) = &col.default {
1178 sql.push_str(&format!(" DEFAULT {}", default));
1179 }
1180 sql
1181 }
1182 TableChange::AddIndex(name, cols) => {
1183 format!(
1184 "CREATE INDEX {} ON {} ({})",
1185 name,
1186 self.quote(table),
1187 cols.join(", ")
1188 )
1189 }
1190 TableChange::DropIndex(name) => {
1191 format!("DROP INDEX {}", name)
1192 }
1193 TableChange::AddForeignKey {
1194 columns,
1195 reference_table,
1196 reference_columns,
1197 } => {
1198 format!(
1199 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1200 self.quote(table),
1201 table,
1202 columns.join("_"),
1203 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1204 self.quote(reference_table),
1205 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1206 )
1207 }
1208 })
1209 .collect();
1210
1211 stmts.join("; ")
1212 }
1213}
1214
1215fn map_to_sqlserver_type(sql_type: &str) -> String {
1224 let upper = sql_type.to_uppercase();
1225 let trimmed = upper.trim();
1226
1227 if trimmed.starts_with("BIGINT") {
1228 sql_type.to_string()
1229 } else if matches!(trimmed, "INT" | "INTEGER") {
1230 "INT".to_string()
1231 } else if trimmed.starts_with("NVARCHAR") {
1232 sql_type.to_string()
1233 } else if trimmed.starts_with("VARCHAR") {
1234 sql_type.replacen("VARCHAR", "NVARCHAR", 1)
1235 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1236 "NVARCHAR(MAX)".to_string()
1237 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1238 "BIT".to_string()
1239 } else {
1240 sql_type.to_string()
1241 }
1242}
1243
1244#[derive(Debug, Clone)]
1246pub struct SqlServerDialect;
1247
1248impl Dialect for SqlServerDialect {
1249 fn clone_box(&self) -> Box<dyn Dialect> {
1250 Box::new(SqlServerDialect)
1251 }
1252
1253 fn db_type(&self) -> DbType {
1254 DbType::SqlServer
1255 }
1256
1257 fn quote(&self, identifier: &str) -> String {
1258 format!("[{}]", identifier.replace(']', "]]"))
1260 }
1261
1262 fn escape_string(&self, s: &str) -> String {
1263 let mut escaped = String::with_capacity(s.len() * 2);
1265 for c in s.chars() {
1266 match c {
1267 '\'' => escaped.push_str("''"),
1268 _ => escaped.push(c),
1269 }
1270 }
1271 escaped
1272 }
1273
1274 fn supports_returning(&self) -> bool {
1275 true
1277 }
1278
1279 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1280 let offset = page.saturating_sub(1).saturating_mul(limit);
1282 format!(
1283 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1284 sql, offset, limit
1285 )
1286 }
1287
1288 fn json_type(&self) -> &'static str {
1289 "NVARCHAR(MAX)"
1291 }
1292
1293 fn json_extract(&self, column: &str, path: &str) -> String {
1294 let normalized = if path.starts_with('$') {
1296 path.to_string()
1297 } else {
1298 format!("$.{}", path)
1299 };
1300 format!(
1301 "JSON_VALUE({}, '{}')",
1302 column,
1303 self.escape_string(&normalized)
1304 )
1305 }
1306
1307 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1308 if columns.is_empty() {
1310 return "0".to_string();
1311 }
1312 let escaped = self.escape_string(keyword);
1313 let cols = columns.join(", ");
1314 format!("CONTAINS({}, '{}')", cols, escaped)
1315 }
1316
1317 fn bool_to_int(&self, expr: &str) -> String {
1318 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1320 }
1321
1322 fn concat(&self, parts: &[&str]) -> String {
1323 if parts.is_empty() {
1324 return "NULL".to_string();
1325 }
1326 format!("CONCAT({})", parts.join(", "))
1327 }
1328
1329 fn supports_if_exists(&self) -> bool {
1330 true
1332 }
1333
1334 fn supports_if_not_exists(&self) -> bool {
1335 true
1337 }
1338
1339 fn auto_increment_keyword(&self) -> &'static str {
1340 "IDENTITY(1,1)"
1342 }
1343
1344 fn last_insert_id_sql(&self) -> Option<&'static str> {
1345 Some("SCOPE_IDENTITY()")
1347 }
1348
1349 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1350 let cols: Vec<String> = columns
1351 .iter()
1352 .map(|col| {
1353 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1354 let mut sql = format!("{} {}", self.quote(&col.name), sqlserver_type);
1355 if !col.nullable {
1356 sql.push_str(" NOT NULL");
1357 }
1358 if let Some(default) = &col.default {
1359 sql.push_str(&format!(" DEFAULT {}", default));
1360 }
1361 if col.auto_increment {
1362 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1363 }
1364 if col.primary_key {
1365 sql.push_str(" PRIMARY KEY");
1366 }
1367 sql
1368 })
1369 .collect();
1370
1371 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1372 }
1373
1374 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1375 let stmts: Vec<String> = changes
1376 .iter()
1377 .map(|change| match change {
1378 TableChange::AddColumn(col) => {
1379 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1380 let mut sql = format!(
1381 "ALTER TABLE {} ADD {} {}",
1382 self.quote(table),
1383 self.quote(&col.name),
1384 sqlserver_type
1385 );
1386 if !col.nullable {
1387 sql.push_str(" NOT NULL");
1388 }
1389 if let Some(default) = &col.default {
1390 sql.push_str(&format!(" DEFAULT {}", default));
1391 }
1392 sql
1393 }
1394 TableChange::DropColumn(name) => {
1395 format!(
1396 "ALTER TABLE {} DROP COLUMN {}",
1397 self.quote(table),
1398 self.quote(name)
1399 )
1400 }
1401 TableChange::ModifyColumn(col) => {
1402 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1404 let mut sql = format!(
1405 "ALTER TABLE {} ALTER COLUMN {} {}",
1406 self.quote(table),
1407 self.quote(&col.name),
1408 sqlserver_type
1409 );
1410 if !col.nullable {
1411 sql.push_str(" NOT NULL");
1412 }
1413 if let Some(default) = &col.default {
1414 sql.push_str(&format!(" DEFAULT {}", default));
1415 }
1416 sql
1417 }
1418 TableChange::AddIndex(name, cols) => {
1419 format!(
1420 "CREATE INDEX {} ON {} ({})",
1421 name,
1422 self.quote(table),
1423 cols.join(", ")
1424 )
1425 }
1426 TableChange::DropIndex(name) => {
1427 format!("DROP INDEX {} ON {}", name, self.quote(table))
1429 }
1430 TableChange::AddForeignKey {
1431 columns,
1432 reference_table,
1433 reference_columns,
1434 } => {
1435 format!(
1436 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1437 self.quote(table),
1438 table,
1439 columns.join("_"),
1440 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1441 self.quote(reference_table),
1442 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1443 )
1444 }
1445 })
1446 .collect();
1447
1448 stmts.join("; ")
1449 }
1450
1451 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
1452 format!("INSERT INTO {}", self.quote(table))
1458 }
1459}
1460
1461macro_rules! delegate_dialect_to {
1478 ($wrapper:ident, $base:ident, $db_type:expr) => {
1479 #[derive(Debug, Clone)]
1481 pub struct $wrapper;
1482
1483 impl Dialect for $wrapper {
1484 fn clone_box(&self) -> Box<dyn Dialect> {
1485 Box::new($wrapper)
1486 }
1487
1488 fn db_type(&self) -> DbType {
1489 $db_type
1490 }
1491 fn quote(&self, identifier: &str) -> String {
1492 $base.quote(identifier)
1493 }
1494 fn escape_string(&self, s: &str) -> String {
1495 $base.escape_string(s)
1496 }
1497 fn supports_returning(&self) -> bool {
1498 $base.supports_returning()
1499 }
1500 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1501 $base.build_pagination(sql, page, limit)
1502 }
1503 fn json_type(&self) -> &'static str {
1504 $base.json_type()
1505 }
1506 fn json_extract(&self, column: &str, path: &str) -> String {
1507 $base.json_extract(column, path)
1508 }
1509 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1510 $base.full_text_search(columns, keyword)
1511 }
1512 fn bool_to_int(&self, expr: &str) -> String {
1513 $base.bool_to_int(expr)
1514 }
1515 fn concat(&self, parts: &[&str]) -> String {
1516 $base.concat(parts)
1517 }
1518 fn supports_if_exists(&self) -> bool {
1519 $base.supports_if_exists()
1520 }
1521 fn supports_if_not_exists(&self) -> bool {
1522 $base.supports_if_not_exists()
1523 }
1524 fn auto_increment_keyword(&self) -> &'static str {
1525 $base.auto_increment_keyword()
1526 }
1527 fn last_insert_id_sql(&self) -> Option<&'static str> {
1528 $base.last_insert_id_sql()
1529 }
1530 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1531 $base.build_create_table(table, columns)
1532 }
1533 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1534 $base.build_alter_table(table, changes)
1535 }
1536 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1537 $base.build_drop_table(table, if_exists)
1538 }
1539 }
1540 };
1541}
1542
1543delegate_dialect_to!(MariaDbDialect, MySqlDialect, DbType::MariaDB);
1545
1546delegate_dialect_to!(TiDbDialect, MySqlDialect, DbType::TiDB);
1548
1549delegate_dialect_to!(OceanBaseDialect, MySqlDialect, DbType::OceanBase);
1551
1552delegate_dialect_to!(KingbaseDialect, PostgreSqlDialect, DbType::Kingbase);
1554
1555delegate_dialect_to!(PolarDbDialect, PostgreSqlDialect, DbType::PolarDB);
1557
1558delegate_dialect_to!(GaussDbDialect, PostgreSqlDialect, DbType::GaussDB);
1560
1561delegate_dialect_to!(DamengDialect, OracleDialect, DbType::Dameng);
1563
1564delegate_dialect_to!(SybaseDialect, SqlServerDialect, DbType::Sybase);
1566
1567delegate_dialect_to!(GBaseDialect, SqlServerDialect, DbType::GBase);
1569
1570#[cfg(feature = "dialect-cockroachdb")]
1581delegate_dialect_to!(CockroachDbDialect, PostgreSqlDialect, DbType::CockroachDB);
1582
1583#[cfg(feature = "dialect-yugabytedb")]
1594delegate_dialect_to!(YugabyteDbDialect, PostgreSqlDialect, DbType::YugabyteDB);
1595
1596#[cfg(feature = "dialect-snowflake")]
1617#[derive(Debug, Clone)]
1618pub struct SnowflakeDialect;
1619
1620#[cfg(feature = "dialect-snowflake")]
1621impl Dialect for SnowflakeDialect {
1622 fn clone_box(&self) -> Box<dyn Dialect> {
1623 Box::new(SnowflakeDialect)
1624 }
1625
1626 fn db_type(&self) -> DbType {
1627 DbType::Snowflake
1628 }
1629
1630 fn quote(&self, identifier: &str) -> String {
1631 format!("\"{}\"", identifier.replace('"', "\"\""))
1633 }
1634
1635 fn escape_string(&self, s: &str) -> String {
1636 let mut escaped = String::with_capacity(s.len() * 2);
1638 for c in s.chars() {
1639 match c {
1640 '\'' => escaped.push_str("''"),
1641 '\\' => escaped.push_str("\\\\"),
1642 _ => escaped.push(c),
1643 }
1644 }
1645 escaped
1646 }
1647
1648 fn supports_returning(&self) -> bool {
1649 true
1651 }
1652
1653 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1654 let offset = page.saturating_sub(1).saturating_mul(limit);
1656 format!("{} LIMIT {}, {}", sql, offset, limit)
1657 }
1658
1659 fn json_type(&self) -> &'static str {
1660 "VARIANT"
1662 }
1663
1664 fn json_extract(&self, column: &str, path: &str) -> String {
1665 let normalized = path.trim_start_matches("$.");
1667 format!("{}:{}", column, normalized)
1668 }
1669
1670 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1671 if columns.is_empty() {
1673 return "FALSE".to_string();
1674 }
1675 let escaped = self.escape_string(keyword);
1676 let parts: Vec<String> = columns
1677 .iter()
1678 .map(|c| format!("{} ILIKE '%{}%'", c, escaped))
1679 .collect();
1680 parts.join(" OR ")
1681 }
1682
1683 fn bool_to_int(&self, expr: &str) -> String {
1684 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1685 }
1686
1687 fn concat(&self, parts: &[&str]) -> String {
1688 if parts.is_empty() {
1689 return "NULL".to_string();
1690 }
1691 format!("CONCAT({})", parts.join(", "))
1693 }
1694
1695 fn supports_if_exists(&self) -> bool {
1696 true
1697 }
1698
1699 fn supports_if_not_exists(&self) -> bool {
1700 true
1701 }
1702
1703 fn auto_increment_keyword(&self) -> &'static str {
1704 "AUTOINCREMENT"
1706 }
1707
1708 fn last_insert_id_sql(&self) -> Option<&'static str> {
1709 None
1711 }
1712
1713 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1714 let cols: Vec<String> = columns
1715 .iter()
1716 .map(|col| {
1717 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
1718 if !col.nullable {
1719 sql.push_str(" NOT NULL");
1720 }
1721 if let Some(default) = &col.default {
1722 sql.push_str(&format!(" DEFAULT {}", default));
1723 }
1724 if col.primary_key {
1725 sql.push_str(" PRIMARY KEY");
1726 }
1727 sql
1728 })
1729 .collect();
1730 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1731 }
1732
1733 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1734 let stmts: Vec<String> = changes
1735 .iter()
1736 .map(|change| match change {
1737 TableChange::AddColumn(col) => {
1738 let mut sql = format!(
1739 "ALTER TABLE {} ADD COLUMN {} {}",
1740 self.quote(table),
1741 self.quote(&col.name),
1742 col.sql_type
1743 );
1744 if !col.nullable {
1745 sql.push_str(" NOT NULL");
1746 }
1747 if let Some(default) = &col.default {
1748 sql.push_str(&format!(" DEFAULT {}", default));
1749 }
1750 sql
1751 }
1752 TableChange::DropColumn(name) => {
1753 format!(
1754 "ALTER TABLE {} DROP COLUMN {}",
1755 self.quote(table),
1756 self.quote(name)
1757 )
1758 }
1759 TableChange::ModifyColumn(col) => {
1760 format!(
1761 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
1762 self.quote(table),
1763 self.quote(&col.name),
1764 col.sql_type
1765 )
1766 }
1767 TableChange::AddIndex(name, cols) => {
1768 format!(
1769 "CREATE INDEX {} ON {} ({})",
1770 name,
1771 self.quote(table),
1772 cols.join(", ")
1773 )
1774 }
1775 TableChange::DropIndex(name) => {
1776 format!("DROP INDEX {}", name)
1777 }
1778 TableChange::AddForeignKey {
1779 columns,
1780 reference_table,
1781 reference_columns,
1782 } => {
1783 format!(
1784 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1785 self.quote(table),
1786 table,
1787 columns.join("_"),
1788 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1789 self.quote(reference_table),
1790 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1791 )
1792 }
1793 })
1794 .collect();
1795 stmts.join("; ")
1796 }
1797}
1798
1799#[cfg(feature = "dialect-snowflake")]
1801impl SnowflakeDialect {
1802 pub fn build_copy_into(&self, target: &str, source: &str) -> String {
1806 format!("COPY INTO {} FROM {}", self.quote(target), source)
1807 }
1808
1809 pub fn build_time_travel_at(&self, table: &str, timestamp: &str) -> String {
1813 format!(
1814 "SELECT * FROM {} AT(OBJECT => '{}')",
1815 self.quote(table),
1816 self.escape_string(timestamp)
1817 )
1818 }
1819
1820 pub fn build_time_travel_before(&self, table: &str, timestamp: &str) -> String {
1824 format!(
1825 "SELECT * FROM {} BEFORE(timestamp => '{}')",
1826 self.quote(table),
1827 self.escape_string(timestamp)
1828 )
1829 }
1830}
1831
1832#[cfg(feature = "dialect-redshift")]
1845delegate_dialect_to!(RedshiftDialect, PostgreSqlDialect, DbType::Redshift);
1846
1847#[cfg(feature = "dialect-redshift")]
1849impl RedshiftDialect {
1850 pub fn build_copy(&self, target: &str, source: &str, credentials: &str) -> String {
1854 format!(
1855 "COPY {} FROM '{}' CREDENTIALS '{}' FORMAT AS CSV",
1856 PostgreSqlDialect.quote(target),
1857 source,
1858 credentials
1859 )
1860 }
1861
1862 pub fn build_unload(&self, query: &str, s3_path: &str, credentials: &str) -> String {
1866 format!(
1867 "UNLOAD ('{}') TO '{}' CREDENTIALS '{}'",
1868 query, s3_path, credentials
1869 )
1870 }
1871}
1872
1873#[derive(Debug, Clone)]
1887pub struct ClickHouseDialect;
1888
1889impl Dialect for ClickHouseDialect {
1890 fn clone_box(&self) -> Box<dyn Dialect> {
1891 Box::new(ClickHouseDialect)
1892 }
1893
1894 fn db_type(&self) -> DbType {
1895 DbType::ClickHouse
1896 }
1897
1898 fn quote(&self, identifier: &str) -> String {
1899 format!("`{}`", identifier.replace('`', "``"))
1901 }
1902
1903 fn escape_string(&self, s: &str) -> String {
1904 let mut escaped = String::with_capacity(s.len() * 2);
1906 for c in s.chars() {
1907 match c {
1908 '\'' => escaped.push_str("\\'"),
1909 '\\' => escaped.push_str("\\\\"),
1910 '\n' => escaped.push_str("\\n"),
1911 '\r' => escaped.push_str("\\r"),
1912 '\t' => escaped.push_str("\\t"),
1913 _ => escaped.push(c),
1914 }
1915 }
1916 escaped
1917 }
1918
1919 fn supports_returning(&self) -> bool {
1920 false
1922 }
1923
1924 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1925 let offset = page.saturating_sub(1).saturating_mul(limit);
1927 format!("{} LIMIT {}, {}", sql, offset, limit)
1928 }
1929
1930 fn json_type(&self) -> &'static str {
1931 "String"
1933 }
1934
1935 fn json_extract(&self, column: &str, path: &str) -> String {
1936 let normalized = if path.starts_with('$') {
1938 path.to_string()
1939 } else {
1940 format!("$.{}", path)
1941 };
1942 format!(
1943 "JSONExtractString({}, '{}')",
1944 column,
1945 self.escape_string(&normalized)
1946 )
1947 }
1948
1949 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1950 if columns.is_empty() {
1952 return "0".to_string();
1953 }
1954 let escaped = self.escape_string(keyword);
1955 let parts: Vec<String> = columns
1956 .iter()
1957 .map(|c| format!("position({}, '{}') > 0", c, escaped))
1958 .collect();
1959 parts.join(" OR ")
1960 }
1961
1962 fn bool_to_int(&self, expr: &str) -> String {
1963 format!("toUInt8({})", expr)
1965 }
1966
1967 fn concat(&self, parts: &[&str]) -> String {
1968 if parts.is_empty() {
1970 return "''".to_string();
1971 }
1972 format!("concat({})", parts.join(", "))
1973 }
1974
1975 fn supports_if_exists(&self) -> bool {
1976 true
1977 }
1978
1979 fn supports_if_not_exists(&self) -> bool {
1980 true
1981 }
1982
1983 fn auto_increment_keyword(&self) -> &'static str {
1984 ""
1986 }
1987
1988 fn last_insert_id_sql(&self) -> Option<&'static str> {
1989 None
1991 }
1992
1993 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1994 let cols: Vec<String> = columns
1995 .iter()
1996 .map(|col| {
1997 let ch_type = map_to_clickhouse_type(&col.sql_type);
1998 let mut sql = format!("{} {}", self.quote(&col.name), ch_type);
1999 if let Some(default) = &col.default {
2000 sql.push_str(&format!(" DEFAULT {}", default));
2001 }
2002 if col.primary_key {
2003 sql.push_str(" PRIMARY KEY");
2004 }
2005 sql
2006 })
2007 .collect();
2008
2009 format!(
2011 "CREATE TABLE {} ({}) ENGINE = MergeTree()",
2012 self.quote(table),
2013 cols.join(", ")
2014 )
2015 }
2016
2017 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2018 let stmts: Vec<String> = changes
2019 .iter()
2020 .map(|change| match change {
2021 TableChange::AddColumn(col) => {
2022 let ch_type = map_to_clickhouse_type(&col.sql_type);
2023 format!(
2024 "ALTER TABLE {} ADD COLUMN {} {}",
2025 self.quote(table),
2026 self.quote(&col.name),
2027 ch_type
2028 )
2029 }
2030 TableChange::DropColumn(name) => {
2031 format!(
2032 "ALTER TABLE {} DROP COLUMN {}",
2033 self.quote(table),
2034 self.quote(name)
2035 )
2036 }
2037 TableChange::ModifyColumn(col) => {
2038 let ch_type = map_to_clickhouse_type(&col.sql_type);
2039 format!(
2040 "ALTER TABLE {} MODIFY COLUMN {} {}",
2041 self.quote(table),
2042 self.quote(&col.name),
2043 ch_type
2044 )
2045 }
2046 TableChange::AddIndex(name, cols) => {
2047 format!(
2048 "ALTER TABLE {} ADD INDEX {} ({})",
2049 self.quote(table),
2050 name,
2051 cols.join(", ")
2052 )
2053 }
2054 TableChange::DropIndex(name) => {
2055 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
2056 }
2057 TableChange::AddForeignKey { .. } => {
2058 String::new()
2060 }
2061 })
2062 .filter(|s| !s.is_empty())
2063 .collect();
2064
2065 stmts.join("; ")
2066 }
2067
2068 fn supports_lock_for_update(&self) -> bool {
2069 false
2071 }
2072
2073 fn supports_lock_shared(&self) -> bool {
2074 false
2076 }
2077
2078 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
2079 format!("INSERT INTO {}", self.quote(table))
2081 }
2082}
2083
2084fn map_to_clickhouse_type(sql_type: &str) -> String {
2094 let upper = sql_type.to_uppercase();
2095 let trimmed = upper.trim();
2096
2097 if trimmed.starts_with("BIGINT") {
2098 "Int64".to_string()
2099 } else if matches!(trimmed, "INT" | "INTEGER") {
2100 "Int32".to_string()
2101 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
2102 "Int16".to_string()
2103 } else if trimmed.starts_with("VARCHAR")
2104 || trimmed.starts_with("CHAR")
2105 || matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT")
2106 {
2107 "String".to_string()
2108 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2109 "UInt8".to_string()
2110 } else if matches!(trimmed, "FLOAT" | "REAL") {
2111 "Float32".to_string()
2112 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2113 "Float64".to_string()
2114 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2115 "DateTime".to_string()
2116 } else if matches!(trimmed, "DATE") {
2117 "Date".to_string()
2118 } else if trimmed.starts_with("DECIMAL") || trimmed.starts_with("NUMERIC") {
2119 "Decimal(38, 4)".to_string()
2120 } else {
2121 sql_type.to_string()
2122 }
2123}
2124
2125#[derive(Debug, Clone)]
2140pub struct DuckDBDialect;
2141
2142impl Dialect for DuckDBDialect {
2143 fn clone_box(&self) -> Box<dyn Dialect> {
2144 Box::new(DuckDBDialect)
2145 }
2146
2147 fn db_type(&self) -> DbType {
2148 DbType::DuckDB
2149 }
2150
2151 fn quote(&self, identifier: &str) -> String {
2152 format!("\"{}\"", identifier.replace('"', "\"\""))
2154 }
2155
2156 fn escape_string(&self, s: &str) -> String {
2157 let mut escaped = String::with_capacity(s.len() * 2);
2159 for c in s.chars() {
2160 match c {
2161 '\'' => escaped.push_str("''"),
2162 _ => escaped.push(c),
2163 }
2164 }
2165 escaped
2166 }
2167
2168 fn supports_returning(&self) -> bool {
2169 false
2171 }
2172
2173 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2174 let offset = page.saturating_sub(1).saturating_mul(limit);
2176 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
2177 }
2178
2179 fn json_type(&self) -> &'static str {
2180 "JSON"
2182 }
2183
2184 fn json_extract(&self, column: &str, path: &str) -> String {
2185 let normalized = if path.starts_with('$') {
2187 path[2..].to_string()
2188 } else {
2189 path.to_string()
2190 };
2191 format!("{} -> '{}'", column, normalized)
2192 }
2193
2194 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2195 if columns.is_empty() {
2197 return "0".to_string();
2198 }
2199 let escaped = self.escape_string(keyword);
2200 let parts: Vec<String> = columns
2201 .iter()
2202 .map(|c| format!("{} LIKE '%{}%'", c, escaped))
2203 .collect();
2204 parts.join(" OR ")
2205 }
2206
2207 fn bool_to_int(&self, expr: &str) -> String {
2208 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2210 }
2211
2212 fn concat(&self, parts: &[&str]) -> String {
2213 if parts.is_empty() {
2215 return "''".to_string();
2216 }
2217 parts.join(" || ")
2218 }
2219
2220 fn supports_if_exists(&self) -> bool {
2221 true
2222 }
2223
2224 fn supports_if_not_exists(&self) -> bool {
2225 true
2226 }
2227
2228 fn auto_increment_keyword(&self) -> &'static str {
2229 ""
2231 }
2232
2233 fn last_insert_id_sql(&self) -> Option<&'static str> {
2234 None
2236 }
2237
2238 fn supports_lock_for_update(&self) -> bool {
2239 false
2241 }
2242
2243 fn supports_lock_shared(&self) -> bool {
2244 false
2246 }
2247
2248 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
2249 format!("INSERT OR IGNORE INTO {}", self.quote(table))
2251 }
2252
2253 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2254 let cols: Vec<String> = columns
2255 .iter()
2256 .map(|col| {
2257 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
2258 if col.auto_increment {
2259 sql = format!("{} BIGINT PRIMARY KEY", self.quote(&col.name));
2261 }
2262 if let Some(default) = &col.default {
2263 sql.push_str(&format!(" DEFAULT {}", default));
2264 }
2265 if col.primary_key && !col.auto_increment {
2266 sql.push_str(" PRIMARY KEY");
2267 }
2268 sql
2269 })
2270 .collect();
2271
2272 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2273 }
2274
2275 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2276 let stmts: Vec<String> = changes
2277 .iter()
2278 .map(|change| match change {
2279 TableChange::AddColumn(col) => {
2280 let mut sql = format!(
2281 "ALTER TABLE {} ADD COLUMN {} {}",
2282 self.quote(table),
2283 self.quote(&col.name),
2284 col.sql_type
2285 );
2286 if let Some(default) = &col.default {
2287 sql.push_str(&format!(" DEFAULT {}", default));
2288 }
2289 sql
2290 }
2291 TableChange::DropColumn(name) => {
2292 format!(
2293 "ALTER TABLE {} DROP COLUMN {}",
2294 self.quote(table),
2295 self.quote(name)
2296 )
2297 }
2298 TableChange::ModifyColumn(col) => {
2299 format!(
2300 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
2301 self.quote(table),
2302 self.quote(&col.name),
2303 col.sql_type
2304 )
2305 }
2306 TableChange::AddIndex(name, _cols) => {
2307 format!(
2309 "CREATE INDEX {} ON {} (id)",
2310 self.quote(name),
2311 self.quote(table)
2312 )
2313 }
2314 TableChange::DropIndex(name) => {
2315 format!("DROP INDEX {}", self.quote(name))
2316 }
2317 TableChange::AddForeignKey { .. } => {
2318 String::new()
2320 }
2321 })
2322 .filter(|s| !s.is_empty())
2323 .collect();
2324
2325 stmts.join("; ")
2326 }
2327}
2328
2329#[derive(Debug, Clone)]
2343pub struct Db2Dialect;
2344
2345impl Dialect for Db2Dialect {
2346 fn clone_box(&self) -> Box<dyn Dialect> {
2347 Box::new(Db2Dialect)
2348 }
2349
2350 fn db_type(&self) -> DbType {
2351 DbType::Db2
2352 }
2353
2354 fn quote(&self, identifier: &str) -> String {
2355 format!("\"{}\"", identifier.replace('"', "\"\""))
2357 }
2358
2359 fn escape_string(&self, s: &str) -> String {
2360 let mut escaped = String::with_capacity(s.len() * 2);
2362 for c in s.chars() {
2363 match c {
2364 '\'' => escaped.push_str("''"),
2365 _ => escaped.push(c),
2366 }
2367 }
2368 escaped
2369 }
2370
2371 fn supports_returning(&self) -> bool {
2372 false
2374 }
2375
2376 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2377 let offset = page.saturating_sub(1).saturating_mul(limit);
2379 format!(
2380 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
2381 sql, offset, limit
2382 )
2383 }
2384
2385 fn json_type(&self) -> &'static str {
2386 "JSON"
2388 }
2389
2390 fn json_extract(&self, column: &str, path: &str) -> String {
2391 let normalized = if path.starts_with('$') {
2393 path.to_string()
2394 } else {
2395 format!("$.{}", path)
2396 };
2397 format!(
2398 "JSON_VALUE({}, '{}')",
2399 column,
2400 self.escape_string(&normalized)
2401 )
2402 }
2403
2404 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2405 if columns.is_empty() {
2407 return "0".to_string();
2408 }
2409 let escaped = self.escape_string(keyword);
2410 let parts: Vec<String> = columns
2411 .iter()
2412 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2413 .collect();
2414 parts.join(" OR ")
2415 }
2416
2417 fn bool_to_int(&self, expr: &str) -> String {
2418 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2420 }
2421
2422 fn concat(&self, parts: &[&str]) -> String {
2423 if parts.is_empty() {
2425 return "''".to_string();
2426 }
2427 parts.join(" || ")
2428 }
2429
2430 fn supports_if_exists(&self) -> bool {
2431 false
2433 }
2434
2435 fn supports_if_not_exists(&self) -> bool {
2436 false
2438 }
2439
2440 fn auto_increment_keyword(&self) -> &'static str {
2441 "GENERATED ALWAYS AS IDENTITY"
2443 }
2444
2445 fn last_insert_id_sql(&self) -> Option<&'static str> {
2446 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
2448 }
2449
2450 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2451 let cols: Vec<String> = columns
2452 .iter()
2453 .map(|col| {
2454 let db2_type = map_to_db2_type(&col.sql_type);
2455 let mut sql = format!("{} {}", self.quote(&col.name), db2_type);
2456 if !col.nullable && !col.auto_increment {
2457 sql.push_str(" NOT NULL");
2458 }
2459 if let Some(default) = &col.default {
2460 sql.push_str(&format!(" DEFAULT {}", default));
2461 }
2462 if col.auto_increment {
2463 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2464 }
2465 if col.primary_key {
2466 sql.push_str(" PRIMARY KEY");
2467 }
2468 sql
2469 })
2470 .collect();
2471
2472 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2473 }
2474
2475 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2476 let stmts: Vec<String> = changes
2477 .iter()
2478 .map(|change| match change {
2479 TableChange::AddColumn(col) => {
2480 let db2_type = map_to_db2_type(&col.sql_type);
2481 let mut sql = format!(
2482 "ALTER TABLE {} ADD COLUMN {} {}",
2483 self.quote(table),
2484 self.quote(&col.name),
2485 db2_type
2486 );
2487 if !col.nullable {
2488 sql.push_str(" NOT NULL");
2489 }
2490 if let Some(default) = &col.default {
2491 sql.push_str(&format!(" DEFAULT {}", default));
2492 }
2493 sql
2494 }
2495 TableChange::DropColumn(name) => {
2496 format!(
2497 "ALTER TABLE {} DROP COLUMN {}",
2498 self.quote(table),
2499 self.quote(name)
2500 )
2501 }
2502 TableChange::ModifyColumn(col) => {
2503 let db2_type = map_to_db2_type(&col.sql_type);
2504 format!(
2505 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
2506 self.quote(table),
2507 self.quote(&col.name),
2508 db2_type
2509 )
2510 }
2511 TableChange::AddIndex(name, cols) => {
2512 format!(
2513 "CREATE INDEX {} ON {} ({})",
2514 name,
2515 self.quote(table),
2516 cols.join(", ")
2517 )
2518 }
2519 TableChange::DropIndex(name) => {
2520 format!("DROP INDEX {}", name)
2521 }
2522 TableChange::AddForeignKey {
2523 columns,
2524 reference_table,
2525 reference_columns,
2526 } => {
2527 format!(
2528 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
2529 self.quote(table),
2530 table,
2531 columns.join("_"),
2532 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
2533 self.quote(reference_table),
2534 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
2535 )
2536 }
2537 })
2538 .collect();
2539
2540 stmts.join("; ")
2541 }
2542
2543 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
2544 let _ = if_exists;
2546 format!("DROP TABLE {}", self.quote(table))
2547 }
2548}
2549
2550fn map_to_db2_type(sql_type: &str) -> String {
2559 let upper = sql_type.to_uppercase();
2560 let trimmed = upper.trim();
2561
2562 if trimmed.starts_with("BIGINT") {
2563 "BIGINT".to_string()
2564 } else if matches!(trimmed, "INT" | "INTEGER") {
2565 "INTEGER".to_string()
2566 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
2567 "SMALLINT".to_string()
2568 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2569 sql_type.to_string()
2570 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
2571 "CLOB(2G)".to_string()
2572 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2573 "SMALLINT".to_string()
2574 } else if matches!(trimmed, "FLOAT" | "REAL") {
2575 "REAL".to_string()
2576 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2577 "DOUBLE".to_string()
2578 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2579 "TIMESTAMP".to_string()
2580 } else if matches!(trimmed, "DATE") {
2581 "DATE".to_string()
2582 } else {
2583 sql_type.to_string()
2585 }
2586}
2587
2588pub fn get_dialect(db_type: DbType) -> Result<Box<dyn Dialect>, DbError> {
2611 match db_type {
2612 DbType::MySQL => Ok(Box::new(MySqlDialect)),
2613 DbType::PostgreSQL => Ok(Box::new(PostgreSqlDialect)),
2614 DbType::Sqlite => Ok(Box::new(SqliteDialect)),
2615 DbType::Redis => Err(DbError::Unsupported(
2616 "Redis does not support standard SQL dialect".to_string(),
2617 )),
2618 DbType::MongoDB => Err(DbError::Unsupported(
2619 "MongoDB uses different query syntax".to_string(),
2620 )),
2621 DbType::ClickHouse => Ok(Box::new(ClickHouseDialect)),
2622 DbType::Oracle => Ok(Box::new(OracleDialect)),
2623 DbType::OceanBase => Ok(Box::new(OceanBaseDialect)),
2624 DbType::SqlServer => Ok(Box::new(SqlServerDialect)),
2625 DbType::VectorDb => Err(DbError::Unsupported(
2626 "Vector databases have specific APIs".to_string(),
2627 )),
2628 DbType::PureJsDb => Err(DbError::Unsupported(
2629 "PureJS database uses JavaScript".to_string(),
2630 )),
2631 DbType::Dameng => Ok(Box::new(DamengDialect)),
2633 DbType::Kingbase => Ok(Box::new(KingbaseDialect)),
2634 DbType::Db2 => Ok(Box::new(Db2Dialect)),
2635 DbType::MariaDB => Ok(Box::new(MariaDbDialect)),
2636 DbType::TiDB => Ok(Box::new(TiDbDialect)),
2637 DbType::PolarDB => Ok(Box::new(PolarDbDialect)),
2638 DbType::GaussDB => Ok(Box::new(GaussDbDialect)),
2639 DbType::GBase => Ok(Box::new(GBaseDialect)),
2640 DbType::Sybase => Ok(Box::new(SybaseDialect)),
2641 DbType::DuckDB => Ok(Box::new(DuckDBDialect)),
2642 #[cfg(feature = "dialect-cockroachdb")]
2643 DbType::CockroachDB => Ok(Box::new(CockroachDbDialect)),
2644 #[cfg(feature = "dialect-yugabytedb")]
2645 DbType::YugabyteDB => Ok(Box::new(YugabyteDbDialect)),
2646 #[cfg(feature = "dialect-snowflake")]
2647 DbType::Snowflake => Ok(Box::new(SnowflakeDialect)),
2648 #[cfg(feature = "dialect-redshift")]
2649 DbType::Redshift => Ok(Box::new(RedshiftDialect)),
2650 #[cfg(feature = "dialect-informix")]
2651 DbType::Informix => Ok(Box::new(InformixDialect)),
2652 #[cfg(feature = "dialect-saphana")]
2653 DbType::SapHana => Ok(Box::new(SapHanaDialect)),
2654 #[cfg(feature = "dialect-firebird")]
2655 DbType::Firebird => Ok(Box::new(FirebirdDialect)),
2656 }
2657}
2658
2659impl fmt::Display for dyn Dialect {
2660 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2661 write!(f, "Dialect({})", self.db_type())
2662 }
2663}
2664
2665#[cfg(feature = "dialect-informix")]
2685#[derive(Debug, Clone)]
2686pub struct InformixDialect;
2687
2688#[cfg(feature = "dialect-informix")]
2689impl Dialect for InformixDialect {
2690 fn clone_box(&self) -> Box<dyn Dialect> {
2691 Box::new(InformixDialect)
2692 }
2693
2694 fn db_type(&self) -> DbType {
2695 DbType::Informix
2696 }
2697
2698 fn quote(&self, identifier: &str) -> String {
2699 format!("\"{}\"", identifier.replace('"', "\"\""))
2700 }
2701
2702 fn escape_string(&self, s: &str) -> String {
2703 let mut escaped = String::with_capacity(s.len() * 2);
2704 for c in s.chars() {
2705 match c {
2706 '\'' => escaped.push_str("''"),
2707 _ => escaped.push(c),
2708 }
2709 }
2710 escaped
2711 }
2712
2713 fn supports_returning(&self) -> bool {
2714 false
2715 }
2716
2717 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2718 let offset = page.saturating_sub(1).saturating_mul(limit);
2719 format!("{} SKIP {} FIRST {}", sql, offset, limit)
2720 }
2721
2722 fn json_type(&self) -> &'static str {
2723 "LVARCHAR"
2724 }
2725
2726 fn json_extract(&self, column: &str, path: &str) -> String {
2727 let normalized = if path.starts_with('$') {
2728 path.to_string()
2729 } else {
2730 format!("$.{}", path)
2731 };
2732 format!(
2733 "JSONExtract({}, '{}')",
2734 column,
2735 self.escape_string(&normalized)
2736 )
2737 }
2738
2739 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2740 if columns.is_empty() {
2741 return "0".to_string();
2742 }
2743 let escaped = self.escape_string(keyword);
2744 let parts: Vec<String> = columns
2745 .iter()
2746 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2747 .collect();
2748 parts.join(" OR ")
2749 }
2750
2751 fn bool_to_int(&self, expr: &str) -> String {
2752 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2753 }
2754
2755 fn concat(&self, parts: &[&str]) -> String {
2756 if parts.is_empty() {
2757 return "''".to_string();
2758 }
2759 parts.join(" || ")
2760 }
2761
2762 fn supports_if_exists(&self) -> bool {
2763 true
2764 }
2765
2766 fn supports_if_not_exists(&self) -> bool {
2767 true
2768 }
2769
2770 fn auto_increment_keyword(&self) -> &'static str {
2771 "SERIAL"
2772 }
2773
2774 fn last_insert_id_sql(&self) -> Option<&'static str> {
2775 Some("SELECT DBINFO('sqlca.sqlerrd1') FROM systables WHERE tabid = 1")
2776 }
2777
2778 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2779 let cols: Vec<String> = columns
2780 .iter()
2781 .map(|col| {
2782 let informix_type = map_to_informix_type(&col.sql_type);
2783 let mut sql = format!("{} {}", self.quote(&col.name), informix_type);
2784 if !col.nullable && !col.auto_increment {
2785 sql.push_str(" NOT NULL");
2786 }
2787 if let Some(default) = &col.default {
2788 sql.push_str(&format!(" DEFAULT {}", default));
2789 }
2790 if col.primary_key {
2791 sql.push_str(" PRIMARY KEY");
2792 }
2793 sql
2794 })
2795 .collect();
2796
2797 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2798 }
2799
2800 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2801 let stmts: Vec<String> = changes
2802 .iter()
2803 .map(|change| match change {
2804 TableChange::AddColumn(col) => {
2805 let informix_type = map_to_informix_type(&col.sql_type);
2806 let mut sql = format!(
2807 "ALTER TABLE {} ADD COLUMN {} {}",
2808 self.quote(table),
2809 self.quote(&col.name),
2810 informix_type
2811 );
2812 if !col.nullable {
2813 sql.push_str(" NOT NULL");
2814 }
2815 if let Some(default) = &col.default {
2816 sql.push_str(&format!(" DEFAULT {}", default));
2817 }
2818 sql
2819 }
2820 TableChange::DropColumn(name) => {
2821 format!(
2822 "ALTER TABLE {} DROP COLUMN {}",
2823 self.quote(table),
2824 self.quote(name)
2825 )
2826 }
2827 TableChange::ModifyColumn(col) => {
2828 let informix_type = map_to_informix_type(&col.sql_type);
2829 format!(
2830 "ALTER TABLE {} MODIFY {} {}",
2831 self.quote(table),
2832 self.quote(&col.name),
2833 informix_type
2834 )
2835 }
2836 TableChange::AddIndex(name, cols) => {
2837 format!(
2838 "CREATE INDEX {} ON {} ({})",
2839 name,
2840 self.quote(table),
2841 cols.join(", ")
2842 )
2843 }
2844 TableChange::DropIndex(name) => {
2845 format!("DROP INDEX {}", name)
2846 }
2847 TableChange::AddForeignKey {
2848 columns,
2849 reference_table,
2850 reference_columns,
2851 } => {
2852 format!(
2853 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
2854 self.quote(table),
2855 columns
2856 .iter()
2857 .map(|c| self.quote(c))
2858 .collect::<Vec<_>>()
2859 .join(", "),
2860 self.quote(reference_table),
2861 reference_columns
2862 .iter()
2863 .map(|c| self.quote(c))
2864 .collect::<Vec<_>>()
2865 .join(", ")
2866 )
2867 }
2868 })
2869 .collect();
2870
2871 stmts.join("; ")
2872 }
2873}
2874
2875#[cfg(feature = "dialect-informix")]
2877fn map_to_informix_type(sql_type: &str) -> String {
2878 let upper = sql_type.to_uppercase();
2879 let trimmed = upper.trim();
2880
2881 if trimmed.starts_with("BIGINT") {
2882 "BIGINT".to_string()
2883 } else if matches!(trimmed, "INT" | "INTEGER") {
2884 "INTEGER".to_string()
2885 } else if matches!(trimmed, "SMALLINT") {
2886 "SMALLINT".to_string()
2887 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2888 sql_type.to_string()
2889 } else if matches!(trimmed, "TEXT" | "LVARCHAR") {
2890 "LVARCHAR".to_string()
2891 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2892 "BOOLEAN".to_string()
2893 } else if matches!(trimmed, "FLOAT" | "REAL") {
2894 "SMALLFLOAT".to_string()
2895 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2896 "FLOAT".to_string()
2897 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2898 "DATETIME YEAR TO SECOND".to_string()
2899 } else if matches!(trimmed, "DATE") {
2900 "DATE".to_string()
2901 } else {
2902 sql_type.to_string()
2903 }
2904}
2905
2906#[cfg(feature = "dialect-saphana")]
2911#[derive(Debug, Clone)]
2912pub struct SapHanaDialect;
2913
2914#[cfg(feature = "dialect-saphana")]
2915impl Dialect for SapHanaDialect {
2916 fn clone_box(&self) -> Box<dyn Dialect> {
2917 Box::new(SapHanaDialect)
2918 }
2919
2920 fn db_type(&self) -> DbType {
2921 DbType::SapHana
2922 }
2923
2924 fn quote(&self, identifier: &str) -> String {
2925 format!("\"{}\"", identifier.replace('"', "\"\""))
2926 }
2927
2928 fn escape_string(&self, s: &str) -> String {
2929 let mut escaped = String::with_capacity(s.len() * 2);
2930 for c in s.chars() {
2931 match c {
2932 '\'' => escaped.push_str("''"),
2933 _ => escaped.push(c),
2934 }
2935 }
2936 escaped
2937 }
2938
2939 fn supports_returning(&self) -> bool {
2940 false
2941 }
2942
2943 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2944 let offset = page.saturating_sub(1).saturating_mul(limit);
2945 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
2946 }
2947
2948 fn json_type(&self) -> &'static str {
2949 "NCLOB"
2950 }
2951
2952 fn json_extract(&self, column: &str, path: &str) -> String {
2953 let normalized = if path.starts_with('$') {
2954 path.to_string()
2955 } else {
2956 format!("$.{}", path)
2957 };
2958 format!(
2959 "JSON_VALUE({}, '{}')",
2960 column,
2961 self.escape_string(&normalized)
2962 )
2963 }
2964
2965 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2966 if columns.is_empty() {
2967 return "0".to_string();
2968 }
2969 let escaped = self.escape_string(keyword);
2970 let parts: Vec<String> = columns
2971 .iter()
2972 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2973 .collect();
2974 parts.join(" OR ")
2975 }
2976
2977 fn bool_to_int(&self, expr: &str) -> String {
2978 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2979 }
2980
2981 fn concat(&self, parts: &[&str]) -> String {
2982 if parts.is_empty() {
2983 return "''".to_string();
2984 }
2985 parts.join(" || ")
2986 }
2987
2988 fn supports_if_exists(&self) -> bool {
2989 true
2990 }
2991
2992 fn supports_if_not_exists(&self) -> bool {
2993 true
2994 }
2995
2996 fn auto_increment_keyword(&self) -> &'static str {
2997 "GENERATED BY DEFAULT AS IDENTITY"
2998 }
2999
3000 fn last_insert_id_sql(&self) -> Option<&'static str> {
3001 None
3002 }
3003
3004 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
3005 let cols: Vec<String> = columns
3006 .iter()
3007 .map(|col| {
3008 let hana_type = map_to_saphana_type(&col.sql_type);
3009 let mut sql = format!("{} {}", self.quote(&col.name), hana_type);
3010 if !col.nullable && !col.auto_increment {
3011 sql.push_str(" NOT NULL");
3012 }
3013 if let Some(default) = &col.default {
3014 sql.push_str(&format!(" DEFAULT {}", default));
3015 }
3016 if col.auto_increment {
3017 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
3018 }
3019 if col.primary_key {
3020 sql.push_str(" PRIMARY KEY");
3021 }
3022 sql
3023 })
3024 .collect();
3025
3026 format!(
3027 "CREATE COLUMN TABLE {} ({})",
3028 self.quote(table),
3029 cols.join(", ")
3030 )
3031 }
3032
3033 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
3034 let stmts: Vec<String> = changes
3035 .iter()
3036 .map(|change| match change {
3037 TableChange::AddColumn(col) => {
3038 let hana_type = map_to_saphana_type(&col.sql_type);
3039 let mut sql = format!(
3040 "ALTER TABLE {} ADD ({} {})",
3041 self.quote(table),
3042 self.quote(&col.name),
3043 hana_type
3044 );
3045 if !col.nullable {
3046 sql.push_str(" NOT NULL");
3047 }
3048 if let Some(default) = &col.default {
3049 sql.push_str(&format!(" DEFAULT {}", default));
3050 }
3051 sql
3052 }
3053 TableChange::DropColumn(name) => {
3054 format!(
3055 "ALTER TABLE {} DROP ({})",
3056 self.quote(table),
3057 self.quote(name)
3058 )
3059 }
3060 TableChange::ModifyColumn(col) => {
3061 let hana_type = map_to_saphana_type(&col.sql_type);
3062 format!(
3063 "ALTER TABLE {} ALTER ({} {})",
3064 self.quote(table),
3065 self.quote(&col.name),
3066 hana_type
3067 )
3068 }
3069 TableChange::AddIndex(name, cols) => {
3070 format!(
3071 "CREATE INDEX {} ON {} ({})",
3072 name,
3073 self.quote(table),
3074 cols.join(", ")
3075 )
3076 }
3077 TableChange::DropIndex(name) => {
3078 format!("DROP INDEX {}", name)
3079 }
3080 TableChange::AddForeignKey {
3081 columns,
3082 reference_table,
3083 reference_columns,
3084 } => {
3085 format!(
3086 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
3087 self.quote(table),
3088 columns
3089 .iter()
3090 .map(|c| self.quote(c))
3091 .collect::<Vec<_>>()
3092 .join(", "),
3093 self.quote(reference_table),
3094 reference_columns
3095 .iter()
3096 .map(|c| self.quote(c))
3097 .collect::<Vec<_>>()
3098 .join(", ")
3099 )
3100 }
3101 })
3102 .collect();
3103
3104 stmts.join("; ")
3105 }
3106}
3107
3108#[cfg(feature = "dialect-saphana")]
3110fn map_to_saphana_type(sql_type: &str) -> String {
3111 let upper = sql_type.to_uppercase();
3112 let trimmed = upper.trim();
3113
3114 if trimmed.starts_with("BIGINT") {
3115 "BIGINT".to_string()
3116 } else if matches!(trimmed, "INT" | "INTEGER") {
3117 "INTEGER".to_string()
3118 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
3119 "SMALLINT".to_string()
3120 } else if trimmed.starts_with("VARCHAR")
3121 || trimmed.starts_with("NVARCHAR")
3122 || trimmed.starts_with("CHAR")
3123 || trimmed.starts_with("NCHAR")
3124 {
3125 sql_type.to_string()
3126 } else if matches!(trimmed, "TEXT" | "CLOB") {
3127 "NCLOB".to_string()
3128 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3129 "BOOLEAN".to_string()
3130 } else if matches!(trimmed, "FLOAT" | "REAL") {
3131 "REAL".to_string()
3132 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3133 "DOUBLE".to_string()
3134 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3135 "TIMESTAMP".to_string()
3136 } else if matches!(trimmed, "DATE") {
3137 "DATE".to_string()
3138 } else {
3139 sql_type.to_string()
3140 }
3141}
3142
3143#[cfg(feature = "dialect-firebird")]
3148#[derive(Debug, Clone)]
3149pub struct FirebirdDialect;
3150
3151#[cfg(feature = "dialect-firebird")]
3152impl Dialect for FirebirdDialect {
3153 fn clone_box(&self) -> Box<dyn Dialect> {
3154 Box::new(FirebirdDialect)
3155 }
3156
3157 fn db_type(&self) -> DbType {
3158 DbType::Firebird
3159 }
3160
3161 fn quote(&self, identifier: &str) -> String {
3162 format!("\"{}\"", identifier.replace('"', "\"\""))
3163 }
3164
3165 fn escape_string(&self, s: &str) -> String {
3166 let mut escaped = String::with_capacity(s.len() * 2);
3167 for c in s.chars() {
3168 match c {
3169 '\'' => escaped.push_str("''"),
3170 _ => escaped.push(c),
3171 }
3172 }
3173 escaped
3174 }
3175
3176 fn supports_returning(&self) -> bool {
3177 true
3178 }
3179
3180 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
3181 let offset = page.saturating_sub(1).saturating_mul(limit);
3182 let to = offset + limit;
3183 format!("{} ROWS {} TO {}", sql, offset + 1, to)
3184 }
3185
3186 fn json_type(&self) -> &'static str {
3187 "BLOB SUB_TYPE TEXT"
3188 }
3189
3190 fn json_extract(&self, column: &str, path: &str) -> String {
3191 let normalized = if path.starts_with('$') {
3192 path.to_string()
3193 } else {
3194 format!("$.{}", path)
3195 };
3196 format!(
3197 "JSON_GET({}, '{}')",
3198 column,
3199 self.escape_string(&normalized)
3200 )
3201 }
3202
3203 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
3204 if columns.is_empty() {
3205 return "0".to_string();
3206 }
3207 let escaped = self.escape_string(keyword);
3208 let parts: Vec<String> = columns
3209 .iter()
3210 .map(|c| format!("CONTAINING({}, '{}')", c, escaped))
3211 .collect();
3212 parts.join(" OR ")
3213 }
3214
3215 fn bool_to_int(&self, expr: &str) -> String {
3216 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
3217 }
3218
3219 fn concat(&self, parts: &[&str]) -> String {
3220 if parts.is_empty() {
3221 return "''".to_string();
3222 }
3223 parts.join(" || ")
3224 }
3225
3226 fn supports_if_exists(&self) -> bool {
3227 true
3228 }
3229
3230 fn supports_if_not_exists(&self) -> bool {
3231 true
3232 }
3233
3234 fn auto_increment_keyword(&self) -> &'static str {
3235 "GENERATED BY DEFAULT AS IDENTITY"
3236 }
3237
3238 fn last_insert_id_sql(&self) -> Option<&'static str> {
3239 Some("SELECT GEN_ID(SQLITE_SEQUENCE, 0) FROM RDB$DATABASE")
3240 }
3241
3242 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
3243 let cols: Vec<String> = columns
3244 .iter()
3245 .map(|col| {
3246 let fb_type = map_to_firebird_type(&col.sql_type);
3247 let mut sql = format!("{} {}", self.quote(&col.name), fb_type);
3248 if !col.nullable && !col.auto_increment {
3249 sql.push_str(" NOT NULL");
3250 }
3251 if let Some(default) = &col.default {
3252 sql.push_str(&format!(" DEFAULT {}", default));
3253 }
3254 if col.auto_increment {
3255 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
3256 }
3257 if col.primary_key {
3258 sql.push_str(" PRIMARY KEY");
3259 }
3260 sql
3261 })
3262 .collect();
3263
3264 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
3265 }
3266
3267 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
3268 let stmts: Vec<String> = changes
3269 .iter()
3270 .map(|change| match change {
3271 TableChange::AddColumn(col) => {
3272 let fb_type = map_to_firebird_type(&col.sql_type);
3273 let mut sql = format!(
3274 "ALTER TABLE {} ADD {} {}",
3275 self.quote(table),
3276 self.quote(&col.name),
3277 fb_type
3278 );
3279 if !col.nullable {
3280 sql.push_str(" NOT NULL");
3281 }
3282 if let Some(default) = &col.default {
3283 sql.push_str(&format!(" DEFAULT {}", default));
3284 }
3285 sql
3286 }
3287 TableChange::DropColumn(name) => {
3288 format!(
3289 "ALTER TABLE {} DROP {}",
3290 self.quote(table),
3291 self.quote(name)
3292 )
3293 }
3294 TableChange::ModifyColumn(col) => {
3295 let fb_type = map_to_firebird_type(&col.sql_type);
3296 format!(
3297 "ALTER TABLE {} ALTER COLUMN {} TYPE {}",
3298 self.quote(table),
3299 self.quote(&col.name),
3300 fb_type
3301 )
3302 }
3303 TableChange::AddIndex(name, cols) => {
3304 format!(
3305 "CREATE INDEX {} ON {} ({})",
3306 name,
3307 self.quote(table),
3308 cols.join(", ")
3309 )
3310 }
3311 TableChange::DropIndex(name) => {
3312 format!("DROP INDEX {}", name)
3313 }
3314 TableChange::AddForeignKey {
3315 columns,
3316 reference_table,
3317 reference_columns,
3318 } => {
3319 format!(
3320 "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
3321 self.quote(table),
3322 columns
3323 .iter()
3324 .map(|c| self.quote(c))
3325 .collect::<Vec<_>>()
3326 .join(", "),
3327 self.quote(reference_table),
3328 reference_columns
3329 .iter()
3330 .map(|c| self.quote(c))
3331 .collect::<Vec<_>>()
3332 .join(", ")
3333 )
3334 }
3335 })
3336 .collect();
3337
3338 stmts.join("; ")
3339 }
3340}
3341
3342#[cfg(feature = "dialect-firebird")]
3344fn map_to_firebird_type(sql_type: &str) -> String {
3345 let upper = sql_type.to_uppercase();
3346 let trimmed = upper.trim();
3347
3348 if trimmed.starts_with("BIGINT") {
3349 "BIGINT".to_string()
3350 } else if matches!(trimmed, "INT" | "INTEGER") {
3351 "INTEGER".to_string()
3352 } else if matches!(trimmed, "SMALLINT") {
3353 "SMALLINT".to_string()
3354 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
3355 sql_type.to_string()
3356 } else if matches!(trimmed, "TEXT" | "BLOB") {
3357 "BLOB SUB_TYPE TEXT".to_string()
3358 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3359 "BOOLEAN".to_string()
3360 } else if matches!(trimmed, "FLOAT" | "REAL") {
3361 "FLOAT".to_string()
3362 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3363 "DOUBLE PRECISION".to_string()
3364 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3365 "TIMESTAMP".to_string()
3366 } else if matches!(trimmed, "DATE") {
3367 "DATE".to_string()
3368 } else {
3369 sql_type.to_string()
3370 }
3371}
3372
3373#[cfg(test)]
3374mod tests {
3375 use super::*;
3376
3377 #[test]
3378 fn test_mysql_quote() {
3379 let dialect = MySqlDialect;
3380 assert_eq!(dialect.quote("users"), "`users`");
3381 assert_eq!(dialect.quote("user`id"), "`user``id`");
3382 }
3383
3384 #[test]
3385 fn test_mysql_escape() {
3386 let dialect = MySqlDialect;
3387 assert_eq!(dialect.escape_string("hello"), "hello");
3388 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3389 assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
3390 }
3391
3392 #[test]
3393 fn test_mysql_pagination() {
3394 let dialect = MySqlDialect;
3395 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3396 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
3397 }
3398
3399 #[test]
3400 fn test_postgres_quote() {
3401 let dialect = PostgreSqlDialect;
3402 assert_eq!(dialect.quote("users"), "\"users\"");
3403 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3404 }
3405
3406 #[test]
3407 fn test_postgres_pagination() {
3408 let dialect = PostgreSqlDialect;
3409 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3410 assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
3411 }
3412
3413 #[test]
3414 fn test_postgres_returning() {
3415 let dialect = PostgreSqlDialect;
3416 assert!(dialect.supports_returning());
3417 }
3418
3419 #[test]
3420 fn test_sqlite_quote() {
3421 let dialect = SqliteDialect;
3422 assert_eq!(dialect.quote("users"), "\"users\"");
3423 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3424 }
3425
3426 #[test]
3427 fn test_sqlite_escape() {
3428 let dialect = SqliteDialect;
3429 assert_eq!(dialect.escape_string("hello"), "hello");
3430 assert_eq!(dialect.escape_string("it's"), "it''s");
3431 }
3432
3433 #[test]
3434 fn test_get_dialect() {
3435 let dialect = get_dialect(DbType::MySQL);
3436 assert!(dialect.is_ok());
3437
3438 let dialect = get_dialect(DbType::Redis);
3439 assert!(dialect.is_err());
3440 }
3441
3442 #[test]
3443 fn test_bool_to_int() {
3444 let mysql = MySqlDialect;
3445 assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
3446
3447 let pg = PostgreSqlDialect;
3448 assert_eq!(
3449 pg.bool_to_int("active"),
3450 "(CASE WHEN active THEN 1 ELSE 0 END)"
3451 );
3452 }
3453
3454 #[test]
3455 fn test_json_extract_with_path() {
3456 let mysql = MySqlDialect;
3457 let sql = mysql.json_extract("data", "$.user.name");
3458 assert!(sql.contains("$.user.name"));
3459 assert!(sql.contains("JSON_EXTRACT"));
3460
3461 let pg = PostgreSqlDialect;
3462 let sql = pg.json_extract("data", "user.name");
3463 assert!(sql.contains("#>>"));
3464
3465 let sqlite = SqliteDialect;
3466 let sql = sqlite.json_extract("data", "$.user.name");
3467 assert!(sql.contains("$.user.name"));
3468 assert!(sql.contains("json_extract"));
3469 }
3470
3471 #[test]
3472 fn test_sqlite_full_text_search() {
3473 let sqlite = SqliteDialect;
3474 let sql = sqlite.full_text_search(&["title", "content"], "hello");
3475 assert!(sql.contains("LIKE"));
3477 assert!(sql.contains("title LIKE '%hello%'"));
3478 assert!(sql.contains("content LIKE '%hello%'"));
3479 assert!(sql.contains(" OR "));
3480
3481 assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
3483
3484 let sql = sqlite.full_text_search(&["title"], "it's");
3486 assert!(sql.contains("title LIKE '%it''s%'"));
3487 }
3488
3489 #[test]
3490 fn test_alter_table_modify_column() {
3491 let mysql = MySqlDialect;
3492 let col = ColumnDef {
3493 name: "name".to_string(),
3494 sql_type: "VARCHAR(255)".to_string(),
3495 nullable: false,
3496 default: None,
3497 auto_increment: false,
3498 primary_key: false,
3499 };
3500 let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3501 assert!(sql.contains("MODIFY COLUMN"));
3502
3503 let pg = PostgreSqlDialect;
3504 let col = ColumnDef {
3505 name: "name".to_string(),
3506 sql_type: "VARCHAR(255)".to_string(),
3507 nullable: false,
3508 default: None,
3509 auto_increment: false,
3510 primary_key: false,
3511 };
3512 let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3513 assert!(sql.contains("ALTER COLUMN"));
3514 assert!(sql.contains("TYPE"));
3515 }
3516
3517 #[test]
3518 fn test_alter_table_add_foreign_key() {
3519 let mysql = MySqlDialect;
3520 let sql = mysql.build_alter_table(
3521 "orders",
3522 &[TableChange::AddForeignKey {
3523 columns: vec!["user_id".to_string()],
3524 reference_table: "users".to_string(),
3525 reference_columns: vec!["id".to_string()],
3526 }],
3527 );
3528 assert!(sql.contains("FOREIGN KEY"));
3529 assert!(sql.contains("REFERENCES"));
3530
3531 let sqlite = SqliteDialect;
3532 let sql = sqlite.build_alter_table(
3533 "orders",
3534 &[TableChange::AddForeignKey {
3535 columns: vec!["user_id".to_string()],
3536 reference_table: "users".to_string(),
3537 reference_columns: vec!["id".to_string()],
3538 }],
3539 );
3540 assert!(sql.starts_with("--"));
3542 }
3543
3544 #[test]
3545 fn test_sqlite_alter_table_add_column() {
3546 let sqlite = SqliteDialect;
3547 let col = ColumnDef {
3548 name: "email".to_string(),
3549 sql_type: "TEXT".to_string(),
3550 nullable: true,
3551 default: None,
3552 auto_increment: false,
3553 primary_key: false,
3554 };
3555 let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
3556 assert!(sql.contains("ADD COLUMN"));
3557 assert!(sql.contains("email"));
3558 }
3559
3560 #[test]
3563 fn test_oracle_quote_and_escape() {
3564 let dialect = OracleDialect;
3565 assert_eq!(dialect.quote("users"), "\"users\"");
3567 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3568 assert_eq!(dialect.quote("column_name"), "\"column_name\"");
3569
3570 assert_eq!(dialect.escape_string("hello"), "hello");
3572 assert_eq!(dialect.escape_string("it's"), "it''s");
3573 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3574 assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
3575 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3577 }
3578
3579 #[test]
3580 fn test_oracle_pagination() {
3581 let dialect = OracleDialect;
3582 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3584 assert_eq!(
3585 sql,
3586 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3587 );
3588 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3590 assert_eq!(
3591 sql,
3592 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3593 );
3594 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3596 assert_eq!(
3597 sql,
3598 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3599 );
3600 }
3601
3602 #[test]
3603 fn test_oracle_json_extract() {
3604 let dialect = OracleDialect;
3605 let sql = dialect.json_extract("data", "$.user.name");
3607 assert!(sql.contains("JSON_VALUE"));
3608 assert!(sql.contains("$.user.name"));
3609 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3610
3611 let sql = dialect.json_extract("data", "user.name");
3613 assert!(sql.contains("$.user.name"));
3614 assert!(sql.contains("JSON_VALUE"));
3615
3616 let sql = dialect.json_extract("data", "$.key's");
3618 assert!(sql.contains("$.key''s"));
3619 }
3620
3621 #[test]
3622 fn test_oracle_create_table() {
3623 let dialect = OracleDialect;
3624 let columns = vec![
3625 ColumnDef {
3626 name: "id".to_string(),
3627 sql_type: "BIGINT".to_string(),
3628 nullable: false,
3629 default: None,
3630 auto_increment: true,
3631 primary_key: true,
3632 },
3633 ColumnDef {
3634 name: "name".to_string(),
3635 sql_type: "VARCHAR(255)".to_string(),
3636 nullable: false,
3637 default: None,
3638 auto_increment: false,
3639 primary_key: false,
3640 },
3641 ColumnDef {
3642 name: "bio".to_string(),
3643 sql_type: "TEXT".to_string(),
3644 nullable: true,
3645 default: None,
3646 auto_increment: false,
3647 primary_key: false,
3648 },
3649 ColumnDef {
3650 name: "is_active".to_string(),
3651 sql_type: "BOOLEAN".to_string(),
3652 nullable: false,
3653 default: Some("1".to_string()),
3654 auto_increment: false,
3655 primary_key: false,
3656 },
3657 ];
3658 let sql = dialect.build_create_table("users", &columns);
3659 assert!(
3661 sql.contains("NUMBER(19)"),
3662 "BIGINT should map to NUMBER(19): {}",
3663 sql
3664 );
3665 assert!(
3666 sql.contains("VARCHAR2(255)"),
3667 "VARCHAR should map to VARCHAR2: {}",
3668 sql
3669 );
3670 assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
3671 assert!(
3672 sql.contains("NUMBER(1)"),
3673 "BOOLEAN should map to NUMBER(1): {}",
3674 sql
3675 );
3676 assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
3678 assert!(sql.contains("PRIMARY KEY"));
3679 assert!(sql.contains("NOT NULL"));
3680 assert!(sql.contains("DEFAULT 1"));
3681 assert!(sql.contains("\"users\""));
3683 assert!(sql.contains("\"id\""));
3684 }
3685
3686 #[test]
3687 fn test_oracle_bool_to_int_and_concat() {
3688 let dialect = OracleDialect;
3689 assert_eq!(
3691 dialect.bool_to_int("active"),
3692 "(CASE WHEN active THEN 1 ELSE 0 END)"
3693 );
3694 assert_eq!(
3695 dialect.bool_to_int("x > 0"),
3696 "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
3697 );
3698 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
3700 assert_eq!(
3701 dialect.concat(&["first_name", "last_name"]),
3702 "first_name || last_name"
3703 );
3704 assert_eq!(dialect.concat(&[]), "NULL");
3706 }
3707
3708 #[test]
3709 fn test_oracle_misc_dialect_methods() {
3710 let dialect = OracleDialect;
3711 assert_eq!(dialect.db_type(), DbType::Oracle);
3713 assert!(dialect.supports_returning());
3715 assert!(dialect.supports_if_exists());
3717 assert!(dialect.supports_if_not_exists());
3718 assert_eq!(
3720 dialect.auto_increment_keyword(),
3721 "GENERATED BY DEFAULT AS IDENTITY"
3722 );
3723 assert_eq!(dialect.last_insert_id_sql(), None);
3726 assert_eq!(dialect.json_type(), "JSON");
3728 }
3729
3730 #[test]
3731 fn test_oracle_get_dialect() {
3732 let dialect = get_dialect(DbType::Oracle);
3734 assert!(dialect.is_ok(), "Oracle dialect should be available");
3735 let dialect = dialect.unwrap();
3736 assert_eq!(dialect.db_type(), DbType::Oracle);
3737 assert_eq!(dialect.quote("users"), "\"users\"");
3739 assert!(dialect.supports_returning());
3741 assert_eq!(dialect.last_insert_id_sql(), None);
3743 }
3744
3745 #[test]
3746 fn test_oracle_drop_table() {
3747 let dialect = OracleDialect;
3748 let sql = dialect.build_drop_table("users", true);
3750 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
3751 let sql = dialect.build_drop_table("users", false);
3753 assert_eq!(sql, "DROP TABLE \"users\"");
3754 }
3755
3756 #[test]
3757 fn test_oracle_alter_table() {
3758 let dialect = OracleDialect;
3759 let col = ColumnDef {
3761 name: "name".to_string(),
3762 sql_type: "VARCHAR(255)".to_string(),
3763 nullable: false,
3764 default: None,
3765 auto_increment: false,
3766 primary_key: false,
3767 };
3768 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3769 assert!(sql.contains("MODIFY"));
3770 assert!(sql.contains("VARCHAR2(255)"));
3771 assert!(!sql.contains("MODIFY COLUMN")); let col = ColumnDef {
3775 name: "email".to_string(),
3776 sql_type: "VARCHAR(255)".to_string(),
3777 nullable: true,
3778 default: None,
3779 auto_increment: false,
3780 primary_key: false,
3781 };
3782 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
3783 assert!(sql.contains("ADD \"email\""));
3784 assert!(sql.contains("VARCHAR2(255)"));
3785
3786 let sql =
3788 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
3789 assert!(sql.contains("DROP COLUMN"));
3790 assert!(sql.contains("\"email\""));
3791 }
3792
3793 #[test]
3796 fn test_sqlite_concat_handles_null() {
3797 let sqlite = SqliteDialect;
3798 let sql = sqlite.concat(&["a", "b"]);
3801 assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
3802 let sql = sqlite.concat(&["a"]);
3804 assert_eq!(sql, "COALESCE(a, '')");
3805 assert_eq!(sqlite.concat(&[]), "NULL");
3807 }
3808
3809 #[test]
3812 fn test_sqlserver_quote_and_escape() {
3813 let dialect = SqlServerDialect;
3814 assert_eq!(dialect.quote("users"), "[users]");
3816 assert_eq!(dialect.quote("col]name"), "[col]]name]");
3817 assert_eq!(dialect.escape_string("hello"), "hello");
3819 assert_eq!(dialect.escape_string("it's"), "it''s");
3820 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3821 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3822 }
3823
3824 #[test]
3825 fn test_sqlserver_pagination() {
3826 let dialect = SqlServerDialect;
3827 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3829 assert_eq!(
3830 sql,
3831 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3832 );
3833 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3834 assert_eq!(
3835 sql,
3836 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3837 );
3838 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3840 assert_eq!(
3841 sql,
3842 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3843 );
3844 }
3845
3846 #[test]
3847 fn test_sqlserver_misc_dialect_methods() {
3848 let dialect = SqlServerDialect;
3849 assert_eq!(dialect.db_type(), DbType::SqlServer);
3850 assert!(dialect.supports_returning());
3852 assert!(dialect.supports_if_exists());
3854 assert!(dialect.supports_if_not_exists());
3855 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
3857 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
3859 assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
3861 }
3862
3863 #[test]
3864 fn test_sqlserver_insert_or_ignore_fallback_to_plain_insert() {
3865 let dialect = SqlServerDialect;
3866 let sql = dialect.build_insert_or_ignore_prefix("users");
3867 assert_eq!(sql, "INSERT INTO [users]");
3869 assert!(!sql.contains("OR IGNORE"));
3870 }
3871
3872 #[test]
3873 fn test_sqlserver_json_extract() {
3874 let dialect = SqlServerDialect;
3875 let sql = dialect.json_extract("data", "$.user.name");
3876 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3877 let sql = dialect.json_extract("data", "user.name");
3879 assert!(sql.contains("$.user.name"));
3880 assert!(sql.contains("JSON_VALUE"));
3881 let sql = dialect.json_extract("data", "$.key's");
3883 assert!(sql.contains("$.key''s"));
3884 }
3885
3886 #[test]
3887 fn test_sqlserver_full_text_search() {
3888 let dialect = SqlServerDialect;
3889 let sql = dialect.full_text_search(&["title", "content"], "hello");
3890 assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
3891 assert_eq!(dialect.full_text_search(&[], "hello"), "0");
3893 let sql = dialect.full_text_search(&["title"], "it's");
3895 assert!(sql.contains("it''s"));
3896 }
3897
3898 #[test]
3899 fn test_sqlserver_bool_to_int_and_concat() {
3900 let dialect = SqlServerDialect;
3901 assert_eq!(
3902 dialect.bool_to_int("active"),
3903 "(CASE WHEN active THEN 1 ELSE 0 END)"
3904 );
3905 assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
3906 assert_eq!(dialect.concat(&[]), "NULL");
3907 }
3908
3909 #[test]
3910 fn test_sqlserver_create_table() {
3911 let dialect = SqlServerDialect;
3912 let columns = vec![
3913 ColumnDef {
3914 name: "id".to_string(),
3915 sql_type: "BIGINT".to_string(),
3916 nullable: false,
3917 default: None,
3918 auto_increment: true,
3919 primary_key: true,
3920 },
3921 ColumnDef {
3922 name: "name".to_string(),
3923 sql_type: "VARCHAR(255)".to_string(),
3924 nullable: false,
3925 default: None,
3926 auto_increment: false,
3927 primary_key: false,
3928 },
3929 ColumnDef {
3930 name: "bio".to_string(),
3931 sql_type: "TEXT".to_string(),
3932 nullable: true,
3933 default: None,
3934 auto_increment: false,
3935 primary_key: false,
3936 },
3937 ColumnDef {
3938 name: "is_active".to_string(),
3939 sql_type: "BOOLEAN".to_string(),
3940 nullable: false,
3941 default: Some("1".to_string()),
3942 auto_increment: false,
3943 primary_key: false,
3944 },
3945 ];
3946 let sql = dialect.build_create_table("users", &columns);
3947 assert!(sql.contains("[users]"));
3949 assert!(sql.contains("[id]"));
3950 assert!(sql.contains("IDENTITY(1,1)"));
3952 assert!(
3953 sql.contains("NVARCHAR(255)"),
3954 "VARCHAR should map to NVARCHAR: {}",
3955 sql
3956 );
3957 assert!(
3958 sql.contains("NVARCHAR(MAX)"),
3959 "TEXT should map to NVARCHAR(MAX): {}",
3960 sql
3961 );
3962 assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
3963 assert!(sql.contains("PRIMARY KEY"));
3964 assert!(sql.contains("NOT NULL"));
3965 assert!(sql.contains("DEFAULT 1"));
3966 }
3967
3968 #[test]
3969 fn test_sqlserver_drop_table() {
3970 let dialect = SqlServerDialect;
3971 assert_eq!(
3972 dialect.build_drop_table("users", true),
3973 "DROP TABLE IF EXISTS [users]"
3974 );
3975 assert_eq!(
3976 dialect.build_drop_table("users", false),
3977 "DROP TABLE [users]"
3978 );
3979 }
3980
3981 #[test]
3982 fn test_sqlserver_alter_table() {
3983 let dialect = SqlServerDialect;
3984 let col = ColumnDef {
3986 name: "name".to_string(),
3987 sql_type: "VARCHAR(255)".to_string(),
3988 nullable: false,
3989 default: None,
3990 auto_increment: false,
3991 primary_key: false,
3992 };
3993 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3994 assert!(sql.contains("ALTER COLUMN"));
3995 assert!(sql.contains("NVARCHAR(255)"));
3996 assert!(!sql.contains("MODIFY"));
3997
3998 let col = ColumnDef {
4000 name: "email".to_string(),
4001 sql_type: "VARCHAR(255)".to_string(),
4002 nullable: true,
4003 default: None,
4004 auto_increment: false,
4005 primary_key: false,
4006 };
4007 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
4008 assert!(sql.contains("ADD [email]"));
4009 assert!(sql.contains("NVARCHAR(255)"));
4010
4011 let sql =
4013 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
4014 assert!(sql.contains("DROP COLUMN"));
4015 assert!(sql.contains("[email]"));
4016
4017 let sql =
4019 dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
4020 assert!(sql.contains("DROP INDEX idx_name ON [users]"));
4021 }
4022
4023 #[test]
4024 fn test_sqlserver_get_dialect() {
4025 let dialect = get_dialect(DbType::SqlServer);
4027 assert!(dialect.is_ok(), "SqlServer dialect should be available");
4028 let dialect = dialect.unwrap();
4029 assert_eq!(dialect.db_type(), DbType::SqlServer);
4030 assert_eq!(dialect.quote("users"), "[users]");
4032 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
4034 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
4035 }
4036
4037 #[test]
4038 fn test_clickhouse_get_dialect_unsupported() {
4039 let dialect = get_dialect(DbType::ClickHouse);
4041 assert!(dialect.is_ok(), "ClickHouse should be supported");
4042 let dialect = dialect.unwrap();
4043 assert_eq!(dialect.db_type(), DbType::ClickHouse);
4044 assert_eq!(dialect.quote("users"), "`users`");
4046 assert!(!dialect.supports_returning());
4048 let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
4050 assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
4051 assert_eq!(dialect.auto_increment_keyword(), "");
4053 }
4054
4055 #[test]
4056 fn test_get_dialect_all_supported_types() {
4057 assert!(get_dialect(DbType::MySQL).is_ok());
4059 assert!(get_dialect(DbType::PostgreSQL).is_ok());
4060 assert!(get_dialect(DbType::Sqlite).is_ok());
4061 assert!(get_dialect(DbType::Oracle).is_ok());
4062 assert!(get_dialect(DbType::SqlServer).is_ok());
4063 assert!(get_dialect(DbType::OceanBase).is_ok());
4064 assert!(get_dialect(DbType::ClickHouse).is_ok());
4065 assert!(get_dialect(DbType::Dameng).is_ok());
4067 assert!(get_dialect(DbType::Kingbase).is_ok());
4068 assert!(get_dialect(DbType::Db2).is_ok());
4069 assert!(get_dialect(DbType::MariaDB).is_ok());
4070 assert!(get_dialect(DbType::TiDB).is_ok());
4071 assert!(get_dialect(DbType::PolarDB).is_ok());
4072 assert!(get_dialect(DbType::GaussDB).is_ok());
4073 assert!(get_dialect(DbType::GBase).is_ok());
4074 assert!(get_dialect(DbType::Sybase).is_ok());
4075 assert!(get_dialect(DbType::Redis).is_err());
4077 assert!(get_dialect(DbType::MongoDB).is_err());
4078 assert!(get_dialect(DbType::VectorDb).is_err());
4079 assert!(get_dialect(DbType::PureJsDb).is_err());
4080 }
4081
4082 #[test]
4085 fn test_mariadb_dialect() {
4086 let dialect = get_dialect(DbType::MariaDB).unwrap();
4087 assert_eq!(dialect.db_type(), DbType::MariaDB);
4088 assert_eq!(dialect.quote("users"), "`users`");
4090 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4091 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4092 assert!(!dialect.supports_returning());
4094 }
4095
4096 #[test]
4097 fn test_tidb_dialect() {
4098 let dialect = get_dialect(DbType::TiDB).unwrap();
4099 assert_eq!(dialect.db_type(), DbType::TiDB);
4100 assert_eq!(dialect.quote("users"), "`users`");
4102 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4103 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4104 }
4105
4106 #[test]
4107 fn test_dameng_dialect() {
4108 let dialect = get_dialect(DbType::Dameng).unwrap();
4109 assert_eq!(dialect.db_type(), DbType::Dameng);
4110 assert_eq!(dialect.quote("users"), "\"users\"");
4112 assert_eq!(dialect.escape_string("it's"), "it''s");
4113 assert_eq!(
4115 dialect.auto_increment_keyword(),
4116 "GENERATED BY DEFAULT AS IDENTITY"
4117 );
4118 assert!(dialect.supports_returning());
4120 }
4121
4122 #[test]
4123 fn test_kingbase_dialect() {
4124 let dialect = get_dialect(DbType::Kingbase).unwrap();
4125 assert_eq!(dialect.db_type(), DbType::Kingbase);
4126 assert_eq!(dialect.quote("users"), "\"users\"");
4128 assert_eq!(dialect.escape_string("it's"), "it''s");
4129 assert!(dialect.supports_returning());
4131 assert_eq!(
4133 dialect.auto_increment_keyword(),
4134 "GENERATED BY DEFAULT AS IDENTITY"
4135 );
4136 }
4137
4138 #[test]
4139 fn test_polardb_dialect() {
4140 let dialect = get_dialect(DbType::PolarDB).unwrap();
4141 assert_eq!(dialect.db_type(), DbType::PolarDB);
4142 assert_eq!(dialect.quote("users"), "\"users\"");
4144 assert!(dialect.supports_returning());
4145 }
4146
4147 #[test]
4148 fn test_gaussdb_dialect() {
4149 let dialect = get_dialect(DbType::GaussDB).unwrap();
4150 assert_eq!(dialect.db_type(), DbType::GaussDB);
4151 assert_eq!(dialect.quote("users"), "\"users\"");
4153 assert!(dialect.supports_returning());
4154 }
4155
4156 #[test]
4157 fn test_gbase_dialect() {
4158 let dialect = get_dialect(DbType::GBase).unwrap();
4159 assert_eq!(dialect.db_type(), DbType::GBase);
4160 assert_eq!(dialect.quote("users"), "[users]");
4162 }
4163
4164 #[test]
4165 fn test_sybase_dialect() {
4166 let dialect = get_dialect(DbType::Sybase).unwrap();
4167 assert_eq!(dialect.db_type(), DbType::Sybase);
4168 assert_eq!(dialect.quote("users"), "[users]");
4170 }
4171
4172 #[test]
4175 fn test_db2_dialect_basic() {
4176 let dialect = get_dialect(DbType::Db2).unwrap();
4177 assert_eq!(dialect.db_type(), DbType::Db2);
4178 assert_eq!(dialect.quote("users"), "\"users\"");
4180 assert_eq!(dialect.escape_string("it's"), "it''s");
4182 assert_eq!(
4184 dialect.auto_increment_keyword(),
4185 "GENERATED ALWAYS AS IDENTITY"
4186 );
4187 assert!(!dialect.supports_if_exists());
4189 assert!(!dialect.supports_if_not_exists());
4190 assert!(!dialect.supports_returning());
4192 }
4193
4194 #[test]
4195 fn test_db2_pagination() {
4196 let dialect = Db2Dialect;
4197 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4199 assert_eq!(
4200 sql,
4201 "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
4202 );
4203 }
4204
4205 #[test]
4206 fn test_db2_last_insert_id() {
4207 let dialect = Db2Dialect;
4208 assert_eq!(
4210 dialect.last_insert_id_sql(),
4211 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
4212 );
4213 }
4214
4215 #[test]
4216 fn test_db2_concat() {
4217 let dialect = Db2Dialect;
4218 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
4220 assert_eq!(dialect.concat(&[]), "''");
4221 }
4222
4223 #[test]
4224 fn test_db2_create_table() {
4225 let dialect = Db2Dialect;
4226 let cols = vec![ColumnDef {
4227 name: "id".to_string(),
4228 sql_type: "BIGINT".to_string(),
4229 nullable: false,
4230 default: None,
4231 auto_increment: true,
4232 primary_key: true,
4233 }];
4234 let sql = dialect.build_create_table("users", &cols);
4235 assert!(sql.contains("\"id\" BIGINT"));
4236 assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
4237 assert!(sql.contains("PRIMARY KEY"));
4238 }
4239
4240 #[test]
4241 fn test_db2_type_mapping() {
4242 assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
4244 assert_eq!(map_to_db2_type("INT"), "INTEGER");
4245 assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
4246 assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
4247 assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
4248 assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
4249 assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
4250 assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
4251 assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
4252 assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
4253 assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
4254 assert_eq!(map_to_db2_type("DATE"), "DATE");
4255 assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
4256 }
4257
4258 #[test]
4261 fn test_clickhouse_dialect_basic() {
4262 let dialect = get_dialect(DbType::ClickHouse).unwrap();
4263 assert_eq!(dialect.db_type(), DbType::ClickHouse);
4264 assert_eq!(dialect.quote("users"), "`users`");
4266 assert_eq!(dialect.escape_string("it's"), "it\\'s");
4268 assert!(!dialect.supports_returning());
4270 assert_eq!(dialect.auto_increment_keyword(), "");
4272 assert!(dialect.supports_if_exists());
4274 assert!(dialect.supports_if_not_exists());
4275 }
4276
4277 #[test]
4278 fn test_clickhouse_type_mapping() {
4279 assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
4280 assert_eq!(map_to_clickhouse_type("INT"), "Int32");
4281 assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
4282 assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
4283 assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
4284 assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
4285 assert_eq!(map_to_clickhouse_type("TEXT"), "String");
4286 assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
4287 assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
4288 assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
4289 assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
4290 assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
4291 assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
4292 assert_eq!(map_to_clickhouse_type("DATE"), "Date");
4293 }
4294
4295 #[test]
4296 fn test_clickhouse_create_table() {
4297 let dialect = ClickHouseDialect;
4298 let cols = vec![ColumnDef {
4299 name: "id".to_string(),
4300 sql_type: "BIGINT".to_string(),
4301 nullable: false,
4302 default: None,
4303 auto_increment: false, primary_key: true,
4305 }];
4306 let sql = dialect.build_create_table("users", &cols);
4307 assert!(
4309 sql.contains("ENGINE = MergeTree()"),
4310 "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
4311 sql
4312 );
4313 assert!(sql.contains("`id` Int64"));
4314 assert!(sql.contains("PRIMARY KEY"));
4315 }
4316
4317 #[test]
4318 fn test_clickhouse_json_extract() {
4319 let dialect = ClickHouseDialect;
4320 let sql = dialect.json_extract("data", "$.name");
4321 assert!(
4322 sql.contains("JSONExtractString"),
4323 "ClickHouse 应使用 JSONExtractString: {}",
4324 sql
4325 );
4326 }
4327
4328 #[test]
4329 fn test_clickhouse_concat() {
4330 let dialect = ClickHouseDialect;
4331 assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
4333 assert_eq!(dialect.concat(&[]), "''");
4334 }
4335
4336 #[test]
4339 fn test_db_type_dameng_str() {
4340 assert_eq!(DbType::Dameng.as_str(), "dameng");
4341 assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
4342 assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
4343 assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
4344 assert_eq!(DbType::Dameng.default_port(), 5236);
4345 }
4346
4347 #[test]
4348 fn test_db_type_kingbase_str() {
4349 assert_eq!(DbType::Kingbase.as_str(), "kingbase");
4350 assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
4351 assert_eq!(DbType::Kingbase.default_port(), 54321);
4352 }
4353
4354 #[test]
4355 fn test_db_type_db2_str() {
4356 assert_eq!(DbType::Db2.as_str(), "db2");
4357 assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
4358 assert_eq!(DbType::Db2.default_port(), 50000);
4359 }
4360
4361 #[test]
4362 fn test_db_type_mariadb_str() {
4363 assert_eq!(DbType::MariaDB.as_str(), "mariadb");
4364 assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
4365 assert_eq!(DbType::MariaDB.default_port(), 3306);
4366 }
4367
4368 #[test]
4369 fn test_db_type_tidb_str() {
4370 assert_eq!(DbType::TiDB.as_str(), "tidb");
4371 assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
4372 assert_eq!(DbType::TiDB.default_port(), 4000);
4373 }
4374
4375 #[test]
4376 fn test_db_type_polardb_str() {
4377 assert_eq!(DbType::PolarDB.as_str(), "polardb");
4378 assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
4379 assert_eq!(DbType::PolarDB.default_port(), 5432);
4380 }
4381
4382 #[test]
4383 fn test_db_type_gaussdb_str() {
4384 assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
4385 assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
4386 assert_eq!(DbType::GaussDB.default_port(), 25308);
4387 }
4388
4389 #[test]
4390 fn test_db_type_gbase_str() {
4391 assert_eq!(DbType::GBase.as_str(), "gbase");
4392 assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
4393 assert_eq!(DbType::GBase.default_port(), 9088);
4394 }
4395
4396 #[test]
4397 fn test_db_type_sybase_str() {
4398 assert_eq!(DbType::Sybase.as_str(), "sybase");
4399 assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
4400 assert_eq!(DbType::Sybase.default_port(), 5000);
4401 }
4402
4403 #[test]
4404 fn test_db_type_family_classification() {
4405 assert!(DbType::MySQL.is_mysql_family());
4407 assert!(DbType::MariaDB.is_mysql_family());
4408 assert!(DbType::TiDB.is_mysql_family());
4409 assert!(DbType::OceanBase.is_mysql_family());
4410 assert!(!DbType::PostgreSQL.is_mysql_family());
4411
4412 assert!(DbType::PostgreSQL.is_postgres_family());
4414 assert!(DbType::Kingbase.is_postgres_family());
4415 assert!(DbType::GaussDB.is_postgres_family());
4416 assert!(!DbType::MySQL.is_postgres_family());
4417
4418 assert!(DbType::Oracle.is_oracle_family());
4420 assert!(DbType::Dameng.is_oracle_family());
4421 assert!(!DbType::MySQL.is_oracle_family());
4422 }
4423
4424 #[test]
4425 fn test_db_type_supports_stored_procedure_extended() {
4426 assert!(DbType::Dameng.supports_stored_procedure());
4428 assert!(DbType::Kingbase.supports_stored_procedure());
4429 assert!(DbType::Db2.supports_stored_procedure());
4430 assert!(DbType::MariaDB.supports_stored_procedure());
4431 assert!(DbType::TiDB.supports_stored_procedure());
4432 assert!(DbType::PolarDB.supports_stored_procedure());
4433 assert!(DbType::GaussDB.supports_stored_procedure());
4434 assert!(DbType::GBase.supports_stored_procedure());
4435 assert!(DbType::Sybase.supports_stored_procedure());
4436 }
4437
4438 #[test]
4441 fn test_l4_max_identifier_len_constant() {
4442 assert_eq!(MAX_IDENTIFIER_LEN, 63);
4444 }
4445
4446 #[test]
4447 fn test_l4_quote_checked_valid_identifier() {
4448 let dialect = MySqlDialect;
4449 assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
4450 assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
4451 let name_63 = "a".repeat(63);
4453 assert!(dialect.quote_checked(&name_63).is_ok());
4454 }
4455
4456 #[test]
4457 fn test_l4_quote_checked_rejects_too_long() {
4458 let dialect = MySqlDialect;
4459 let long_name = "a".repeat(64); let result = dialect.quote_checked(&long_name);
4461 assert!(result.is_err());
4462 match result {
4463 Err(DbError::InvalidInput(msg)) => {
4464 assert!(
4465 msg.contains("too long"),
4466 "expected 'too long' error, got: {}",
4467 msg
4468 );
4469 }
4470 _ => panic!("Expected DbError::InvalidInput"),
4471 }
4472 }
4473
4474 #[test]
4475 fn test_l4_quote_checked_rejects_empty() {
4476 let dialect = MySqlDialect;
4477 let result = dialect.quote_checked("");
4478 assert!(result.is_err());
4479 }
4480
4481 #[test]
4482 fn test_l4_quote_checked_rejects_sql_injection() {
4483 let dialect = MySqlDialect;
4484 assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
4486 assert!(dialect.quote_checked("user'name").is_err());
4488 assert!(dialect.quote_checked("user name").is_err());
4490 assert!(dialect.quote_checked("1users").is_err());
4492 assert!(dialect.quote_checked("schema.table").is_err());
4494 }
4495
4496 #[test]
4497 fn test_l4_quote_checked_postgres() {
4498 let dialect = PostgreSqlDialect;
4499 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4500 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4501 }
4502
4503 #[test]
4504 fn test_l4_quote_checked_sqlite() {
4505 let dialect = SqliteDialect;
4506 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4507 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4508 }
4509
4510 #[test]
4511 fn test_l4_quote_checked_oracle() {
4512 let dialect = OracleDialect;
4513 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4514 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4515 }
4516
4517 #[test]
4518 fn test_l4_quote_checked_sql_server() {
4519 let dialect = SqlServerDialect;
4520 assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
4521 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4522 }
4523
4524 #[test]
4527 fn test_duckdb_quote() {
4528 let dialect = DuckDBDialect;
4529 assert_eq!(dialect.quote("users"), "\"users\"");
4530 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
4531 }
4532
4533 #[test]
4534 fn test_duckdb_escape() {
4535 let dialect = DuckDBDialect;
4536 assert_eq!(dialect.escape_string("hello"), "hello");
4537 assert_eq!(dialect.escape_string("it's"), "it''s");
4538 }
4539
4540 #[test]
4541 fn test_duckdb_pagination() {
4542 let dialect = DuckDBDialect;
4543 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4544 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
4545 }
4546
4547 #[test]
4548 fn test_duckdb_supports() {
4549 let dialect = DuckDBDialect;
4550 assert!(!dialect.supports_returning());
4551 assert!(!dialect.supports_lock_for_update());
4552 assert!(!dialect.supports_lock_shared());
4553 assert!(dialect.supports_if_exists());
4554 assert!(dialect.supports_if_not_exists());
4555 }
4556
4557 #[test]
4558 fn test_duckdb_insert_or_ignore() {
4559 let dialect = DuckDBDialect;
4560 let sql = dialect.build_insert_or_ignore_prefix("users");
4561 assert_eq!(sql, "INSERT OR IGNORE INTO \"users\"");
4562 }
4563
4564 #[test]
4565 fn test_duckdb_create_table() {
4566 let dialect = DuckDBDialect;
4567 let columns = vec![
4568 ColumnDef {
4569 name: "id".to_string(),
4570 sql_type: "BIGINT".to_string(),
4571 nullable: false,
4572 default: None,
4573 auto_increment: true,
4574 primary_key: true,
4575 },
4576 ColumnDef {
4577 name: "name".to_string(),
4578 sql_type: "VARCHAR(255)".to_string(),
4579 nullable: false,
4580 default: None,
4581 auto_increment: false,
4582 primary_key: false,
4583 },
4584 ];
4585 let sql = dialect.build_create_table("users", &columns);
4586 assert!(sql.contains("CREATE TABLE \"users\""));
4587 assert!(sql.contains("\"id\" BIGINT PRIMARY KEY"));
4588 assert!(sql.contains("\"name\" VARCHAR(255)"));
4589 }
4590
4591 #[test]
4592 fn test_duckdb_alter_table() {
4593 let dialect = DuckDBDialect;
4594 let changes = vec![TableChange::AddColumn(ColumnDef {
4595 name: "age".to_string(),
4596 sql_type: "INTEGER".to_string(),
4597 nullable: true,
4598 default: None,
4599 auto_increment: false,
4600 primary_key: false,
4601 })];
4602 let sql = dialect.build_alter_table("users", &changes);
4603 assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN \"age\" INTEGER"));
4604 }
4605
4606 #[test]
4607 fn test_duckdb_json_extract() {
4608 let dialect = DuckDBDialect;
4609 let sql = dialect.json_extract("data", "$.user.name");
4610 assert!(sql.contains("->"));
4611 assert!(sql.contains("user.name"));
4612 }
4613
4614 #[test]
4615 fn test_duckdb_concat() {
4616 let dialect = DuckDBDialect;
4617 let sql = dialect.concat(&["a", "b", "c"]);
4618 assert_eq!(sql, "a || b || c");
4619 }
4620
4621 #[test]
4622 fn test_duckdb_bool_to_int() {
4623 let dialect = DuckDBDialect;
4624 let sql = dialect.bool_to_int("active");
4625 assert!(sql.contains("CASE WHEN"));
4626 assert!(sql.contains("THEN 1"));
4627 assert!(sql.contains("ELSE 0"));
4628 }
4629
4630 #[test]
4631 fn test_get_dialect_duckdb() {
4632 let result = get_dialect(DbType::DuckDB);
4633 assert!(result.is_ok());
4634 let dialect = result.unwrap();
4635 assert_eq!(dialect.db_type(), DbType::DuckDB);
4636 }
4637}
4638
4639#[cfg(feature = "perf-enum-dispatch")]
4648#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4649pub enum DialectKind {
4650 MySQL,
4652 PostgreSQL,
4654 SQLite,
4656 Oracle,
4658 MSSQL,
4660}
4661
4662#[cfg(feature = "perf-enum-dispatch")]
4663impl DialectKind {
4664 pub fn from_db_type(db_type: DbType) -> Option<Self> {
4666 match db_type {
4667 DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase => Some(Self::MySQL),
4668 DbType::PostgreSQL | DbType::Kingbase | DbType::PolarDB | DbType::GaussDB => {
4669 Some(Self::PostgreSQL)
4670 }
4671 DbType::Sqlite => Some(Self::SQLite),
4672 DbType::Oracle | DbType::Dameng => Some(Self::Oracle),
4673 DbType::SqlServer | DbType::Sybase | DbType::GBase => Some(Self::MSSQL),
4674 _ => None,
4675 }
4676 }
4677
4678 pub fn quote(&self, identifier: &str) -> String {
4680 match self {
4681 Self::MySQL => MySqlDialect.quote(identifier),
4682 Self::PostgreSQL => PostgreSqlDialect.quote(identifier),
4683 Self::SQLite => SqliteDialect.quote(identifier),
4684 Self::Oracle => OracleDialect.quote(identifier),
4685 Self::MSSQL => SqlServerDialect.quote(identifier),
4686 }
4687 }
4688
4689 pub fn escape_string(&self, s: &str) -> String {
4691 match self {
4692 Self::MySQL => MySqlDialect.escape_string(s),
4693 Self::PostgreSQL => PostgreSqlDialect.escape_string(s),
4694 Self::SQLite => SqliteDialect.escape_string(s),
4695 Self::Oracle => OracleDialect.escape_string(s),
4696 Self::MSSQL => SqlServerDialect.escape_string(s),
4697 }
4698 }
4699
4700 pub fn db_type(&self) -> DbType {
4702 match self {
4703 Self::MySQL => DbType::MySQL,
4704 Self::PostgreSQL => DbType::PostgreSQL,
4705 Self::SQLite => DbType::Sqlite,
4706 Self::Oracle => DbType::Oracle,
4707 Self::MSSQL => DbType::SqlServer,
4708 }
4709 }
4710
4711 pub fn to_dialect(&self) -> Box<dyn Dialect> {
4713 match self {
4714 Self::MySQL => Box::new(MySqlDialect),
4715 Self::PostgreSQL => Box::new(PostgreSqlDialect),
4716 Self::SQLite => Box::new(SqliteDialect),
4717 Self::Oracle => Box::new(OracleDialect),
4718 Self::MSSQL => Box::new(SqlServerDialect),
4719 }
4720 }
4721
4722 pub fn quote_into(&self, identifier: &str, buf: &mut String) {
4724 match self {
4725 Self::MySQL => MySqlDialect.quote_into(identifier, buf),
4726 Self::PostgreSQL => PostgreSqlDialect.quote_into(identifier, buf),
4727 Self::SQLite => SqliteDialect.quote_into(identifier, buf),
4728 Self::Oracle => OracleDialect.quote_into(identifier, buf),
4729 Self::MSSQL => SqlServerDialect.quote_into(identifier, buf),
4730 }
4731 }
4732}
4733
4734#[cfg(all(test, feature = "perf-enum-dispatch"))]
4735mod enum_dispatch_tests {
4736 use super::*;
4737
4738 #[test]
4739 fn test_dialect_kind_from_db_type() {
4740 assert_eq!(
4741 DialectKind::from_db_type(DbType::MySQL),
4742 Some(DialectKind::MySQL)
4743 );
4744 assert_eq!(
4745 DialectKind::from_db_type(DbType::PostgreSQL),
4746 Some(DialectKind::PostgreSQL)
4747 );
4748 assert_eq!(
4749 DialectKind::from_db_type(DbType::Sqlite),
4750 Some(DialectKind::SQLite)
4751 );
4752 assert_eq!(
4753 DialectKind::from_db_type(DbType::Oracle),
4754 Some(DialectKind::Oracle)
4755 );
4756 assert_eq!(
4757 DialectKind::from_db_type(DbType::SqlServer),
4758 Some(DialectKind::MSSQL)
4759 );
4760 assert_eq!(DialectKind::from_db_type(DbType::Redis), None);
4761 }
4762
4763 #[test]
4764 fn test_dialect_kind_quote_diff() {
4765 let kinds = [
4766 DialectKind::MySQL,
4767 DialectKind::PostgreSQL,
4768 DialectKind::SQLite,
4769 DialectKind::Oracle,
4770 DialectKind::MSSQL,
4771 ];
4772 for kind in &kinds {
4773 let enum_result = kind.quote("users");
4774 let box_result = kind.to_dialect().quote("users");
4775 assert_eq!(enum_result, box_result);
4776 }
4777 }
4778
4779 #[test]
4780 fn test_dialect_kind_escape_diff() {
4781 let kinds = [
4782 DialectKind::MySQL,
4783 DialectKind::PostgreSQL,
4784 DialectKind::SQLite,
4785 DialectKind::Oracle,
4786 DialectKind::MSSQL,
4787 ];
4788 for kind in &kinds {
4789 let enum_result = kind.escape_string("it's");
4790 let box_result = kind.to_dialect().escape_string("it's");
4791 assert_eq!(enum_result, box_result);
4792 }
4793 }
4794
4795 #[test]
4796 fn test_dialect_kind_quote_into() {
4797 let kinds = [
4798 DialectKind::MySQL,
4799 DialectKind::PostgreSQL,
4800 DialectKind::SQLite,
4801 DialectKind::Oracle,
4802 DialectKind::MSSQL,
4803 ];
4804 for kind in &kinds {
4805 let mut buf = String::new();
4806 kind.quote_into("users", &mut buf);
4807 assert_eq!(buf, kind.quote("users"));
4808 let mut buf2 = String::new();
4809 kind.quote_into("order", &mut buf2);
4810 assert_eq!(buf2, kind.quote("order"));
4811 }
4812 }
4813}