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