1pub use crate::db_type::DbType;
6use crate::error::DbError;
7use std::fmt;
8
9pub const MAX_IDENTIFIER_LEN: usize = 63;
19
20pub trait Dialect: Send + Sync {
24 fn clone_box(&self) -> Box<dyn Dialect>;
26
27 fn db_type(&self) -> DbType;
29
30 fn quote(&self, identifier: &str) -> String;
32
33 fn quote_checked(&self, identifier: &str) -> Result<String, DbError> {
44 crate::sql_safety::validate_identifier(identifier, "identifier")?;
45 Ok(self.quote(identifier))
46 }
47
48 fn escape_string(&self, s: &str) -> String;
50
51 fn supports_returning(&self) -> bool;
53
54 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String;
56
57 fn json_type(&self) -> &'static str;
59
60 fn json_extract(&self, column: &str, path: &str) -> String;
62
63 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String;
65
66 fn bool_to_int(&self, expr: &str) -> String;
68
69 fn concat(&self, parts: &[&str]) -> String;
71
72 fn supports_if_exists(&self) -> bool;
74
75 fn supports_if_not_exists(&self) -> bool;
77
78 fn auto_increment_keyword(&self) -> &'static str;
80
81 fn last_insert_id_sql(&self) -> Option<&'static str>;
87
88 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String;
90
91 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String;
93
94 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
99 if if_exists && self.supports_if_exists() {
100 format!("DROP TABLE IF EXISTS {}", self.quote(table))
101 } else {
102 format!("DROP TABLE {}", self.quote(table))
103 }
104 }
105
106 fn build_upsert_on_conflict(
121 &self,
122 conflict_columns: &[&str],
123 update_columns: &[&str],
124 all_columns: &[String],
125 ) -> Option<String> {
126 let _ = (conflict_columns, update_columns, all_columns);
127 None
128 }
129
130 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
139 let _ = lock_type;
140 None
141 }
142
143 fn supports_lock_for_update(&self) -> bool {
148 true
149 }
150
151 fn supports_lock_shared(&self) -> bool {
156 true
157 }
158
159 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
166 format!("INSERT OR IGNORE INTO {}", self.quote(table))
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub enum LockType {
179 ForUpdate,
181 Shared,
183}
184
185#[derive(Debug, Clone)]
187pub struct ColumnDef {
188 pub name: String,
189 pub sql_type: String,
190 pub nullable: bool,
191 pub default: Option<String>,
192 pub auto_increment: bool,
193 pub primary_key: bool,
194}
195
196#[derive(Debug, Clone)]
198pub enum TableChange {
199 AddColumn(ColumnDef),
200 DropColumn(String),
201 ModifyColumn(ColumnDef),
202 AddIndex(String, Vec<String>),
203 DropIndex(String),
204 AddForeignKey {
205 columns: Vec<String>,
206 reference_table: String,
207 reference_columns: Vec<String>,
208 },
209}
210
211#[derive(Debug, Clone)]
213pub struct MySqlDialect;
214
215impl Dialect for MySqlDialect {
216 fn clone_box(&self) -> Box<dyn Dialect> {
217 Box::new(MySqlDialect)
218 }
219
220 fn db_type(&self) -> DbType {
221 DbType::MySQL
222 }
223
224 fn quote(&self, identifier: &str) -> String {
225 format!("`{}`", identifier.replace('`', "``"))
226 }
227
228 fn escape_string(&self, s: &str) -> String {
229 let mut escaped = String::with_capacity(s.len() * 2);
230 for c in s.chars() {
231 match c {
232 '\\' => escaped.push_str("\\\\"),
233 '\'' => escaped.push_str("\\'"),
234 '\0' => escaped.push_str("\\0"),
235 '\n' => escaped.push_str("\\n"),
236 '\r' => escaped.push_str("\\r"),
237 '\t' => escaped.push_str("\\t"),
238 '\x1a' => escaped.push_str("\\Z"),
239 _ => escaped.push(c),
240 }
241 }
242 escaped
243 }
244
245 fn supports_returning(&self) -> bool {
246 false
247 }
248
249 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
250 let offset = page.saturating_sub(1).saturating_mul(limit);
258 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
259 }
260
261 fn json_type(&self) -> &'static str {
262 "JSON"
263 }
264
265 fn json_extract(&self, column: &str, path: &str) -> String {
266 let normalized = if path.starts_with('$') {
268 path.to_string()
269 } else {
270 format!("$.{}", path)
271 };
272 format!(
273 "JSON_EXTRACT({}, '{}')",
274 column,
275 self.escape_string(&normalized)
276 )
277 }
278
279 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
280 let cols = columns.join(", ");
281 let escaped = self.escape_string(keyword);
282 format!(
283 "MATCH({}) AGAINST('{}' IN NATURAL LANGUAGE MODE)",
284 cols, escaped
285 )
286 }
287
288 fn bool_to_int(&self, expr: &str) -> String {
289 format!("IF({}, 1, 0)", expr)
291 }
292
293 fn concat(&self, parts: &[&str]) -> String {
294 if parts.is_empty() {
295 return "NULL".to_string();
296 }
297 let concat_parts: Vec<String> = parts
298 .iter()
299 .map(|p| format!("CAST({} AS CHAR)", p))
300 .collect();
301 format!("CONCAT({})", concat_parts.join(", "))
302 }
303
304 fn supports_if_exists(&self) -> bool {
305 true
306 }
307
308 fn supports_if_not_exists(&self) -> bool {
309 true
310 }
311
312 fn auto_increment_keyword(&self) -> &'static str {
313 "AUTO_INCREMENT"
314 }
315
316 fn last_insert_id_sql(&self) -> Option<&'static str> {
317 Some("LAST_INSERT_ID()")
318 }
319
320 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
321 let cols: Vec<String> = columns
322 .iter()
323 .map(|col| {
324 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
325 if !col.nullable {
326 sql.push_str(" NOT NULL");
327 }
328 if let Some(default) = &col.default {
329 sql.push_str(&format!(" DEFAULT {}", default));
330 }
331 if col.auto_increment {
332 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
333 }
334 if col.primary_key {
335 sql.push_str(" PRIMARY KEY");
336 }
337 sql
338 })
339 .collect();
340
341 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
342 }
343
344 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
345 let stmts: Vec<String> = changes.iter().map(|change| {
346 match change {
347 TableChange::AddColumn(col) => {
348 let mut sql = format!("ALTER TABLE {} ADD {}", self.quote(table), self.quote(&col.name));
349 sql.push_str(&format!(" {}", col.sql_type));
350 if !col.nullable {
351 sql.push_str(" NOT NULL");
352 }
353 if let Some(default) = &col.default {
354 sql.push_str(&format!(" DEFAULT {}", default));
355 }
356 sql
357 }
358 TableChange::DropColumn(name) => {
359 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
360 }
361 TableChange::ModifyColumn(col) => {
362 let mut sql = format!("ALTER TABLE {} MODIFY COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
364 if !col.nullable {
365 sql.push_str(" NOT NULL");
366 }
367 if let Some(default) = &col.default {
368 sql.push_str(&format!(" DEFAULT {}", default));
369 }
370 sql
371 }
372 TableChange::AddIndex(name, cols) => {
373 format!("ALTER TABLE {} ADD INDEX {} ({})", self.quote(table), name, cols.join(", "))
374 }
375 TableChange::DropIndex(name) => {
376 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
377 }
378 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
379 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
380 self.quote(table),
381 table,
382 columns.join("_"),
383 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
384 self.quote(reference_table),
385 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
386 }
387 }
388 }).collect();
389
390 stmts.join("; ")
391 }
392
393 fn build_upsert_on_conflict(
398 &self,
399 _conflict_columns: &[&str],
400 update_columns: &[&str],
401 all_columns: &[String],
402 ) -> Option<String> {
403 let cols_to_update: Vec<String> = if update_columns.is_empty() {
405 all_columns.iter().map(|c| self.quote(c)).collect()
406 } else {
407 update_columns.iter().map(|c| self.quote(c)).collect()
408 };
409 if cols_to_update.is_empty() {
410 return None;
411 }
412 let set_clauses: Vec<String> = cols_to_update
413 .iter()
414 .map(|col| format!("{}=VALUES({})", col, col))
415 .collect();
416 Some(format!(
417 "ON DUPLICATE KEY UPDATE {}",
418 set_clauses.join(", ")
419 ))
420 }
421
422 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
423 match lock_type {
424 LockType::ForUpdate => Some("FOR UPDATE".to_string()),
425 LockType::Shared => Some("LOCK IN SHARE MODE".to_string()),
426 }
427 }
428
429 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
430 format!("INSERT IGNORE INTO {}", self.quote(table))
431 }
432}
433
434#[derive(Debug, Clone)]
436pub struct PostgreSqlDialect;
437
438impl Dialect for PostgreSqlDialect {
439 fn clone_box(&self) -> Box<dyn Dialect> {
440 Box::new(PostgreSqlDialect)
441 }
442
443 fn db_type(&self) -> DbType {
444 DbType::PostgreSQL
445 }
446
447 fn quote(&self, identifier: &str) -> String {
448 format!("\"{}\"", identifier.replace('"', "\"\""))
449 }
450
451 fn escape_string(&self, s: &str) -> String {
452 let mut escaped = String::with_capacity(s.len() * 2);
455 for c in s.chars() {
456 match c {
457 '\'' => escaped.push_str("''"),
458 _ => escaped.push(c),
459 }
460 }
461 escaped
462 }
463
464 fn supports_returning(&self) -> bool {
465 true
466 }
467
468 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
469 let offset = page.saturating_sub(1).saturating_mul(limit);
470 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
471 }
472
473 fn json_type(&self) -> &'static str {
474 "JSONB"
475 }
476
477 fn json_extract(&self, column: &str, path: &str) -> String {
478 let normalized = path.trim_start_matches("$.");
483 let parts: Vec<&str> = normalized.split('.').filter(|s| !s.is_empty()).collect();
484 let path_lit = parts
485 .iter()
486 .map(|p| {
487 let needs_quoting = p.chars().any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\'));
489 if needs_quoting {
490 let escaped = p.replace('\\', "\\\\").replace('"', "\\\"");
491 format!("\"{}\"", escaped)
492 } else {
493 p.to_string()
494 }
495 })
496 .collect::<Vec<_>>()
497 .join(",");
498 let path_lit_escaped = path_lit.replace('\'', "''");
500 format!("{}#>>'{{{}}}'", column, path_lit_escaped)
501 }
502
503 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
504 let cols = columns
505 .iter()
506 .map(|c| format!("{}::text", c))
507 .collect::<Vec<_>>()
508 .join(" || ' ' || ");
509 let escaped = self.escape_string(keyword);
510 format!("to_tsvector({}) @@ to_tsquery('{}')", cols, escaped)
511 }
512
513 fn bool_to_int(&self, expr: &str) -> String {
514 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
515 }
516
517 fn concat(&self, parts: &[&str]) -> String {
518 if parts.is_empty() {
519 return "NULL".to_string();
520 }
521 format!("CONCAT({})", parts.join(", "))
522 }
523
524 fn supports_if_exists(&self) -> bool {
525 true
526 }
527
528 fn supports_if_not_exists(&self) -> bool {
529 true
530 }
531
532 fn auto_increment_keyword(&self) -> &'static str {
533 "GENERATED BY DEFAULT AS IDENTITY"
534 }
535
536 fn last_insert_id_sql(&self) -> Option<&'static str> {
537 Some("lastval()")
538 }
539
540 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
541 let cols: Vec<String> = columns
542 .iter()
543 .map(|col| {
544 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
545 if !col.nullable {
546 sql.push_str(" NOT NULL");
547 }
548 if let Some(default) = &col.default {
549 sql.push_str(&format!(" DEFAULT {}", default));
550 }
551 if col.primary_key {
552 sql.push_str(" PRIMARY KEY");
553 }
554 sql
555 })
556 .collect();
557
558 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
559 }
560
561 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
562 let stmts: Vec<String> = changes.iter().map(|change| {
563 match change {
564 TableChange::AddColumn(col) => {
565 let mut sql = format!("ALTER TABLE {} ADD COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
566 if !col.nullable {
567 sql.push_str(" NOT NULL");
568 }
569 if let Some(default) = &col.default {
570 sql.push_str(&format!(" DEFAULT {}", default));
571 }
572 sql
573 }
574 TableChange::DropColumn(name) => {
575 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
576 }
577 TableChange::ModifyColumn(col) => {
578 let mut sql = format!("ALTER TABLE {} ALTER COLUMN {} TYPE {}", self.quote(table), self.quote(&col.name), col.sql_type);
580 if !col.nullable {
581 sql.push_str(&format!(", ALTER COLUMN {} SET NOT NULL", self.quote(&col.name)));
582 }
583 if let Some(default) = &col.default {
584 sql.push_str(&format!(", ALTER COLUMN {} SET DEFAULT {}", self.quote(&col.name), default));
585 }
586 sql
587 }
588 TableChange::AddIndex(name, cols) => {
589 format!("CREATE INDEX {} ON {} ({})", name, self.quote(table), cols.join(", "))
590 }
591 TableChange::DropIndex(name) => {
592 format!("DROP INDEX {}", name)
593 }
594 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
595 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
596 self.quote(table),
597 table,
598 columns.join("_"),
599 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
600 self.quote(reference_table),
601 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
602 }
603 }
604 }).collect();
605
606 stmts.join("; ")
607 }
608
609 fn build_upsert_on_conflict(
613 &self,
614 conflict_columns: &[&str],
615 update_columns: &[&str],
616 all_columns: &[String],
617 ) -> Option<String> {
618 if conflict_columns.is_empty() {
619 return None;
620 }
621 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
622 let conflict_set: std::collections::HashSet<&str> =
624 conflict_columns.iter().copied().collect();
625 let cols_to_update: Vec<String> = if update_columns.is_empty() {
626 all_columns
627 .iter()
628 .filter(|c| !conflict_set.contains(c.as_str()))
629 .map(|c| self.quote(c))
630 .collect()
631 } else {
632 update_columns.iter().map(|c| self.quote(c)).collect()
633 };
634 if cols_to_update.is_empty() {
635 return Some(format!(
637 "ON CONFLICT ({}) DO NOTHING",
638 conflict_cols.join(", ")
639 ));
640 }
641 let set_clauses: Vec<String> = cols_to_update
642 .iter()
643 .map(|col| format!("{}=EXCLUDED.{}", col, col))
644 .collect();
645 Some(format!(
646 "ON CONFLICT ({}) DO UPDATE SET {}",
647 conflict_cols.join(", "),
648 set_clauses.join(", ")
649 ))
650 }
651
652 fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
653 match lock_type {
654 LockType::ForUpdate => Some("FOR UPDATE".to_string()),
655 LockType::Shared => Some("FOR SHARE".to_string()),
656 }
657 }
658}
659
660#[derive(Debug, Clone)]
662pub struct SqliteDialect;
663
664impl Dialect for SqliteDialect {
665 fn clone_box(&self) -> Box<dyn Dialect> {
666 Box::new(SqliteDialect)
667 }
668
669 fn db_type(&self) -> DbType {
670 DbType::Sqlite
671 }
672
673 fn quote(&self, identifier: &str) -> String {
674 format!("\"{}\"", identifier.replace('"', "\"\""))
675 }
676
677 fn escape_string(&self, s: &str) -> String {
678 let mut escaped = String::with_capacity(s.len() * 2);
679 for c in s.chars() {
680 match c {
681 '\'' => escaped.push_str("''"),
682 _ => escaped.push(c),
683 }
684 }
685 escaped
686 }
687
688 fn supports_returning(&self) -> bool {
689 true
690 }
691
692 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
693 let offset = page.saturating_sub(1).saturating_mul(limit);
694 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
695 }
696
697 fn json_type(&self) -> &'static str {
698 "TEXT"
699 }
700
701 fn json_extract(&self, column: &str, path: &str) -> String {
702 let normalized = if path.starts_with('$') {
704 path.to_string()
705 } else {
706 format!("$.{}", path)
707 };
708 format!(
709 "json_extract({}, '{}')",
710 column,
711 self.escape_string(&normalized)
712 )
713 }
714
715 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
716 if columns.is_empty() {
720 return "0".to_string();
721 }
722 let escaped = self.escape_string(keyword);
723 columns
724 .iter()
725 .map(|c| format!("{} LIKE '%{}%'", c.trim(), escaped))
726 .collect::<Vec<_>>()
727 .join(" OR ")
728 }
729
730 fn bool_to_int(&self, expr: &str) -> String {
731 expr.to_string()
732 }
733
734 fn concat(&self, parts: &[&str]) -> String {
735 if parts.is_empty() {
736 return "NULL".to_string();
737 }
738 let coalesced: Vec<String> = parts
741 .iter()
742 .map(|p| format!("COALESCE({}, '')", p))
743 .collect();
744 coalesced.join(" || ")
745 }
746
747 fn supports_if_exists(&self) -> bool {
748 true
749 }
750
751 fn supports_if_not_exists(&self) -> bool {
752 true
753 }
754
755 fn auto_increment_keyword(&self) -> &'static str {
756 "AUTOINCREMENT"
757 }
758
759 fn last_insert_id_sql(&self) -> Option<&'static str> {
760 Some("last_insert_rowid()")
761 }
762
763 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
764 let cols: Vec<String> = columns
765 .iter()
766 .map(|col| {
767 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
768 if !col.nullable {
769 sql.push_str(" NOT NULL");
770 }
771 if let Some(default) = &col.default {
772 sql.push_str(&format!(" DEFAULT {}", default));
773 }
774 if col.auto_increment {
775 sql.push_str(" PRIMARY KEY AUTOINCREMENT");
776 } else if col.primary_key {
777 sql.push_str(" PRIMARY KEY");
778 }
779 sql
780 })
781 .collect();
782
783 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
784 }
785
786 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
787 let stmts: Vec<String> = changes
790 .iter()
791 .map(|change| {
792 match change {
793 TableChange::AddColumn(col) => {
794 let mut sql = format!(
795 "ALTER TABLE {} ADD COLUMN {} {}",
796 self.quote(table),
797 self.quote(&col.name),
798 col.sql_type
799 );
800 if !col.nullable {
801 sql.push_str(" NOT NULL");
802 }
803 if let Some(default) = &col.default {
804 sql.push_str(&format!(" DEFAULT {}", default));
805 }
806 sql
807 }
808 TableChange::DropColumn(name) => {
809 format!(
811 "ALTER TABLE {} DROP COLUMN {}",
812 self.quote(table),
813 self.quote(name)
814 )
815 }
816 TableChange::ModifyColumn(col) => {
817 format!(
820 "-- SQLite 不支持 MODIFY COLUMN({} {}),需重建表",
821 col.name, col.sql_type
822 )
823 }
824 TableChange::AddIndex(name, cols) => {
825 format!(
826 "CREATE INDEX {} ON {} ({})",
827 name,
828 self.quote(table),
829 cols.join(", ")
830 )
831 }
832 TableChange::DropIndex(name) => {
833 format!("DROP INDEX {}", name)
834 }
835 TableChange::AddForeignKey {
836 columns,
837 reference_table,
838 reference_columns: _,
839 } => {
840 format!(
842 "-- SQLite 不支持 ADD FOREIGN KEY({} -> {}),需重建表",
843 columns.join(","),
844 reference_table
845 )
846 }
847 }
848 })
849 .collect();
850
851 stmts.join("; ")
852 }
853
854 fn build_upsert_on_conflict(
858 &self,
859 conflict_columns: &[&str],
860 update_columns: &[&str],
861 all_columns: &[String],
862 ) -> Option<String> {
863 if conflict_columns.is_empty() {
864 return None;
865 }
866 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
867 let conflict_set: std::collections::HashSet<&str> =
868 conflict_columns.iter().copied().collect();
869 let cols_to_update: Vec<String> = if update_columns.is_empty() {
870 all_columns
871 .iter()
872 .filter(|c| !conflict_set.contains(c.as_str()))
873 .map(|c| self.quote(c))
874 .collect()
875 } else {
876 update_columns.iter().map(|c| self.quote(c)).collect()
877 };
878 if cols_to_update.is_empty() {
879 return Some(format!(
880 "ON CONFLICT ({}) DO NOTHING",
881 conflict_cols.join(", ")
882 ));
883 }
884 let set_clauses: Vec<String> = cols_to_update
885 .iter()
886 .map(|col| format!("{}=EXCLUDED.{}", col, col))
887 .collect();
888 Some(format!(
889 "ON CONFLICT ({}) DO UPDATE SET {}",
890 conflict_cols.join(", "),
891 set_clauses.join(", ")
892 ))
893 }
894
895 fn supports_lock_for_update(&self) -> bool {
896 false
897 }
898
899 fn supports_lock_shared(&self) -> bool {
900 false
901 }
902}
903
904fn map_to_oracle_type(sql_type: &str) -> String {
913 let upper = sql_type.to_uppercase();
914 let trimmed = upper.trim();
915
916 if trimmed.starts_with("BIGINT") {
917 sql_type.replacen("BIGINT", "NUMBER(19)", 1)
918 } else if trimmed.starts_with("VARCHAR2") {
919 sql_type.to_string()
920 } else if trimmed.starts_with("VARCHAR") {
921 sql_type.replacen("VARCHAR", "VARCHAR2", 1)
922 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
923 "CLOB".to_string()
924 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
925 "NUMBER(1)".to_string()
926 } else if trimmed == "INTEGER" {
927 "NUMBER(10)".to_string()
928 } else if trimmed.starts_with("INT") {
929 sql_type.replacen("INT", "NUMBER(10)", 1)
930 } else {
931 sql_type.to_string()
932 }
933}
934
935#[derive(Debug, Clone)]
937pub struct OracleDialect;
938
939impl Dialect for OracleDialect {
940 fn clone_box(&self) -> Box<dyn Dialect> {
941 Box::new(OracleDialect)
942 }
943
944 fn db_type(&self) -> DbType {
945 DbType::Oracle
946 }
947
948 fn quote(&self, identifier: &str) -> String {
949 format!("\"{}\"", identifier.replace('"', "\"\""))
951 }
952
953 fn escape_string(&self, s: &str) -> String {
954 let mut escaped = String::with_capacity(s.len() * 2);
956 for c in s.chars() {
957 match c {
958 '\'' => escaped.push_str("''"),
959 _ => escaped.push(c),
960 }
961 }
962 escaped
963 }
964
965 fn supports_returning(&self) -> bool {
966 true
968 }
969
970 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
971 let offset = page.saturating_sub(1).saturating_mul(limit);
974 format!(
975 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
976 sql, offset, limit
977 )
978 }
979
980 fn json_type(&self) -> &'static str {
981 "JSON"
983 }
984
985 fn json_extract(&self, column: &str, path: &str) -> String {
986 let normalized = if path.starts_with('$') {
988 path.to_string()
989 } else {
990 format!("$.{}", path)
991 };
992 format!(
993 "JSON_VALUE({}, '{}')",
994 column,
995 self.escape_string(&normalized)
996 )
997 }
998
999 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1000 if columns.is_empty() {
1003 return "0".to_string();
1004 }
1005 let escaped = self.escape_string(keyword);
1006 let parts: Vec<String> = columns
1007 .iter()
1008 .map(|c| format!("CONTAINS({}, '{}', 1) > 0", c, escaped))
1009 .collect();
1010 parts.join(" OR ")
1011 }
1012
1013 fn bool_to_int(&self, expr: &str) -> String {
1014 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1016 }
1017
1018 fn concat(&self, parts: &[&str]) -> String {
1019 if parts.is_empty() {
1021 return "NULL".to_string();
1022 }
1023 parts.join(" || ")
1024 }
1025
1026 fn supports_if_exists(&self) -> bool {
1027 true
1029 }
1030
1031 fn supports_if_not_exists(&self) -> bool {
1032 true
1034 }
1035
1036 fn auto_increment_keyword(&self) -> &'static str {
1037 "GENERATED BY DEFAULT AS IDENTITY"
1039 }
1040
1041 fn last_insert_id_sql(&self) -> Option<&'static str> {
1042 None
1047 }
1048
1049 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1050 let cols: Vec<String> = columns
1051 .iter()
1052 .map(|col| {
1053 let oracle_type = map_to_oracle_type(&col.sql_type);
1054 let mut sql = format!("{} {}", self.quote(&col.name), oracle_type);
1055 if !col.nullable && !col.auto_increment {
1057 sql.push_str(" NOT NULL");
1058 }
1059 if let Some(default) = &col.default {
1060 sql.push_str(&format!(" DEFAULT {}", default));
1061 }
1062 if col.auto_increment {
1063 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1064 }
1065 if col.primary_key {
1066 sql.push_str(" PRIMARY KEY");
1067 }
1068 sql
1069 })
1070 .collect();
1071
1072 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1073 }
1074
1075 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1076 let stmts: Vec<String> = changes
1077 .iter()
1078 .map(|change| match change {
1079 TableChange::AddColumn(col) => {
1080 let oracle_type = map_to_oracle_type(&col.sql_type);
1081 let mut sql = format!(
1082 "ALTER TABLE {} ADD {} {}",
1083 self.quote(table),
1084 self.quote(&col.name),
1085 oracle_type
1086 );
1087 if !col.nullable {
1088 sql.push_str(" NOT NULL");
1089 }
1090 if let Some(default) = &col.default {
1091 sql.push_str(&format!(" DEFAULT {}", default));
1092 }
1093 sql
1094 }
1095 TableChange::DropColumn(name) => {
1096 format!(
1097 "ALTER TABLE {} DROP COLUMN {}",
1098 self.quote(table),
1099 self.quote(name)
1100 )
1101 }
1102 TableChange::ModifyColumn(col) => {
1103 let oracle_type = map_to_oracle_type(&col.sql_type);
1105 let mut sql = format!(
1106 "ALTER TABLE {} MODIFY {} {}",
1107 self.quote(table),
1108 self.quote(&col.name),
1109 oracle_type
1110 );
1111 if !col.nullable {
1112 sql.push_str(" NOT NULL");
1113 }
1114 if let Some(default) = &col.default {
1115 sql.push_str(&format!(" DEFAULT {}", default));
1116 }
1117 sql
1118 }
1119 TableChange::AddIndex(name, cols) => {
1120 format!(
1121 "CREATE INDEX {} ON {} ({})",
1122 name,
1123 self.quote(table),
1124 cols.join(", ")
1125 )
1126 }
1127 TableChange::DropIndex(name) => {
1128 format!("DROP INDEX {}", name)
1129 }
1130 TableChange::AddForeignKey {
1131 columns,
1132 reference_table,
1133 reference_columns,
1134 } => {
1135 format!(
1136 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1137 self.quote(table),
1138 table,
1139 columns.join("_"),
1140 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1141 self.quote(reference_table),
1142 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1143 )
1144 }
1145 })
1146 .collect();
1147
1148 stmts.join("; ")
1149 }
1150}
1151
1152fn map_to_sqlserver_type(sql_type: &str) -> String {
1161 let upper = sql_type.to_uppercase();
1162 let trimmed = upper.trim();
1163
1164 if trimmed.starts_with("BIGINT") {
1165 sql_type.to_string()
1166 } else if matches!(trimmed, "INT" | "INTEGER") {
1167 "INT".to_string()
1168 } else if trimmed.starts_with("NVARCHAR") {
1169 sql_type.to_string()
1170 } else if trimmed.starts_with("VARCHAR") {
1171 sql_type.replacen("VARCHAR", "NVARCHAR", 1)
1172 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1173 "NVARCHAR(MAX)".to_string()
1174 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1175 "BIT".to_string()
1176 } else {
1177 sql_type.to_string()
1178 }
1179}
1180
1181#[derive(Debug, Clone)]
1183pub struct SqlServerDialect;
1184
1185impl Dialect for SqlServerDialect {
1186 fn clone_box(&self) -> Box<dyn Dialect> {
1187 Box::new(SqlServerDialect)
1188 }
1189
1190 fn db_type(&self) -> DbType {
1191 DbType::SqlServer
1192 }
1193
1194 fn quote(&self, identifier: &str) -> String {
1195 format!("[{}]", identifier.replace(']', "]]"))
1197 }
1198
1199 fn escape_string(&self, s: &str) -> String {
1200 let mut escaped = String::with_capacity(s.len() * 2);
1202 for c in s.chars() {
1203 match c {
1204 '\'' => escaped.push_str("''"),
1205 _ => escaped.push(c),
1206 }
1207 }
1208 escaped
1209 }
1210
1211 fn supports_returning(&self) -> bool {
1212 true
1214 }
1215
1216 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1217 let offset = page.saturating_sub(1).saturating_mul(limit);
1219 format!(
1220 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1221 sql, offset, limit
1222 )
1223 }
1224
1225 fn json_type(&self) -> &'static str {
1226 "NVARCHAR(MAX)"
1228 }
1229
1230 fn json_extract(&self, column: &str, path: &str) -> String {
1231 let normalized = if path.starts_with('$') {
1233 path.to_string()
1234 } else {
1235 format!("$.{}", path)
1236 };
1237 format!(
1238 "JSON_VALUE({}, '{}')",
1239 column,
1240 self.escape_string(&normalized)
1241 )
1242 }
1243
1244 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1245 if columns.is_empty() {
1247 return "0".to_string();
1248 }
1249 let escaped = self.escape_string(keyword);
1250 let cols = columns.join(", ");
1251 format!("CONTAINS({}, '{}')", cols, escaped)
1252 }
1253
1254 fn bool_to_int(&self, expr: &str) -> String {
1255 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1257 }
1258
1259 fn concat(&self, parts: &[&str]) -> String {
1260 if parts.is_empty() {
1261 return "NULL".to_string();
1262 }
1263 format!("CONCAT({})", parts.join(", "))
1264 }
1265
1266 fn supports_if_exists(&self) -> bool {
1267 true
1269 }
1270
1271 fn supports_if_not_exists(&self) -> bool {
1272 true
1274 }
1275
1276 fn auto_increment_keyword(&self) -> &'static str {
1277 "IDENTITY(1,1)"
1279 }
1280
1281 fn last_insert_id_sql(&self) -> Option<&'static str> {
1282 Some("SCOPE_IDENTITY()")
1284 }
1285
1286 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1287 let cols: Vec<String> = columns
1288 .iter()
1289 .map(|col| {
1290 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1291 let mut sql = format!("{} {}", self.quote(&col.name), sqlserver_type);
1292 if !col.nullable {
1293 sql.push_str(" NOT NULL");
1294 }
1295 if let Some(default) = &col.default {
1296 sql.push_str(&format!(" DEFAULT {}", default));
1297 }
1298 if col.auto_increment {
1299 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1300 }
1301 if col.primary_key {
1302 sql.push_str(" PRIMARY KEY");
1303 }
1304 sql
1305 })
1306 .collect();
1307
1308 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1309 }
1310
1311 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1312 let stmts: Vec<String> = changes
1313 .iter()
1314 .map(|change| match change {
1315 TableChange::AddColumn(col) => {
1316 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1317 let mut sql = format!(
1318 "ALTER TABLE {} ADD {} {}",
1319 self.quote(table),
1320 self.quote(&col.name),
1321 sqlserver_type
1322 );
1323 if !col.nullable {
1324 sql.push_str(" NOT NULL");
1325 }
1326 if let Some(default) = &col.default {
1327 sql.push_str(&format!(" DEFAULT {}", default));
1328 }
1329 sql
1330 }
1331 TableChange::DropColumn(name) => {
1332 format!(
1333 "ALTER TABLE {} DROP COLUMN {}",
1334 self.quote(table),
1335 self.quote(name)
1336 )
1337 }
1338 TableChange::ModifyColumn(col) => {
1339 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1341 let mut sql = format!(
1342 "ALTER TABLE {} ALTER COLUMN {} {}",
1343 self.quote(table),
1344 self.quote(&col.name),
1345 sqlserver_type
1346 );
1347 if !col.nullable {
1348 sql.push_str(" NOT NULL");
1349 }
1350 if let Some(default) = &col.default {
1351 sql.push_str(&format!(" DEFAULT {}", default));
1352 }
1353 sql
1354 }
1355 TableChange::AddIndex(name, cols) => {
1356 format!(
1357 "CREATE INDEX {} ON {} ({})",
1358 name,
1359 self.quote(table),
1360 cols.join(", ")
1361 )
1362 }
1363 TableChange::DropIndex(name) => {
1364 format!("DROP INDEX {} ON {}", name, self.quote(table))
1366 }
1367 TableChange::AddForeignKey {
1368 columns,
1369 reference_table,
1370 reference_columns,
1371 } => {
1372 format!(
1373 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1374 self.quote(table),
1375 table,
1376 columns.join("_"),
1377 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1378 self.quote(reference_table),
1379 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1380 )
1381 }
1382 })
1383 .collect();
1384
1385 stmts.join("; ")
1386 }
1387
1388 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
1389 format!("INSERT INTO {}", self.quote(table))
1395 }
1396}
1397
1398macro_rules! delegate_dialect_to {
1415 ($wrapper:ident, $base:ident, $db_type:expr) => {
1416 #[derive(Debug, Clone)]
1418 pub struct $wrapper;
1419
1420 impl Dialect for $wrapper {
1421 fn clone_box(&self) -> Box<dyn Dialect> {
1422 Box::new($wrapper)
1423 }
1424
1425 fn db_type(&self) -> DbType {
1426 $db_type
1427 }
1428 fn quote(&self, identifier: &str) -> String {
1429 $base.quote(identifier)
1430 }
1431 fn escape_string(&self, s: &str) -> String {
1432 $base.escape_string(s)
1433 }
1434 fn supports_returning(&self) -> bool {
1435 $base.supports_returning()
1436 }
1437 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1438 $base.build_pagination(sql, page, limit)
1439 }
1440 fn json_type(&self) -> &'static str {
1441 $base.json_type()
1442 }
1443 fn json_extract(&self, column: &str, path: &str) -> String {
1444 $base.json_extract(column, path)
1445 }
1446 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1447 $base.full_text_search(columns, keyword)
1448 }
1449 fn bool_to_int(&self, expr: &str) -> String {
1450 $base.bool_to_int(expr)
1451 }
1452 fn concat(&self, parts: &[&str]) -> String {
1453 $base.concat(parts)
1454 }
1455 fn supports_if_exists(&self) -> bool {
1456 $base.supports_if_exists()
1457 }
1458 fn supports_if_not_exists(&self) -> bool {
1459 $base.supports_if_not_exists()
1460 }
1461 fn auto_increment_keyword(&self) -> &'static str {
1462 $base.auto_increment_keyword()
1463 }
1464 fn last_insert_id_sql(&self) -> Option<&'static str> {
1465 $base.last_insert_id_sql()
1466 }
1467 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1468 $base.build_create_table(table, columns)
1469 }
1470 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1471 $base.build_alter_table(table, changes)
1472 }
1473 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1474 $base.build_drop_table(table, if_exists)
1475 }
1476 }
1477 };
1478}
1479
1480delegate_dialect_to!(MariaDbDialect, MySqlDialect, DbType::MariaDB);
1482
1483delegate_dialect_to!(TiDbDialect, MySqlDialect, DbType::TiDB);
1485
1486delegate_dialect_to!(KingbaseDialect, PostgreSqlDialect, DbType::Kingbase);
1488
1489delegate_dialect_to!(PolarDbDialect, PostgreSqlDialect, DbType::PolarDB);
1491
1492delegate_dialect_to!(GaussDbDialect, PostgreSqlDialect, DbType::GaussDB);
1494
1495delegate_dialect_to!(DamengDialect, OracleDialect, DbType::Dameng);
1497
1498delegate_dialect_to!(SybaseDialect, SqlServerDialect, DbType::Sybase);
1500
1501delegate_dialect_to!(GBaseDialect, SqlServerDialect, DbType::GBase);
1503
1504#[derive(Debug, Clone)]
1518pub struct ClickHouseDialect;
1519
1520impl Dialect for ClickHouseDialect {
1521 fn clone_box(&self) -> Box<dyn Dialect> {
1522 Box::new(ClickHouseDialect)
1523 }
1524
1525 fn db_type(&self) -> DbType {
1526 DbType::ClickHouse
1527 }
1528
1529 fn quote(&self, identifier: &str) -> String {
1530 format!("`{}`", identifier.replace('`', "``"))
1532 }
1533
1534 fn escape_string(&self, s: &str) -> String {
1535 let mut escaped = String::with_capacity(s.len() * 2);
1537 for c in s.chars() {
1538 match c {
1539 '\'' => escaped.push_str("\\'"),
1540 '\\' => escaped.push_str("\\\\"),
1541 '\n' => escaped.push_str("\\n"),
1542 '\r' => escaped.push_str("\\r"),
1543 '\t' => escaped.push_str("\\t"),
1544 _ => escaped.push(c),
1545 }
1546 }
1547 escaped
1548 }
1549
1550 fn supports_returning(&self) -> bool {
1551 false
1553 }
1554
1555 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1556 let offset = page.saturating_sub(1).saturating_mul(limit);
1558 format!("{} LIMIT {}, {}", sql, offset, limit)
1559 }
1560
1561 fn json_type(&self) -> &'static str {
1562 "String"
1564 }
1565
1566 fn json_extract(&self, column: &str, path: &str) -> String {
1567 let normalized = if path.starts_with('$') {
1569 path.to_string()
1570 } else {
1571 format!("$.{}", path)
1572 };
1573 format!(
1574 "JSONExtractString({}, '{}')",
1575 column,
1576 self.escape_string(&normalized)
1577 )
1578 }
1579
1580 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1581 if columns.is_empty() {
1583 return "0".to_string();
1584 }
1585 let escaped = self.escape_string(keyword);
1586 let parts: Vec<String> = columns
1587 .iter()
1588 .map(|c| format!("position({}, '{}') > 0", c, escaped))
1589 .collect();
1590 parts.join(" OR ")
1591 }
1592
1593 fn bool_to_int(&self, expr: &str) -> String {
1594 format!("toUInt8({})", expr)
1596 }
1597
1598 fn concat(&self, parts: &[&str]) -> String {
1599 if parts.is_empty() {
1601 return "''".to_string();
1602 }
1603 format!("concat({})", parts.join(", "))
1604 }
1605
1606 fn supports_if_exists(&self) -> bool {
1607 true
1608 }
1609
1610 fn supports_if_not_exists(&self) -> bool {
1611 true
1612 }
1613
1614 fn auto_increment_keyword(&self) -> &'static str {
1615 ""
1617 }
1618
1619 fn last_insert_id_sql(&self) -> Option<&'static str> {
1620 None
1622 }
1623
1624 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1625 let cols: Vec<String> = columns
1626 .iter()
1627 .map(|col| {
1628 let ch_type = map_to_clickhouse_type(&col.sql_type);
1629 let mut sql = format!("{} {}", self.quote(&col.name), ch_type);
1630 if let Some(default) = &col.default {
1631 sql.push_str(&format!(" DEFAULT {}", default));
1632 }
1633 if col.primary_key {
1634 sql.push_str(" PRIMARY KEY");
1635 }
1636 sql
1637 })
1638 .collect();
1639
1640 format!(
1642 "CREATE TABLE {} ({}) ENGINE = MergeTree()",
1643 self.quote(table),
1644 cols.join(", ")
1645 )
1646 }
1647
1648 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1649 let stmts: Vec<String> = changes
1650 .iter()
1651 .map(|change| match change {
1652 TableChange::AddColumn(col) => {
1653 let ch_type = map_to_clickhouse_type(&col.sql_type);
1654 format!(
1655 "ALTER TABLE {} ADD COLUMN {} {}",
1656 self.quote(table),
1657 self.quote(&col.name),
1658 ch_type
1659 )
1660 }
1661 TableChange::DropColumn(name) => {
1662 format!(
1663 "ALTER TABLE {} DROP COLUMN {}",
1664 self.quote(table),
1665 self.quote(name)
1666 )
1667 }
1668 TableChange::ModifyColumn(col) => {
1669 let ch_type = map_to_clickhouse_type(&col.sql_type);
1670 format!(
1671 "ALTER TABLE {} MODIFY COLUMN {} {}",
1672 self.quote(table),
1673 self.quote(&col.name),
1674 ch_type
1675 )
1676 }
1677 TableChange::AddIndex(name, cols) => {
1678 format!(
1679 "ALTER TABLE {} ADD INDEX {} ({})",
1680 self.quote(table),
1681 name,
1682 cols.join(", ")
1683 )
1684 }
1685 TableChange::DropIndex(name) => {
1686 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
1687 }
1688 TableChange::AddForeignKey { .. } => {
1689 String::new()
1691 }
1692 })
1693 .filter(|s| !s.is_empty())
1694 .collect();
1695
1696 stmts.join("; ")
1697 }
1698
1699 fn supports_lock_for_update(&self) -> bool {
1700 false
1702 }
1703
1704 fn supports_lock_shared(&self) -> bool {
1705 false
1707 }
1708
1709 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
1710 format!("INSERT INTO {}", self.quote(table))
1712 }
1713}
1714
1715fn map_to_clickhouse_type(sql_type: &str) -> String {
1725 let upper = sql_type.to_uppercase();
1726 let trimmed = upper.trim();
1727
1728 if trimmed.starts_with("BIGINT") {
1729 "Int64".to_string()
1730 } else if matches!(trimmed, "INT" | "INTEGER") {
1731 "Int32".to_string()
1732 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
1733 "Int16".to_string()
1734 } else if trimmed.starts_with("VARCHAR")
1735 || trimmed.starts_with("CHAR")
1736 || matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT")
1737 {
1738 "String".to_string()
1739 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1740 "UInt8".to_string()
1741 } else if matches!(trimmed, "FLOAT" | "REAL") {
1742 "Float32".to_string()
1743 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
1744 "Float64".to_string()
1745 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
1746 "DateTime".to_string()
1747 } else if matches!(trimmed, "DATE") {
1748 "Date".to_string()
1749 } else if trimmed.starts_with("DECIMAL") || trimmed.starts_with("NUMERIC") {
1750 "Decimal(38, 4)".to_string()
1751 } else {
1752 sql_type.to_string()
1753 }
1754}
1755
1756#[derive(Debug, Clone)]
1771pub struct DuckDBDialect;
1772
1773impl Dialect for DuckDBDialect {
1774 fn clone_box(&self) -> Box<dyn Dialect> {
1775 Box::new(DuckDBDialect)
1776 }
1777
1778 fn db_type(&self) -> DbType {
1779 DbType::DuckDB
1780 }
1781
1782 fn quote(&self, identifier: &str) -> String {
1783 format!("\"{}\"", identifier.replace('"', "\"\""))
1785 }
1786
1787 fn escape_string(&self, s: &str) -> String {
1788 let mut escaped = String::with_capacity(s.len() * 2);
1790 for c in s.chars() {
1791 match c {
1792 '\'' => escaped.push_str("''"),
1793 _ => escaped.push(c),
1794 }
1795 }
1796 escaped
1797 }
1798
1799 fn supports_returning(&self) -> bool {
1800 false
1802 }
1803
1804 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1805 let offset = page.saturating_sub(1).saturating_mul(limit);
1807 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
1808 }
1809
1810 fn json_type(&self) -> &'static str {
1811 "JSON"
1813 }
1814
1815 fn json_extract(&self, column: &str, path: &str) -> String {
1816 let normalized = if path.starts_with('$') {
1818 path[2..].to_string()
1819 } else {
1820 path.to_string()
1821 };
1822 format!("{} -> '{}'", column, normalized)
1823 }
1824
1825 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1826 if columns.is_empty() {
1828 return "0".to_string();
1829 }
1830 let escaped = self.escape_string(keyword);
1831 let parts: Vec<String> = columns
1832 .iter()
1833 .map(|c| format!("{} LIKE '%{}%'", c, escaped))
1834 .collect();
1835 parts.join(" OR ")
1836 }
1837
1838 fn bool_to_int(&self, expr: &str) -> String {
1839 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1841 }
1842
1843 fn concat(&self, parts: &[&str]) -> String {
1844 if parts.is_empty() {
1846 return "''".to_string();
1847 }
1848 parts.join(" || ")
1849 }
1850
1851 fn supports_if_exists(&self) -> bool {
1852 true
1853 }
1854
1855 fn supports_if_not_exists(&self) -> bool {
1856 true
1857 }
1858
1859 fn auto_increment_keyword(&self) -> &'static str {
1860 ""
1862 }
1863
1864 fn last_insert_id_sql(&self) -> Option<&'static str> {
1865 None
1867 }
1868
1869 fn supports_lock_for_update(&self) -> bool {
1870 false
1872 }
1873
1874 fn supports_lock_shared(&self) -> bool {
1875 false
1877 }
1878
1879 fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
1880 format!("INSERT OR IGNORE INTO {}", self.quote(table))
1882 }
1883
1884 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1885 let cols: Vec<String> = columns
1886 .iter()
1887 .map(|col| {
1888 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
1889 if col.auto_increment {
1890 sql = format!("{} BIGINT PRIMARY KEY", self.quote(&col.name));
1892 }
1893 if let Some(default) = &col.default {
1894 sql.push_str(&format!(" DEFAULT {}", default));
1895 }
1896 if col.primary_key && !col.auto_increment {
1897 sql.push_str(" PRIMARY KEY");
1898 }
1899 sql
1900 })
1901 .collect();
1902
1903 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1904 }
1905
1906 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1907 let stmts: Vec<String> = changes
1908 .iter()
1909 .map(|change| match change {
1910 TableChange::AddColumn(col) => {
1911 let mut sql = format!(
1912 "ALTER TABLE {} ADD COLUMN {} {}",
1913 self.quote(table),
1914 self.quote(&col.name),
1915 col.sql_type
1916 );
1917 if let Some(default) = &col.default {
1918 sql.push_str(&format!(" DEFAULT {}", default));
1919 }
1920 sql
1921 }
1922 TableChange::DropColumn(name) => {
1923 format!(
1924 "ALTER TABLE {} DROP COLUMN {}",
1925 self.quote(table),
1926 self.quote(name)
1927 )
1928 }
1929 TableChange::ModifyColumn(col) => {
1930 format!(
1931 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
1932 self.quote(table),
1933 self.quote(&col.name),
1934 col.sql_type
1935 )
1936 }
1937 TableChange::AddIndex(name, _cols) => {
1938 format!(
1940 "CREATE INDEX {} ON {} (id)",
1941 self.quote(name),
1942 self.quote(table)
1943 )
1944 }
1945 TableChange::DropIndex(name) => {
1946 format!("DROP INDEX {}", self.quote(name))
1947 }
1948 TableChange::AddForeignKey { .. } => {
1949 String::new()
1951 }
1952 })
1953 .filter(|s| !s.is_empty())
1954 .collect();
1955
1956 stmts.join("; ")
1957 }
1958}
1959
1960#[derive(Debug, Clone)]
1974pub struct Db2Dialect;
1975
1976impl Dialect for Db2Dialect {
1977 fn clone_box(&self) -> Box<dyn Dialect> {
1978 Box::new(Db2Dialect)
1979 }
1980
1981 fn db_type(&self) -> DbType {
1982 DbType::Db2
1983 }
1984
1985 fn quote(&self, identifier: &str) -> String {
1986 format!("\"{}\"", identifier.replace('"', "\"\""))
1988 }
1989
1990 fn escape_string(&self, s: &str) -> String {
1991 let mut escaped = String::with_capacity(s.len() * 2);
1993 for c in s.chars() {
1994 match c {
1995 '\'' => escaped.push_str("''"),
1996 _ => escaped.push(c),
1997 }
1998 }
1999 escaped
2000 }
2001
2002 fn supports_returning(&self) -> bool {
2003 false
2005 }
2006
2007 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2008 let offset = page.saturating_sub(1).saturating_mul(limit);
2010 format!(
2011 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
2012 sql, offset, limit
2013 )
2014 }
2015
2016 fn json_type(&self) -> &'static str {
2017 "JSON"
2019 }
2020
2021 fn json_extract(&self, column: &str, path: &str) -> String {
2022 let normalized = if path.starts_with('$') {
2024 path.to_string()
2025 } else {
2026 format!("$.{}", path)
2027 };
2028 format!(
2029 "JSON_VALUE({}, '{}')",
2030 column,
2031 self.escape_string(&normalized)
2032 )
2033 }
2034
2035 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2036 if columns.is_empty() {
2038 return "0".to_string();
2039 }
2040 let escaped = self.escape_string(keyword);
2041 let parts: Vec<String> = columns
2042 .iter()
2043 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2044 .collect();
2045 parts.join(" OR ")
2046 }
2047
2048 fn bool_to_int(&self, expr: &str) -> String {
2049 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2051 }
2052
2053 fn concat(&self, parts: &[&str]) -> String {
2054 if parts.is_empty() {
2056 return "''".to_string();
2057 }
2058 parts.join(" || ")
2059 }
2060
2061 fn supports_if_exists(&self) -> bool {
2062 false
2064 }
2065
2066 fn supports_if_not_exists(&self) -> bool {
2067 false
2069 }
2070
2071 fn auto_increment_keyword(&self) -> &'static str {
2072 "GENERATED ALWAYS AS IDENTITY"
2074 }
2075
2076 fn last_insert_id_sql(&self) -> Option<&'static str> {
2077 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
2079 }
2080
2081 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2082 let cols: Vec<String> = columns
2083 .iter()
2084 .map(|col| {
2085 let db2_type = map_to_db2_type(&col.sql_type);
2086 let mut sql = format!("{} {}", self.quote(&col.name), db2_type);
2087 if !col.nullable && !col.auto_increment {
2088 sql.push_str(" NOT NULL");
2089 }
2090 if let Some(default) = &col.default {
2091 sql.push_str(&format!(" DEFAULT {}", default));
2092 }
2093 if col.auto_increment {
2094 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2095 }
2096 if col.primary_key {
2097 sql.push_str(" PRIMARY KEY");
2098 }
2099 sql
2100 })
2101 .collect();
2102
2103 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2104 }
2105
2106 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2107 let stmts: Vec<String> = changes
2108 .iter()
2109 .map(|change| match change {
2110 TableChange::AddColumn(col) => {
2111 let db2_type = map_to_db2_type(&col.sql_type);
2112 let mut sql = format!(
2113 "ALTER TABLE {} ADD COLUMN {} {}",
2114 self.quote(table),
2115 self.quote(&col.name),
2116 db2_type
2117 );
2118 if !col.nullable {
2119 sql.push_str(" NOT NULL");
2120 }
2121 if let Some(default) = &col.default {
2122 sql.push_str(&format!(" DEFAULT {}", default));
2123 }
2124 sql
2125 }
2126 TableChange::DropColumn(name) => {
2127 format!(
2128 "ALTER TABLE {} DROP COLUMN {}",
2129 self.quote(table),
2130 self.quote(name)
2131 )
2132 }
2133 TableChange::ModifyColumn(col) => {
2134 let db2_type = map_to_db2_type(&col.sql_type);
2135 format!(
2136 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
2137 self.quote(table),
2138 self.quote(&col.name),
2139 db2_type
2140 )
2141 }
2142 TableChange::AddIndex(name, cols) => {
2143 format!(
2144 "CREATE INDEX {} ON {} ({})",
2145 name,
2146 self.quote(table),
2147 cols.join(", ")
2148 )
2149 }
2150 TableChange::DropIndex(name) => {
2151 format!("DROP INDEX {}", name)
2152 }
2153 TableChange::AddForeignKey {
2154 columns,
2155 reference_table,
2156 reference_columns,
2157 } => {
2158 format!(
2159 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
2160 self.quote(table),
2161 table,
2162 columns.join("_"),
2163 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
2164 self.quote(reference_table),
2165 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
2166 )
2167 }
2168 })
2169 .collect();
2170
2171 stmts.join("; ")
2172 }
2173
2174 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
2175 let _ = if_exists;
2177 format!("DROP TABLE {}", self.quote(table))
2178 }
2179}
2180
2181fn map_to_db2_type(sql_type: &str) -> String {
2190 let upper = sql_type.to_uppercase();
2191 let trimmed = upper.trim();
2192
2193 if trimmed.starts_with("BIGINT") {
2194 "BIGINT".to_string()
2195 } else if matches!(trimmed, "INT" | "INTEGER") {
2196 "INTEGER".to_string()
2197 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
2198 "SMALLINT".to_string()
2199 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2200 sql_type.to_string()
2201 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
2202 "CLOB(2G)".to_string()
2203 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2204 "SMALLINT".to_string()
2205 } else if matches!(trimmed, "FLOAT" | "REAL") {
2206 "REAL".to_string()
2207 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2208 "DOUBLE".to_string()
2209 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2210 "TIMESTAMP".to_string()
2211 } else if matches!(trimmed, "DATE") {
2212 "DATE".to_string()
2213 } else {
2214 sql_type.to_string()
2216 }
2217}
2218
2219pub fn get_dialect(db_type: DbType) -> Result<Box<dyn Dialect>, DbError> {
2242 match db_type {
2243 DbType::MySQL => Ok(Box::new(MySqlDialect)),
2244 DbType::PostgreSQL => Ok(Box::new(PostgreSqlDialect)),
2245 DbType::Sqlite => Ok(Box::new(SqliteDialect)),
2246 DbType::Redis => Err(DbError::Unsupported(
2247 "Redis does not support standard SQL dialect".to_string(),
2248 )),
2249 DbType::MongoDB => Err(DbError::Unsupported(
2250 "MongoDB uses different query syntax".to_string(),
2251 )),
2252 DbType::ClickHouse => Ok(Box::new(ClickHouseDialect)),
2253 DbType::Oracle => Ok(Box::new(OracleDialect)),
2254 DbType::OceanBase => Ok(Box::new(MySqlDialect)),
2255 DbType::SqlServer => Ok(Box::new(SqlServerDialect)),
2256 DbType::VectorDb => Err(DbError::Unsupported(
2257 "Vector databases have specific APIs".to_string(),
2258 )),
2259 DbType::PureJsDb => Err(DbError::Unsupported(
2260 "PureJS database uses JavaScript".to_string(),
2261 )),
2262 DbType::Dameng => Ok(Box::new(DamengDialect)),
2264 DbType::Kingbase => Ok(Box::new(KingbaseDialect)),
2265 DbType::Db2 => Ok(Box::new(Db2Dialect)),
2266 DbType::MariaDB => Ok(Box::new(MariaDbDialect)),
2267 DbType::TiDB => Ok(Box::new(TiDbDialect)),
2268 DbType::PolarDB => Ok(Box::new(PolarDbDialect)),
2269 DbType::GaussDB => Ok(Box::new(GaussDbDialect)),
2270 DbType::GBase => Ok(Box::new(GBaseDialect)),
2271 DbType::Sybase => Ok(Box::new(SybaseDialect)),
2272 DbType::DuckDB => Ok(Box::new(DuckDBDialect)),
2273 }
2274}
2275
2276impl fmt::Display for dyn Dialect {
2277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2278 write!(f, "Dialect({})", self.db_type())
2279 }
2280}
2281
2282#[cfg(test)]
2283mod tests {
2284 use super::*;
2285
2286 #[test]
2287 fn test_mysql_quote() {
2288 let dialect = MySqlDialect;
2289 assert_eq!(dialect.quote("users"), "`users`");
2290 assert_eq!(dialect.quote("user`id"), "`user``id`");
2291 }
2292
2293 #[test]
2294 fn test_mysql_escape() {
2295 let dialect = MySqlDialect;
2296 assert_eq!(dialect.escape_string("hello"), "hello");
2297 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2298 assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
2299 }
2300
2301 #[test]
2302 fn test_mysql_pagination() {
2303 let dialect = MySqlDialect;
2304 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
2305 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
2306 }
2307
2308 #[test]
2309 fn test_postgres_quote() {
2310 let dialect = PostgreSqlDialect;
2311 assert_eq!(dialect.quote("users"), "\"users\"");
2312 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2313 }
2314
2315 #[test]
2316 fn test_postgres_pagination() {
2317 let dialect = PostgreSqlDialect;
2318 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2319 assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
2320 }
2321
2322 #[test]
2323 fn test_postgres_returning() {
2324 let dialect = PostgreSqlDialect;
2325 assert!(dialect.supports_returning());
2326 }
2327
2328 #[test]
2329 fn test_sqlite_quote() {
2330 let dialect = SqliteDialect;
2331 assert_eq!(dialect.quote("users"), "\"users\"");
2332 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2333 }
2334
2335 #[test]
2336 fn test_sqlite_escape() {
2337 let dialect = SqliteDialect;
2338 assert_eq!(dialect.escape_string("hello"), "hello");
2339 assert_eq!(dialect.escape_string("it's"), "it''s");
2340 }
2341
2342 #[test]
2343 fn test_get_dialect() {
2344 let dialect = get_dialect(DbType::MySQL);
2345 assert!(dialect.is_ok());
2346
2347 let dialect = get_dialect(DbType::Redis);
2348 assert!(dialect.is_err());
2349 }
2350
2351 #[test]
2352 fn test_bool_to_int() {
2353 let mysql = MySqlDialect;
2354 assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
2355
2356 let pg = PostgreSqlDialect;
2357 assert_eq!(
2358 pg.bool_to_int("active"),
2359 "(CASE WHEN active THEN 1 ELSE 0 END)"
2360 );
2361 }
2362
2363 #[test]
2364 fn test_json_extract_with_path() {
2365 let mysql = MySqlDialect;
2366 let sql = mysql.json_extract("data", "$.user.name");
2367 assert!(sql.contains("$.user.name"));
2368 assert!(sql.contains("JSON_EXTRACT"));
2369
2370 let pg = PostgreSqlDialect;
2371 let sql = pg.json_extract("data", "user.name");
2372 assert!(sql.contains("#>>"));
2373
2374 let sqlite = SqliteDialect;
2375 let sql = sqlite.json_extract("data", "$.user.name");
2376 assert!(sql.contains("$.user.name"));
2377 assert!(sql.contains("json_extract"));
2378 }
2379
2380 #[test]
2381 fn test_sqlite_full_text_search() {
2382 let sqlite = SqliteDialect;
2383 let sql = sqlite.full_text_search(&["title", "content"], "hello");
2384 assert!(sql.contains("LIKE"));
2386 assert!(sql.contains("title LIKE '%hello%'"));
2387 assert!(sql.contains("content LIKE '%hello%'"));
2388 assert!(sql.contains(" OR "));
2389
2390 assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
2392
2393 let sql = sqlite.full_text_search(&["title"], "it's");
2395 assert!(sql.contains("title LIKE '%it''s%'"));
2396 }
2397
2398 #[test]
2399 fn test_alter_table_modify_column() {
2400 let mysql = MySqlDialect;
2401 let col = ColumnDef {
2402 name: "name".to_string(),
2403 sql_type: "VARCHAR(255)".to_string(),
2404 nullable: false,
2405 default: None,
2406 auto_increment: false,
2407 primary_key: false,
2408 };
2409 let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2410 assert!(sql.contains("MODIFY COLUMN"));
2411
2412 let pg = PostgreSqlDialect;
2413 let col = ColumnDef {
2414 name: "name".to_string(),
2415 sql_type: "VARCHAR(255)".to_string(),
2416 nullable: false,
2417 default: None,
2418 auto_increment: false,
2419 primary_key: false,
2420 };
2421 let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2422 assert!(sql.contains("ALTER COLUMN"));
2423 assert!(sql.contains("TYPE"));
2424 }
2425
2426 #[test]
2427 fn test_alter_table_add_foreign_key() {
2428 let mysql = MySqlDialect;
2429 let sql = mysql.build_alter_table(
2430 "orders",
2431 &[TableChange::AddForeignKey {
2432 columns: vec!["user_id".to_string()],
2433 reference_table: "users".to_string(),
2434 reference_columns: vec!["id".to_string()],
2435 }],
2436 );
2437 assert!(sql.contains("FOREIGN KEY"));
2438 assert!(sql.contains("REFERENCES"));
2439
2440 let sqlite = SqliteDialect;
2441 let sql = sqlite.build_alter_table(
2442 "orders",
2443 &[TableChange::AddForeignKey {
2444 columns: vec!["user_id".to_string()],
2445 reference_table: "users".to_string(),
2446 reference_columns: vec!["id".to_string()],
2447 }],
2448 );
2449 assert!(sql.starts_with("--"));
2451 }
2452
2453 #[test]
2454 fn test_sqlite_alter_table_add_column() {
2455 let sqlite = SqliteDialect;
2456 let col = ColumnDef {
2457 name: "email".to_string(),
2458 sql_type: "TEXT".to_string(),
2459 nullable: true,
2460 default: None,
2461 auto_increment: false,
2462 primary_key: false,
2463 };
2464 let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
2465 assert!(sql.contains("ADD COLUMN"));
2466 assert!(sql.contains("email"));
2467 }
2468
2469 #[test]
2472 fn test_oracle_quote_and_escape() {
2473 let dialect = OracleDialect;
2474 assert_eq!(dialect.quote("users"), "\"users\"");
2476 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2477 assert_eq!(dialect.quote("column_name"), "\"column_name\"");
2478
2479 assert_eq!(dialect.escape_string("hello"), "hello");
2481 assert_eq!(dialect.escape_string("it's"), "it''s");
2482 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2483 assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
2484 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2486 }
2487
2488 #[test]
2489 fn test_oracle_pagination() {
2490 let dialect = OracleDialect;
2491 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2493 assert_eq!(
2494 sql,
2495 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2496 );
2497 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2499 assert_eq!(
2500 sql,
2501 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2502 );
2503 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2505 assert_eq!(
2506 sql,
2507 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2508 );
2509 }
2510
2511 #[test]
2512 fn test_oracle_json_extract() {
2513 let dialect = OracleDialect;
2514 let sql = dialect.json_extract("data", "$.user.name");
2516 assert!(sql.contains("JSON_VALUE"));
2517 assert!(sql.contains("$.user.name"));
2518 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2519
2520 let sql = dialect.json_extract("data", "user.name");
2522 assert!(sql.contains("$.user.name"));
2523 assert!(sql.contains("JSON_VALUE"));
2524
2525 let sql = dialect.json_extract("data", "$.key's");
2527 assert!(sql.contains("$.key''s"));
2528 }
2529
2530 #[test]
2531 fn test_oracle_create_table() {
2532 let dialect = OracleDialect;
2533 let columns = vec![
2534 ColumnDef {
2535 name: "id".to_string(),
2536 sql_type: "BIGINT".to_string(),
2537 nullable: false,
2538 default: None,
2539 auto_increment: true,
2540 primary_key: true,
2541 },
2542 ColumnDef {
2543 name: "name".to_string(),
2544 sql_type: "VARCHAR(255)".to_string(),
2545 nullable: false,
2546 default: None,
2547 auto_increment: false,
2548 primary_key: false,
2549 },
2550 ColumnDef {
2551 name: "bio".to_string(),
2552 sql_type: "TEXT".to_string(),
2553 nullable: true,
2554 default: None,
2555 auto_increment: false,
2556 primary_key: false,
2557 },
2558 ColumnDef {
2559 name: "is_active".to_string(),
2560 sql_type: "BOOLEAN".to_string(),
2561 nullable: false,
2562 default: Some("1".to_string()),
2563 auto_increment: false,
2564 primary_key: false,
2565 },
2566 ];
2567 let sql = dialect.build_create_table("users", &columns);
2568 assert!(
2570 sql.contains("NUMBER(19)"),
2571 "BIGINT should map to NUMBER(19): {}",
2572 sql
2573 );
2574 assert!(
2575 sql.contains("VARCHAR2(255)"),
2576 "VARCHAR should map to VARCHAR2: {}",
2577 sql
2578 );
2579 assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
2580 assert!(
2581 sql.contains("NUMBER(1)"),
2582 "BOOLEAN should map to NUMBER(1): {}",
2583 sql
2584 );
2585 assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
2587 assert!(sql.contains("PRIMARY KEY"));
2588 assert!(sql.contains("NOT NULL"));
2589 assert!(sql.contains("DEFAULT 1"));
2590 assert!(sql.contains("\"users\""));
2592 assert!(sql.contains("\"id\""));
2593 }
2594
2595 #[test]
2596 fn test_oracle_bool_to_int_and_concat() {
2597 let dialect = OracleDialect;
2598 assert_eq!(
2600 dialect.bool_to_int("active"),
2601 "(CASE WHEN active THEN 1 ELSE 0 END)"
2602 );
2603 assert_eq!(
2604 dialect.bool_to_int("x > 0"),
2605 "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
2606 );
2607 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
2609 assert_eq!(
2610 dialect.concat(&["first_name", "last_name"]),
2611 "first_name || last_name"
2612 );
2613 assert_eq!(dialect.concat(&[]), "NULL");
2615 }
2616
2617 #[test]
2618 fn test_oracle_misc_dialect_methods() {
2619 let dialect = OracleDialect;
2620 assert_eq!(dialect.db_type(), DbType::Oracle);
2622 assert!(dialect.supports_returning());
2624 assert!(dialect.supports_if_exists());
2626 assert!(dialect.supports_if_not_exists());
2627 assert_eq!(
2629 dialect.auto_increment_keyword(),
2630 "GENERATED BY DEFAULT AS IDENTITY"
2631 );
2632 assert_eq!(dialect.last_insert_id_sql(), None);
2635 assert_eq!(dialect.json_type(), "JSON");
2637 }
2638
2639 #[test]
2640 fn test_oracle_get_dialect() {
2641 let dialect = get_dialect(DbType::Oracle);
2643 assert!(dialect.is_ok(), "Oracle dialect should be available");
2644 let dialect = dialect.unwrap();
2645 assert_eq!(dialect.db_type(), DbType::Oracle);
2646 assert_eq!(dialect.quote("users"), "\"users\"");
2648 assert!(dialect.supports_returning());
2650 assert_eq!(dialect.last_insert_id_sql(), None);
2652 }
2653
2654 #[test]
2655 fn test_oracle_drop_table() {
2656 let dialect = OracleDialect;
2657 let sql = dialect.build_drop_table("users", true);
2659 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
2660 let sql = dialect.build_drop_table("users", false);
2662 assert_eq!(sql, "DROP TABLE \"users\"");
2663 }
2664
2665 #[test]
2666 fn test_oracle_alter_table() {
2667 let dialect = OracleDialect;
2668 let col = ColumnDef {
2670 name: "name".to_string(),
2671 sql_type: "VARCHAR(255)".to_string(),
2672 nullable: false,
2673 default: None,
2674 auto_increment: false,
2675 primary_key: false,
2676 };
2677 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2678 assert!(sql.contains("MODIFY"));
2679 assert!(sql.contains("VARCHAR2(255)"));
2680 assert!(!sql.contains("MODIFY COLUMN")); let col = ColumnDef {
2684 name: "email".to_string(),
2685 sql_type: "VARCHAR(255)".to_string(),
2686 nullable: true,
2687 default: None,
2688 auto_increment: false,
2689 primary_key: false,
2690 };
2691 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2692 assert!(sql.contains("ADD \"email\""));
2693 assert!(sql.contains("VARCHAR2(255)"));
2694
2695 let sql =
2697 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2698 assert!(sql.contains("DROP COLUMN"));
2699 assert!(sql.contains("\"email\""));
2700 }
2701
2702 #[test]
2705 fn test_sqlite_concat_handles_null() {
2706 let sqlite = SqliteDialect;
2707 let sql = sqlite.concat(&["a", "b"]);
2710 assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
2711 let sql = sqlite.concat(&["a"]);
2713 assert_eq!(sql, "COALESCE(a, '')");
2714 assert_eq!(sqlite.concat(&[]), "NULL");
2716 }
2717
2718 #[test]
2721 fn test_sqlserver_quote_and_escape() {
2722 let dialect = SqlServerDialect;
2723 assert_eq!(dialect.quote("users"), "[users]");
2725 assert_eq!(dialect.quote("col]name"), "[col]]name]");
2726 assert_eq!(dialect.escape_string("hello"), "hello");
2728 assert_eq!(dialect.escape_string("it's"), "it''s");
2729 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2730 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2731 }
2732
2733 #[test]
2734 fn test_sqlserver_pagination() {
2735 let dialect = SqlServerDialect;
2736 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2738 assert_eq!(
2739 sql,
2740 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2741 );
2742 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2743 assert_eq!(
2744 sql,
2745 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2746 );
2747 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2749 assert_eq!(
2750 sql,
2751 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2752 );
2753 }
2754
2755 #[test]
2756 fn test_sqlserver_misc_dialect_methods() {
2757 let dialect = SqlServerDialect;
2758 assert_eq!(dialect.db_type(), DbType::SqlServer);
2759 assert!(dialect.supports_returning());
2761 assert!(dialect.supports_if_exists());
2763 assert!(dialect.supports_if_not_exists());
2764 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2766 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2768 assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
2770 }
2771
2772 #[test]
2773 fn test_sqlserver_insert_or_ignore_fallback_to_plain_insert() {
2774 let dialect = SqlServerDialect;
2775 let sql = dialect.build_insert_or_ignore_prefix("users");
2776 assert_eq!(sql, "INSERT INTO [users]");
2778 assert!(!sql.contains("OR IGNORE"));
2779 }
2780
2781 #[test]
2782 fn test_sqlserver_json_extract() {
2783 let dialect = SqlServerDialect;
2784 let sql = dialect.json_extract("data", "$.user.name");
2785 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2786 let sql = dialect.json_extract("data", "user.name");
2788 assert!(sql.contains("$.user.name"));
2789 assert!(sql.contains("JSON_VALUE"));
2790 let sql = dialect.json_extract("data", "$.key's");
2792 assert!(sql.contains("$.key''s"));
2793 }
2794
2795 #[test]
2796 fn test_sqlserver_full_text_search() {
2797 let dialect = SqlServerDialect;
2798 let sql = dialect.full_text_search(&["title", "content"], "hello");
2799 assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
2800 assert_eq!(dialect.full_text_search(&[], "hello"), "0");
2802 let sql = dialect.full_text_search(&["title"], "it's");
2804 assert!(sql.contains("it''s"));
2805 }
2806
2807 #[test]
2808 fn test_sqlserver_bool_to_int_and_concat() {
2809 let dialect = SqlServerDialect;
2810 assert_eq!(
2811 dialect.bool_to_int("active"),
2812 "(CASE WHEN active THEN 1 ELSE 0 END)"
2813 );
2814 assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
2815 assert_eq!(dialect.concat(&[]), "NULL");
2816 }
2817
2818 #[test]
2819 fn test_sqlserver_create_table() {
2820 let dialect = SqlServerDialect;
2821 let columns = vec![
2822 ColumnDef {
2823 name: "id".to_string(),
2824 sql_type: "BIGINT".to_string(),
2825 nullable: false,
2826 default: None,
2827 auto_increment: true,
2828 primary_key: true,
2829 },
2830 ColumnDef {
2831 name: "name".to_string(),
2832 sql_type: "VARCHAR(255)".to_string(),
2833 nullable: false,
2834 default: None,
2835 auto_increment: false,
2836 primary_key: false,
2837 },
2838 ColumnDef {
2839 name: "bio".to_string(),
2840 sql_type: "TEXT".to_string(),
2841 nullable: true,
2842 default: None,
2843 auto_increment: false,
2844 primary_key: false,
2845 },
2846 ColumnDef {
2847 name: "is_active".to_string(),
2848 sql_type: "BOOLEAN".to_string(),
2849 nullable: false,
2850 default: Some("1".to_string()),
2851 auto_increment: false,
2852 primary_key: false,
2853 },
2854 ];
2855 let sql = dialect.build_create_table("users", &columns);
2856 assert!(sql.contains("[users]"));
2858 assert!(sql.contains("[id]"));
2859 assert!(sql.contains("IDENTITY(1,1)"));
2861 assert!(
2862 sql.contains("NVARCHAR(255)"),
2863 "VARCHAR should map to NVARCHAR: {}",
2864 sql
2865 );
2866 assert!(
2867 sql.contains("NVARCHAR(MAX)"),
2868 "TEXT should map to NVARCHAR(MAX): {}",
2869 sql
2870 );
2871 assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
2872 assert!(sql.contains("PRIMARY KEY"));
2873 assert!(sql.contains("NOT NULL"));
2874 assert!(sql.contains("DEFAULT 1"));
2875 }
2876
2877 #[test]
2878 fn test_sqlserver_drop_table() {
2879 let dialect = SqlServerDialect;
2880 assert_eq!(
2881 dialect.build_drop_table("users", true),
2882 "DROP TABLE IF EXISTS [users]"
2883 );
2884 assert_eq!(
2885 dialect.build_drop_table("users", false),
2886 "DROP TABLE [users]"
2887 );
2888 }
2889
2890 #[test]
2891 fn test_sqlserver_alter_table() {
2892 let dialect = SqlServerDialect;
2893 let col = ColumnDef {
2895 name: "name".to_string(),
2896 sql_type: "VARCHAR(255)".to_string(),
2897 nullable: false,
2898 default: None,
2899 auto_increment: false,
2900 primary_key: false,
2901 };
2902 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2903 assert!(sql.contains("ALTER COLUMN"));
2904 assert!(sql.contains("NVARCHAR(255)"));
2905 assert!(!sql.contains("MODIFY"));
2906
2907 let col = ColumnDef {
2909 name: "email".to_string(),
2910 sql_type: "VARCHAR(255)".to_string(),
2911 nullable: true,
2912 default: None,
2913 auto_increment: false,
2914 primary_key: false,
2915 };
2916 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2917 assert!(sql.contains("ADD [email]"));
2918 assert!(sql.contains("NVARCHAR(255)"));
2919
2920 let sql =
2922 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2923 assert!(sql.contains("DROP COLUMN"));
2924 assert!(sql.contains("[email]"));
2925
2926 let sql =
2928 dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
2929 assert!(sql.contains("DROP INDEX idx_name ON [users]"));
2930 }
2931
2932 #[test]
2933 fn test_sqlserver_get_dialect() {
2934 let dialect = get_dialect(DbType::SqlServer);
2936 assert!(dialect.is_ok(), "SqlServer dialect should be available");
2937 let dialect = dialect.unwrap();
2938 assert_eq!(dialect.db_type(), DbType::SqlServer);
2939 assert_eq!(dialect.quote("users"), "[users]");
2941 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2943 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2944 }
2945
2946 #[test]
2947 fn test_clickhouse_get_dialect_unsupported() {
2948 let dialect = get_dialect(DbType::ClickHouse);
2950 assert!(dialect.is_ok(), "ClickHouse should be supported");
2951 let dialect = dialect.unwrap();
2952 assert_eq!(dialect.db_type(), DbType::ClickHouse);
2953 assert_eq!(dialect.quote("users"), "`users`");
2955 assert!(!dialect.supports_returning());
2957 let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
2959 assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
2960 assert_eq!(dialect.auto_increment_keyword(), "");
2962 }
2963
2964 #[test]
2965 fn test_get_dialect_all_supported_types() {
2966 assert!(get_dialect(DbType::MySQL).is_ok());
2968 assert!(get_dialect(DbType::PostgreSQL).is_ok());
2969 assert!(get_dialect(DbType::Sqlite).is_ok());
2970 assert!(get_dialect(DbType::Oracle).is_ok());
2971 assert!(get_dialect(DbType::SqlServer).is_ok());
2972 assert!(get_dialect(DbType::OceanBase).is_ok());
2973 assert!(get_dialect(DbType::ClickHouse).is_ok());
2974 assert!(get_dialect(DbType::Dameng).is_ok());
2976 assert!(get_dialect(DbType::Kingbase).is_ok());
2977 assert!(get_dialect(DbType::Db2).is_ok());
2978 assert!(get_dialect(DbType::MariaDB).is_ok());
2979 assert!(get_dialect(DbType::TiDB).is_ok());
2980 assert!(get_dialect(DbType::PolarDB).is_ok());
2981 assert!(get_dialect(DbType::GaussDB).is_ok());
2982 assert!(get_dialect(DbType::GBase).is_ok());
2983 assert!(get_dialect(DbType::Sybase).is_ok());
2984 assert!(get_dialect(DbType::Redis).is_err());
2986 assert!(get_dialect(DbType::MongoDB).is_err());
2987 assert!(get_dialect(DbType::VectorDb).is_err());
2988 assert!(get_dialect(DbType::PureJsDb).is_err());
2989 }
2990
2991 #[test]
2994 fn test_mariadb_dialect() {
2995 let dialect = get_dialect(DbType::MariaDB).unwrap();
2996 assert_eq!(dialect.db_type(), DbType::MariaDB);
2997 assert_eq!(dialect.quote("users"), "`users`");
2999 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3000 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
3001 assert!(!dialect.supports_returning());
3003 }
3004
3005 #[test]
3006 fn test_tidb_dialect() {
3007 let dialect = get_dialect(DbType::TiDB).unwrap();
3008 assert_eq!(dialect.db_type(), DbType::TiDB);
3009 assert_eq!(dialect.quote("users"), "`users`");
3011 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3012 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
3013 }
3014
3015 #[test]
3016 fn test_dameng_dialect() {
3017 let dialect = get_dialect(DbType::Dameng).unwrap();
3018 assert_eq!(dialect.db_type(), DbType::Dameng);
3019 assert_eq!(dialect.quote("users"), "\"users\"");
3021 assert_eq!(dialect.escape_string("it's"), "it''s");
3022 assert_eq!(
3024 dialect.auto_increment_keyword(),
3025 "GENERATED BY DEFAULT AS IDENTITY"
3026 );
3027 assert!(dialect.supports_returning());
3029 }
3030
3031 #[test]
3032 fn test_kingbase_dialect() {
3033 let dialect = get_dialect(DbType::Kingbase).unwrap();
3034 assert_eq!(dialect.db_type(), DbType::Kingbase);
3035 assert_eq!(dialect.quote("users"), "\"users\"");
3037 assert_eq!(dialect.escape_string("it's"), "it''s");
3038 assert!(dialect.supports_returning());
3040 assert_eq!(
3042 dialect.auto_increment_keyword(),
3043 "GENERATED BY DEFAULT AS IDENTITY"
3044 );
3045 }
3046
3047 #[test]
3048 fn test_polardb_dialect() {
3049 let dialect = get_dialect(DbType::PolarDB).unwrap();
3050 assert_eq!(dialect.db_type(), DbType::PolarDB);
3051 assert_eq!(dialect.quote("users"), "\"users\"");
3053 assert!(dialect.supports_returning());
3054 }
3055
3056 #[test]
3057 fn test_gaussdb_dialect() {
3058 let dialect = get_dialect(DbType::GaussDB).unwrap();
3059 assert_eq!(dialect.db_type(), DbType::GaussDB);
3060 assert_eq!(dialect.quote("users"), "\"users\"");
3062 assert!(dialect.supports_returning());
3063 }
3064
3065 #[test]
3066 fn test_gbase_dialect() {
3067 let dialect = get_dialect(DbType::GBase).unwrap();
3068 assert_eq!(dialect.db_type(), DbType::GBase);
3069 assert_eq!(dialect.quote("users"), "[users]");
3071 }
3072
3073 #[test]
3074 fn test_sybase_dialect() {
3075 let dialect = get_dialect(DbType::Sybase).unwrap();
3076 assert_eq!(dialect.db_type(), DbType::Sybase);
3077 assert_eq!(dialect.quote("users"), "[users]");
3079 }
3080
3081 #[test]
3084 fn test_db2_dialect_basic() {
3085 let dialect = get_dialect(DbType::Db2).unwrap();
3086 assert_eq!(dialect.db_type(), DbType::Db2);
3087 assert_eq!(dialect.quote("users"), "\"users\"");
3089 assert_eq!(dialect.escape_string("it's"), "it''s");
3091 assert_eq!(
3093 dialect.auto_increment_keyword(),
3094 "GENERATED ALWAYS AS IDENTITY"
3095 );
3096 assert!(!dialect.supports_if_exists());
3098 assert!(!dialect.supports_if_not_exists());
3099 assert!(!dialect.supports_returning());
3101 }
3102
3103 #[test]
3104 fn test_db2_pagination() {
3105 let dialect = Db2Dialect;
3106 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3108 assert_eq!(
3109 sql,
3110 "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
3111 );
3112 }
3113
3114 #[test]
3115 fn test_db2_last_insert_id() {
3116 let dialect = Db2Dialect;
3117 assert_eq!(
3119 dialect.last_insert_id_sql(),
3120 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
3121 );
3122 }
3123
3124 #[test]
3125 fn test_db2_concat() {
3126 let dialect = Db2Dialect;
3127 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
3129 assert_eq!(dialect.concat(&[]), "''");
3130 }
3131
3132 #[test]
3133 fn test_db2_create_table() {
3134 let dialect = Db2Dialect;
3135 let cols = vec![ColumnDef {
3136 name: "id".to_string(),
3137 sql_type: "BIGINT".to_string(),
3138 nullable: false,
3139 default: None,
3140 auto_increment: true,
3141 primary_key: true,
3142 }];
3143 let sql = dialect.build_create_table("users", &cols);
3144 assert!(sql.contains("\"id\" BIGINT"));
3145 assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
3146 assert!(sql.contains("PRIMARY KEY"));
3147 }
3148
3149 #[test]
3150 fn test_db2_type_mapping() {
3151 assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
3153 assert_eq!(map_to_db2_type("INT"), "INTEGER");
3154 assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
3155 assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
3156 assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
3157 assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
3158 assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
3159 assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
3160 assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
3161 assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
3162 assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
3163 assert_eq!(map_to_db2_type("DATE"), "DATE");
3164 assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
3165 }
3166
3167 #[test]
3170 fn test_clickhouse_dialect_basic() {
3171 let dialect = get_dialect(DbType::ClickHouse).unwrap();
3172 assert_eq!(dialect.db_type(), DbType::ClickHouse);
3173 assert_eq!(dialect.quote("users"), "`users`");
3175 assert_eq!(dialect.escape_string("it's"), "it\\'s");
3177 assert!(!dialect.supports_returning());
3179 assert_eq!(dialect.auto_increment_keyword(), "");
3181 assert!(dialect.supports_if_exists());
3183 assert!(dialect.supports_if_not_exists());
3184 }
3185
3186 #[test]
3187 fn test_clickhouse_type_mapping() {
3188 assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
3189 assert_eq!(map_to_clickhouse_type("INT"), "Int32");
3190 assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
3191 assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
3192 assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
3193 assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
3194 assert_eq!(map_to_clickhouse_type("TEXT"), "String");
3195 assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
3196 assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
3197 assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
3198 assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
3199 assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
3200 assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
3201 assert_eq!(map_to_clickhouse_type("DATE"), "Date");
3202 }
3203
3204 #[test]
3205 fn test_clickhouse_create_table() {
3206 let dialect = ClickHouseDialect;
3207 let cols = vec![ColumnDef {
3208 name: "id".to_string(),
3209 sql_type: "BIGINT".to_string(),
3210 nullable: false,
3211 default: None,
3212 auto_increment: false, primary_key: true,
3214 }];
3215 let sql = dialect.build_create_table("users", &cols);
3216 assert!(
3218 sql.contains("ENGINE = MergeTree()"),
3219 "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
3220 sql
3221 );
3222 assert!(sql.contains("`id` Int64"));
3223 assert!(sql.contains("PRIMARY KEY"));
3224 }
3225
3226 #[test]
3227 fn test_clickhouse_json_extract() {
3228 let dialect = ClickHouseDialect;
3229 let sql = dialect.json_extract("data", "$.name");
3230 assert!(
3231 sql.contains("JSONExtractString"),
3232 "ClickHouse 应使用 JSONExtractString: {}",
3233 sql
3234 );
3235 }
3236
3237 #[test]
3238 fn test_clickhouse_concat() {
3239 let dialect = ClickHouseDialect;
3240 assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
3242 assert_eq!(dialect.concat(&[]), "''");
3243 }
3244
3245 #[test]
3248 fn test_db_type_dameng_str() {
3249 assert_eq!(DbType::Dameng.as_str(), "dameng");
3250 assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
3251 assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
3252 assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
3253 assert_eq!(DbType::Dameng.default_port(), 5236);
3254 }
3255
3256 #[test]
3257 fn test_db_type_kingbase_str() {
3258 assert_eq!(DbType::Kingbase.as_str(), "kingbase");
3259 assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
3260 assert_eq!(DbType::Kingbase.default_port(), 54321);
3261 }
3262
3263 #[test]
3264 fn test_db_type_db2_str() {
3265 assert_eq!(DbType::Db2.as_str(), "db2");
3266 assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
3267 assert_eq!(DbType::Db2.default_port(), 50000);
3268 }
3269
3270 #[test]
3271 fn test_db_type_mariadb_str() {
3272 assert_eq!(DbType::MariaDB.as_str(), "mariadb");
3273 assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
3274 assert_eq!(DbType::MariaDB.default_port(), 3306);
3275 }
3276
3277 #[test]
3278 fn test_db_type_tidb_str() {
3279 assert_eq!(DbType::TiDB.as_str(), "tidb");
3280 assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
3281 assert_eq!(DbType::TiDB.default_port(), 4000);
3282 }
3283
3284 #[test]
3285 fn test_db_type_polardb_str() {
3286 assert_eq!(DbType::PolarDB.as_str(), "polardb");
3287 assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
3288 assert_eq!(DbType::PolarDB.default_port(), 5432);
3289 }
3290
3291 #[test]
3292 fn test_db_type_gaussdb_str() {
3293 assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
3294 assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
3295 assert_eq!(DbType::GaussDB.default_port(), 25308);
3296 }
3297
3298 #[test]
3299 fn test_db_type_gbase_str() {
3300 assert_eq!(DbType::GBase.as_str(), "gbase");
3301 assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
3302 assert_eq!(DbType::GBase.default_port(), 9088);
3303 }
3304
3305 #[test]
3306 fn test_db_type_sybase_str() {
3307 assert_eq!(DbType::Sybase.as_str(), "sybase");
3308 assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
3309 assert_eq!(DbType::Sybase.default_port(), 5000);
3310 }
3311
3312 #[test]
3313 fn test_db_type_family_classification() {
3314 assert!(DbType::MySQL.is_mysql_family());
3316 assert!(DbType::MariaDB.is_mysql_family());
3317 assert!(DbType::TiDB.is_mysql_family());
3318 assert!(DbType::OceanBase.is_mysql_family());
3319 assert!(!DbType::PostgreSQL.is_mysql_family());
3320
3321 assert!(DbType::PostgreSQL.is_postgres_family());
3323 assert!(DbType::Kingbase.is_postgres_family());
3324 assert!(DbType::GaussDB.is_postgres_family());
3325 assert!(!DbType::MySQL.is_postgres_family());
3326
3327 assert!(DbType::Oracle.is_oracle_family());
3329 assert!(DbType::Dameng.is_oracle_family());
3330 assert!(!DbType::MySQL.is_oracle_family());
3331 }
3332
3333 #[test]
3334 fn test_db_type_supports_stored_procedure_extended() {
3335 assert!(DbType::Dameng.supports_stored_procedure());
3337 assert!(DbType::Kingbase.supports_stored_procedure());
3338 assert!(DbType::Db2.supports_stored_procedure());
3339 assert!(DbType::MariaDB.supports_stored_procedure());
3340 assert!(DbType::TiDB.supports_stored_procedure());
3341 assert!(DbType::PolarDB.supports_stored_procedure());
3342 assert!(DbType::GaussDB.supports_stored_procedure());
3343 assert!(DbType::GBase.supports_stored_procedure());
3344 assert!(DbType::Sybase.supports_stored_procedure());
3345 }
3346
3347 #[test]
3350 fn test_l4_max_identifier_len_constant() {
3351 assert_eq!(MAX_IDENTIFIER_LEN, 63);
3353 }
3354
3355 #[test]
3356 fn test_l4_quote_checked_valid_identifier() {
3357 let dialect = MySqlDialect;
3358 assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
3359 assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
3360 let name_63 = "a".repeat(63);
3362 assert!(dialect.quote_checked(&name_63).is_ok());
3363 }
3364
3365 #[test]
3366 fn test_l4_quote_checked_rejects_too_long() {
3367 let dialect = MySqlDialect;
3368 let long_name = "a".repeat(64); let result = dialect.quote_checked(&long_name);
3370 assert!(result.is_err());
3371 match result {
3372 Err(DbError::InvalidInput(msg)) => {
3373 assert!(
3374 msg.contains("too long"),
3375 "expected 'too long' error, got: {}",
3376 msg
3377 );
3378 }
3379 _ => panic!("Expected DbError::InvalidInput"),
3380 }
3381 }
3382
3383 #[test]
3384 fn test_l4_quote_checked_rejects_empty() {
3385 let dialect = MySqlDialect;
3386 let result = dialect.quote_checked("");
3387 assert!(result.is_err());
3388 }
3389
3390 #[test]
3391 fn test_l4_quote_checked_rejects_sql_injection() {
3392 let dialect = MySqlDialect;
3393 assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
3395 assert!(dialect.quote_checked("user'name").is_err());
3397 assert!(dialect.quote_checked("user name").is_err());
3399 assert!(dialect.quote_checked("1users").is_err());
3401 assert!(dialect.quote_checked("schema.table").is_err());
3403 }
3404
3405 #[test]
3406 fn test_l4_quote_checked_postgres() {
3407 let dialect = PostgreSqlDialect;
3408 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3409 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3410 }
3411
3412 #[test]
3413 fn test_l4_quote_checked_sqlite() {
3414 let dialect = SqliteDialect;
3415 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3416 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3417 }
3418
3419 #[test]
3420 fn test_l4_quote_checked_oracle() {
3421 let dialect = OracleDialect;
3422 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3423 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3424 }
3425
3426 #[test]
3427 fn test_l4_quote_checked_sql_server() {
3428 let dialect = SqlServerDialect;
3429 assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
3430 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3431 }
3432
3433 #[test]
3436 fn test_duckdb_quote() {
3437 let dialect = DuckDBDialect;
3438 assert_eq!(dialect.quote("users"), "\"users\"");
3439 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3440 }
3441
3442 #[test]
3443 fn test_duckdb_escape() {
3444 let dialect = DuckDBDialect;
3445 assert_eq!(dialect.escape_string("hello"), "hello");
3446 assert_eq!(dialect.escape_string("it's"), "it''s");
3447 }
3448
3449 #[test]
3450 fn test_duckdb_pagination() {
3451 let dialect = DuckDBDialect;
3452 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3453 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
3454 }
3455
3456 #[test]
3457 fn test_duckdb_supports() {
3458 let dialect = DuckDBDialect;
3459 assert!(!dialect.supports_returning());
3460 assert!(!dialect.supports_lock_for_update());
3461 assert!(!dialect.supports_lock_shared());
3462 assert!(dialect.supports_if_exists());
3463 assert!(dialect.supports_if_not_exists());
3464 }
3465
3466 #[test]
3467 fn test_duckdb_insert_or_ignore() {
3468 let dialect = DuckDBDialect;
3469 let sql = dialect.build_insert_or_ignore_prefix("users");
3470 assert_eq!(sql, "INSERT OR IGNORE INTO \"users\"");
3471 }
3472
3473 #[test]
3474 fn test_duckdb_create_table() {
3475 let dialect = DuckDBDialect;
3476 let columns = vec![
3477 ColumnDef {
3478 name: "id".to_string(),
3479 sql_type: "BIGINT".to_string(),
3480 nullable: false,
3481 default: None,
3482 auto_increment: true,
3483 primary_key: true,
3484 },
3485 ColumnDef {
3486 name: "name".to_string(),
3487 sql_type: "VARCHAR(255)".to_string(),
3488 nullable: false,
3489 default: None,
3490 auto_increment: false,
3491 primary_key: false,
3492 },
3493 ];
3494 let sql = dialect.build_create_table("users", &columns);
3495 assert!(sql.contains("CREATE TABLE \"users\""));
3496 assert!(sql.contains("\"id\" BIGINT PRIMARY KEY"));
3497 assert!(sql.contains("\"name\" VARCHAR(255)"));
3498 }
3499
3500 #[test]
3501 fn test_duckdb_alter_table() {
3502 let dialect = DuckDBDialect;
3503 let changes = vec![TableChange::AddColumn(ColumnDef {
3504 name: "age".to_string(),
3505 sql_type: "INTEGER".to_string(),
3506 nullable: true,
3507 default: None,
3508 auto_increment: false,
3509 primary_key: false,
3510 })];
3511 let sql = dialect.build_alter_table("users", &changes);
3512 assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN \"age\" INTEGER"));
3513 }
3514
3515 #[test]
3516 fn test_duckdb_json_extract() {
3517 let dialect = DuckDBDialect;
3518 let sql = dialect.json_extract("data", "$.user.name");
3519 assert!(sql.contains("->"));
3520 assert!(sql.contains("user.name"));
3521 }
3522
3523 #[test]
3524 fn test_duckdb_concat() {
3525 let dialect = DuckDBDialect;
3526 let sql = dialect.concat(&["a", "b", "c"]);
3527 assert_eq!(sql, "a || b || c");
3528 }
3529
3530 #[test]
3531 fn test_duckdb_bool_to_int() {
3532 let dialect = DuckDBDialect;
3533 let sql = dialect.bool_to_int("active");
3534 assert!(sql.contains("CASE WHEN"));
3535 assert!(sql.contains("THEN 1"));
3536 assert!(sql.contains("ELSE 0"));
3537 }
3538
3539 #[test]
3540 fn test_get_dialect_duckdb() {
3541 let result = get_dialect(DbType::DuckDB);
3542 assert!(result.is_ok());
3543 let dialect = result.unwrap();
3544 assert_eq!(dialect.db_type(), DbType::DuckDB);
3545 }
3546}