1use 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 db_type(&self) -> DbType;
26
27 fn quote(&self, identifier: &str) -> String;
29
30 fn quote_checked(&self, identifier: &str) -> Result<String, DbError> {
41 crate::sql_safety::validate_identifier(identifier, "identifier")?;
42 Ok(self.quote(identifier))
43 }
44
45 fn escape_string(&self, s: &str) -> String;
47
48 fn supports_returning(&self) -> bool;
50
51 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String;
53
54 fn json_type(&self) -> &'static str;
56
57 fn json_extract(&self, column: &str, path: &str) -> String;
59
60 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String;
62
63 fn bool_to_int(&self, expr: &str) -> String;
65
66 fn concat(&self, parts: &[&str]) -> String;
68
69 fn supports_if_exists(&self) -> bool;
71
72 fn supports_if_not_exists(&self) -> bool;
74
75 fn auto_increment_keyword(&self) -> &'static str;
77
78 fn last_insert_id_sql(&self) -> Option<&'static str>;
84
85 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String;
87
88 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String;
90
91 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
96 if if_exists && self.supports_if_exists() {
97 format!("DROP TABLE IF EXISTS {}", self.quote(table))
98 } else {
99 format!("DROP TABLE {}", self.quote(table))
100 }
101 }
102
103 fn build_upsert_on_conflict(
118 &self,
119 conflict_columns: &[&str],
120 update_columns: &[&str],
121 all_columns: &[String],
122 ) -> Option<String> {
123 let _ = (conflict_columns, update_columns, all_columns);
124 None
125 }
126}
127
128#[derive(Debug, Clone)]
130pub struct ColumnDef {
131 pub name: String,
132 pub sql_type: String,
133 pub nullable: bool,
134 pub default: Option<String>,
135 pub auto_increment: bool,
136 pub primary_key: bool,
137}
138
139#[derive(Debug, Clone)]
141pub enum TableChange {
142 AddColumn(ColumnDef),
143 DropColumn(String),
144 ModifyColumn(ColumnDef),
145 AddIndex(String, Vec<String>),
146 DropIndex(String),
147 AddForeignKey {
148 columns: Vec<String>,
149 reference_table: String,
150 reference_columns: Vec<String>,
151 },
152}
153
154pub struct MySqlDialect;
156
157impl Dialect for MySqlDialect {
158 fn db_type(&self) -> DbType {
159 DbType::MySQL
160 }
161
162 fn quote(&self, identifier: &str) -> String {
163 format!("`{}`", identifier.replace('`', "``"))
164 }
165
166 fn escape_string(&self, s: &str) -> String {
167 let mut escaped = String::with_capacity(s.len() * 2);
168 for c in s.chars() {
169 match c {
170 '\\' => escaped.push_str("\\\\"),
171 '\'' => escaped.push_str("\\'"),
172 '\0' => escaped.push_str("\\0"),
173 '\n' => escaped.push_str("\\n"),
174 '\r' => escaped.push_str("\\r"),
175 '\t' => escaped.push_str("\\t"),
176 '\x1a' => escaped.push_str("\\Z"),
177 _ => escaped.push(c),
178 }
179 }
180 escaped
181 }
182
183 fn supports_returning(&self) -> bool {
184 false
185 }
186
187 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
188 let offset = page.saturating_sub(1).saturating_mul(limit);
196 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
197 }
198
199 fn json_type(&self) -> &'static str {
200 "JSON"
201 }
202
203 fn json_extract(&self, column: &str, path: &str) -> String {
204 let normalized = if path.starts_with('$') {
206 path.to_string()
207 } else {
208 format!("$.{}", path)
209 };
210 format!(
211 "JSON_EXTRACT({}, '{}')",
212 column,
213 self.escape_string(&normalized)
214 )
215 }
216
217 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
218 let cols = columns.join(", ");
219 let escaped = self.escape_string(keyword);
220 format!(
221 "MATCH({}) AGAINST('{}' IN NATURAL LANGUAGE MODE)",
222 cols, escaped
223 )
224 }
225
226 fn bool_to_int(&self, expr: &str) -> String {
227 format!("IF({}, 1, 0)", expr)
229 }
230
231 fn concat(&self, parts: &[&str]) -> String {
232 if parts.is_empty() {
233 return "NULL".to_string();
234 }
235 let concat_parts: Vec<String> = parts
236 .iter()
237 .map(|p| format!("CAST({} AS CHAR)", p))
238 .collect();
239 format!("CONCAT({})", concat_parts.join(", "))
240 }
241
242 fn supports_if_exists(&self) -> bool {
243 true
244 }
245
246 fn supports_if_not_exists(&self) -> bool {
247 true
248 }
249
250 fn auto_increment_keyword(&self) -> &'static str {
251 "AUTO_INCREMENT"
252 }
253
254 fn last_insert_id_sql(&self) -> Option<&'static str> {
255 Some("LAST_INSERT_ID()")
256 }
257
258 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
259 let cols: Vec<String> = columns
260 .iter()
261 .map(|col| {
262 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
263 if !col.nullable {
264 sql.push_str(" NOT NULL");
265 }
266 if let Some(default) = &col.default {
267 sql.push_str(&format!(" DEFAULT {}", default));
268 }
269 if col.auto_increment {
270 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
271 }
272 if col.primary_key {
273 sql.push_str(" PRIMARY KEY");
274 }
275 sql
276 })
277 .collect();
278
279 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
280 }
281
282 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
283 let stmts: Vec<String> = changes.iter().map(|change| {
284 match change {
285 TableChange::AddColumn(col) => {
286 let mut sql = format!("ALTER TABLE {} ADD {}", self.quote(table), self.quote(&col.name));
287 sql.push_str(&format!(" {}", col.sql_type));
288 if !col.nullable {
289 sql.push_str(" NOT NULL");
290 }
291 if let Some(default) = &col.default {
292 sql.push_str(&format!(" DEFAULT {}", default));
293 }
294 sql
295 }
296 TableChange::DropColumn(name) => {
297 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
298 }
299 TableChange::ModifyColumn(col) => {
300 let mut sql = format!("ALTER TABLE {} MODIFY COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
302 if !col.nullable {
303 sql.push_str(" NOT NULL");
304 }
305 if let Some(default) = &col.default {
306 sql.push_str(&format!(" DEFAULT {}", default));
307 }
308 sql
309 }
310 TableChange::AddIndex(name, cols) => {
311 format!("ALTER TABLE {} ADD INDEX {} ({})", self.quote(table), name, cols.join(", "))
312 }
313 TableChange::DropIndex(name) => {
314 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
315 }
316 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
317 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
318 self.quote(table),
319 table,
320 columns.join("_"),
321 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
322 self.quote(reference_table),
323 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
324 }
325 }
326 }).collect();
327
328 stmts.join("; ")
329 }
330
331 fn build_upsert_on_conflict(
336 &self,
337 _conflict_columns: &[&str],
338 update_columns: &[&str],
339 all_columns: &[String],
340 ) -> Option<String> {
341 let cols_to_update: Vec<String> = if update_columns.is_empty() {
343 all_columns.iter().map(|c| self.quote(c)).collect()
344 } else {
345 update_columns.iter().map(|c| self.quote(c)).collect()
346 };
347 if cols_to_update.is_empty() {
348 return None;
349 }
350 let set_clauses: Vec<String> = cols_to_update
351 .iter()
352 .map(|col| format!("{}=VALUES({})", col, col))
353 .collect();
354 Some(format!(
355 "ON DUPLICATE KEY UPDATE {}",
356 set_clauses.join(", ")
357 ))
358 }
359}
360
361pub struct PostgreSqlDialect;
363
364impl Dialect for PostgreSqlDialect {
365 fn db_type(&self) -> DbType {
366 DbType::PostgreSQL
367 }
368
369 fn quote(&self, identifier: &str) -> String {
370 format!("\"{}\"", identifier.replace('"', "\"\""))
371 }
372
373 fn escape_string(&self, s: &str) -> String {
374 let mut escaped = String::with_capacity(s.len() * 2);
377 for c in s.chars() {
378 match c {
379 '\'' => escaped.push_str("''"),
380 _ => escaped.push(c),
381 }
382 }
383 escaped
384 }
385
386 fn supports_returning(&self) -> bool {
387 true
388 }
389
390 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
391 let offset = page.saturating_sub(1).saturating_mul(limit);
392 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
393 }
394
395 fn json_type(&self) -> &'static str {
396 "JSONB"
397 }
398
399 fn json_extract(&self, column: &str, path: &str) -> String {
400 let normalized = path.trim_start_matches("$.");
405 let parts: Vec<&str> = normalized.split('.').filter(|s| !s.is_empty()).collect();
406 let path_lit = parts
407 .iter()
408 .map(|p| {
409 let needs_quoting = p.chars().any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\'));
411 if needs_quoting {
412 let escaped = p.replace('\\', "\\\\").replace('"', "\\\"");
413 format!("\"{}\"", escaped)
414 } else {
415 p.to_string()
416 }
417 })
418 .collect::<Vec<_>>()
419 .join(",");
420 let path_lit_escaped = path_lit.replace('\'', "''");
422 format!("{}#>>'{{{}}}'", column, path_lit_escaped)
423 }
424
425 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
426 let cols = columns
427 .iter()
428 .map(|c| format!("{}::text", c))
429 .collect::<Vec<_>>()
430 .join(" || ' ' || ");
431 let escaped = self.escape_string(keyword);
432 format!("to_tsvector({}) @@ to_tsquery('{}')", cols, escaped)
433 }
434
435 fn bool_to_int(&self, expr: &str) -> String {
436 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
437 }
438
439 fn concat(&self, parts: &[&str]) -> String {
440 if parts.is_empty() {
441 return "NULL".to_string();
442 }
443 format!("CONCAT({})", parts.join(", "))
444 }
445
446 fn supports_if_exists(&self) -> bool {
447 true
448 }
449
450 fn supports_if_not_exists(&self) -> bool {
451 true
452 }
453
454 fn auto_increment_keyword(&self) -> &'static str {
455 "GENERATED BY DEFAULT AS IDENTITY"
456 }
457
458 fn last_insert_id_sql(&self) -> Option<&'static str> {
459 Some("lastval()")
460 }
461
462 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
463 let cols: Vec<String> = columns
464 .iter()
465 .map(|col| {
466 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
467 if !col.nullable {
468 sql.push_str(" NOT NULL");
469 }
470 if let Some(default) = &col.default {
471 sql.push_str(&format!(" DEFAULT {}", default));
472 }
473 if col.primary_key {
474 sql.push_str(" PRIMARY KEY");
475 }
476 sql
477 })
478 .collect();
479
480 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
481 }
482
483 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
484 let stmts: Vec<String> = changes.iter().map(|change| {
485 match change {
486 TableChange::AddColumn(col) => {
487 let mut sql = format!("ALTER TABLE {} ADD COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
488 if !col.nullable {
489 sql.push_str(" NOT NULL");
490 }
491 if let Some(default) = &col.default {
492 sql.push_str(&format!(" DEFAULT {}", default));
493 }
494 sql
495 }
496 TableChange::DropColumn(name) => {
497 format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
498 }
499 TableChange::ModifyColumn(col) => {
500 let mut sql = format!("ALTER TABLE {} ALTER COLUMN {} TYPE {}", self.quote(table), self.quote(&col.name), col.sql_type);
502 if !col.nullable {
503 sql.push_str(&format!(", ALTER COLUMN {} SET NOT NULL", self.quote(&col.name)));
504 }
505 if let Some(default) = &col.default {
506 sql.push_str(&format!(", ALTER COLUMN {} SET DEFAULT {}", self.quote(&col.name), default));
507 }
508 sql
509 }
510 TableChange::AddIndex(name, cols) => {
511 format!("CREATE INDEX {} ON {} ({})", name, self.quote(table), cols.join(", "))
512 }
513 TableChange::DropIndex(name) => {
514 format!("DROP INDEX {}", name)
515 }
516 TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
517 format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
518 self.quote(table),
519 table,
520 columns.join("_"),
521 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
522 self.quote(reference_table),
523 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
524 }
525 }
526 }).collect();
527
528 stmts.join("; ")
529 }
530
531 fn build_upsert_on_conflict(
535 &self,
536 conflict_columns: &[&str],
537 update_columns: &[&str],
538 all_columns: &[String],
539 ) -> Option<String> {
540 if conflict_columns.is_empty() {
541 return None;
542 }
543 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
544 let conflict_set: std::collections::HashSet<&str> = conflict_columns.iter().copied().collect();
546 let cols_to_update: Vec<String> = if update_columns.is_empty() {
547 all_columns
548 .iter()
549 .filter(|c| !conflict_set.contains(c.as_str()))
550 .map(|c| self.quote(c))
551 .collect()
552 } else {
553 update_columns.iter().map(|c| self.quote(c)).collect()
554 };
555 if cols_to_update.is_empty() {
556 return Some(format!(
558 "ON CONFLICT ({}) DO NOTHING",
559 conflict_cols.join(", ")
560 ));
561 }
562 let set_clauses: Vec<String> = cols_to_update
563 .iter()
564 .map(|col| format!("{}=EXCLUDED.{}", col, col))
565 .collect();
566 Some(format!(
567 "ON CONFLICT ({}) DO UPDATE SET {}",
568 conflict_cols.join(", "),
569 set_clauses.join(", ")
570 ))
571 }
572}
573
574pub struct SqliteDialect;
576
577impl Dialect for SqliteDialect {
578 fn db_type(&self) -> DbType {
579 DbType::Sqlite
580 }
581
582 fn quote(&self, identifier: &str) -> String {
583 format!("\"{}\"", identifier.replace('"', "\"\""))
584 }
585
586 fn escape_string(&self, s: &str) -> String {
587 let mut escaped = String::with_capacity(s.len() * 2);
588 for c in s.chars() {
589 match c {
590 '\'' => escaped.push_str("''"),
591 _ => escaped.push(c),
592 }
593 }
594 escaped
595 }
596
597 fn supports_returning(&self) -> bool {
598 true
599 }
600
601 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
602 let offset = page.saturating_sub(1).saturating_mul(limit);
603 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
604 }
605
606 fn json_type(&self) -> &'static str {
607 "TEXT"
608 }
609
610 fn json_extract(&self, column: &str, path: &str) -> String {
611 let normalized = if path.starts_with('$') {
613 path.to_string()
614 } else {
615 format!("$.{}", path)
616 };
617 format!(
618 "json_extract({}, '{}')",
619 column,
620 self.escape_string(&normalized)
621 )
622 }
623
624 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
625 if columns.is_empty() {
629 return "0".to_string();
630 }
631 let escaped = self.escape_string(keyword);
632 columns
633 .iter()
634 .map(|c| format!("{} LIKE '%{}%'", c.trim(), escaped))
635 .collect::<Vec<_>>()
636 .join(" OR ")
637 }
638
639 fn bool_to_int(&self, expr: &str) -> String {
640 expr.to_string()
641 }
642
643 fn concat(&self, parts: &[&str]) -> String {
644 if parts.is_empty() {
645 return "NULL".to_string();
646 }
647 let coalesced: Vec<String> = parts
650 .iter()
651 .map(|p| format!("COALESCE({}, '')", p))
652 .collect();
653 coalesced.join(" || ")
654 }
655
656 fn supports_if_exists(&self) -> bool {
657 true
658 }
659
660 fn supports_if_not_exists(&self) -> bool {
661 true
662 }
663
664 fn auto_increment_keyword(&self) -> &'static str {
665 "AUTOINCREMENT"
666 }
667
668 fn last_insert_id_sql(&self) -> Option<&'static str> {
669 Some("last_insert_rowid()")
670 }
671
672 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
673 let cols: Vec<String> = columns
674 .iter()
675 .map(|col| {
676 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
677 if !col.nullable {
678 sql.push_str(" NOT NULL");
679 }
680 if let Some(default) = &col.default {
681 sql.push_str(&format!(" DEFAULT {}", default));
682 }
683 if col.auto_increment {
684 sql.push_str(" PRIMARY KEY AUTOINCREMENT");
685 } else if col.primary_key {
686 sql.push_str(" PRIMARY KEY");
687 }
688 sql
689 })
690 .collect();
691
692 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
693 }
694
695 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
696 let stmts: Vec<String> = changes
699 .iter()
700 .map(|change| {
701 match change {
702 TableChange::AddColumn(col) => {
703 let mut sql = format!(
704 "ALTER TABLE {} ADD COLUMN {} {}",
705 self.quote(table),
706 self.quote(&col.name),
707 col.sql_type
708 );
709 if !col.nullable {
710 sql.push_str(" NOT NULL");
711 }
712 if let Some(default) = &col.default {
713 sql.push_str(&format!(" DEFAULT {}", default));
714 }
715 sql
716 }
717 TableChange::DropColumn(name) => {
718 format!(
720 "ALTER TABLE {} DROP COLUMN {}",
721 self.quote(table),
722 self.quote(name)
723 )
724 }
725 TableChange::ModifyColumn(col) => {
726 format!(
729 "-- SQLite 不支持 MODIFY COLUMN({} {}),需重建表",
730 col.name, col.sql_type
731 )
732 }
733 TableChange::AddIndex(name, cols) => {
734 format!(
735 "CREATE INDEX {} ON {} ({})",
736 name,
737 self.quote(table),
738 cols.join(", ")
739 )
740 }
741 TableChange::DropIndex(name) => {
742 format!("DROP INDEX {}", name)
743 }
744 TableChange::AddForeignKey {
745 columns,
746 reference_table,
747 reference_columns: _,
748 } => {
749 format!(
751 "-- SQLite 不支持 ADD FOREIGN KEY({} -> {}),需重建表",
752 columns.join(","),
753 reference_table
754 )
755 }
756 }
757 })
758 .collect();
759
760 stmts.join("; ")
761 }
762
763 fn build_upsert_on_conflict(
767 &self,
768 conflict_columns: &[&str],
769 update_columns: &[&str],
770 all_columns: &[String],
771 ) -> Option<String> {
772 if conflict_columns.is_empty() {
773 return None;
774 }
775 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
776 let conflict_set: std::collections::HashSet<&str> = conflict_columns.iter().copied().collect();
777 let cols_to_update: Vec<String> = if update_columns.is_empty() {
778 all_columns
779 .iter()
780 .filter(|c| !conflict_set.contains(c.as_str()))
781 .map(|c| self.quote(c))
782 .collect()
783 } else {
784 update_columns.iter().map(|c| self.quote(c)).collect()
785 };
786 if cols_to_update.is_empty() {
787 return Some(format!(
788 "ON CONFLICT ({}) DO NOTHING",
789 conflict_cols.join(", ")
790 ));
791 }
792 let set_clauses: Vec<String> = cols_to_update
793 .iter()
794 .map(|col| format!("{}=EXCLUDED.{}", col, col))
795 .collect();
796 Some(format!(
797 "ON CONFLICT ({}) DO UPDATE SET {}",
798 conflict_cols.join(", "),
799 set_clauses.join(", ")
800 ))
801 }
802}
803
804fn map_to_oracle_type(sql_type: &str) -> String {
813 let upper = sql_type.to_uppercase();
814 let trimmed = upper.trim();
815
816 if trimmed.starts_with("BIGINT") {
817 sql_type.replacen("BIGINT", "NUMBER(19)", 1)
818 } else if trimmed.starts_with("VARCHAR2") {
819 sql_type.to_string()
820 } else if trimmed.starts_with("VARCHAR") {
821 sql_type.replacen("VARCHAR", "VARCHAR2", 1)
822 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
823 "CLOB".to_string()
824 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
825 "NUMBER(1)".to_string()
826 } else if trimmed == "INTEGER" {
827 "NUMBER(10)".to_string()
828 } else if trimmed.starts_with("INT") {
829 sql_type.replacen("INT", "NUMBER(10)", 1)
830 } else {
831 sql_type.to_string()
832 }
833}
834
835pub struct OracleDialect;
837
838impl Dialect for OracleDialect {
839 fn db_type(&self) -> DbType {
840 DbType::Oracle
841 }
842
843 fn quote(&self, identifier: &str) -> String {
844 format!("\"{}\"", identifier.replace('"', "\"\""))
846 }
847
848 fn escape_string(&self, s: &str) -> String {
849 let mut escaped = String::with_capacity(s.len() * 2);
851 for c in s.chars() {
852 match c {
853 '\'' => escaped.push_str("''"),
854 _ => escaped.push(c),
855 }
856 }
857 escaped
858 }
859
860 fn supports_returning(&self) -> bool {
861 true
863 }
864
865 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
866 let offset = page.saturating_sub(1).saturating_mul(limit);
869 format!(
870 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
871 sql, offset, limit
872 )
873 }
874
875 fn json_type(&self) -> &'static str {
876 "JSON"
878 }
879
880 fn json_extract(&self, column: &str, path: &str) -> String {
881 let normalized = if path.starts_with('$') {
883 path.to_string()
884 } else {
885 format!("$.{}", path)
886 };
887 format!(
888 "JSON_VALUE({}, '{}')",
889 column,
890 self.escape_string(&normalized)
891 )
892 }
893
894 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
895 if columns.is_empty() {
898 return "0".to_string();
899 }
900 let escaped = self.escape_string(keyword);
901 let parts: Vec<String> = columns
902 .iter()
903 .map(|c| format!("CONTAINS({}, '{}', 1) > 0", c, escaped))
904 .collect();
905 parts.join(" OR ")
906 }
907
908 fn bool_to_int(&self, expr: &str) -> String {
909 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
911 }
912
913 fn concat(&self, parts: &[&str]) -> String {
914 if parts.is_empty() {
916 return "NULL".to_string();
917 }
918 parts.join(" || ")
919 }
920
921 fn supports_if_exists(&self) -> bool {
922 true
924 }
925
926 fn supports_if_not_exists(&self) -> bool {
927 true
929 }
930
931 fn auto_increment_keyword(&self) -> &'static str {
932 "GENERATED BY DEFAULT AS IDENTITY"
934 }
935
936 fn last_insert_id_sql(&self) -> Option<&'static str> {
937 None
942 }
943
944 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
945 let cols: Vec<String> = columns
946 .iter()
947 .map(|col| {
948 let oracle_type = map_to_oracle_type(&col.sql_type);
949 let mut sql = format!("{} {}", self.quote(&col.name), oracle_type);
950 if !col.nullable && !col.auto_increment {
952 sql.push_str(" NOT NULL");
953 }
954 if let Some(default) = &col.default {
955 sql.push_str(&format!(" DEFAULT {}", default));
956 }
957 if col.auto_increment {
958 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
959 }
960 if col.primary_key {
961 sql.push_str(" PRIMARY KEY");
962 }
963 sql
964 })
965 .collect();
966
967 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
968 }
969
970 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
971 let stmts: Vec<String> = changes
972 .iter()
973 .map(|change| match change {
974 TableChange::AddColumn(col) => {
975 let oracle_type = map_to_oracle_type(&col.sql_type);
976 let mut sql = format!(
977 "ALTER TABLE {} ADD {} {}",
978 self.quote(table),
979 self.quote(&col.name),
980 oracle_type
981 );
982 if !col.nullable {
983 sql.push_str(" NOT NULL");
984 }
985 if let Some(default) = &col.default {
986 sql.push_str(&format!(" DEFAULT {}", default));
987 }
988 sql
989 }
990 TableChange::DropColumn(name) => {
991 format!(
992 "ALTER TABLE {} DROP COLUMN {}",
993 self.quote(table),
994 self.quote(name)
995 )
996 }
997 TableChange::ModifyColumn(col) => {
998 let oracle_type = map_to_oracle_type(&col.sql_type);
1000 let mut sql = format!(
1001 "ALTER TABLE {} MODIFY {} {}",
1002 self.quote(table),
1003 self.quote(&col.name),
1004 oracle_type
1005 );
1006 if !col.nullable {
1007 sql.push_str(" NOT NULL");
1008 }
1009 if let Some(default) = &col.default {
1010 sql.push_str(&format!(" DEFAULT {}", default));
1011 }
1012 sql
1013 }
1014 TableChange::AddIndex(name, cols) => {
1015 format!(
1016 "CREATE INDEX {} ON {} ({})",
1017 name,
1018 self.quote(table),
1019 cols.join(", ")
1020 )
1021 }
1022 TableChange::DropIndex(name) => {
1023 format!("DROP INDEX {}", name)
1024 }
1025 TableChange::AddForeignKey {
1026 columns,
1027 reference_table,
1028 reference_columns,
1029 } => {
1030 format!(
1031 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1032 self.quote(table),
1033 table,
1034 columns.join("_"),
1035 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1036 self.quote(reference_table),
1037 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1038 )
1039 }
1040 })
1041 .collect();
1042
1043 stmts.join("; ")
1044 }
1045}
1046
1047fn map_to_sqlserver_type(sql_type: &str) -> String {
1056 let upper = sql_type.to_uppercase();
1057 let trimmed = upper.trim();
1058
1059 if trimmed.starts_with("BIGINT") {
1060 sql_type.to_string()
1061 } else if matches!(trimmed, "INT" | "INTEGER") {
1062 "INT".to_string()
1063 } else if trimmed.starts_with("NVARCHAR") {
1064 sql_type.to_string()
1065 } else if trimmed.starts_with("VARCHAR") {
1066 sql_type.replacen("VARCHAR", "NVARCHAR", 1)
1067 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1068 "NVARCHAR(MAX)".to_string()
1069 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1070 "BIT".to_string()
1071 } else {
1072 sql_type.to_string()
1073 }
1074}
1075
1076pub struct SqlServerDialect;
1078
1079impl Dialect for SqlServerDialect {
1080 fn db_type(&self) -> DbType {
1081 DbType::SqlServer
1082 }
1083
1084 fn quote(&self, identifier: &str) -> String {
1085 format!("[{}]", identifier.replace(']', "]]"))
1087 }
1088
1089 fn escape_string(&self, s: &str) -> String {
1090 let mut escaped = String::with_capacity(s.len() * 2);
1092 for c in s.chars() {
1093 match c {
1094 '\'' => escaped.push_str("''"),
1095 _ => escaped.push(c),
1096 }
1097 }
1098 escaped
1099 }
1100
1101 fn supports_returning(&self) -> bool {
1102 true
1104 }
1105
1106 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1107 let offset = page.saturating_sub(1).saturating_mul(limit);
1109 format!(
1110 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1111 sql, offset, limit
1112 )
1113 }
1114
1115 fn json_type(&self) -> &'static str {
1116 "NVARCHAR(MAX)"
1118 }
1119
1120 fn json_extract(&self, column: &str, path: &str) -> String {
1121 let normalized = if path.starts_with('$') {
1123 path.to_string()
1124 } else {
1125 format!("$.{}", path)
1126 };
1127 format!(
1128 "JSON_VALUE({}, '{}')",
1129 column,
1130 self.escape_string(&normalized)
1131 )
1132 }
1133
1134 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1135 if columns.is_empty() {
1137 return "0".to_string();
1138 }
1139 let escaped = self.escape_string(keyword);
1140 let cols = columns.join(", ");
1141 format!("CONTAINS({}, '{}')", cols, escaped)
1142 }
1143
1144 fn bool_to_int(&self, expr: &str) -> String {
1145 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1147 }
1148
1149 fn concat(&self, parts: &[&str]) -> String {
1150 if parts.is_empty() {
1151 return "NULL".to_string();
1152 }
1153 format!("CONCAT({})", parts.join(", "))
1154 }
1155
1156 fn supports_if_exists(&self) -> bool {
1157 true
1159 }
1160
1161 fn supports_if_not_exists(&self) -> bool {
1162 true
1164 }
1165
1166 fn auto_increment_keyword(&self) -> &'static str {
1167 "IDENTITY(1,1)"
1169 }
1170
1171 fn last_insert_id_sql(&self) -> Option<&'static str> {
1172 Some("SCOPE_IDENTITY()")
1174 }
1175
1176 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1177 let cols: Vec<String> = columns
1178 .iter()
1179 .map(|col| {
1180 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1181 let mut sql = format!("{} {}", self.quote(&col.name), sqlserver_type);
1182 if !col.nullable {
1183 sql.push_str(" NOT NULL");
1184 }
1185 if let Some(default) = &col.default {
1186 sql.push_str(&format!(" DEFAULT {}", default));
1187 }
1188 if col.auto_increment {
1189 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1190 }
1191 if col.primary_key {
1192 sql.push_str(" PRIMARY KEY");
1193 }
1194 sql
1195 })
1196 .collect();
1197
1198 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1199 }
1200
1201 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1202 let stmts: Vec<String> = changes
1203 .iter()
1204 .map(|change| match change {
1205 TableChange::AddColumn(col) => {
1206 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1207 let mut sql = format!(
1208 "ALTER TABLE {} ADD {} {}",
1209 self.quote(table),
1210 self.quote(&col.name),
1211 sqlserver_type
1212 );
1213 if !col.nullable {
1214 sql.push_str(" NOT NULL");
1215 }
1216 if let Some(default) = &col.default {
1217 sql.push_str(&format!(" DEFAULT {}", default));
1218 }
1219 sql
1220 }
1221 TableChange::DropColumn(name) => {
1222 format!(
1223 "ALTER TABLE {} DROP COLUMN {}",
1224 self.quote(table),
1225 self.quote(name)
1226 )
1227 }
1228 TableChange::ModifyColumn(col) => {
1229 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1231 let mut sql = format!(
1232 "ALTER TABLE {} ALTER COLUMN {} {}",
1233 self.quote(table),
1234 self.quote(&col.name),
1235 sqlserver_type
1236 );
1237 if !col.nullable {
1238 sql.push_str(" NOT NULL");
1239 }
1240 if let Some(default) = &col.default {
1241 sql.push_str(&format!(" DEFAULT {}", default));
1242 }
1243 sql
1244 }
1245 TableChange::AddIndex(name, cols) => {
1246 format!(
1247 "CREATE INDEX {} ON {} ({})",
1248 name,
1249 self.quote(table),
1250 cols.join(", ")
1251 )
1252 }
1253 TableChange::DropIndex(name) => {
1254 format!("DROP INDEX {} ON {}", name, self.quote(table))
1256 }
1257 TableChange::AddForeignKey {
1258 columns,
1259 reference_table,
1260 reference_columns,
1261 } => {
1262 format!(
1263 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1264 self.quote(table),
1265 table,
1266 columns.join("_"),
1267 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1268 self.quote(reference_table),
1269 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1270 )
1271 }
1272 })
1273 .collect();
1274
1275 stmts.join("; ")
1276 }
1277}
1278
1279macro_rules! delegate_dialect_to {
1296 ($wrapper:ident, $base:ident, $db_type:expr) => {
1297 pub struct $wrapper;
1299
1300 impl Dialect for $wrapper {
1301 fn db_type(&self) -> DbType {
1302 $db_type
1303 }
1304 fn quote(&self, identifier: &str) -> String {
1305 $base.quote(identifier)
1306 }
1307 fn escape_string(&self, s: &str) -> String {
1308 $base.escape_string(s)
1309 }
1310 fn supports_returning(&self) -> bool {
1311 $base.supports_returning()
1312 }
1313 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1314 $base.build_pagination(sql, page, limit)
1315 }
1316 fn json_type(&self) -> &'static str {
1317 $base.json_type()
1318 }
1319 fn json_extract(&self, column: &str, path: &str) -> String {
1320 $base.json_extract(column, path)
1321 }
1322 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1323 $base.full_text_search(columns, keyword)
1324 }
1325 fn bool_to_int(&self, expr: &str) -> String {
1326 $base.bool_to_int(expr)
1327 }
1328 fn concat(&self, parts: &[&str]) -> String {
1329 $base.concat(parts)
1330 }
1331 fn supports_if_exists(&self) -> bool {
1332 $base.supports_if_exists()
1333 }
1334 fn supports_if_not_exists(&self) -> bool {
1335 $base.supports_if_not_exists()
1336 }
1337 fn auto_increment_keyword(&self) -> &'static str {
1338 $base.auto_increment_keyword()
1339 }
1340 fn last_insert_id_sql(&self) -> Option<&'static str> {
1341 $base.last_insert_id_sql()
1342 }
1343 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1344 $base.build_create_table(table, columns)
1345 }
1346 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1347 $base.build_alter_table(table, changes)
1348 }
1349 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1350 $base.build_drop_table(table, if_exists)
1351 }
1352 }
1353 };
1354}
1355
1356delegate_dialect_to!(MariaDbDialect, MySqlDialect, DbType::MariaDB);
1358
1359delegate_dialect_to!(TiDbDialect, MySqlDialect, DbType::TiDB);
1361
1362delegate_dialect_to!(KingbaseDialect, PostgreSqlDialect, DbType::Kingbase);
1364
1365delegate_dialect_to!(PolarDbDialect, PostgreSqlDialect, DbType::PolarDB);
1367
1368delegate_dialect_to!(GaussDbDialect, PostgreSqlDialect, DbType::GaussDB);
1370
1371delegate_dialect_to!(DamengDialect, OracleDialect, DbType::Dameng);
1373
1374delegate_dialect_to!(SybaseDialect, SqlServerDialect, DbType::Sybase);
1376
1377delegate_dialect_to!(GBaseDialect, SqlServerDialect, DbType::GBase);
1379
1380pub struct ClickHouseDialect;
1394
1395impl Dialect for ClickHouseDialect {
1396 fn db_type(&self) -> DbType {
1397 DbType::ClickHouse
1398 }
1399
1400 fn quote(&self, identifier: &str) -> String {
1401 format!("`{}`", identifier.replace('`', "``"))
1403 }
1404
1405 fn escape_string(&self, s: &str) -> String {
1406 let mut escaped = String::with_capacity(s.len() * 2);
1408 for c in s.chars() {
1409 match c {
1410 '\'' => escaped.push_str("\\'"),
1411 '\\' => escaped.push_str("\\\\"),
1412 '\n' => escaped.push_str("\\n"),
1413 '\r' => escaped.push_str("\\r"),
1414 '\t' => escaped.push_str("\\t"),
1415 _ => escaped.push(c),
1416 }
1417 }
1418 escaped
1419 }
1420
1421 fn supports_returning(&self) -> bool {
1422 false
1424 }
1425
1426 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1427 let offset = page.saturating_sub(1).saturating_mul(limit);
1429 format!("{} LIMIT {}, {}", sql, offset, limit)
1430 }
1431
1432 fn json_type(&self) -> &'static str {
1433 "String"
1435 }
1436
1437 fn json_extract(&self, column: &str, path: &str) -> String {
1438 let normalized = if path.starts_with('$') {
1440 path.to_string()
1441 } else {
1442 format!("$.{}", path)
1443 };
1444 format!(
1445 "JSONExtractString({}, '{}')",
1446 column,
1447 self.escape_string(&normalized)
1448 )
1449 }
1450
1451 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1452 if columns.is_empty() {
1454 return "0".to_string();
1455 }
1456 let escaped = self.escape_string(keyword);
1457 let parts: Vec<String> = columns
1458 .iter()
1459 .map(|c| format!("position({}, '{}') > 0", c, escaped))
1460 .collect();
1461 parts.join(" OR ")
1462 }
1463
1464 fn bool_to_int(&self, expr: &str) -> String {
1465 format!("toUInt8({})", expr)
1467 }
1468
1469 fn concat(&self, parts: &[&str]) -> String {
1470 if parts.is_empty() {
1472 return "''".to_string();
1473 }
1474 format!("concat({})", parts.join(", "))
1475 }
1476
1477 fn supports_if_exists(&self) -> bool {
1478 true
1479 }
1480
1481 fn supports_if_not_exists(&self) -> bool {
1482 true
1483 }
1484
1485 fn auto_increment_keyword(&self) -> &'static str {
1486 ""
1488 }
1489
1490 fn last_insert_id_sql(&self) -> Option<&'static str> {
1491 None
1493 }
1494
1495 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1496 let cols: Vec<String> = columns
1497 .iter()
1498 .map(|col| {
1499 let ch_type = map_to_clickhouse_type(&col.sql_type);
1500 let mut sql = format!("{} {}", self.quote(&col.name), ch_type);
1501 if let Some(default) = &col.default {
1502 sql.push_str(&format!(" DEFAULT {}", default));
1503 }
1504 if col.primary_key {
1505 sql.push_str(" PRIMARY KEY");
1506 }
1507 sql
1508 })
1509 .collect();
1510
1511 format!(
1513 "CREATE TABLE {} ({}) ENGINE = MergeTree()",
1514 self.quote(table),
1515 cols.join(", ")
1516 )
1517 }
1518
1519 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1520 let stmts: Vec<String> = changes
1521 .iter()
1522 .map(|change| match change {
1523 TableChange::AddColumn(col) => {
1524 let ch_type = map_to_clickhouse_type(&col.sql_type);
1525 format!(
1526 "ALTER TABLE {} ADD COLUMN {} {}",
1527 self.quote(table),
1528 self.quote(&col.name),
1529 ch_type
1530 )
1531 }
1532 TableChange::DropColumn(name) => {
1533 format!(
1534 "ALTER TABLE {} DROP COLUMN {}",
1535 self.quote(table),
1536 self.quote(name)
1537 )
1538 }
1539 TableChange::ModifyColumn(col) => {
1540 let ch_type = map_to_clickhouse_type(&col.sql_type);
1541 format!(
1542 "ALTER TABLE {} MODIFY COLUMN {} {}",
1543 self.quote(table),
1544 self.quote(&col.name),
1545 ch_type
1546 )
1547 }
1548 TableChange::AddIndex(name, cols) => {
1549 format!(
1550 "ALTER TABLE {} ADD INDEX {} ({})",
1551 self.quote(table),
1552 name,
1553 cols.join(", ")
1554 )
1555 }
1556 TableChange::DropIndex(name) => {
1557 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
1558 }
1559 TableChange::AddForeignKey { .. } => {
1560 String::new()
1562 }
1563 })
1564 .filter(|s| !s.is_empty())
1565 .collect();
1566
1567 stmts.join("; ")
1568 }
1569}
1570
1571fn map_to_clickhouse_type(sql_type: &str) -> String {
1581 let upper = sql_type.to_uppercase();
1582 let trimmed = upper.trim();
1583
1584 if trimmed.starts_with("BIGINT") {
1585 "Int64".to_string()
1586 } else if matches!(trimmed, "INT" | "INTEGER") {
1587 "Int32".to_string()
1588 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
1589 "Int16".to_string()
1590 } else if trimmed.starts_with("VARCHAR")
1591 || trimmed.starts_with("CHAR")
1592 || matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT")
1593 {
1594 "String".to_string()
1595 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1596 "UInt8".to_string()
1597 } else if matches!(trimmed, "FLOAT" | "REAL") {
1598 "Float32".to_string()
1599 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
1600 "Float64".to_string()
1601 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
1602 "DateTime".to_string()
1603 } else if matches!(trimmed, "DATE") {
1604 "Date".to_string()
1605 } else if trimmed.starts_with("DECIMAL") || trimmed.starts_with("NUMERIC") {
1606 "Decimal(38, 4)".to_string()
1607 } else {
1608 sql_type.to_string()
1609 }
1610}
1611
1612pub struct Db2Dialect;
1626
1627impl Dialect for Db2Dialect {
1628 fn db_type(&self) -> DbType {
1629 DbType::Db2
1630 }
1631
1632 fn quote(&self, identifier: &str) -> String {
1633 format!("\"{}\"", identifier.replace('"', "\"\""))
1635 }
1636
1637 fn escape_string(&self, s: &str) -> String {
1638 let mut escaped = String::with_capacity(s.len() * 2);
1640 for c in s.chars() {
1641 match c {
1642 '\'' => escaped.push_str("''"),
1643 _ => escaped.push(c),
1644 }
1645 }
1646 escaped
1647 }
1648
1649 fn supports_returning(&self) -> bool {
1650 false
1652 }
1653
1654 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1655 let offset = page.saturating_sub(1).saturating_mul(limit);
1657 format!(
1658 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1659 sql, offset, limit
1660 )
1661 }
1662
1663 fn json_type(&self) -> &'static str {
1664 "JSON"
1666 }
1667
1668 fn json_extract(&self, column: &str, path: &str) -> String {
1669 let normalized = if path.starts_with('$') {
1671 path.to_string()
1672 } else {
1673 format!("$.{}", path)
1674 };
1675 format!(
1676 "JSON_VALUE({}, '{}')",
1677 column,
1678 self.escape_string(&normalized)
1679 )
1680 }
1681
1682 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1683 if columns.is_empty() {
1685 return "0".to_string();
1686 }
1687 let escaped = self.escape_string(keyword);
1688 let parts: Vec<String> = columns
1689 .iter()
1690 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
1691 .collect();
1692 parts.join(" OR ")
1693 }
1694
1695 fn bool_to_int(&self, expr: &str) -> String {
1696 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1698 }
1699
1700 fn concat(&self, parts: &[&str]) -> String {
1701 if parts.is_empty() {
1703 return "''".to_string();
1704 }
1705 parts.join(" || ")
1706 }
1707
1708 fn supports_if_exists(&self) -> bool {
1709 false
1711 }
1712
1713 fn supports_if_not_exists(&self) -> bool {
1714 false
1716 }
1717
1718 fn auto_increment_keyword(&self) -> &'static str {
1719 "GENERATED ALWAYS AS IDENTITY"
1721 }
1722
1723 fn last_insert_id_sql(&self) -> Option<&'static str> {
1724 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
1726 }
1727
1728 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1729 let cols: Vec<String> = columns
1730 .iter()
1731 .map(|col| {
1732 let db2_type = map_to_db2_type(&col.sql_type);
1733 let mut sql = format!("{} {}", self.quote(&col.name), db2_type);
1734 if !col.nullable && !col.auto_increment {
1735 sql.push_str(" NOT NULL");
1736 }
1737 if let Some(default) = &col.default {
1738 sql.push_str(&format!(" DEFAULT {}", default));
1739 }
1740 if col.auto_increment {
1741 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1742 }
1743 if col.primary_key {
1744 sql.push_str(" PRIMARY KEY");
1745 }
1746 sql
1747 })
1748 .collect();
1749
1750 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1751 }
1752
1753 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1754 let stmts: Vec<String> = changes
1755 .iter()
1756 .map(|change| match change {
1757 TableChange::AddColumn(col) => {
1758 let db2_type = map_to_db2_type(&col.sql_type);
1759 let mut sql = format!(
1760 "ALTER TABLE {} ADD COLUMN {} {}",
1761 self.quote(table),
1762 self.quote(&col.name),
1763 db2_type
1764 );
1765 if !col.nullable {
1766 sql.push_str(" NOT NULL");
1767 }
1768 if let Some(default) = &col.default {
1769 sql.push_str(&format!(" DEFAULT {}", default));
1770 }
1771 sql
1772 }
1773 TableChange::DropColumn(name) => {
1774 format!(
1775 "ALTER TABLE {} DROP COLUMN {}",
1776 self.quote(table),
1777 self.quote(name)
1778 )
1779 }
1780 TableChange::ModifyColumn(col) => {
1781 let db2_type = map_to_db2_type(&col.sql_type);
1782 format!(
1783 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
1784 self.quote(table),
1785 self.quote(&col.name),
1786 db2_type
1787 )
1788 }
1789 TableChange::AddIndex(name, cols) => {
1790 format!(
1791 "CREATE INDEX {} ON {} ({})",
1792 name,
1793 self.quote(table),
1794 cols.join(", ")
1795 )
1796 }
1797 TableChange::DropIndex(name) => {
1798 format!("DROP INDEX {}", name)
1799 }
1800 TableChange::AddForeignKey {
1801 columns,
1802 reference_table,
1803 reference_columns,
1804 } => {
1805 format!(
1806 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1807 self.quote(table),
1808 table,
1809 columns.join("_"),
1810 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1811 self.quote(reference_table),
1812 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1813 )
1814 }
1815 })
1816 .collect();
1817
1818 stmts.join("; ")
1819 }
1820
1821 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1822 let _ = if_exists;
1824 format!("DROP TABLE {}", self.quote(table))
1825 }
1826}
1827
1828fn map_to_db2_type(sql_type: &str) -> String {
1837 let upper = sql_type.to_uppercase();
1838 let trimmed = upper.trim();
1839
1840 if trimmed.starts_with("BIGINT") {
1841 "BIGINT".to_string()
1842 } else if matches!(trimmed, "INT" | "INTEGER") {
1843 "INTEGER".to_string()
1844 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
1845 "SMALLINT".to_string()
1846 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
1847 sql_type.to_string()
1848 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1849 "CLOB(2G)".to_string()
1850 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1851 "SMALLINT".to_string()
1852 } else if matches!(trimmed, "FLOAT" | "REAL") {
1853 "REAL".to_string()
1854 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
1855 "DOUBLE".to_string()
1856 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
1857 "TIMESTAMP".to_string()
1858 } else if matches!(trimmed, "DATE") {
1859 "DATE".to_string()
1860 } else {
1861 sql_type.to_string()
1863 }
1864}
1865
1866pub fn get_dialect(db_type: DbType) -> Result<Box<dyn Dialect>, DbError> {
1889 match db_type {
1890 DbType::MySQL => Ok(Box::new(MySqlDialect)),
1891 DbType::PostgreSQL => Ok(Box::new(PostgreSqlDialect)),
1892 DbType::Sqlite => Ok(Box::new(SqliteDialect)),
1893 DbType::Redis => Err(DbError::Unsupported(
1894 "Redis does not support standard SQL dialect".to_string(),
1895 )),
1896 DbType::MongoDB => Err(DbError::Unsupported(
1897 "MongoDB uses different query syntax".to_string(),
1898 )),
1899 DbType::ClickHouse => Ok(Box::new(ClickHouseDialect)),
1900 DbType::Oracle => Ok(Box::new(OracleDialect)),
1901 DbType::OceanBase => Ok(Box::new(MySqlDialect)),
1902 DbType::SqlServer => Ok(Box::new(SqlServerDialect)),
1903 DbType::VectorDb => Err(DbError::Unsupported(
1904 "Vector databases have specific APIs".to_string(),
1905 )),
1906 DbType::PureJsDb => Err(DbError::Unsupported(
1907 "PureJS database uses JavaScript".to_string(),
1908 )),
1909 DbType::Dameng => Ok(Box::new(DamengDialect)),
1911 DbType::Kingbase => Ok(Box::new(KingbaseDialect)),
1912 DbType::Db2 => Ok(Box::new(Db2Dialect)),
1913 DbType::MariaDB => Ok(Box::new(MariaDbDialect)),
1914 DbType::TiDB => Ok(Box::new(TiDbDialect)),
1915 DbType::PolarDB => Ok(Box::new(PolarDbDialect)),
1916 DbType::GaussDB => Ok(Box::new(GaussDbDialect)),
1917 DbType::GBase => Ok(Box::new(GBaseDialect)),
1918 DbType::Sybase => Ok(Box::new(SybaseDialect)),
1919 }
1920}
1921
1922impl fmt::Display for dyn Dialect {
1923 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1924 write!(f, "Dialect({})", self.db_type())
1925 }
1926}
1927
1928#[cfg(test)]
1929mod tests {
1930 use super::*;
1931
1932 #[test]
1933 fn test_mysql_quote() {
1934 let dialect = MySqlDialect;
1935 assert_eq!(dialect.quote("users"), "`users`");
1936 assert_eq!(dialect.quote("user`id"), "`user``id`");
1937 }
1938
1939 #[test]
1940 fn test_mysql_escape() {
1941 let dialect = MySqlDialect;
1942 assert_eq!(dialect.escape_string("hello"), "hello");
1943 assert_eq!(dialect.escape_string("it's"), "it\\'s");
1944 assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
1945 }
1946
1947 #[test]
1948 fn test_mysql_pagination() {
1949 let dialect = MySqlDialect;
1950 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
1951 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
1952 }
1953
1954 #[test]
1955 fn test_postgres_quote() {
1956 let dialect = PostgreSqlDialect;
1957 assert_eq!(dialect.quote("users"), "\"users\"");
1958 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
1959 }
1960
1961 #[test]
1962 fn test_postgres_pagination() {
1963 let dialect = PostgreSqlDialect;
1964 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
1965 assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
1966 }
1967
1968 #[test]
1969 fn test_postgres_returning() {
1970 let dialect = PostgreSqlDialect;
1971 assert!(dialect.supports_returning());
1972 }
1973
1974 #[test]
1975 fn test_sqlite_quote() {
1976 let dialect = SqliteDialect;
1977 assert_eq!(dialect.quote("users"), "\"users\"");
1978 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
1979 }
1980
1981 #[test]
1982 fn test_sqlite_escape() {
1983 let dialect = SqliteDialect;
1984 assert_eq!(dialect.escape_string("hello"), "hello");
1985 assert_eq!(dialect.escape_string("it's"), "it''s");
1986 }
1987
1988 #[test]
1989 fn test_get_dialect() {
1990 let dialect = get_dialect(DbType::MySQL);
1991 assert!(dialect.is_ok());
1992
1993 let dialect = get_dialect(DbType::Redis);
1994 assert!(dialect.is_err());
1995 }
1996
1997 #[test]
1998 fn test_bool_to_int() {
1999 let mysql = MySqlDialect;
2000 assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
2001
2002 let pg = PostgreSqlDialect;
2003 assert_eq!(
2004 pg.bool_to_int("active"),
2005 "(CASE WHEN active THEN 1 ELSE 0 END)"
2006 );
2007 }
2008
2009 #[test]
2010 fn test_json_extract_with_path() {
2011 let mysql = MySqlDialect;
2012 let sql = mysql.json_extract("data", "$.user.name");
2013 assert!(sql.contains("$.user.name"));
2014 assert!(sql.contains("JSON_EXTRACT"));
2015
2016 let pg = PostgreSqlDialect;
2017 let sql = pg.json_extract("data", "user.name");
2018 assert!(sql.contains("#>>"));
2019
2020 let sqlite = SqliteDialect;
2021 let sql = sqlite.json_extract("data", "$.user.name");
2022 assert!(sql.contains("$.user.name"));
2023 assert!(sql.contains("json_extract"));
2024 }
2025
2026 #[test]
2027 fn test_sqlite_full_text_search() {
2028 let sqlite = SqliteDialect;
2029 let sql = sqlite.full_text_search(&["title", "content"], "hello");
2030 assert!(sql.contains("LIKE"));
2032 assert!(sql.contains("title LIKE '%hello%'"));
2033 assert!(sql.contains("content LIKE '%hello%'"));
2034 assert!(sql.contains(" OR "));
2035
2036 assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
2038
2039 let sql = sqlite.full_text_search(&["title"], "it's");
2041 assert!(sql.contains("title LIKE '%it''s%'"));
2042 }
2043
2044 #[test]
2045 fn test_alter_table_modify_column() {
2046 let mysql = MySqlDialect;
2047 let col = ColumnDef {
2048 name: "name".to_string(),
2049 sql_type: "VARCHAR(255)".to_string(),
2050 nullable: false,
2051 default: None,
2052 auto_increment: false,
2053 primary_key: false,
2054 };
2055 let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2056 assert!(sql.contains("MODIFY COLUMN"));
2057
2058 let pg = PostgreSqlDialect;
2059 let col = ColumnDef {
2060 name: "name".to_string(),
2061 sql_type: "VARCHAR(255)".to_string(),
2062 nullable: false,
2063 default: None,
2064 auto_increment: false,
2065 primary_key: false,
2066 };
2067 let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2068 assert!(sql.contains("ALTER COLUMN"));
2069 assert!(sql.contains("TYPE"));
2070 }
2071
2072 #[test]
2073 fn test_alter_table_add_foreign_key() {
2074 let mysql = MySqlDialect;
2075 let sql = mysql.build_alter_table(
2076 "orders",
2077 &[TableChange::AddForeignKey {
2078 columns: vec!["user_id".to_string()],
2079 reference_table: "users".to_string(),
2080 reference_columns: vec!["id".to_string()],
2081 }],
2082 );
2083 assert!(sql.contains("FOREIGN KEY"));
2084 assert!(sql.contains("REFERENCES"));
2085
2086 let sqlite = SqliteDialect;
2087 let sql = sqlite.build_alter_table(
2088 "orders",
2089 &[TableChange::AddForeignKey {
2090 columns: vec!["user_id".to_string()],
2091 reference_table: "users".to_string(),
2092 reference_columns: vec!["id".to_string()],
2093 }],
2094 );
2095 assert!(sql.starts_with("--"));
2097 }
2098
2099 #[test]
2100 fn test_sqlite_alter_table_add_column() {
2101 let sqlite = SqliteDialect;
2102 let col = ColumnDef {
2103 name: "email".to_string(),
2104 sql_type: "TEXT".to_string(),
2105 nullable: true,
2106 default: None,
2107 auto_increment: false,
2108 primary_key: false,
2109 };
2110 let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
2111 assert!(sql.contains("ADD COLUMN"));
2112 assert!(sql.contains("email"));
2113 }
2114
2115 #[test]
2118 fn test_oracle_quote_and_escape() {
2119 let dialect = OracleDialect;
2120 assert_eq!(dialect.quote("users"), "\"users\"");
2122 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2123 assert_eq!(dialect.quote("column_name"), "\"column_name\"");
2124
2125 assert_eq!(dialect.escape_string("hello"), "hello");
2127 assert_eq!(dialect.escape_string("it's"), "it''s");
2128 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2129 assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
2130 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2132 }
2133
2134 #[test]
2135 fn test_oracle_pagination() {
2136 let dialect = OracleDialect;
2137 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2139 assert_eq!(
2140 sql,
2141 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2142 );
2143 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2145 assert_eq!(
2146 sql,
2147 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2148 );
2149 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2151 assert_eq!(
2152 sql,
2153 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2154 );
2155 }
2156
2157 #[test]
2158 fn test_oracle_json_extract() {
2159 let dialect = OracleDialect;
2160 let sql = dialect.json_extract("data", "$.user.name");
2162 assert!(sql.contains("JSON_VALUE"));
2163 assert!(sql.contains("$.user.name"));
2164 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2165
2166 let sql = dialect.json_extract("data", "user.name");
2168 assert!(sql.contains("$.user.name"));
2169 assert!(sql.contains("JSON_VALUE"));
2170
2171 let sql = dialect.json_extract("data", "$.key's");
2173 assert!(sql.contains("$.key''s"));
2174 }
2175
2176 #[test]
2177 fn test_oracle_create_table() {
2178 let dialect = OracleDialect;
2179 let columns = vec![
2180 ColumnDef {
2181 name: "id".to_string(),
2182 sql_type: "BIGINT".to_string(),
2183 nullable: false,
2184 default: None,
2185 auto_increment: true,
2186 primary_key: true,
2187 },
2188 ColumnDef {
2189 name: "name".to_string(),
2190 sql_type: "VARCHAR(255)".to_string(),
2191 nullable: false,
2192 default: None,
2193 auto_increment: false,
2194 primary_key: false,
2195 },
2196 ColumnDef {
2197 name: "bio".to_string(),
2198 sql_type: "TEXT".to_string(),
2199 nullable: true,
2200 default: None,
2201 auto_increment: false,
2202 primary_key: false,
2203 },
2204 ColumnDef {
2205 name: "is_active".to_string(),
2206 sql_type: "BOOLEAN".to_string(),
2207 nullable: false,
2208 default: Some("1".to_string()),
2209 auto_increment: false,
2210 primary_key: false,
2211 },
2212 ];
2213 let sql = dialect.build_create_table("users", &columns);
2214 assert!(
2216 sql.contains("NUMBER(19)"),
2217 "BIGINT should map to NUMBER(19): {}",
2218 sql
2219 );
2220 assert!(
2221 sql.contains("VARCHAR2(255)"),
2222 "VARCHAR should map to VARCHAR2: {}",
2223 sql
2224 );
2225 assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
2226 assert!(
2227 sql.contains("NUMBER(1)"),
2228 "BOOLEAN should map to NUMBER(1): {}",
2229 sql
2230 );
2231 assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
2233 assert!(sql.contains("PRIMARY KEY"));
2234 assert!(sql.contains("NOT NULL"));
2235 assert!(sql.contains("DEFAULT 1"));
2236 assert!(sql.contains("\"users\""));
2238 assert!(sql.contains("\"id\""));
2239 }
2240
2241 #[test]
2242 fn test_oracle_bool_to_int_and_concat() {
2243 let dialect = OracleDialect;
2244 assert_eq!(
2246 dialect.bool_to_int("active"),
2247 "(CASE WHEN active THEN 1 ELSE 0 END)"
2248 );
2249 assert_eq!(
2250 dialect.bool_to_int("x > 0"),
2251 "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
2252 );
2253 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
2255 assert_eq!(
2256 dialect.concat(&["first_name", "last_name"]),
2257 "first_name || last_name"
2258 );
2259 assert_eq!(dialect.concat(&[]), "NULL");
2261 }
2262
2263 #[test]
2264 fn test_oracle_misc_dialect_methods() {
2265 let dialect = OracleDialect;
2266 assert_eq!(dialect.db_type(), DbType::Oracle);
2268 assert!(dialect.supports_returning());
2270 assert!(dialect.supports_if_exists());
2272 assert!(dialect.supports_if_not_exists());
2273 assert_eq!(
2275 dialect.auto_increment_keyword(),
2276 "GENERATED BY DEFAULT AS IDENTITY"
2277 );
2278 assert_eq!(dialect.last_insert_id_sql(), None);
2281 assert_eq!(dialect.json_type(), "JSON");
2283 }
2284
2285 #[test]
2286 fn test_oracle_get_dialect() {
2287 let dialect = get_dialect(DbType::Oracle);
2289 assert!(dialect.is_ok(), "Oracle dialect should be available");
2290 let dialect = dialect.unwrap();
2291 assert_eq!(dialect.db_type(), DbType::Oracle);
2292 assert_eq!(dialect.quote("users"), "\"users\"");
2294 assert!(dialect.supports_returning());
2296 assert_eq!(dialect.last_insert_id_sql(), None);
2298 }
2299
2300 #[test]
2301 fn test_oracle_drop_table() {
2302 let dialect = OracleDialect;
2303 let sql = dialect.build_drop_table("users", true);
2305 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
2306 let sql = dialect.build_drop_table("users", false);
2308 assert_eq!(sql, "DROP TABLE \"users\"");
2309 }
2310
2311 #[test]
2312 fn test_oracle_alter_table() {
2313 let dialect = OracleDialect;
2314 let col = ColumnDef {
2316 name: "name".to_string(),
2317 sql_type: "VARCHAR(255)".to_string(),
2318 nullable: false,
2319 default: None,
2320 auto_increment: false,
2321 primary_key: false,
2322 };
2323 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2324 assert!(sql.contains("MODIFY"));
2325 assert!(sql.contains("VARCHAR2(255)"));
2326 assert!(!sql.contains("MODIFY COLUMN")); let col = ColumnDef {
2330 name: "email".to_string(),
2331 sql_type: "VARCHAR(255)".to_string(),
2332 nullable: true,
2333 default: None,
2334 auto_increment: false,
2335 primary_key: false,
2336 };
2337 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2338 assert!(sql.contains("ADD \"email\""));
2339 assert!(sql.contains("VARCHAR2(255)"));
2340
2341 let sql =
2343 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2344 assert!(sql.contains("DROP COLUMN"));
2345 assert!(sql.contains("\"email\""));
2346 }
2347
2348 #[test]
2351 fn test_sqlite_concat_handles_null() {
2352 let sqlite = SqliteDialect;
2353 let sql = sqlite.concat(&["a", "b"]);
2356 assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
2357 let sql = sqlite.concat(&["a"]);
2359 assert_eq!(sql, "COALESCE(a, '')");
2360 assert_eq!(sqlite.concat(&[]), "NULL");
2362 }
2363
2364 #[test]
2367 fn test_sqlserver_quote_and_escape() {
2368 let dialect = SqlServerDialect;
2369 assert_eq!(dialect.quote("users"), "[users]");
2371 assert_eq!(dialect.quote("col]name"), "[col]]name]");
2372 assert_eq!(dialect.escape_string("hello"), "hello");
2374 assert_eq!(dialect.escape_string("it's"), "it''s");
2375 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2376 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2377 }
2378
2379 #[test]
2380 fn test_sqlserver_pagination() {
2381 let dialect = SqlServerDialect;
2382 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2384 assert_eq!(
2385 sql,
2386 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2387 );
2388 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2389 assert_eq!(
2390 sql,
2391 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2392 );
2393 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2395 assert_eq!(
2396 sql,
2397 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2398 );
2399 }
2400
2401 #[test]
2402 fn test_sqlserver_misc_dialect_methods() {
2403 let dialect = SqlServerDialect;
2404 assert_eq!(dialect.db_type(), DbType::SqlServer);
2405 assert!(dialect.supports_returning());
2407 assert!(dialect.supports_if_exists());
2409 assert!(dialect.supports_if_not_exists());
2410 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2412 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2414 assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
2416 }
2417
2418 #[test]
2419 fn test_sqlserver_json_extract() {
2420 let dialect = SqlServerDialect;
2421 let sql = dialect.json_extract("data", "$.user.name");
2422 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2423 let sql = dialect.json_extract("data", "user.name");
2425 assert!(sql.contains("$.user.name"));
2426 assert!(sql.contains("JSON_VALUE"));
2427 let sql = dialect.json_extract("data", "$.key's");
2429 assert!(sql.contains("$.key''s"));
2430 }
2431
2432 #[test]
2433 fn test_sqlserver_full_text_search() {
2434 let dialect = SqlServerDialect;
2435 let sql = dialect.full_text_search(&["title", "content"], "hello");
2436 assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
2437 assert_eq!(dialect.full_text_search(&[], "hello"), "0");
2439 let sql = dialect.full_text_search(&["title"], "it's");
2441 assert!(sql.contains("it''s"));
2442 }
2443
2444 #[test]
2445 fn test_sqlserver_bool_to_int_and_concat() {
2446 let dialect = SqlServerDialect;
2447 assert_eq!(
2448 dialect.bool_to_int("active"),
2449 "(CASE WHEN active THEN 1 ELSE 0 END)"
2450 );
2451 assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
2452 assert_eq!(dialect.concat(&[]), "NULL");
2453 }
2454
2455 #[test]
2456 fn test_sqlserver_create_table() {
2457 let dialect = SqlServerDialect;
2458 let columns = vec![
2459 ColumnDef {
2460 name: "id".to_string(),
2461 sql_type: "BIGINT".to_string(),
2462 nullable: false,
2463 default: None,
2464 auto_increment: true,
2465 primary_key: true,
2466 },
2467 ColumnDef {
2468 name: "name".to_string(),
2469 sql_type: "VARCHAR(255)".to_string(),
2470 nullable: false,
2471 default: None,
2472 auto_increment: false,
2473 primary_key: false,
2474 },
2475 ColumnDef {
2476 name: "bio".to_string(),
2477 sql_type: "TEXT".to_string(),
2478 nullable: true,
2479 default: None,
2480 auto_increment: false,
2481 primary_key: false,
2482 },
2483 ColumnDef {
2484 name: "is_active".to_string(),
2485 sql_type: "BOOLEAN".to_string(),
2486 nullable: false,
2487 default: Some("1".to_string()),
2488 auto_increment: false,
2489 primary_key: false,
2490 },
2491 ];
2492 let sql = dialect.build_create_table("users", &columns);
2493 assert!(sql.contains("[users]"));
2495 assert!(sql.contains("[id]"));
2496 assert!(sql.contains("IDENTITY(1,1)"));
2498 assert!(
2499 sql.contains("NVARCHAR(255)"),
2500 "VARCHAR should map to NVARCHAR: {}",
2501 sql
2502 );
2503 assert!(
2504 sql.contains("NVARCHAR(MAX)"),
2505 "TEXT should map to NVARCHAR(MAX): {}",
2506 sql
2507 );
2508 assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
2509 assert!(sql.contains("PRIMARY KEY"));
2510 assert!(sql.contains("NOT NULL"));
2511 assert!(sql.contains("DEFAULT 1"));
2512 }
2513
2514 #[test]
2515 fn test_sqlserver_drop_table() {
2516 let dialect = SqlServerDialect;
2517 assert_eq!(
2518 dialect.build_drop_table("users", true),
2519 "DROP TABLE IF EXISTS [users]"
2520 );
2521 assert_eq!(
2522 dialect.build_drop_table("users", false),
2523 "DROP TABLE [users]"
2524 );
2525 }
2526
2527 #[test]
2528 fn test_sqlserver_alter_table() {
2529 let dialect = SqlServerDialect;
2530 let col = ColumnDef {
2532 name: "name".to_string(),
2533 sql_type: "VARCHAR(255)".to_string(),
2534 nullable: false,
2535 default: None,
2536 auto_increment: false,
2537 primary_key: false,
2538 };
2539 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2540 assert!(sql.contains("ALTER COLUMN"));
2541 assert!(sql.contains("NVARCHAR(255)"));
2542 assert!(!sql.contains("MODIFY"));
2543
2544 let col = ColumnDef {
2546 name: "email".to_string(),
2547 sql_type: "VARCHAR(255)".to_string(),
2548 nullable: true,
2549 default: None,
2550 auto_increment: false,
2551 primary_key: false,
2552 };
2553 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2554 assert!(sql.contains("ADD [email]"));
2555 assert!(sql.contains("NVARCHAR(255)"));
2556
2557 let sql =
2559 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2560 assert!(sql.contains("DROP COLUMN"));
2561 assert!(sql.contains("[email]"));
2562
2563 let sql =
2565 dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
2566 assert!(sql.contains("DROP INDEX idx_name ON [users]"));
2567 }
2568
2569 #[test]
2570 fn test_sqlserver_get_dialect() {
2571 let dialect = get_dialect(DbType::SqlServer);
2573 assert!(dialect.is_ok(), "SqlServer dialect should be available");
2574 let dialect = dialect.unwrap();
2575 assert_eq!(dialect.db_type(), DbType::SqlServer);
2576 assert_eq!(dialect.quote("users"), "[users]");
2578 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2580 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2581 }
2582
2583 #[test]
2584 fn test_clickhouse_get_dialect_unsupported() {
2585 let dialect = get_dialect(DbType::ClickHouse);
2587 assert!(dialect.is_ok(), "ClickHouse should be supported");
2588 let dialect = dialect.unwrap();
2589 assert_eq!(dialect.db_type(), DbType::ClickHouse);
2590 assert_eq!(dialect.quote("users"), "`users`");
2592 assert!(!dialect.supports_returning());
2594 let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
2596 assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
2597 assert_eq!(dialect.auto_increment_keyword(), "");
2599 }
2600
2601 #[test]
2602 fn test_get_dialect_all_supported_types() {
2603 assert!(get_dialect(DbType::MySQL).is_ok());
2605 assert!(get_dialect(DbType::PostgreSQL).is_ok());
2606 assert!(get_dialect(DbType::Sqlite).is_ok());
2607 assert!(get_dialect(DbType::Oracle).is_ok());
2608 assert!(get_dialect(DbType::SqlServer).is_ok());
2609 assert!(get_dialect(DbType::OceanBase).is_ok());
2610 assert!(get_dialect(DbType::ClickHouse).is_ok());
2611 assert!(get_dialect(DbType::Dameng).is_ok());
2613 assert!(get_dialect(DbType::Kingbase).is_ok());
2614 assert!(get_dialect(DbType::Db2).is_ok());
2615 assert!(get_dialect(DbType::MariaDB).is_ok());
2616 assert!(get_dialect(DbType::TiDB).is_ok());
2617 assert!(get_dialect(DbType::PolarDB).is_ok());
2618 assert!(get_dialect(DbType::GaussDB).is_ok());
2619 assert!(get_dialect(DbType::GBase).is_ok());
2620 assert!(get_dialect(DbType::Sybase).is_ok());
2621 assert!(get_dialect(DbType::Redis).is_err());
2623 assert!(get_dialect(DbType::MongoDB).is_err());
2624 assert!(get_dialect(DbType::VectorDb).is_err());
2625 assert!(get_dialect(DbType::PureJsDb).is_err());
2626 }
2627
2628 #[test]
2631 fn test_mariadb_dialect() {
2632 let dialect = get_dialect(DbType::MariaDB).unwrap();
2633 assert_eq!(dialect.db_type(), DbType::MariaDB);
2634 assert_eq!(dialect.quote("users"), "`users`");
2636 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2637 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
2638 assert!(!dialect.supports_returning());
2640 }
2641
2642 #[test]
2643 fn test_tidb_dialect() {
2644 let dialect = get_dialect(DbType::TiDB).unwrap();
2645 assert_eq!(dialect.db_type(), DbType::TiDB);
2646 assert_eq!(dialect.quote("users"), "`users`");
2648 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2649 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
2650 }
2651
2652 #[test]
2653 fn test_dameng_dialect() {
2654 let dialect = get_dialect(DbType::Dameng).unwrap();
2655 assert_eq!(dialect.db_type(), DbType::Dameng);
2656 assert_eq!(dialect.quote("users"), "\"users\"");
2658 assert_eq!(dialect.escape_string("it's"), "it''s");
2659 assert_eq!(
2661 dialect.auto_increment_keyword(),
2662 "GENERATED BY DEFAULT AS IDENTITY"
2663 );
2664 assert!(dialect.supports_returning());
2666 }
2667
2668 #[test]
2669 fn test_kingbase_dialect() {
2670 let dialect = get_dialect(DbType::Kingbase).unwrap();
2671 assert_eq!(dialect.db_type(), DbType::Kingbase);
2672 assert_eq!(dialect.quote("users"), "\"users\"");
2674 assert_eq!(dialect.escape_string("it's"), "it''s");
2675 assert!(dialect.supports_returning());
2677 assert_eq!(
2679 dialect.auto_increment_keyword(),
2680 "GENERATED BY DEFAULT AS IDENTITY"
2681 );
2682 }
2683
2684 #[test]
2685 fn test_polardb_dialect() {
2686 let dialect = get_dialect(DbType::PolarDB).unwrap();
2687 assert_eq!(dialect.db_type(), DbType::PolarDB);
2688 assert_eq!(dialect.quote("users"), "\"users\"");
2690 assert!(dialect.supports_returning());
2691 }
2692
2693 #[test]
2694 fn test_gaussdb_dialect() {
2695 let dialect = get_dialect(DbType::GaussDB).unwrap();
2696 assert_eq!(dialect.db_type(), DbType::GaussDB);
2697 assert_eq!(dialect.quote("users"), "\"users\"");
2699 assert!(dialect.supports_returning());
2700 }
2701
2702 #[test]
2703 fn test_gbase_dialect() {
2704 let dialect = get_dialect(DbType::GBase).unwrap();
2705 assert_eq!(dialect.db_type(), DbType::GBase);
2706 assert_eq!(dialect.quote("users"), "[users]");
2708 }
2709
2710 #[test]
2711 fn test_sybase_dialect() {
2712 let dialect = get_dialect(DbType::Sybase).unwrap();
2713 assert_eq!(dialect.db_type(), DbType::Sybase);
2714 assert_eq!(dialect.quote("users"), "[users]");
2716 }
2717
2718 #[test]
2721 fn test_db2_dialect_basic() {
2722 let dialect = get_dialect(DbType::Db2).unwrap();
2723 assert_eq!(dialect.db_type(), DbType::Db2);
2724 assert_eq!(dialect.quote("users"), "\"users\"");
2726 assert_eq!(dialect.escape_string("it's"), "it''s");
2728 assert_eq!(
2730 dialect.auto_increment_keyword(),
2731 "GENERATED ALWAYS AS IDENTITY"
2732 );
2733 assert!(!dialect.supports_if_exists());
2735 assert!(!dialect.supports_if_not_exists());
2736 assert!(!dialect.supports_returning());
2738 }
2739
2740 #[test]
2741 fn test_db2_pagination() {
2742 let dialect = Db2Dialect;
2743 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
2745 assert_eq!(
2746 sql,
2747 "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
2748 );
2749 }
2750
2751 #[test]
2752 fn test_db2_last_insert_id() {
2753 let dialect = Db2Dialect;
2754 assert_eq!(
2756 dialect.last_insert_id_sql(),
2757 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
2758 );
2759 }
2760
2761 #[test]
2762 fn test_db2_concat() {
2763 let dialect = Db2Dialect;
2764 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
2766 assert_eq!(dialect.concat(&[]), "''");
2767 }
2768
2769 #[test]
2770 fn test_db2_create_table() {
2771 let dialect = Db2Dialect;
2772 let cols = vec![ColumnDef {
2773 name: "id".to_string(),
2774 sql_type: "BIGINT".to_string(),
2775 nullable: false,
2776 default: None,
2777 auto_increment: true,
2778 primary_key: true,
2779 }];
2780 let sql = dialect.build_create_table("users", &cols);
2781 assert!(sql.contains("\"id\" BIGINT"));
2782 assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
2783 assert!(sql.contains("PRIMARY KEY"));
2784 }
2785
2786 #[test]
2787 fn test_db2_type_mapping() {
2788 assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
2790 assert_eq!(map_to_db2_type("INT"), "INTEGER");
2791 assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
2792 assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
2793 assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
2794 assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
2795 assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
2796 assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
2797 assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
2798 assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
2799 assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
2800 assert_eq!(map_to_db2_type("DATE"), "DATE");
2801 assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
2802 }
2803
2804 #[test]
2807 fn test_clickhouse_dialect_basic() {
2808 let dialect = get_dialect(DbType::ClickHouse).unwrap();
2809 assert_eq!(dialect.db_type(), DbType::ClickHouse);
2810 assert_eq!(dialect.quote("users"), "`users`");
2812 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2814 assert!(!dialect.supports_returning());
2816 assert_eq!(dialect.auto_increment_keyword(), "");
2818 assert!(dialect.supports_if_exists());
2820 assert!(dialect.supports_if_not_exists());
2821 }
2822
2823 #[test]
2824 fn test_clickhouse_type_mapping() {
2825 assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
2826 assert_eq!(map_to_clickhouse_type("INT"), "Int32");
2827 assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
2828 assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
2829 assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
2830 assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
2831 assert_eq!(map_to_clickhouse_type("TEXT"), "String");
2832 assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
2833 assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
2834 assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
2835 assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
2836 assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
2837 assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
2838 assert_eq!(map_to_clickhouse_type("DATE"), "Date");
2839 }
2840
2841 #[test]
2842 fn test_clickhouse_create_table() {
2843 let dialect = ClickHouseDialect;
2844 let cols = vec![ColumnDef {
2845 name: "id".to_string(),
2846 sql_type: "BIGINT".to_string(),
2847 nullable: false,
2848 default: None,
2849 auto_increment: false, primary_key: true,
2851 }];
2852 let sql = dialect.build_create_table("users", &cols);
2853 assert!(
2855 sql.contains("ENGINE = MergeTree()"),
2856 "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
2857 sql
2858 );
2859 assert!(sql.contains("`id` Int64"));
2860 assert!(sql.contains("PRIMARY KEY"));
2861 }
2862
2863 #[test]
2864 fn test_clickhouse_json_extract() {
2865 let dialect = ClickHouseDialect;
2866 let sql = dialect.json_extract("data", "$.name");
2867 assert!(
2868 sql.contains("JSONExtractString"),
2869 "ClickHouse 应使用 JSONExtractString: {}",
2870 sql
2871 );
2872 }
2873
2874 #[test]
2875 fn test_clickhouse_concat() {
2876 let dialect = ClickHouseDialect;
2877 assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
2879 assert_eq!(dialect.concat(&[]), "''");
2880 }
2881
2882 #[test]
2885 fn test_db_type_dameng_str() {
2886 assert_eq!(DbType::Dameng.as_str(), "dameng");
2887 assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
2888 assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
2889 assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
2890 assert_eq!(DbType::Dameng.default_port(), 5236);
2891 }
2892
2893 #[test]
2894 fn test_db_type_kingbase_str() {
2895 assert_eq!(DbType::Kingbase.as_str(), "kingbase");
2896 assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
2897 assert_eq!(DbType::Kingbase.default_port(), 54321);
2898 }
2899
2900 #[test]
2901 fn test_db_type_db2_str() {
2902 assert_eq!(DbType::Db2.as_str(), "db2");
2903 assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
2904 assert_eq!(DbType::Db2.default_port(), 50000);
2905 }
2906
2907 #[test]
2908 fn test_db_type_mariadb_str() {
2909 assert_eq!(DbType::MariaDB.as_str(), "mariadb");
2910 assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
2911 assert_eq!(DbType::MariaDB.default_port(), 3306);
2912 }
2913
2914 #[test]
2915 fn test_db_type_tidb_str() {
2916 assert_eq!(DbType::TiDB.as_str(), "tidb");
2917 assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
2918 assert_eq!(DbType::TiDB.default_port(), 4000);
2919 }
2920
2921 #[test]
2922 fn test_db_type_polardb_str() {
2923 assert_eq!(DbType::PolarDB.as_str(), "polardb");
2924 assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
2925 assert_eq!(DbType::PolarDB.default_port(), 5432);
2926 }
2927
2928 #[test]
2929 fn test_db_type_gaussdb_str() {
2930 assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
2931 assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
2932 assert_eq!(DbType::GaussDB.default_port(), 25308);
2933 }
2934
2935 #[test]
2936 fn test_db_type_gbase_str() {
2937 assert_eq!(DbType::GBase.as_str(), "gbase");
2938 assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
2939 assert_eq!(DbType::GBase.default_port(), 9088);
2940 }
2941
2942 #[test]
2943 fn test_db_type_sybase_str() {
2944 assert_eq!(DbType::Sybase.as_str(), "sybase");
2945 assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
2946 assert_eq!(DbType::Sybase.default_port(), 5000);
2947 }
2948
2949 #[test]
2950 fn test_db_type_family_classification() {
2951 assert!(DbType::MySQL.is_mysql_family());
2953 assert!(DbType::MariaDB.is_mysql_family());
2954 assert!(DbType::TiDB.is_mysql_family());
2955 assert!(DbType::OceanBase.is_mysql_family());
2956 assert!(!DbType::PostgreSQL.is_mysql_family());
2957
2958 assert!(DbType::PostgreSQL.is_postgres_family());
2960 assert!(DbType::Kingbase.is_postgres_family());
2961 assert!(DbType::GaussDB.is_postgres_family());
2962 assert!(!DbType::MySQL.is_postgres_family());
2963
2964 assert!(DbType::Oracle.is_oracle_family());
2966 assert!(DbType::Dameng.is_oracle_family());
2967 assert!(!DbType::MySQL.is_oracle_family());
2968 }
2969
2970 #[test]
2971 fn test_db_type_supports_stored_procedure_extended() {
2972 assert!(DbType::Dameng.supports_stored_procedure());
2974 assert!(DbType::Kingbase.supports_stored_procedure());
2975 assert!(DbType::Db2.supports_stored_procedure());
2976 assert!(DbType::MariaDB.supports_stored_procedure());
2977 assert!(DbType::TiDB.supports_stored_procedure());
2978 assert!(DbType::PolarDB.supports_stored_procedure());
2979 assert!(DbType::GaussDB.supports_stored_procedure());
2980 assert!(DbType::GBase.supports_stored_procedure());
2981 assert!(DbType::Sybase.supports_stored_procedure());
2982 }
2983
2984 #[test]
2987 fn test_l4_max_identifier_len_constant() {
2988 assert_eq!(MAX_IDENTIFIER_LEN, 63);
2990 }
2991
2992 #[test]
2993 fn test_l4_quote_checked_valid_identifier() {
2994 let dialect = MySqlDialect;
2995 assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
2996 assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
2997 let name_63 = "a".repeat(63);
2999 assert!(dialect.quote_checked(&name_63).is_ok());
3000 }
3001
3002 #[test]
3003 fn test_l4_quote_checked_rejects_too_long() {
3004 let dialect = MySqlDialect;
3005 let long_name = "a".repeat(64); let result = dialect.quote_checked(&long_name);
3007 assert!(result.is_err());
3008 match result {
3009 Err(DbError::InvalidInput(msg)) => {
3010 assert!(
3011 msg.contains("too long"),
3012 "expected 'too long' error, got: {}",
3013 msg
3014 );
3015 }
3016 _ => panic!("Expected DbError::InvalidInput"),
3017 }
3018 }
3019
3020 #[test]
3021 fn test_l4_quote_checked_rejects_empty() {
3022 let dialect = MySqlDialect;
3023 let result = dialect.quote_checked("");
3024 assert!(result.is_err());
3025 }
3026
3027 #[test]
3028 fn test_l4_quote_checked_rejects_sql_injection() {
3029 let dialect = MySqlDialect;
3030 assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
3032 assert!(dialect.quote_checked("user'name").is_err());
3034 assert!(dialect.quote_checked("user name").is_err());
3036 assert!(dialect.quote_checked("1users").is_err());
3038 assert!(dialect.quote_checked("schema.table").is_err());
3040 }
3041
3042 #[test]
3043 fn test_l4_quote_checked_postgres() {
3044 let dialect = PostgreSqlDialect;
3045 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3046 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3047 }
3048
3049 #[test]
3050 fn test_l4_quote_checked_sqlite() {
3051 let dialect = SqliteDialect;
3052 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3053 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3054 }
3055
3056 #[test]
3057 fn test_l4_quote_checked_oracle() {
3058 let dialect = OracleDialect;
3059 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3060 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3061 }
3062
3063 #[test]
3064 fn test_l4_quote_checked_sql_server() {
3065 let dialect = SqlServerDialect;
3066 assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
3067 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3068 }
3069}