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> =
546 conflict_columns.iter().copied().collect();
547 let cols_to_update: Vec<String> = if update_columns.is_empty() {
548 all_columns
549 .iter()
550 .filter(|c| !conflict_set.contains(c.as_str()))
551 .map(|c| self.quote(c))
552 .collect()
553 } else {
554 update_columns.iter().map(|c| self.quote(c)).collect()
555 };
556 if cols_to_update.is_empty() {
557 return Some(format!(
559 "ON CONFLICT ({}) DO NOTHING",
560 conflict_cols.join(", ")
561 ));
562 }
563 let set_clauses: Vec<String> = cols_to_update
564 .iter()
565 .map(|col| format!("{}=EXCLUDED.{}", col, col))
566 .collect();
567 Some(format!(
568 "ON CONFLICT ({}) DO UPDATE SET {}",
569 conflict_cols.join(", "),
570 set_clauses.join(", ")
571 ))
572 }
573}
574
575pub struct SqliteDialect;
577
578impl Dialect for SqliteDialect {
579 fn db_type(&self) -> DbType {
580 DbType::Sqlite
581 }
582
583 fn quote(&self, identifier: &str) -> String {
584 format!("\"{}\"", identifier.replace('"', "\"\""))
585 }
586
587 fn escape_string(&self, s: &str) -> String {
588 let mut escaped = String::with_capacity(s.len() * 2);
589 for c in s.chars() {
590 match c {
591 '\'' => escaped.push_str("''"),
592 _ => escaped.push(c),
593 }
594 }
595 escaped
596 }
597
598 fn supports_returning(&self) -> bool {
599 true
600 }
601
602 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
603 let offset = page.saturating_sub(1).saturating_mul(limit);
604 format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
605 }
606
607 fn json_type(&self) -> &'static str {
608 "TEXT"
609 }
610
611 fn json_extract(&self, column: &str, path: &str) -> String {
612 let normalized = if path.starts_with('$') {
614 path.to_string()
615 } else {
616 format!("$.{}", path)
617 };
618 format!(
619 "json_extract({}, '{}')",
620 column,
621 self.escape_string(&normalized)
622 )
623 }
624
625 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
626 if columns.is_empty() {
630 return "0".to_string();
631 }
632 let escaped = self.escape_string(keyword);
633 columns
634 .iter()
635 .map(|c| format!("{} LIKE '%{}%'", c.trim(), escaped))
636 .collect::<Vec<_>>()
637 .join(" OR ")
638 }
639
640 fn bool_to_int(&self, expr: &str) -> String {
641 expr.to_string()
642 }
643
644 fn concat(&self, parts: &[&str]) -> String {
645 if parts.is_empty() {
646 return "NULL".to_string();
647 }
648 let coalesced: Vec<String> = parts
651 .iter()
652 .map(|p| format!("COALESCE({}, '')", p))
653 .collect();
654 coalesced.join(" || ")
655 }
656
657 fn supports_if_exists(&self) -> bool {
658 true
659 }
660
661 fn supports_if_not_exists(&self) -> bool {
662 true
663 }
664
665 fn auto_increment_keyword(&self) -> &'static str {
666 "AUTOINCREMENT"
667 }
668
669 fn last_insert_id_sql(&self) -> Option<&'static str> {
670 Some("last_insert_rowid()")
671 }
672
673 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
674 let cols: Vec<String> = columns
675 .iter()
676 .map(|col| {
677 let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
678 if !col.nullable {
679 sql.push_str(" NOT NULL");
680 }
681 if let Some(default) = &col.default {
682 sql.push_str(&format!(" DEFAULT {}", default));
683 }
684 if col.auto_increment {
685 sql.push_str(" PRIMARY KEY AUTOINCREMENT");
686 } else if col.primary_key {
687 sql.push_str(" PRIMARY KEY");
688 }
689 sql
690 })
691 .collect();
692
693 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
694 }
695
696 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
697 let stmts: Vec<String> = changes
700 .iter()
701 .map(|change| {
702 match change {
703 TableChange::AddColumn(col) => {
704 let mut sql = format!(
705 "ALTER TABLE {} ADD COLUMN {} {}",
706 self.quote(table),
707 self.quote(&col.name),
708 col.sql_type
709 );
710 if !col.nullable {
711 sql.push_str(" NOT NULL");
712 }
713 if let Some(default) = &col.default {
714 sql.push_str(&format!(" DEFAULT {}", default));
715 }
716 sql
717 }
718 TableChange::DropColumn(name) => {
719 format!(
721 "ALTER TABLE {} DROP COLUMN {}",
722 self.quote(table),
723 self.quote(name)
724 )
725 }
726 TableChange::ModifyColumn(col) => {
727 format!(
730 "-- SQLite 不支持 MODIFY COLUMN({} {}),需重建表",
731 col.name, col.sql_type
732 )
733 }
734 TableChange::AddIndex(name, cols) => {
735 format!(
736 "CREATE INDEX {} ON {} ({})",
737 name,
738 self.quote(table),
739 cols.join(", ")
740 )
741 }
742 TableChange::DropIndex(name) => {
743 format!("DROP INDEX {}", name)
744 }
745 TableChange::AddForeignKey {
746 columns,
747 reference_table,
748 reference_columns: _,
749 } => {
750 format!(
752 "-- SQLite 不支持 ADD FOREIGN KEY({} -> {}),需重建表",
753 columns.join(","),
754 reference_table
755 )
756 }
757 }
758 })
759 .collect();
760
761 stmts.join("; ")
762 }
763
764 fn build_upsert_on_conflict(
768 &self,
769 conflict_columns: &[&str],
770 update_columns: &[&str],
771 all_columns: &[String],
772 ) -> Option<String> {
773 if conflict_columns.is_empty() {
774 return None;
775 }
776 let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
777 let conflict_set: std::collections::HashSet<&str> =
778 conflict_columns.iter().copied().collect();
779 let cols_to_update: Vec<String> = if update_columns.is_empty() {
780 all_columns
781 .iter()
782 .filter(|c| !conflict_set.contains(c.as_str()))
783 .map(|c| self.quote(c))
784 .collect()
785 } else {
786 update_columns.iter().map(|c| self.quote(c)).collect()
787 };
788 if cols_to_update.is_empty() {
789 return Some(format!(
790 "ON CONFLICT ({}) DO NOTHING",
791 conflict_cols.join(", ")
792 ));
793 }
794 let set_clauses: Vec<String> = cols_to_update
795 .iter()
796 .map(|col| format!("{}=EXCLUDED.{}", col, col))
797 .collect();
798 Some(format!(
799 "ON CONFLICT ({}) DO UPDATE SET {}",
800 conflict_cols.join(", "),
801 set_clauses.join(", ")
802 ))
803 }
804}
805
806fn map_to_oracle_type(sql_type: &str) -> String {
815 let upper = sql_type.to_uppercase();
816 let trimmed = upper.trim();
817
818 if trimmed.starts_with("BIGINT") {
819 sql_type.replacen("BIGINT", "NUMBER(19)", 1)
820 } else if trimmed.starts_with("VARCHAR2") {
821 sql_type.to_string()
822 } else if trimmed.starts_with("VARCHAR") {
823 sql_type.replacen("VARCHAR", "VARCHAR2", 1)
824 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
825 "CLOB".to_string()
826 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
827 "NUMBER(1)".to_string()
828 } else if trimmed == "INTEGER" {
829 "NUMBER(10)".to_string()
830 } else if trimmed.starts_with("INT") {
831 sql_type.replacen("INT", "NUMBER(10)", 1)
832 } else {
833 sql_type.to_string()
834 }
835}
836
837pub struct OracleDialect;
839
840impl Dialect for OracleDialect {
841 fn db_type(&self) -> DbType {
842 DbType::Oracle
843 }
844
845 fn quote(&self, identifier: &str) -> String {
846 format!("\"{}\"", identifier.replace('"', "\"\""))
848 }
849
850 fn escape_string(&self, s: &str) -> String {
851 let mut escaped = String::with_capacity(s.len() * 2);
853 for c in s.chars() {
854 match c {
855 '\'' => escaped.push_str("''"),
856 _ => escaped.push(c),
857 }
858 }
859 escaped
860 }
861
862 fn supports_returning(&self) -> bool {
863 true
865 }
866
867 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
868 let offset = page.saturating_sub(1).saturating_mul(limit);
871 format!(
872 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
873 sql, offset, limit
874 )
875 }
876
877 fn json_type(&self) -> &'static str {
878 "JSON"
880 }
881
882 fn json_extract(&self, column: &str, path: &str) -> String {
883 let normalized = if path.starts_with('$') {
885 path.to_string()
886 } else {
887 format!("$.{}", path)
888 };
889 format!(
890 "JSON_VALUE({}, '{}')",
891 column,
892 self.escape_string(&normalized)
893 )
894 }
895
896 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
897 if columns.is_empty() {
900 return "0".to_string();
901 }
902 let escaped = self.escape_string(keyword);
903 let parts: Vec<String> = columns
904 .iter()
905 .map(|c| format!("CONTAINS({}, '{}', 1) > 0", c, escaped))
906 .collect();
907 parts.join(" OR ")
908 }
909
910 fn bool_to_int(&self, expr: &str) -> String {
911 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
913 }
914
915 fn concat(&self, parts: &[&str]) -> String {
916 if parts.is_empty() {
918 return "NULL".to_string();
919 }
920 parts.join(" || ")
921 }
922
923 fn supports_if_exists(&self) -> bool {
924 true
926 }
927
928 fn supports_if_not_exists(&self) -> bool {
929 true
931 }
932
933 fn auto_increment_keyword(&self) -> &'static str {
934 "GENERATED BY DEFAULT AS IDENTITY"
936 }
937
938 fn last_insert_id_sql(&self) -> Option<&'static str> {
939 None
944 }
945
946 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
947 let cols: Vec<String> = columns
948 .iter()
949 .map(|col| {
950 let oracle_type = map_to_oracle_type(&col.sql_type);
951 let mut sql = format!("{} {}", self.quote(&col.name), oracle_type);
952 if !col.nullable && !col.auto_increment {
954 sql.push_str(" NOT NULL");
955 }
956 if let Some(default) = &col.default {
957 sql.push_str(&format!(" DEFAULT {}", default));
958 }
959 if col.auto_increment {
960 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
961 }
962 if col.primary_key {
963 sql.push_str(" PRIMARY KEY");
964 }
965 sql
966 })
967 .collect();
968
969 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
970 }
971
972 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
973 let stmts: Vec<String> = changes
974 .iter()
975 .map(|change| match change {
976 TableChange::AddColumn(col) => {
977 let oracle_type = map_to_oracle_type(&col.sql_type);
978 let mut sql = format!(
979 "ALTER TABLE {} ADD {} {}",
980 self.quote(table),
981 self.quote(&col.name),
982 oracle_type
983 );
984 if !col.nullable {
985 sql.push_str(" NOT NULL");
986 }
987 if let Some(default) = &col.default {
988 sql.push_str(&format!(" DEFAULT {}", default));
989 }
990 sql
991 }
992 TableChange::DropColumn(name) => {
993 format!(
994 "ALTER TABLE {} DROP COLUMN {}",
995 self.quote(table),
996 self.quote(name)
997 )
998 }
999 TableChange::ModifyColumn(col) => {
1000 let oracle_type = map_to_oracle_type(&col.sql_type);
1002 let mut sql = format!(
1003 "ALTER TABLE {} MODIFY {} {}",
1004 self.quote(table),
1005 self.quote(&col.name),
1006 oracle_type
1007 );
1008 if !col.nullable {
1009 sql.push_str(" NOT NULL");
1010 }
1011 if let Some(default) = &col.default {
1012 sql.push_str(&format!(" DEFAULT {}", default));
1013 }
1014 sql
1015 }
1016 TableChange::AddIndex(name, cols) => {
1017 format!(
1018 "CREATE INDEX {} ON {} ({})",
1019 name,
1020 self.quote(table),
1021 cols.join(", ")
1022 )
1023 }
1024 TableChange::DropIndex(name) => {
1025 format!("DROP INDEX {}", name)
1026 }
1027 TableChange::AddForeignKey {
1028 columns,
1029 reference_table,
1030 reference_columns,
1031 } => {
1032 format!(
1033 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1034 self.quote(table),
1035 table,
1036 columns.join("_"),
1037 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1038 self.quote(reference_table),
1039 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1040 )
1041 }
1042 })
1043 .collect();
1044
1045 stmts.join("; ")
1046 }
1047}
1048
1049fn map_to_sqlserver_type(sql_type: &str) -> String {
1058 let upper = sql_type.to_uppercase();
1059 let trimmed = upper.trim();
1060
1061 if trimmed.starts_with("BIGINT") {
1062 sql_type.to_string()
1063 } else if matches!(trimmed, "INT" | "INTEGER") {
1064 "INT".to_string()
1065 } else if trimmed.starts_with("NVARCHAR") {
1066 sql_type.to_string()
1067 } else if trimmed.starts_with("VARCHAR") {
1068 sql_type.replacen("VARCHAR", "NVARCHAR", 1)
1069 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1070 "NVARCHAR(MAX)".to_string()
1071 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1072 "BIT".to_string()
1073 } else {
1074 sql_type.to_string()
1075 }
1076}
1077
1078pub struct SqlServerDialect;
1080
1081impl Dialect for SqlServerDialect {
1082 fn db_type(&self) -> DbType {
1083 DbType::SqlServer
1084 }
1085
1086 fn quote(&self, identifier: &str) -> String {
1087 format!("[{}]", identifier.replace(']', "]]"))
1089 }
1090
1091 fn escape_string(&self, s: &str) -> String {
1092 let mut escaped = String::with_capacity(s.len() * 2);
1094 for c in s.chars() {
1095 match c {
1096 '\'' => escaped.push_str("''"),
1097 _ => escaped.push(c),
1098 }
1099 }
1100 escaped
1101 }
1102
1103 fn supports_returning(&self) -> bool {
1104 true
1106 }
1107
1108 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1109 let offset = page.saturating_sub(1).saturating_mul(limit);
1111 format!(
1112 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1113 sql, offset, limit
1114 )
1115 }
1116
1117 fn json_type(&self) -> &'static str {
1118 "NVARCHAR(MAX)"
1120 }
1121
1122 fn json_extract(&self, column: &str, path: &str) -> String {
1123 let normalized = if path.starts_with('$') {
1125 path.to_string()
1126 } else {
1127 format!("$.{}", path)
1128 };
1129 format!(
1130 "JSON_VALUE({}, '{}')",
1131 column,
1132 self.escape_string(&normalized)
1133 )
1134 }
1135
1136 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1137 if columns.is_empty() {
1139 return "0".to_string();
1140 }
1141 let escaped = self.escape_string(keyword);
1142 let cols = columns.join(", ");
1143 format!("CONTAINS({}, '{}')", cols, escaped)
1144 }
1145
1146 fn bool_to_int(&self, expr: &str) -> String {
1147 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1149 }
1150
1151 fn concat(&self, parts: &[&str]) -> String {
1152 if parts.is_empty() {
1153 return "NULL".to_string();
1154 }
1155 format!("CONCAT({})", parts.join(", "))
1156 }
1157
1158 fn supports_if_exists(&self) -> bool {
1159 true
1161 }
1162
1163 fn supports_if_not_exists(&self) -> bool {
1164 true
1166 }
1167
1168 fn auto_increment_keyword(&self) -> &'static str {
1169 "IDENTITY(1,1)"
1171 }
1172
1173 fn last_insert_id_sql(&self) -> Option<&'static str> {
1174 Some("SCOPE_IDENTITY()")
1176 }
1177
1178 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1179 let cols: Vec<String> = columns
1180 .iter()
1181 .map(|col| {
1182 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1183 let mut sql = format!("{} {}", self.quote(&col.name), sqlserver_type);
1184 if !col.nullable {
1185 sql.push_str(" NOT NULL");
1186 }
1187 if let Some(default) = &col.default {
1188 sql.push_str(&format!(" DEFAULT {}", default));
1189 }
1190 if col.auto_increment {
1191 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1192 }
1193 if col.primary_key {
1194 sql.push_str(" PRIMARY KEY");
1195 }
1196 sql
1197 })
1198 .collect();
1199
1200 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1201 }
1202
1203 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1204 let stmts: Vec<String> = changes
1205 .iter()
1206 .map(|change| match change {
1207 TableChange::AddColumn(col) => {
1208 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1209 let mut sql = format!(
1210 "ALTER TABLE {} ADD {} {}",
1211 self.quote(table),
1212 self.quote(&col.name),
1213 sqlserver_type
1214 );
1215 if !col.nullable {
1216 sql.push_str(" NOT NULL");
1217 }
1218 if let Some(default) = &col.default {
1219 sql.push_str(&format!(" DEFAULT {}", default));
1220 }
1221 sql
1222 }
1223 TableChange::DropColumn(name) => {
1224 format!(
1225 "ALTER TABLE {} DROP COLUMN {}",
1226 self.quote(table),
1227 self.quote(name)
1228 )
1229 }
1230 TableChange::ModifyColumn(col) => {
1231 let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1233 let mut sql = format!(
1234 "ALTER TABLE {} ALTER COLUMN {} {}",
1235 self.quote(table),
1236 self.quote(&col.name),
1237 sqlserver_type
1238 );
1239 if !col.nullable {
1240 sql.push_str(" NOT NULL");
1241 }
1242 if let Some(default) = &col.default {
1243 sql.push_str(&format!(" DEFAULT {}", default));
1244 }
1245 sql
1246 }
1247 TableChange::AddIndex(name, cols) => {
1248 format!(
1249 "CREATE INDEX {} ON {} ({})",
1250 name,
1251 self.quote(table),
1252 cols.join(", ")
1253 )
1254 }
1255 TableChange::DropIndex(name) => {
1256 format!("DROP INDEX {} ON {}", name, self.quote(table))
1258 }
1259 TableChange::AddForeignKey {
1260 columns,
1261 reference_table,
1262 reference_columns,
1263 } => {
1264 format!(
1265 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1266 self.quote(table),
1267 table,
1268 columns.join("_"),
1269 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1270 self.quote(reference_table),
1271 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1272 )
1273 }
1274 })
1275 .collect();
1276
1277 stmts.join("; ")
1278 }
1279}
1280
1281macro_rules! delegate_dialect_to {
1298 ($wrapper:ident, $base:ident, $db_type:expr) => {
1299 pub struct $wrapper;
1301
1302 impl Dialect for $wrapper {
1303 fn db_type(&self) -> DbType {
1304 $db_type
1305 }
1306 fn quote(&self, identifier: &str) -> String {
1307 $base.quote(identifier)
1308 }
1309 fn escape_string(&self, s: &str) -> String {
1310 $base.escape_string(s)
1311 }
1312 fn supports_returning(&self) -> bool {
1313 $base.supports_returning()
1314 }
1315 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1316 $base.build_pagination(sql, page, limit)
1317 }
1318 fn json_type(&self) -> &'static str {
1319 $base.json_type()
1320 }
1321 fn json_extract(&self, column: &str, path: &str) -> String {
1322 $base.json_extract(column, path)
1323 }
1324 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1325 $base.full_text_search(columns, keyword)
1326 }
1327 fn bool_to_int(&self, expr: &str) -> String {
1328 $base.bool_to_int(expr)
1329 }
1330 fn concat(&self, parts: &[&str]) -> String {
1331 $base.concat(parts)
1332 }
1333 fn supports_if_exists(&self) -> bool {
1334 $base.supports_if_exists()
1335 }
1336 fn supports_if_not_exists(&self) -> bool {
1337 $base.supports_if_not_exists()
1338 }
1339 fn auto_increment_keyword(&self) -> &'static str {
1340 $base.auto_increment_keyword()
1341 }
1342 fn last_insert_id_sql(&self) -> Option<&'static str> {
1343 $base.last_insert_id_sql()
1344 }
1345 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1346 $base.build_create_table(table, columns)
1347 }
1348 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1349 $base.build_alter_table(table, changes)
1350 }
1351 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1352 $base.build_drop_table(table, if_exists)
1353 }
1354 }
1355 };
1356}
1357
1358delegate_dialect_to!(MariaDbDialect, MySqlDialect, DbType::MariaDB);
1360
1361delegate_dialect_to!(TiDbDialect, MySqlDialect, DbType::TiDB);
1363
1364delegate_dialect_to!(KingbaseDialect, PostgreSqlDialect, DbType::Kingbase);
1366
1367delegate_dialect_to!(PolarDbDialect, PostgreSqlDialect, DbType::PolarDB);
1369
1370delegate_dialect_to!(GaussDbDialect, PostgreSqlDialect, DbType::GaussDB);
1372
1373delegate_dialect_to!(DamengDialect, OracleDialect, DbType::Dameng);
1375
1376delegate_dialect_to!(SybaseDialect, SqlServerDialect, DbType::Sybase);
1378
1379delegate_dialect_to!(GBaseDialect, SqlServerDialect, DbType::GBase);
1381
1382pub struct ClickHouseDialect;
1396
1397impl Dialect for ClickHouseDialect {
1398 fn db_type(&self) -> DbType {
1399 DbType::ClickHouse
1400 }
1401
1402 fn quote(&self, identifier: &str) -> String {
1403 format!("`{}`", identifier.replace('`', "``"))
1405 }
1406
1407 fn escape_string(&self, s: &str) -> String {
1408 let mut escaped = String::with_capacity(s.len() * 2);
1410 for c in s.chars() {
1411 match c {
1412 '\'' => escaped.push_str("\\'"),
1413 '\\' => escaped.push_str("\\\\"),
1414 '\n' => escaped.push_str("\\n"),
1415 '\r' => escaped.push_str("\\r"),
1416 '\t' => escaped.push_str("\\t"),
1417 _ => escaped.push(c),
1418 }
1419 }
1420 escaped
1421 }
1422
1423 fn supports_returning(&self) -> bool {
1424 false
1426 }
1427
1428 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1429 let offset = page.saturating_sub(1).saturating_mul(limit);
1431 format!("{} LIMIT {}, {}", sql, offset, limit)
1432 }
1433
1434 fn json_type(&self) -> &'static str {
1435 "String"
1437 }
1438
1439 fn json_extract(&self, column: &str, path: &str) -> String {
1440 let normalized = if path.starts_with('$') {
1442 path.to_string()
1443 } else {
1444 format!("$.{}", path)
1445 };
1446 format!(
1447 "JSONExtractString({}, '{}')",
1448 column,
1449 self.escape_string(&normalized)
1450 )
1451 }
1452
1453 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1454 if columns.is_empty() {
1456 return "0".to_string();
1457 }
1458 let escaped = self.escape_string(keyword);
1459 let parts: Vec<String> = columns
1460 .iter()
1461 .map(|c| format!("position({}, '{}') > 0", c, escaped))
1462 .collect();
1463 parts.join(" OR ")
1464 }
1465
1466 fn bool_to_int(&self, expr: &str) -> String {
1467 format!("toUInt8({})", expr)
1469 }
1470
1471 fn concat(&self, parts: &[&str]) -> String {
1472 if parts.is_empty() {
1474 return "''".to_string();
1475 }
1476 format!("concat({})", parts.join(", "))
1477 }
1478
1479 fn supports_if_exists(&self) -> bool {
1480 true
1481 }
1482
1483 fn supports_if_not_exists(&self) -> bool {
1484 true
1485 }
1486
1487 fn auto_increment_keyword(&self) -> &'static str {
1488 ""
1490 }
1491
1492 fn last_insert_id_sql(&self) -> Option<&'static str> {
1493 None
1495 }
1496
1497 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1498 let cols: Vec<String> = columns
1499 .iter()
1500 .map(|col| {
1501 let ch_type = map_to_clickhouse_type(&col.sql_type);
1502 let mut sql = format!("{} {}", self.quote(&col.name), ch_type);
1503 if let Some(default) = &col.default {
1504 sql.push_str(&format!(" DEFAULT {}", default));
1505 }
1506 if col.primary_key {
1507 sql.push_str(" PRIMARY KEY");
1508 }
1509 sql
1510 })
1511 .collect();
1512
1513 format!(
1515 "CREATE TABLE {} ({}) ENGINE = MergeTree()",
1516 self.quote(table),
1517 cols.join(", ")
1518 )
1519 }
1520
1521 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1522 let stmts: Vec<String> = changes
1523 .iter()
1524 .map(|change| match change {
1525 TableChange::AddColumn(col) => {
1526 let ch_type = map_to_clickhouse_type(&col.sql_type);
1527 format!(
1528 "ALTER TABLE {} ADD COLUMN {} {}",
1529 self.quote(table),
1530 self.quote(&col.name),
1531 ch_type
1532 )
1533 }
1534 TableChange::DropColumn(name) => {
1535 format!(
1536 "ALTER TABLE {} DROP COLUMN {}",
1537 self.quote(table),
1538 self.quote(name)
1539 )
1540 }
1541 TableChange::ModifyColumn(col) => {
1542 let ch_type = map_to_clickhouse_type(&col.sql_type);
1543 format!(
1544 "ALTER TABLE {} MODIFY COLUMN {} {}",
1545 self.quote(table),
1546 self.quote(&col.name),
1547 ch_type
1548 )
1549 }
1550 TableChange::AddIndex(name, cols) => {
1551 format!(
1552 "ALTER TABLE {} ADD INDEX {} ({})",
1553 self.quote(table),
1554 name,
1555 cols.join(", ")
1556 )
1557 }
1558 TableChange::DropIndex(name) => {
1559 format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
1560 }
1561 TableChange::AddForeignKey { .. } => {
1562 String::new()
1564 }
1565 })
1566 .filter(|s| !s.is_empty())
1567 .collect();
1568
1569 stmts.join("; ")
1570 }
1571}
1572
1573fn map_to_clickhouse_type(sql_type: &str) -> String {
1583 let upper = sql_type.to_uppercase();
1584 let trimmed = upper.trim();
1585
1586 if trimmed.starts_with("BIGINT") {
1587 "Int64".to_string()
1588 } else if matches!(trimmed, "INT" | "INTEGER") {
1589 "Int32".to_string()
1590 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
1591 "Int16".to_string()
1592 } else if trimmed.starts_with("VARCHAR")
1593 || trimmed.starts_with("CHAR")
1594 || matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT")
1595 {
1596 "String".to_string()
1597 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1598 "UInt8".to_string()
1599 } else if matches!(trimmed, "FLOAT" | "REAL") {
1600 "Float32".to_string()
1601 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
1602 "Float64".to_string()
1603 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
1604 "DateTime".to_string()
1605 } else if matches!(trimmed, "DATE") {
1606 "Date".to_string()
1607 } else if trimmed.starts_with("DECIMAL") || trimmed.starts_with("NUMERIC") {
1608 "Decimal(38, 4)".to_string()
1609 } else {
1610 sql_type.to_string()
1611 }
1612}
1613
1614pub struct Db2Dialect;
1628
1629impl Dialect for Db2Dialect {
1630 fn db_type(&self) -> DbType {
1631 DbType::Db2
1632 }
1633
1634 fn quote(&self, identifier: &str) -> String {
1635 format!("\"{}\"", identifier.replace('"', "\"\""))
1637 }
1638
1639 fn escape_string(&self, s: &str) -> String {
1640 let mut escaped = String::with_capacity(s.len() * 2);
1642 for c in s.chars() {
1643 match c {
1644 '\'' => escaped.push_str("''"),
1645 _ => escaped.push(c),
1646 }
1647 }
1648 escaped
1649 }
1650
1651 fn supports_returning(&self) -> bool {
1652 false
1654 }
1655
1656 fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1657 let offset = page.saturating_sub(1).saturating_mul(limit);
1659 format!(
1660 "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1661 sql, offset, limit
1662 )
1663 }
1664
1665 fn json_type(&self) -> &'static str {
1666 "JSON"
1668 }
1669
1670 fn json_extract(&self, column: &str, path: &str) -> String {
1671 let normalized = if path.starts_with('$') {
1673 path.to_string()
1674 } else {
1675 format!("$.{}", path)
1676 };
1677 format!(
1678 "JSON_VALUE({}, '{}')",
1679 column,
1680 self.escape_string(&normalized)
1681 )
1682 }
1683
1684 fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1685 if columns.is_empty() {
1687 return "0".to_string();
1688 }
1689 let escaped = self.escape_string(keyword);
1690 let parts: Vec<String> = columns
1691 .iter()
1692 .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
1693 .collect();
1694 parts.join(" OR ")
1695 }
1696
1697 fn bool_to_int(&self, expr: &str) -> String {
1698 format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1700 }
1701
1702 fn concat(&self, parts: &[&str]) -> String {
1703 if parts.is_empty() {
1705 return "''".to_string();
1706 }
1707 parts.join(" || ")
1708 }
1709
1710 fn supports_if_exists(&self) -> bool {
1711 false
1713 }
1714
1715 fn supports_if_not_exists(&self) -> bool {
1716 false
1718 }
1719
1720 fn auto_increment_keyword(&self) -> &'static str {
1721 "GENERATED ALWAYS AS IDENTITY"
1723 }
1724
1725 fn last_insert_id_sql(&self) -> Option<&'static str> {
1726 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
1728 }
1729
1730 fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1731 let cols: Vec<String> = columns
1732 .iter()
1733 .map(|col| {
1734 let db2_type = map_to_db2_type(&col.sql_type);
1735 let mut sql = format!("{} {}", self.quote(&col.name), db2_type);
1736 if !col.nullable && !col.auto_increment {
1737 sql.push_str(" NOT NULL");
1738 }
1739 if let Some(default) = &col.default {
1740 sql.push_str(&format!(" DEFAULT {}", default));
1741 }
1742 if col.auto_increment {
1743 sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1744 }
1745 if col.primary_key {
1746 sql.push_str(" PRIMARY KEY");
1747 }
1748 sql
1749 })
1750 .collect();
1751
1752 format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1753 }
1754
1755 fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1756 let stmts: Vec<String> = changes
1757 .iter()
1758 .map(|change| match change {
1759 TableChange::AddColumn(col) => {
1760 let db2_type = map_to_db2_type(&col.sql_type);
1761 let mut sql = format!(
1762 "ALTER TABLE {} ADD COLUMN {} {}",
1763 self.quote(table),
1764 self.quote(&col.name),
1765 db2_type
1766 );
1767 if !col.nullable {
1768 sql.push_str(" NOT NULL");
1769 }
1770 if let Some(default) = &col.default {
1771 sql.push_str(&format!(" DEFAULT {}", default));
1772 }
1773 sql
1774 }
1775 TableChange::DropColumn(name) => {
1776 format!(
1777 "ALTER TABLE {} DROP COLUMN {}",
1778 self.quote(table),
1779 self.quote(name)
1780 )
1781 }
1782 TableChange::ModifyColumn(col) => {
1783 let db2_type = map_to_db2_type(&col.sql_type);
1784 format!(
1785 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
1786 self.quote(table),
1787 self.quote(&col.name),
1788 db2_type
1789 )
1790 }
1791 TableChange::AddIndex(name, cols) => {
1792 format!(
1793 "CREATE INDEX {} ON {} ({})",
1794 name,
1795 self.quote(table),
1796 cols.join(", ")
1797 )
1798 }
1799 TableChange::DropIndex(name) => {
1800 format!("DROP INDEX {}", name)
1801 }
1802 TableChange::AddForeignKey {
1803 columns,
1804 reference_table,
1805 reference_columns,
1806 } => {
1807 format!(
1808 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1809 self.quote(table),
1810 table,
1811 columns.join("_"),
1812 columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1813 self.quote(reference_table),
1814 reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1815 )
1816 }
1817 })
1818 .collect();
1819
1820 stmts.join("; ")
1821 }
1822
1823 fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1824 let _ = if_exists;
1826 format!("DROP TABLE {}", self.quote(table))
1827 }
1828}
1829
1830fn map_to_db2_type(sql_type: &str) -> String {
1839 let upper = sql_type.to_uppercase();
1840 let trimmed = upper.trim();
1841
1842 if trimmed.starts_with("BIGINT") {
1843 "BIGINT".to_string()
1844 } else if matches!(trimmed, "INT" | "INTEGER") {
1845 "INTEGER".to_string()
1846 } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
1847 "SMALLINT".to_string()
1848 } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
1849 sql_type.to_string()
1850 } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1851 "CLOB(2G)".to_string()
1852 } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1853 "SMALLINT".to_string()
1854 } else if matches!(trimmed, "FLOAT" | "REAL") {
1855 "REAL".to_string()
1856 } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
1857 "DOUBLE".to_string()
1858 } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
1859 "TIMESTAMP".to_string()
1860 } else if matches!(trimmed, "DATE") {
1861 "DATE".to_string()
1862 } else {
1863 sql_type.to_string()
1865 }
1866}
1867
1868pub fn get_dialect(db_type: DbType) -> Result<Box<dyn Dialect>, DbError> {
1891 match db_type {
1892 DbType::MySQL => Ok(Box::new(MySqlDialect)),
1893 DbType::PostgreSQL => Ok(Box::new(PostgreSqlDialect)),
1894 DbType::Sqlite => Ok(Box::new(SqliteDialect)),
1895 DbType::Redis => Err(DbError::Unsupported(
1896 "Redis does not support standard SQL dialect".to_string(),
1897 )),
1898 DbType::MongoDB => Err(DbError::Unsupported(
1899 "MongoDB uses different query syntax".to_string(),
1900 )),
1901 DbType::ClickHouse => Ok(Box::new(ClickHouseDialect)),
1902 DbType::Oracle => Ok(Box::new(OracleDialect)),
1903 DbType::OceanBase => Ok(Box::new(MySqlDialect)),
1904 DbType::SqlServer => Ok(Box::new(SqlServerDialect)),
1905 DbType::VectorDb => Err(DbError::Unsupported(
1906 "Vector databases have specific APIs".to_string(),
1907 )),
1908 DbType::PureJsDb => Err(DbError::Unsupported(
1909 "PureJS database uses JavaScript".to_string(),
1910 )),
1911 DbType::Dameng => Ok(Box::new(DamengDialect)),
1913 DbType::Kingbase => Ok(Box::new(KingbaseDialect)),
1914 DbType::Db2 => Ok(Box::new(Db2Dialect)),
1915 DbType::MariaDB => Ok(Box::new(MariaDbDialect)),
1916 DbType::TiDB => Ok(Box::new(TiDbDialect)),
1917 DbType::PolarDB => Ok(Box::new(PolarDbDialect)),
1918 DbType::GaussDB => Ok(Box::new(GaussDbDialect)),
1919 DbType::GBase => Ok(Box::new(GBaseDialect)),
1920 DbType::Sybase => Ok(Box::new(SybaseDialect)),
1921 }
1922}
1923
1924impl fmt::Display for dyn Dialect {
1925 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1926 write!(f, "Dialect({})", self.db_type())
1927 }
1928}
1929
1930#[cfg(test)]
1931mod tests {
1932 use super::*;
1933
1934 #[test]
1935 fn test_mysql_quote() {
1936 let dialect = MySqlDialect;
1937 assert_eq!(dialect.quote("users"), "`users`");
1938 assert_eq!(dialect.quote("user`id"), "`user``id`");
1939 }
1940
1941 #[test]
1942 fn test_mysql_escape() {
1943 let dialect = MySqlDialect;
1944 assert_eq!(dialect.escape_string("hello"), "hello");
1945 assert_eq!(dialect.escape_string("it's"), "it\\'s");
1946 assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
1947 }
1948
1949 #[test]
1950 fn test_mysql_pagination() {
1951 let dialect = MySqlDialect;
1952 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
1953 assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
1954 }
1955
1956 #[test]
1957 fn test_postgres_quote() {
1958 let dialect = PostgreSqlDialect;
1959 assert_eq!(dialect.quote("users"), "\"users\"");
1960 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
1961 }
1962
1963 #[test]
1964 fn test_postgres_pagination() {
1965 let dialect = PostgreSqlDialect;
1966 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
1967 assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
1968 }
1969
1970 #[test]
1971 fn test_postgres_returning() {
1972 let dialect = PostgreSqlDialect;
1973 assert!(dialect.supports_returning());
1974 }
1975
1976 #[test]
1977 fn test_sqlite_quote() {
1978 let dialect = SqliteDialect;
1979 assert_eq!(dialect.quote("users"), "\"users\"");
1980 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
1981 }
1982
1983 #[test]
1984 fn test_sqlite_escape() {
1985 let dialect = SqliteDialect;
1986 assert_eq!(dialect.escape_string("hello"), "hello");
1987 assert_eq!(dialect.escape_string("it's"), "it''s");
1988 }
1989
1990 #[test]
1991 fn test_get_dialect() {
1992 let dialect = get_dialect(DbType::MySQL);
1993 assert!(dialect.is_ok());
1994
1995 let dialect = get_dialect(DbType::Redis);
1996 assert!(dialect.is_err());
1997 }
1998
1999 #[test]
2000 fn test_bool_to_int() {
2001 let mysql = MySqlDialect;
2002 assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
2003
2004 let pg = PostgreSqlDialect;
2005 assert_eq!(
2006 pg.bool_to_int("active"),
2007 "(CASE WHEN active THEN 1 ELSE 0 END)"
2008 );
2009 }
2010
2011 #[test]
2012 fn test_json_extract_with_path() {
2013 let mysql = MySqlDialect;
2014 let sql = mysql.json_extract("data", "$.user.name");
2015 assert!(sql.contains("$.user.name"));
2016 assert!(sql.contains("JSON_EXTRACT"));
2017
2018 let pg = PostgreSqlDialect;
2019 let sql = pg.json_extract("data", "user.name");
2020 assert!(sql.contains("#>>"));
2021
2022 let sqlite = SqliteDialect;
2023 let sql = sqlite.json_extract("data", "$.user.name");
2024 assert!(sql.contains("$.user.name"));
2025 assert!(sql.contains("json_extract"));
2026 }
2027
2028 #[test]
2029 fn test_sqlite_full_text_search() {
2030 let sqlite = SqliteDialect;
2031 let sql = sqlite.full_text_search(&["title", "content"], "hello");
2032 assert!(sql.contains("LIKE"));
2034 assert!(sql.contains("title LIKE '%hello%'"));
2035 assert!(sql.contains("content LIKE '%hello%'"));
2036 assert!(sql.contains(" OR "));
2037
2038 assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
2040
2041 let sql = sqlite.full_text_search(&["title"], "it's");
2043 assert!(sql.contains("title LIKE '%it''s%'"));
2044 }
2045
2046 #[test]
2047 fn test_alter_table_modify_column() {
2048 let mysql = MySqlDialect;
2049 let col = ColumnDef {
2050 name: "name".to_string(),
2051 sql_type: "VARCHAR(255)".to_string(),
2052 nullable: false,
2053 default: None,
2054 auto_increment: false,
2055 primary_key: false,
2056 };
2057 let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2058 assert!(sql.contains("MODIFY COLUMN"));
2059
2060 let pg = PostgreSqlDialect;
2061 let col = ColumnDef {
2062 name: "name".to_string(),
2063 sql_type: "VARCHAR(255)".to_string(),
2064 nullable: false,
2065 default: None,
2066 auto_increment: false,
2067 primary_key: false,
2068 };
2069 let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2070 assert!(sql.contains("ALTER COLUMN"));
2071 assert!(sql.contains("TYPE"));
2072 }
2073
2074 #[test]
2075 fn test_alter_table_add_foreign_key() {
2076 let mysql = MySqlDialect;
2077 let sql = mysql.build_alter_table(
2078 "orders",
2079 &[TableChange::AddForeignKey {
2080 columns: vec!["user_id".to_string()],
2081 reference_table: "users".to_string(),
2082 reference_columns: vec!["id".to_string()],
2083 }],
2084 );
2085 assert!(sql.contains("FOREIGN KEY"));
2086 assert!(sql.contains("REFERENCES"));
2087
2088 let sqlite = SqliteDialect;
2089 let sql = sqlite.build_alter_table(
2090 "orders",
2091 &[TableChange::AddForeignKey {
2092 columns: vec!["user_id".to_string()],
2093 reference_table: "users".to_string(),
2094 reference_columns: vec!["id".to_string()],
2095 }],
2096 );
2097 assert!(sql.starts_with("--"));
2099 }
2100
2101 #[test]
2102 fn test_sqlite_alter_table_add_column() {
2103 let sqlite = SqliteDialect;
2104 let col = ColumnDef {
2105 name: "email".to_string(),
2106 sql_type: "TEXT".to_string(),
2107 nullable: true,
2108 default: None,
2109 auto_increment: false,
2110 primary_key: false,
2111 };
2112 let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
2113 assert!(sql.contains("ADD COLUMN"));
2114 assert!(sql.contains("email"));
2115 }
2116
2117 #[test]
2120 fn test_oracle_quote_and_escape() {
2121 let dialect = OracleDialect;
2122 assert_eq!(dialect.quote("users"), "\"users\"");
2124 assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2125 assert_eq!(dialect.quote("column_name"), "\"column_name\"");
2126
2127 assert_eq!(dialect.escape_string("hello"), "hello");
2129 assert_eq!(dialect.escape_string("it's"), "it''s");
2130 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2131 assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
2132 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2134 }
2135
2136 #[test]
2137 fn test_oracle_pagination() {
2138 let dialect = OracleDialect;
2139 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2141 assert_eq!(
2142 sql,
2143 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2144 );
2145 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2147 assert_eq!(
2148 sql,
2149 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2150 );
2151 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2153 assert_eq!(
2154 sql,
2155 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2156 );
2157 }
2158
2159 #[test]
2160 fn test_oracle_json_extract() {
2161 let dialect = OracleDialect;
2162 let sql = dialect.json_extract("data", "$.user.name");
2164 assert!(sql.contains("JSON_VALUE"));
2165 assert!(sql.contains("$.user.name"));
2166 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2167
2168 let sql = dialect.json_extract("data", "user.name");
2170 assert!(sql.contains("$.user.name"));
2171 assert!(sql.contains("JSON_VALUE"));
2172
2173 let sql = dialect.json_extract("data", "$.key's");
2175 assert!(sql.contains("$.key''s"));
2176 }
2177
2178 #[test]
2179 fn test_oracle_create_table() {
2180 let dialect = OracleDialect;
2181 let columns = vec![
2182 ColumnDef {
2183 name: "id".to_string(),
2184 sql_type: "BIGINT".to_string(),
2185 nullable: false,
2186 default: None,
2187 auto_increment: true,
2188 primary_key: true,
2189 },
2190 ColumnDef {
2191 name: "name".to_string(),
2192 sql_type: "VARCHAR(255)".to_string(),
2193 nullable: false,
2194 default: None,
2195 auto_increment: false,
2196 primary_key: false,
2197 },
2198 ColumnDef {
2199 name: "bio".to_string(),
2200 sql_type: "TEXT".to_string(),
2201 nullable: true,
2202 default: None,
2203 auto_increment: false,
2204 primary_key: false,
2205 },
2206 ColumnDef {
2207 name: "is_active".to_string(),
2208 sql_type: "BOOLEAN".to_string(),
2209 nullable: false,
2210 default: Some("1".to_string()),
2211 auto_increment: false,
2212 primary_key: false,
2213 },
2214 ];
2215 let sql = dialect.build_create_table("users", &columns);
2216 assert!(
2218 sql.contains("NUMBER(19)"),
2219 "BIGINT should map to NUMBER(19): {}",
2220 sql
2221 );
2222 assert!(
2223 sql.contains("VARCHAR2(255)"),
2224 "VARCHAR should map to VARCHAR2: {}",
2225 sql
2226 );
2227 assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
2228 assert!(
2229 sql.contains("NUMBER(1)"),
2230 "BOOLEAN should map to NUMBER(1): {}",
2231 sql
2232 );
2233 assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
2235 assert!(sql.contains("PRIMARY KEY"));
2236 assert!(sql.contains("NOT NULL"));
2237 assert!(sql.contains("DEFAULT 1"));
2238 assert!(sql.contains("\"users\""));
2240 assert!(sql.contains("\"id\""));
2241 }
2242
2243 #[test]
2244 fn test_oracle_bool_to_int_and_concat() {
2245 let dialect = OracleDialect;
2246 assert_eq!(
2248 dialect.bool_to_int("active"),
2249 "(CASE WHEN active THEN 1 ELSE 0 END)"
2250 );
2251 assert_eq!(
2252 dialect.bool_to_int("x > 0"),
2253 "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
2254 );
2255 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
2257 assert_eq!(
2258 dialect.concat(&["first_name", "last_name"]),
2259 "first_name || last_name"
2260 );
2261 assert_eq!(dialect.concat(&[]), "NULL");
2263 }
2264
2265 #[test]
2266 fn test_oracle_misc_dialect_methods() {
2267 let dialect = OracleDialect;
2268 assert_eq!(dialect.db_type(), DbType::Oracle);
2270 assert!(dialect.supports_returning());
2272 assert!(dialect.supports_if_exists());
2274 assert!(dialect.supports_if_not_exists());
2275 assert_eq!(
2277 dialect.auto_increment_keyword(),
2278 "GENERATED BY DEFAULT AS IDENTITY"
2279 );
2280 assert_eq!(dialect.last_insert_id_sql(), None);
2283 assert_eq!(dialect.json_type(), "JSON");
2285 }
2286
2287 #[test]
2288 fn test_oracle_get_dialect() {
2289 let dialect = get_dialect(DbType::Oracle);
2291 assert!(dialect.is_ok(), "Oracle dialect should be available");
2292 let dialect = dialect.unwrap();
2293 assert_eq!(dialect.db_type(), DbType::Oracle);
2294 assert_eq!(dialect.quote("users"), "\"users\"");
2296 assert!(dialect.supports_returning());
2298 assert_eq!(dialect.last_insert_id_sql(), None);
2300 }
2301
2302 #[test]
2303 fn test_oracle_drop_table() {
2304 let dialect = OracleDialect;
2305 let sql = dialect.build_drop_table("users", true);
2307 assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
2308 let sql = dialect.build_drop_table("users", false);
2310 assert_eq!(sql, "DROP TABLE \"users\"");
2311 }
2312
2313 #[test]
2314 fn test_oracle_alter_table() {
2315 let dialect = OracleDialect;
2316 let col = ColumnDef {
2318 name: "name".to_string(),
2319 sql_type: "VARCHAR(255)".to_string(),
2320 nullable: false,
2321 default: None,
2322 auto_increment: false,
2323 primary_key: false,
2324 };
2325 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2326 assert!(sql.contains("MODIFY"));
2327 assert!(sql.contains("VARCHAR2(255)"));
2328 assert!(!sql.contains("MODIFY COLUMN")); let col = ColumnDef {
2332 name: "email".to_string(),
2333 sql_type: "VARCHAR(255)".to_string(),
2334 nullable: true,
2335 default: None,
2336 auto_increment: false,
2337 primary_key: false,
2338 };
2339 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2340 assert!(sql.contains("ADD \"email\""));
2341 assert!(sql.contains("VARCHAR2(255)"));
2342
2343 let sql =
2345 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2346 assert!(sql.contains("DROP COLUMN"));
2347 assert!(sql.contains("\"email\""));
2348 }
2349
2350 #[test]
2353 fn test_sqlite_concat_handles_null() {
2354 let sqlite = SqliteDialect;
2355 let sql = sqlite.concat(&["a", "b"]);
2358 assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
2359 let sql = sqlite.concat(&["a"]);
2361 assert_eq!(sql, "COALESCE(a, '')");
2362 assert_eq!(sqlite.concat(&[]), "NULL");
2364 }
2365
2366 #[test]
2369 fn test_sqlserver_quote_and_escape() {
2370 let dialect = SqlServerDialect;
2371 assert_eq!(dialect.quote("users"), "[users]");
2373 assert_eq!(dialect.quote("col]name"), "[col]]name]");
2374 assert_eq!(dialect.escape_string("hello"), "hello");
2376 assert_eq!(dialect.escape_string("it's"), "it''s");
2377 assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2378 assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2379 }
2380
2381 #[test]
2382 fn test_sqlserver_pagination() {
2383 let dialect = SqlServerDialect;
2384 let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2386 assert_eq!(
2387 sql,
2388 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2389 );
2390 let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2391 assert_eq!(
2392 sql,
2393 "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2394 );
2395 let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2397 assert_eq!(
2398 sql,
2399 "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2400 );
2401 }
2402
2403 #[test]
2404 fn test_sqlserver_misc_dialect_methods() {
2405 let dialect = SqlServerDialect;
2406 assert_eq!(dialect.db_type(), DbType::SqlServer);
2407 assert!(dialect.supports_returning());
2409 assert!(dialect.supports_if_exists());
2411 assert!(dialect.supports_if_not_exists());
2412 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2414 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2416 assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
2418 }
2419
2420 #[test]
2421 fn test_sqlserver_json_extract() {
2422 let dialect = SqlServerDialect;
2423 let sql = dialect.json_extract("data", "$.user.name");
2424 assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2425 let sql = dialect.json_extract("data", "user.name");
2427 assert!(sql.contains("$.user.name"));
2428 assert!(sql.contains("JSON_VALUE"));
2429 let sql = dialect.json_extract("data", "$.key's");
2431 assert!(sql.contains("$.key''s"));
2432 }
2433
2434 #[test]
2435 fn test_sqlserver_full_text_search() {
2436 let dialect = SqlServerDialect;
2437 let sql = dialect.full_text_search(&["title", "content"], "hello");
2438 assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
2439 assert_eq!(dialect.full_text_search(&[], "hello"), "0");
2441 let sql = dialect.full_text_search(&["title"], "it's");
2443 assert!(sql.contains("it''s"));
2444 }
2445
2446 #[test]
2447 fn test_sqlserver_bool_to_int_and_concat() {
2448 let dialect = SqlServerDialect;
2449 assert_eq!(
2450 dialect.bool_to_int("active"),
2451 "(CASE WHEN active THEN 1 ELSE 0 END)"
2452 );
2453 assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
2454 assert_eq!(dialect.concat(&[]), "NULL");
2455 }
2456
2457 #[test]
2458 fn test_sqlserver_create_table() {
2459 let dialect = SqlServerDialect;
2460 let columns = vec![
2461 ColumnDef {
2462 name: "id".to_string(),
2463 sql_type: "BIGINT".to_string(),
2464 nullable: false,
2465 default: None,
2466 auto_increment: true,
2467 primary_key: true,
2468 },
2469 ColumnDef {
2470 name: "name".to_string(),
2471 sql_type: "VARCHAR(255)".to_string(),
2472 nullable: false,
2473 default: None,
2474 auto_increment: false,
2475 primary_key: false,
2476 },
2477 ColumnDef {
2478 name: "bio".to_string(),
2479 sql_type: "TEXT".to_string(),
2480 nullable: true,
2481 default: None,
2482 auto_increment: false,
2483 primary_key: false,
2484 },
2485 ColumnDef {
2486 name: "is_active".to_string(),
2487 sql_type: "BOOLEAN".to_string(),
2488 nullable: false,
2489 default: Some("1".to_string()),
2490 auto_increment: false,
2491 primary_key: false,
2492 },
2493 ];
2494 let sql = dialect.build_create_table("users", &columns);
2495 assert!(sql.contains("[users]"));
2497 assert!(sql.contains("[id]"));
2498 assert!(sql.contains("IDENTITY(1,1)"));
2500 assert!(
2501 sql.contains("NVARCHAR(255)"),
2502 "VARCHAR should map to NVARCHAR: {}",
2503 sql
2504 );
2505 assert!(
2506 sql.contains("NVARCHAR(MAX)"),
2507 "TEXT should map to NVARCHAR(MAX): {}",
2508 sql
2509 );
2510 assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
2511 assert!(sql.contains("PRIMARY KEY"));
2512 assert!(sql.contains("NOT NULL"));
2513 assert!(sql.contains("DEFAULT 1"));
2514 }
2515
2516 #[test]
2517 fn test_sqlserver_drop_table() {
2518 let dialect = SqlServerDialect;
2519 assert_eq!(
2520 dialect.build_drop_table("users", true),
2521 "DROP TABLE IF EXISTS [users]"
2522 );
2523 assert_eq!(
2524 dialect.build_drop_table("users", false),
2525 "DROP TABLE [users]"
2526 );
2527 }
2528
2529 #[test]
2530 fn test_sqlserver_alter_table() {
2531 let dialect = SqlServerDialect;
2532 let col = ColumnDef {
2534 name: "name".to_string(),
2535 sql_type: "VARCHAR(255)".to_string(),
2536 nullable: false,
2537 default: None,
2538 auto_increment: false,
2539 primary_key: false,
2540 };
2541 let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2542 assert!(sql.contains("ALTER COLUMN"));
2543 assert!(sql.contains("NVARCHAR(255)"));
2544 assert!(!sql.contains("MODIFY"));
2545
2546 let col = ColumnDef {
2548 name: "email".to_string(),
2549 sql_type: "VARCHAR(255)".to_string(),
2550 nullable: true,
2551 default: None,
2552 auto_increment: false,
2553 primary_key: false,
2554 };
2555 let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2556 assert!(sql.contains("ADD [email]"));
2557 assert!(sql.contains("NVARCHAR(255)"));
2558
2559 let sql =
2561 dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2562 assert!(sql.contains("DROP COLUMN"));
2563 assert!(sql.contains("[email]"));
2564
2565 let sql =
2567 dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
2568 assert!(sql.contains("DROP INDEX idx_name ON [users]"));
2569 }
2570
2571 #[test]
2572 fn test_sqlserver_get_dialect() {
2573 let dialect = get_dialect(DbType::SqlServer);
2575 assert!(dialect.is_ok(), "SqlServer dialect should be available");
2576 let dialect = dialect.unwrap();
2577 assert_eq!(dialect.db_type(), DbType::SqlServer);
2578 assert_eq!(dialect.quote("users"), "[users]");
2580 assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2582 assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2583 }
2584
2585 #[test]
2586 fn test_clickhouse_get_dialect_unsupported() {
2587 let dialect = get_dialect(DbType::ClickHouse);
2589 assert!(dialect.is_ok(), "ClickHouse should be supported");
2590 let dialect = dialect.unwrap();
2591 assert_eq!(dialect.db_type(), DbType::ClickHouse);
2592 assert_eq!(dialect.quote("users"), "`users`");
2594 assert!(!dialect.supports_returning());
2596 let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
2598 assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
2599 assert_eq!(dialect.auto_increment_keyword(), "");
2601 }
2602
2603 #[test]
2604 fn test_get_dialect_all_supported_types() {
2605 assert!(get_dialect(DbType::MySQL).is_ok());
2607 assert!(get_dialect(DbType::PostgreSQL).is_ok());
2608 assert!(get_dialect(DbType::Sqlite).is_ok());
2609 assert!(get_dialect(DbType::Oracle).is_ok());
2610 assert!(get_dialect(DbType::SqlServer).is_ok());
2611 assert!(get_dialect(DbType::OceanBase).is_ok());
2612 assert!(get_dialect(DbType::ClickHouse).is_ok());
2613 assert!(get_dialect(DbType::Dameng).is_ok());
2615 assert!(get_dialect(DbType::Kingbase).is_ok());
2616 assert!(get_dialect(DbType::Db2).is_ok());
2617 assert!(get_dialect(DbType::MariaDB).is_ok());
2618 assert!(get_dialect(DbType::TiDB).is_ok());
2619 assert!(get_dialect(DbType::PolarDB).is_ok());
2620 assert!(get_dialect(DbType::GaussDB).is_ok());
2621 assert!(get_dialect(DbType::GBase).is_ok());
2622 assert!(get_dialect(DbType::Sybase).is_ok());
2623 assert!(get_dialect(DbType::Redis).is_err());
2625 assert!(get_dialect(DbType::MongoDB).is_err());
2626 assert!(get_dialect(DbType::VectorDb).is_err());
2627 assert!(get_dialect(DbType::PureJsDb).is_err());
2628 }
2629
2630 #[test]
2633 fn test_mariadb_dialect() {
2634 let dialect = get_dialect(DbType::MariaDB).unwrap();
2635 assert_eq!(dialect.db_type(), DbType::MariaDB);
2636 assert_eq!(dialect.quote("users"), "`users`");
2638 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2639 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
2640 assert!(!dialect.supports_returning());
2642 }
2643
2644 #[test]
2645 fn test_tidb_dialect() {
2646 let dialect = get_dialect(DbType::TiDB).unwrap();
2647 assert_eq!(dialect.db_type(), DbType::TiDB);
2648 assert_eq!(dialect.quote("users"), "`users`");
2650 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2651 assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
2652 }
2653
2654 #[test]
2655 fn test_dameng_dialect() {
2656 let dialect = get_dialect(DbType::Dameng).unwrap();
2657 assert_eq!(dialect.db_type(), DbType::Dameng);
2658 assert_eq!(dialect.quote("users"), "\"users\"");
2660 assert_eq!(dialect.escape_string("it's"), "it''s");
2661 assert_eq!(
2663 dialect.auto_increment_keyword(),
2664 "GENERATED BY DEFAULT AS IDENTITY"
2665 );
2666 assert!(dialect.supports_returning());
2668 }
2669
2670 #[test]
2671 fn test_kingbase_dialect() {
2672 let dialect = get_dialect(DbType::Kingbase).unwrap();
2673 assert_eq!(dialect.db_type(), DbType::Kingbase);
2674 assert_eq!(dialect.quote("users"), "\"users\"");
2676 assert_eq!(dialect.escape_string("it's"), "it''s");
2677 assert!(dialect.supports_returning());
2679 assert_eq!(
2681 dialect.auto_increment_keyword(),
2682 "GENERATED BY DEFAULT AS IDENTITY"
2683 );
2684 }
2685
2686 #[test]
2687 fn test_polardb_dialect() {
2688 let dialect = get_dialect(DbType::PolarDB).unwrap();
2689 assert_eq!(dialect.db_type(), DbType::PolarDB);
2690 assert_eq!(dialect.quote("users"), "\"users\"");
2692 assert!(dialect.supports_returning());
2693 }
2694
2695 #[test]
2696 fn test_gaussdb_dialect() {
2697 let dialect = get_dialect(DbType::GaussDB).unwrap();
2698 assert_eq!(dialect.db_type(), DbType::GaussDB);
2699 assert_eq!(dialect.quote("users"), "\"users\"");
2701 assert!(dialect.supports_returning());
2702 }
2703
2704 #[test]
2705 fn test_gbase_dialect() {
2706 let dialect = get_dialect(DbType::GBase).unwrap();
2707 assert_eq!(dialect.db_type(), DbType::GBase);
2708 assert_eq!(dialect.quote("users"), "[users]");
2710 }
2711
2712 #[test]
2713 fn test_sybase_dialect() {
2714 let dialect = get_dialect(DbType::Sybase).unwrap();
2715 assert_eq!(dialect.db_type(), DbType::Sybase);
2716 assert_eq!(dialect.quote("users"), "[users]");
2718 }
2719
2720 #[test]
2723 fn test_db2_dialect_basic() {
2724 let dialect = get_dialect(DbType::Db2).unwrap();
2725 assert_eq!(dialect.db_type(), DbType::Db2);
2726 assert_eq!(dialect.quote("users"), "\"users\"");
2728 assert_eq!(dialect.escape_string("it's"), "it''s");
2730 assert_eq!(
2732 dialect.auto_increment_keyword(),
2733 "GENERATED ALWAYS AS IDENTITY"
2734 );
2735 assert!(!dialect.supports_if_exists());
2737 assert!(!dialect.supports_if_not_exists());
2738 assert!(!dialect.supports_returning());
2740 }
2741
2742 #[test]
2743 fn test_db2_pagination() {
2744 let dialect = Db2Dialect;
2745 let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
2747 assert_eq!(
2748 sql,
2749 "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
2750 );
2751 }
2752
2753 #[test]
2754 fn test_db2_last_insert_id() {
2755 let dialect = Db2Dialect;
2756 assert_eq!(
2758 dialect.last_insert_id_sql(),
2759 Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
2760 );
2761 }
2762
2763 #[test]
2764 fn test_db2_concat() {
2765 let dialect = Db2Dialect;
2766 assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
2768 assert_eq!(dialect.concat(&[]), "''");
2769 }
2770
2771 #[test]
2772 fn test_db2_create_table() {
2773 let dialect = Db2Dialect;
2774 let cols = vec![ColumnDef {
2775 name: "id".to_string(),
2776 sql_type: "BIGINT".to_string(),
2777 nullable: false,
2778 default: None,
2779 auto_increment: true,
2780 primary_key: true,
2781 }];
2782 let sql = dialect.build_create_table("users", &cols);
2783 assert!(sql.contains("\"id\" BIGINT"));
2784 assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
2785 assert!(sql.contains("PRIMARY KEY"));
2786 }
2787
2788 #[test]
2789 fn test_db2_type_mapping() {
2790 assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
2792 assert_eq!(map_to_db2_type("INT"), "INTEGER");
2793 assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
2794 assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
2795 assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
2796 assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
2797 assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
2798 assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
2799 assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
2800 assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
2801 assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
2802 assert_eq!(map_to_db2_type("DATE"), "DATE");
2803 assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
2804 }
2805
2806 #[test]
2809 fn test_clickhouse_dialect_basic() {
2810 let dialect = get_dialect(DbType::ClickHouse).unwrap();
2811 assert_eq!(dialect.db_type(), DbType::ClickHouse);
2812 assert_eq!(dialect.quote("users"), "`users`");
2814 assert_eq!(dialect.escape_string("it's"), "it\\'s");
2816 assert!(!dialect.supports_returning());
2818 assert_eq!(dialect.auto_increment_keyword(), "");
2820 assert!(dialect.supports_if_exists());
2822 assert!(dialect.supports_if_not_exists());
2823 }
2824
2825 #[test]
2826 fn test_clickhouse_type_mapping() {
2827 assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
2828 assert_eq!(map_to_clickhouse_type("INT"), "Int32");
2829 assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
2830 assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
2831 assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
2832 assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
2833 assert_eq!(map_to_clickhouse_type("TEXT"), "String");
2834 assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
2835 assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
2836 assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
2837 assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
2838 assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
2839 assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
2840 assert_eq!(map_to_clickhouse_type("DATE"), "Date");
2841 }
2842
2843 #[test]
2844 fn test_clickhouse_create_table() {
2845 let dialect = ClickHouseDialect;
2846 let cols = vec![ColumnDef {
2847 name: "id".to_string(),
2848 sql_type: "BIGINT".to_string(),
2849 nullable: false,
2850 default: None,
2851 auto_increment: false, primary_key: true,
2853 }];
2854 let sql = dialect.build_create_table("users", &cols);
2855 assert!(
2857 sql.contains("ENGINE = MergeTree()"),
2858 "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
2859 sql
2860 );
2861 assert!(sql.contains("`id` Int64"));
2862 assert!(sql.contains("PRIMARY KEY"));
2863 }
2864
2865 #[test]
2866 fn test_clickhouse_json_extract() {
2867 let dialect = ClickHouseDialect;
2868 let sql = dialect.json_extract("data", "$.name");
2869 assert!(
2870 sql.contains("JSONExtractString"),
2871 "ClickHouse 应使用 JSONExtractString: {}",
2872 sql
2873 );
2874 }
2875
2876 #[test]
2877 fn test_clickhouse_concat() {
2878 let dialect = ClickHouseDialect;
2879 assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
2881 assert_eq!(dialect.concat(&[]), "''");
2882 }
2883
2884 #[test]
2887 fn test_db_type_dameng_str() {
2888 assert_eq!(DbType::Dameng.as_str(), "dameng");
2889 assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
2890 assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
2891 assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
2892 assert_eq!(DbType::Dameng.default_port(), 5236);
2893 }
2894
2895 #[test]
2896 fn test_db_type_kingbase_str() {
2897 assert_eq!(DbType::Kingbase.as_str(), "kingbase");
2898 assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
2899 assert_eq!(DbType::Kingbase.default_port(), 54321);
2900 }
2901
2902 #[test]
2903 fn test_db_type_db2_str() {
2904 assert_eq!(DbType::Db2.as_str(), "db2");
2905 assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
2906 assert_eq!(DbType::Db2.default_port(), 50000);
2907 }
2908
2909 #[test]
2910 fn test_db_type_mariadb_str() {
2911 assert_eq!(DbType::MariaDB.as_str(), "mariadb");
2912 assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
2913 assert_eq!(DbType::MariaDB.default_port(), 3306);
2914 }
2915
2916 #[test]
2917 fn test_db_type_tidb_str() {
2918 assert_eq!(DbType::TiDB.as_str(), "tidb");
2919 assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
2920 assert_eq!(DbType::TiDB.default_port(), 4000);
2921 }
2922
2923 #[test]
2924 fn test_db_type_polardb_str() {
2925 assert_eq!(DbType::PolarDB.as_str(), "polardb");
2926 assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
2927 assert_eq!(DbType::PolarDB.default_port(), 5432);
2928 }
2929
2930 #[test]
2931 fn test_db_type_gaussdb_str() {
2932 assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
2933 assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
2934 assert_eq!(DbType::GaussDB.default_port(), 25308);
2935 }
2936
2937 #[test]
2938 fn test_db_type_gbase_str() {
2939 assert_eq!(DbType::GBase.as_str(), "gbase");
2940 assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
2941 assert_eq!(DbType::GBase.default_port(), 9088);
2942 }
2943
2944 #[test]
2945 fn test_db_type_sybase_str() {
2946 assert_eq!(DbType::Sybase.as_str(), "sybase");
2947 assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
2948 assert_eq!(DbType::Sybase.default_port(), 5000);
2949 }
2950
2951 #[test]
2952 fn test_db_type_family_classification() {
2953 assert!(DbType::MySQL.is_mysql_family());
2955 assert!(DbType::MariaDB.is_mysql_family());
2956 assert!(DbType::TiDB.is_mysql_family());
2957 assert!(DbType::OceanBase.is_mysql_family());
2958 assert!(!DbType::PostgreSQL.is_mysql_family());
2959
2960 assert!(DbType::PostgreSQL.is_postgres_family());
2962 assert!(DbType::Kingbase.is_postgres_family());
2963 assert!(DbType::GaussDB.is_postgres_family());
2964 assert!(!DbType::MySQL.is_postgres_family());
2965
2966 assert!(DbType::Oracle.is_oracle_family());
2968 assert!(DbType::Dameng.is_oracle_family());
2969 assert!(!DbType::MySQL.is_oracle_family());
2970 }
2971
2972 #[test]
2973 fn test_db_type_supports_stored_procedure_extended() {
2974 assert!(DbType::Dameng.supports_stored_procedure());
2976 assert!(DbType::Kingbase.supports_stored_procedure());
2977 assert!(DbType::Db2.supports_stored_procedure());
2978 assert!(DbType::MariaDB.supports_stored_procedure());
2979 assert!(DbType::TiDB.supports_stored_procedure());
2980 assert!(DbType::PolarDB.supports_stored_procedure());
2981 assert!(DbType::GaussDB.supports_stored_procedure());
2982 assert!(DbType::GBase.supports_stored_procedure());
2983 assert!(DbType::Sybase.supports_stored_procedure());
2984 }
2985
2986 #[test]
2989 fn test_l4_max_identifier_len_constant() {
2990 assert_eq!(MAX_IDENTIFIER_LEN, 63);
2992 }
2993
2994 #[test]
2995 fn test_l4_quote_checked_valid_identifier() {
2996 let dialect = MySqlDialect;
2997 assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
2998 assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
2999 let name_63 = "a".repeat(63);
3001 assert!(dialect.quote_checked(&name_63).is_ok());
3002 }
3003
3004 #[test]
3005 fn test_l4_quote_checked_rejects_too_long() {
3006 let dialect = MySqlDialect;
3007 let long_name = "a".repeat(64); let result = dialect.quote_checked(&long_name);
3009 assert!(result.is_err());
3010 match result {
3011 Err(DbError::InvalidInput(msg)) => {
3012 assert!(
3013 msg.contains("too long"),
3014 "expected 'too long' error, got: {}",
3015 msg
3016 );
3017 }
3018 _ => panic!("Expected DbError::InvalidInput"),
3019 }
3020 }
3021
3022 #[test]
3023 fn test_l4_quote_checked_rejects_empty() {
3024 let dialect = MySqlDialect;
3025 let result = dialect.quote_checked("");
3026 assert!(result.is_err());
3027 }
3028
3029 #[test]
3030 fn test_l4_quote_checked_rejects_sql_injection() {
3031 let dialect = MySqlDialect;
3032 assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
3034 assert!(dialect.quote_checked("user'name").is_err());
3036 assert!(dialect.quote_checked("user name").is_err());
3038 assert!(dialect.quote_checked("1users").is_err());
3040 assert!(dialect.quote_checked("schema.table").is_err());
3042 }
3043
3044 #[test]
3045 fn test_l4_quote_checked_postgres() {
3046 let dialect = PostgreSqlDialect;
3047 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3048 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3049 }
3050
3051 #[test]
3052 fn test_l4_quote_checked_sqlite() {
3053 let dialect = SqliteDialect;
3054 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3055 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3056 }
3057
3058 #[test]
3059 fn test_l4_quote_checked_oracle() {
3060 let dialect = OracleDialect;
3061 assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3062 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3063 }
3064
3065 #[test]
3066 fn test_l4_quote_checked_sql_server() {
3067 let dialect = SqlServerDialect;
3068 assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
3069 assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3070 }
3071}