1use super::{DialectImpl, DialectType};
11use crate::error::Result;
12use crate::expressions::{
13 BinaryFunc, BinaryOp, Cast, DataType, Expression, Function, JsonExtractFunc, LikeOp, Literal,
14 Paren, UnaryFunc,
15};
16use crate::generator::GeneratorConfig;
17use crate::tokens::TokenizerConfig;
18
19fn wrap_if_json_arrow(expr: Expression) -> Expression {
23 match &expr {
24 Expression::JsonExtract(f) if f.arrow_syntax => Expression::Paren(Box::new(Paren {
25 this: expr,
26 trailing_comments: Vec::new(),
27 })),
28 Expression::JsonExtractScalar(f) if f.arrow_syntax => Expression::Paren(Box::new(Paren {
29 this: expr,
30 trailing_comments: Vec::new(),
31 })),
32 _ => expr,
33 }
34}
35
36fn json_arrow_to_function(expr: Expression) -> Expression {
39 match expr {
40 Expression::JsonExtract(f) if f.arrow_syntax => Expression::Function(Box::new(
41 Function::new("JSON_EXTRACT".to_string(), vec![f.this, f.path]),
42 )),
43 Expression::JsonExtractScalar(f) if f.arrow_syntax => {
44 let json_extract = Expression::Function(Box::new(Function::new(
47 "JSON_EXTRACT".to_string(),
48 vec![f.this, f.path],
49 )));
50 Expression::Function(Box::new(Function::new(
51 "JSON_UNQUOTE".to_string(),
52 vec![json_extract],
53 )))
54 }
55 other => other,
56 }
57}
58
59pub struct MySQLDialect;
61
62impl DialectImpl for MySQLDialect {
63 fn dialect_type(&self) -> DialectType {
64 DialectType::MySQL
65 }
66
67 fn tokenizer_config(&self) -> TokenizerConfig {
68 use crate::tokens::TokenType;
69 let mut config = TokenizerConfig::default();
70 config.identifiers.insert('`', '`');
72 config.identifiers.remove(&'"');
75 config.quotes.insert("\"".to_string(), "\"".to_string());
77 config.string_escapes.push('\\');
79 config.keywords.insert("XOR".to_string(), TokenType::Xor);
81 config.escape_follow_chars = vec!['0', 'b', 'n', 'r', 't', 'Z', '%', '_'];
84 config.identifiers_can_start_with_digit = true;
86 config.hex_number_strings = true;
87 config
88 }
89
90 fn generator_config(&self) -> GeneratorConfig {
91 use crate::generator::IdentifierQuoteStyle;
92 GeneratorConfig {
93 identifier_quote: '`',
94 identifier_quote_style: IdentifierQuoteStyle::BACKTICK,
95 dialect: Some(DialectType::MySQL),
96 null_ordering_supported: false,
98 limit_only_literals: true,
100 semi_anti_join_with_side: false,
102 supports_table_alias_columns: false,
104 values_as_table: false,
106 tablesample_requires_parens: false,
108 tablesample_with_method: false,
109 aggregate_filter_supported: false,
111 try_supported: false,
113 supports_convert_timezone: false,
115 supports_uescape: false,
117 supports_between_flags: false,
119 query_hints: false,
121 parameter_token: "?",
123 supports_window_exclude: false,
125 supports_exploding_projections: false,
127 identifiers_can_start_with_digit: true,
128 locking_reads_supported: true,
130 ..Default::default()
131 }
132 }
133
134 fn transform_expr(&self, expr: Expression) -> Result<Expression> {
135 match expr {
136 Expression::DataType(dt) => self.transform_data_type(dt),
138
139 Expression::Nvl(f) => Ok(Expression::IfNull(f)),
141
142 Expression::TryCast(c) => self.transform_cast(*c),
148
149 Expression::SafeCast(c) => self.transform_cast(*c),
151
152 Expression::Cast(c) => self.transform_cast(*c),
155
156 Expression::ILike(op) => {
158 let lower_left = Expression::Lower(Box::new(UnaryFunc::new(op.left)));
160 let lower_right = Expression::Lower(Box::new(UnaryFunc::new(op.right)));
161 Ok(Expression::Like(Box::new(LikeOp {
162 left: lower_left,
163 right: lower_right,
164 escape: op.escape,
165 quantifier: op.quantifier,
166 inferred_type: None,
167 })))
168 }
169
170 Expression::Concat(op) => Ok(Expression::Concat(op)),
173
174 Expression::Random(_) => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
176 seed: None,
177 lower: None,
178 upper: None,
179 }))),
180
181 Expression::ArrayAgg(f) => Ok(Expression::Function(Box::new(Function::new(
183 "GROUP_CONCAT".to_string(),
184 vec![f.this],
185 )))),
186
187 Expression::StringAgg(f) => {
189 let mut args = vec![f.this.clone()];
190 if let Some(separator) = &f.separator {
191 args.push(separator.clone());
192 }
193 Ok(Expression::Function(Box::new(Function::new(
194 "GROUP_CONCAT".to_string(),
195 args,
196 ))))
197 }
198
199 Expression::Unnest(f) => {
202 Ok(Expression::Function(Box::new(Function::new(
205 "JSON_TABLE".to_string(),
206 vec![f.this],
207 ))))
208 }
209
210 Expression::Substring(mut f) => {
212 f.from_for_syntax = false;
213 Ok(Expression::Substring(f))
214 }
215
216 Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(Function::new(
219 "BIT_AND".to_string(),
220 vec![f.this],
221 )))),
222
223 Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(Function::new(
225 "BIT_OR".to_string(),
226 vec![f.this],
227 )))),
228
229 Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(Function::new(
231 "BIT_XOR".to_string(),
232 vec![f.this],
233 )))),
234
235 Expression::BitwiseCount(f) => Ok(Expression::Function(Box::new(Function::new(
237 "BIT_COUNT".to_string(),
238 vec![f.this],
239 )))),
240
241 Expression::TimeFromParts(f) => {
243 let mut args = Vec::new();
244 if let Some(h) = f.hour {
245 args.push(*h);
246 }
247 if let Some(m) = f.min {
248 args.push(*m);
249 }
250 if let Some(s) = f.sec {
251 args.push(*s);
252 }
253 Ok(Expression::Function(Box::new(Function::new(
254 "MAKETIME".to_string(),
255 args,
256 ))))
257 }
258
259 Expression::LogicalAnd(f) => Ok(Expression::Function(Box::new(Function::new(
263 "MIN".to_string(),
264 vec![f.this],
265 )))),
266
267 Expression::LogicalOr(f) => Ok(Expression::Function(Box::new(Function::new(
269 "MAX".to_string(),
270 vec![f.this],
271 )))),
272
273 Expression::DayOfMonth(f) => Ok(Expression::Function(Box::new(Function::new(
276 "DAYOFMONTH".to_string(),
277 vec![f.this],
278 )))),
279
280 Expression::DayOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
282 "DAYOFWEEK".to_string(),
283 vec![f.this],
284 )))),
285
286 Expression::DayOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
288 "DAYOFYEAR".to_string(),
289 vec![f.this],
290 )))),
291
292 Expression::WeekOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
294 "WEEKOFYEAR".to_string(),
295 vec![f.this],
296 )))),
297
298 Expression::DateDiff(f) => Ok(Expression::Function(Box::new(Function::new(
300 "DATEDIFF".to_string(),
301 vec![f.this, f.expression],
302 )))),
303
304 Expression::TimeStrToUnix(f) => Ok(Expression::Function(Box::new(Function::new(
306 "UNIX_TIMESTAMP".to_string(),
307 vec![f.this],
308 )))),
309
310 Expression::TimestampDiff(f) => Ok(Expression::Function(Box::new(Function::new(
312 "TIMESTAMPDIFF".to_string(),
313 vec![*f.this, *f.expression],
314 )))),
315
316 Expression::StrPosition(f) => {
320 let mut args = vec![];
321 if let Some(substr) = f.substr {
322 args.push(*substr);
323 }
324 args.push(*f.this);
325 if let Some(pos) = f.position {
326 args.push(*pos);
327 }
328 Ok(Expression::Function(Box::new(Function::new(
329 "LOCATE".to_string(),
330 args,
331 ))))
332 }
333
334 Expression::Stuff(f) => {
336 let mut args = vec![*f.this];
337 if let Some(start) = f.start {
338 args.push(*start);
339 }
340 if let Some(length) = f.length {
341 args.push(Expression::number(length));
342 }
343 args.push(*f.expression);
344 Ok(Expression::Function(Box::new(Function::new(
345 "INSERT".to_string(),
346 args,
347 ))))
348 }
349
350 Expression::SessionUser(_) => Ok(Expression::Function(Box::new(Function::new(
353 "SESSION_USER".to_string(),
354 vec![],
355 )))),
356
357 Expression::CurrentDate(_) => {
359 Ok(Expression::CurrentDate(crate::expressions::CurrentDate))
360 }
361
362 Expression::NullSafeNeq(op) => {
365 let null_safe_eq = Expression::NullSafeEq(Box::new(crate::expressions::BinaryOp {
367 left: op.left,
368 right: op.right,
369 left_comments: Vec::new(),
370 operator_comments: Vec::new(),
371 trailing_comments: Vec::new(),
372 inferred_type: None,
373 }));
374 Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
375 this: null_safe_eq,
376 inferred_type: None,
377 })))
378 }
379
380 Expression::JSONExtract(e) if e.variant_extract.is_some() => {
384 let path = match *e.expression {
385 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
386 let Literal::String(s) = lit.as_ref() else {
387 unreachable!()
388 };
389 let s = Self::convert_bracket_to_quoted_path(&s);
391 let normalized = if s.starts_with('$') {
392 s
393 } else if s.starts_with('[') {
394 format!("${}", s)
395 } else {
396 format!("$.{}", s)
397 };
398 Expression::Literal(Box::new(Literal::String(normalized)))
399 }
400 other => other,
401 };
402 Ok(Expression::Function(Box::new(Function::new(
403 "JSON_EXTRACT".to_string(),
404 vec![*e.this, path],
405 ))))
406 }
407
408 Expression::Function(f) => self.transform_function(*f),
410
411 Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
413
414 Expression::Eq(op) => Ok(Expression::Eq(Box::new(BinaryOp {
421 left: wrap_if_json_arrow(op.left),
422 right: wrap_if_json_arrow(op.right),
423 ..*op
424 }))),
425 Expression::Neq(op) => Ok(Expression::Neq(Box::new(BinaryOp {
426 left: wrap_if_json_arrow(op.left),
427 right: wrap_if_json_arrow(op.right),
428 ..*op
429 }))),
430 Expression::Lt(op) => Ok(Expression::Lt(Box::new(BinaryOp {
431 left: wrap_if_json_arrow(op.left),
432 right: wrap_if_json_arrow(op.right),
433 ..*op
434 }))),
435 Expression::Lte(op) => Ok(Expression::Lte(Box::new(BinaryOp {
436 left: wrap_if_json_arrow(op.left),
437 right: wrap_if_json_arrow(op.right),
438 ..*op
439 }))),
440 Expression::Gt(op) => Ok(Expression::Gt(Box::new(BinaryOp {
441 left: wrap_if_json_arrow(op.left),
442 right: wrap_if_json_arrow(op.right),
443 ..*op
444 }))),
445 Expression::Gte(op) => Ok(Expression::Gte(Box::new(BinaryOp {
446 left: wrap_if_json_arrow(op.left),
447 right: wrap_if_json_arrow(op.right),
448 ..*op
449 }))),
450
451 Expression::In(mut i) => {
453 i.this = wrap_if_json_arrow(i.this);
454 Ok(Expression::In(i))
455 }
456
457 Expression::Not(mut n) => {
459 n.this = wrap_if_json_arrow(n.this);
460 Ok(Expression::Not(n))
461 }
462
463 Expression::ArrayOverlaps(op) => Ok(Expression::And(op)),
466
467 Expression::ModFunc(f) => Ok(Expression::Mod(Box::new(BinaryOp {
469 left: f.this,
470 right: f.expression,
471 left_comments: Vec::new(),
472 operator_comments: Vec::new(),
473 trailing_comments: Vec::new(),
474 inferred_type: None,
475 }))),
476
477 Expression::Show(mut s) => {
479 if s.this == "SLAVE STATUS" {
480 s.this = "REPLICA STATUS".to_string();
481 }
482 if matches!(s.this.as_str(), "INDEX" | "COLUMNS") && s.db.is_none() {
483 if let Some(Expression::Table(mut t)) = s.target.take() {
484 if let Some(db_ident) = t.schema.take().or(t.catalog.take()) {
485 s.db = Some(Expression::Identifier(db_ident));
486 s.target = Some(Expression::Identifier(t.name));
487 } else {
488 s.target = Some(Expression::Table(t));
489 }
490 }
491 }
492 Ok(Expression::Show(s))
493 }
494
495 Expression::AtTimeZone(atz) => {
498 let is_current = match &atz.this {
499 Expression::CurrentDate(_) | Expression::CurrentTimestamp(_) => true,
500 Expression::Function(f) => {
501 let n = f.name.to_uppercase();
502 (n == "CURRENT_DATE" || n == "CURRENT_TIMESTAMP") && f.no_parens
503 }
504 _ => false,
505 };
506 if is_current {
507 Ok(Expression::AtTimeZone(atz)) } else {
509 Ok(atz.this) }
511 }
512
513 Expression::MemberOf(mut op) => {
516 op.right = json_arrow_to_function(op.right);
517 Ok(Expression::MemberOf(op))
518 }
519
520 _ => Ok(expr),
522 }
523 }
524}
525
526impl MySQLDialect {
527 fn normalize_mysql_date_format(fmt: &str) -> String {
528 fmt.replace("%H:%i:%s", "%T").replace("%H:%i:%S", "%T")
529 }
530
531 fn convert_bracket_to_quoted_path(path: &str) -> String {
534 let mut result = String::new();
535 let mut chars = path.chars().peekable();
536 while let Some(c) = chars.next() {
537 if c == '[' && chars.peek() == Some(&'"') {
538 chars.next(); let mut key = String::new();
540 while let Some(kc) = chars.next() {
541 if kc == '"' && chars.peek() == Some(&']') {
542 chars.next(); break;
544 }
545 key.push(kc);
546 }
547 if !result.is_empty() && !result.ends_with('.') {
548 result.push('.');
549 }
550 result.push('"');
551 result.push_str(&key);
552 result.push('"');
553 } else {
554 result.push(c);
555 }
556 }
557 result
558 }
559
560 fn transform_data_type(&self, dt: crate::expressions::DataType) -> Result<Expression> {
564 use crate::expressions::DataType;
565 let transformed = match dt {
566 DataType::Timestamp {
568 precision,
569 timezone: _,
570 } => DataType::Timestamp {
571 precision,
572 timezone: false,
573 },
574 DataType::Custom { name }
576 if name.to_uppercase() == "TIMESTAMPTZ"
577 || name.to_uppercase() == "TIMESTAMPLTZ" =>
578 {
579 DataType::Timestamp {
580 precision: None,
581 timezone: false,
582 }
583 }
584 other => other,
587 };
588 Ok(Expression::DataType(transformed))
589 }
590
591 fn transform_cast(&self, cast: Cast) -> Result<Expression> {
595 match &cast.to {
597 DataType::Timestamp { .. } => Ok(Expression::Function(Box::new(Function::new(
598 "TIMESTAMP".to_string(),
599 vec![cast.this],
600 )))),
601 DataType::Custom { name }
602 if name.to_uppercase() == "TIMESTAMPTZ"
603 || name.to_uppercase() == "TIMESTAMPLTZ" =>
604 {
605 Ok(Expression::Function(Box::new(Function::new(
606 "TIMESTAMP".to_string(),
607 vec![cast.this],
608 ))))
609 }
610 _ => Ok(Expression::Cast(Box::new(self.transform_cast_type(cast)))),
612 }
613 }
614
615 fn transform_cast_type(&self, cast: Cast) -> Cast {
619 let new_type = match &cast.to {
620 DataType::VarChar { length, .. } => DataType::Char { length: *length },
622 DataType::Text => DataType::Char { length: None },
623
624 DataType::BigInt { .. } => DataType::Custom {
626 name: "SIGNED".to_string(),
627 },
628 DataType::Int { .. } => DataType::Custom {
629 name: "SIGNED".to_string(),
630 },
631 DataType::SmallInt { .. } => DataType::Custom {
632 name: "SIGNED".to_string(),
633 },
634 DataType::TinyInt { .. } => DataType::Custom {
635 name: "SIGNED".to_string(),
636 },
637 DataType::Boolean => DataType::Custom {
638 name: "SIGNED".to_string(),
639 },
640
641 DataType::Custom { name } => {
643 let upper = name.to_uppercase();
644 match upper.as_str() {
645 "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
648 | "TINYBLOB" => DataType::Custom { name: upper },
649 "MEDIUMINT" => DataType::Custom {
651 name: "SIGNED".to_string(),
652 },
653 "UBIGINT" | "UINT" | "USMALLINT" | "UTINYINT" | "UMEDIUMINT" => {
655 DataType::Custom {
656 name: "UNSIGNED".to_string(),
657 }
658 }
659 _ => cast.to.clone(),
661 }
662 }
663
664 DataType::Binary { .. } => cast.to.clone(),
666 DataType::VarBinary { .. } => cast.to.clone(),
667 DataType::Date => cast.to.clone(),
668 DataType::Time { .. } => cast.to.clone(),
669 DataType::Decimal { .. } => cast.to.clone(),
670 DataType::Json => cast.to.clone(),
671 DataType::Float { .. } => cast.to.clone(),
672 DataType::Double { .. } => cast.to.clone(),
673 DataType::Char { .. } => cast.to.clone(),
674 DataType::CharacterSet { .. } => cast.to.clone(),
675 DataType::Enum { .. } => cast.to.clone(),
676 DataType::Set { .. } => cast.to.clone(),
677 DataType::Timestamp { .. } => cast.to.clone(),
678
679 _ => DataType::Char { length: None },
681 };
682
683 Cast {
684 this: cast.this,
685 to: new_type,
686 trailing_comments: cast.trailing_comments,
687 double_colon_syntax: cast.double_colon_syntax,
688 format: cast.format,
689 default: cast.default,
690 inferred_type: None,
691 }
692 }
693
694 fn transform_function(&self, f: Function) -> Result<Expression> {
695 let name_upper = f.name.to_uppercase();
696 match name_upper.as_str() {
697 "DATE_FORMAT" if f.args.len() >= 2 => {
699 let mut f = f;
700 if let Some(Expression::Literal(lit)) = f.args.get(1) {
701 if let Literal::String(fmt) = lit.as_ref() {
702 let normalized = Self::normalize_mysql_date_format(fmt);
703 if normalized != *fmt {
704 f.args[1] = Expression::Literal(Box::new(Literal::String(normalized)));
705 }
706 }
707 }
708 Ok(Expression::Function(Box::new(f)))
709 }
710
711 "NVL" if f.args.len() == 2 => {
713 let mut args = f.args;
714 let second = args.pop().unwrap();
715 let first = args.pop().unwrap();
716 Ok(Expression::IfNull(Box::new(BinaryFunc {
717 original_name: None,
718 this: first,
719 expression: second,
720 inferred_type: None,
721 })))
722 }
723
724 "ARRAY_AGG" if f.args.len() == 1 => {
729 let mut args = f.args;
730 Ok(Expression::Function(Box::new(Function::new(
731 "GROUP_CONCAT".to_string(),
732 vec![args.pop().unwrap()],
733 ))))
734 }
735
736 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
738 Function::new("GROUP_CONCAT".to_string(), f.args),
739 ))),
740
741 "RANDOM" => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
743 seed: None,
744 lower: None,
745 upper: None,
746 }))),
747
748 "CURRENT_TIMESTAMP" => {
751 let precision = if let Some(Expression::Literal(lit)) = f.args.first() {
752 if let crate::expressions::Literal::Number(n) = lit.as_ref() {
753 n.parse::<u32>().ok()
754 } else {
755 None
756 }
757 } else {
758 None
759 };
760 Ok(Expression::CurrentTimestamp(
761 crate::expressions::CurrentTimestamp {
762 precision,
763 sysdate: false,
764 },
765 ))
766 }
767
768 "POSITION" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
771 "LOCATE".to_string(),
772 f.args,
773 )))),
774
775 "LENGTH" => Ok(Expression::Function(Box::new(f))),
778
779 "CEIL" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
781 "CEILING".to_string(),
782 f.args,
783 )))),
784
785 "STDDEV" => Ok(Expression::Function(Box::new(Function::new(
787 "STD".to_string(),
788 f.args,
789 )))),
790
791 "STDDEV_SAMP" => Ok(Expression::Function(Box::new(Function::new(
793 "STDDEV".to_string(),
794 f.args,
795 )))),
796
797 "TO_DATE" => Ok(Expression::Function(Box::new(Function::new(
799 "STR_TO_DATE".to_string(),
800 f.args,
801 )))),
802
803 "TO_TIMESTAMP" => Ok(Expression::Function(Box::new(Function::new(
805 "STR_TO_DATE".to_string(),
806 f.args,
807 )))),
808
809 "DATE_TRUNC" if f.args.len() >= 2 => {
812 let mut args = f.args;
815 let _unit = args.remove(0);
816 let date = args.remove(0);
817 Ok(Expression::Function(Box::new(Function::new(
818 "DATE".to_string(),
819 vec![date],
820 ))))
821 }
822
823 "COALESCE" if f.args.len() > 2 => Ok(Expression::Function(Box::new(f))),
827
828 "DAY" => Ok(Expression::Function(Box::new(Function::new(
830 "DAYOFMONTH".to_string(),
831 f.args,
832 )))),
833
834 "DAYOFWEEK" => Ok(Expression::Function(Box::new(f))),
836
837 "DAYOFYEAR" => Ok(Expression::Function(Box::new(f))),
839
840 "WEEKOFYEAR" => Ok(Expression::Function(Box::new(f))),
842
843 "LAST_DAY" => Ok(Expression::Function(Box::new(f))),
845
846 "TIMESTAMPADD" => Ok(Expression::Function(Box::new(Function::new(
848 "DATE_ADD".to_string(),
849 f.args,
850 )))),
851
852 "TIMESTAMPDIFF" => Ok(Expression::Function(Box::new(f))),
854
855 "CONVERT_TIMEZONE" if f.args.len() == 3 => {
857 let mut args = f.args;
858 let from_tz = args.remove(0);
859 let to_tz = args.remove(0);
860 let timestamp = args.remove(0);
861 Ok(Expression::Function(Box::new(Function::new(
862 "CONVERT_TZ".to_string(),
863 vec![timestamp, from_tz, to_tz],
864 ))))
865 }
866
867 "UTC_TIMESTAMP" => Ok(Expression::Function(Box::new(f))),
869
870 "UTC_TIME" => Ok(Expression::Function(Box::new(f))),
872
873 "MAKETIME" => Ok(Expression::Function(Box::new(f))),
875
876 "TIME_FROM_PARTS" if f.args.len() == 3 => Ok(Expression::Function(Box::new(
878 Function::new("MAKETIME".to_string(), f.args),
879 ))),
880
881 "STUFF" if f.args.len() == 4 => Ok(Expression::Function(Box::new(Function::new(
883 "INSERT".to_string(),
884 f.args,
885 )))),
886
887 "LOCATE" => Ok(Expression::Function(Box::new(f))),
889
890 "FIND_IN_SET" => Ok(Expression::Function(Box::new(f))),
892
893 "FORMAT" => Ok(Expression::Function(Box::new(f))),
895
896 "JSON_EXTRACT" => Ok(Expression::Function(Box::new(f))),
898
899 "JSON_UNQUOTE" => Ok(Expression::Function(Box::new(f))),
901
902 "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
904 let extract = Expression::Function(Box::new(Function::new(
905 "JSON_EXTRACT".to_string(),
906 f.args,
907 )));
908 Ok(Expression::Function(Box::new(Function::new(
909 "JSON_UNQUOTE".to_string(),
910 vec![extract],
911 ))))
912 }
913
914 "GEN_RANDOM_UUID" | "GENERATE_UUID" => Ok(Expression::Function(Box::new(
916 Function::new("UUID".to_string(), vec![]),
917 ))),
918
919 "DATABASE" => Ok(Expression::Function(Box::new(Function::new(
921 "SCHEMA".to_string(),
922 f.args,
923 )))),
924
925 "INSTR" if f.args.len() == 2 => {
928 let mut args = f.args;
929 let str_arg = args.remove(0);
930 let substr_arg = args.remove(0);
931 Ok(Expression::Function(Box::new(Function::new(
932 "LOCATE".to_string(),
933 vec![substr_arg, str_arg],
934 ))))
935 }
936
937 "TIME_STR_TO_UNIX" => Ok(Expression::Function(Box::new(Function::new(
939 "UNIX_TIMESTAMP".to_string(),
940 f.args,
941 )))),
942
943 "TIME_STR_TO_TIME" if f.args.len() >= 1 => {
945 let mut args = f.args.into_iter();
946 let arg = args.next().unwrap();
947
948 if args.next().is_some() {
950 return Ok(Expression::Function(Box::new(Function::new(
951 "TIMESTAMP".to_string(),
952 vec![arg],
953 ))));
954 }
955
956 let precision = if let Expression::Literal(ref lit) = arg {
958 if let crate::expressions::Literal::String(ref s) = lit.as_ref() {
959 if let Some(dot_pos) = s.rfind('.') {
961 let after_dot = &s[dot_pos + 1..];
962 let frac_digits =
964 after_dot.chars().take_while(|c| c.is_ascii_digit()).count();
965 if frac_digits > 0 {
966 if frac_digits <= 3 {
968 Some(3)
969 } else {
970 Some(6)
971 }
972 } else {
973 None
974 }
975 } else {
976 None
977 }
978 } else {
979 None
980 }
981 } else {
982 None
983 };
984
985 let type_name = match precision {
986 Some(p) => format!("DATETIME({})", p),
987 None => "DATETIME".to_string(),
988 };
989
990 Ok(Expression::Cast(Box::new(Cast {
991 this: arg,
992 to: DataType::Custom { name: type_name },
993 trailing_comments: Vec::new(),
994 double_colon_syntax: false,
995 format: None,
996 default: None,
997 inferred_type: None,
998 })))
999 }
1000
1001 "UCASE" => Ok(Expression::Function(Box::new(Function::new(
1003 "UPPER".to_string(),
1004 f.args,
1005 )))),
1006
1007 "LCASE" => Ok(Expression::Function(Box::new(Function::new(
1009 "LOWER".to_string(),
1010 f.args,
1011 )))),
1012
1013 "DAY_OF_MONTH" => Ok(Expression::Function(Box::new(Function::new(
1015 "DAYOFMONTH".to_string(),
1016 f.args,
1017 )))),
1018
1019 "DAY_OF_WEEK" => Ok(Expression::Function(Box::new(Function::new(
1021 "DAYOFWEEK".to_string(),
1022 f.args,
1023 )))),
1024
1025 "DAY_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
1027 "DAYOFYEAR".to_string(),
1028 f.args,
1029 )))),
1030
1031 "WEEK_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
1033 "WEEKOFYEAR".to_string(),
1034 f.args,
1035 )))),
1036
1037 "MOD" if f.args.len() == 2 => {
1039 let mut args = f.args;
1040 let left = args.remove(0);
1041 let right = args.remove(0);
1042 Ok(Expression::Mod(Box::new(BinaryOp {
1043 left,
1044 right,
1045 left_comments: Vec::new(),
1046 operator_comments: Vec::new(),
1047 trailing_comments: Vec::new(),
1048 inferred_type: None,
1049 })))
1050 }
1051
1052 "PARSE_JSON" if f.args.len() == 1 => Ok(f.args.into_iter().next().unwrap()),
1054
1055 "GET_PATH" if f.args.len() == 2 => {
1057 let mut args = f.args;
1058 let this = args.remove(0);
1059 let path = args.remove(0);
1060 let json_path = match &path {
1061 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
1062 let Literal::String(s) = lit.as_ref() else {
1063 unreachable!()
1064 };
1065 let s = Self::convert_bracket_to_quoted_path(s);
1067 let normalized = if s.starts_with('$') {
1068 s
1069 } else if s.starts_with('[') {
1070 format!("${}", s)
1071 } else {
1072 format!("$.{}", s)
1073 };
1074 Expression::Literal(Box::new(Literal::String(normalized)))
1075 }
1076 _ => path,
1077 };
1078 Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
1079 this,
1080 path: json_path,
1081 returning: None,
1082 arrow_syntax: false,
1083 hash_arrow_syntax: false,
1084 wrapper_option: None,
1085 quotes_option: None,
1086 on_scalar_string: false,
1087 on_error: None,
1088 })))
1089 }
1090
1091 "REGEXP" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
1093 "REGEXP_LIKE".to_string(),
1094 f.args,
1095 )))),
1096
1097 "CURTIME" => Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
1099 precision: None,
1100 })),
1101
1102 "TRUNC" => Ok(Expression::Function(Box::new(Function::new(
1104 "TRUNCATE".to_string(),
1105 f.args,
1106 )))),
1107
1108 _ => Ok(Expression::Function(Box::new(f))),
1110 }
1111 }
1112
1113 fn transform_aggregate_function(
1114 &self,
1115 f: Box<crate::expressions::AggregateFunction>,
1116 ) -> Result<Expression> {
1117 let name_upper = f.name.to_uppercase();
1118 match name_upper.as_str() {
1119 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1121 Function::new("GROUP_CONCAT".to_string(), f.args),
1122 ))),
1123
1124 "ARRAY_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
1126 "GROUP_CONCAT".to_string(),
1127 f.args,
1128 )))),
1129
1130 _ => Ok(Expression::AggregateFunction(f)),
1132 }
1133 }
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138 use super::*;
1139 use crate::dialects::Dialect;
1140
1141 fn transpile_to_mysql(sql: &str) -> String {
1142 let dialect = Dialect::get(DialectType::Generic);
1143 let result = dialect
1144 .transpile(sql, DialectType::MySQL)
1145 .expect("Transpile failed");
1146 result[0].clone()
1147 }
1148
1149 #[test]
1150 fn test_nvl_to_ifnull() {
1151 let result = transpile_to_mysql("SELECT NVL(a, b)");
1152 assert!(
1153 result.contains("IFNULL"),
1154 "Expected IFNULL, got: {}",
1155 result
1156 );
1157 }
1158
1159 #[test]
1160 fn test_coalesce_preserved() {
1161 let result = transpile_to_mysql("SELECT COALESCE(a, b)");
1163 assert!(
1164 result.contains("COALESCE"),
1165 "Expected COALESCE to be preserved, got: {}",
1166 result
1167 );
1168 }
1169
1170 #[test]
1171 fn test_random_to_rand() {
1172 let result = transpile_to_mysql("SELECT RANDOM()");
1173 assert!(result.contains("RAND"), "Expected RAND, got: {}", result);
1174 }
1175
1176 #[test]
1177 fn test_basic_select() {
1178 let result = transpile_to_mysql("SELECT a, b FROM users WHERE id = 1");
1179 assert!(result.contains("SELECT"));
1180 assert!(result.contains("FROM users"));
1181 }
1182
1183 #[test]
1184 fn test_string_agg_to_group_concat() {
1185 let result = transpile_to_mysql("SELECT STRING_AGG(name)");
1186 assert!(
1187 result.contains("GROUP_CONCAT"),
1188 "Expected GROUP_CONCAT, got: {}",
1189 result
1190 );
1191 }
1192
1193 #[test]
1194 fn test_array_agg_to_group_concat() {
1195 let result = transpile_to_mysql("SELECT ARRAY_AGG(name)");
1196 assert!(
1197 result.contains("GROUP_CONCAT"),
1198 "Expected GROUP_CONCAT, got: {}",
1199 result
1200 );
1201 }
1202
1203 #[test]
1204 fn test_to_date_to_str_to_date() {
1205 let result = transpile_to_mysql("SELECT TO_DATE('2023-01-01')");
1206 assert!(
1207 result.contains("STR_TO_DATE"),
1208 "Expected STR_TO_DATE, got: {}",
1209 result
1210 );
1211 }
1212
1213 #[test]
1214 fn test_backtick_identifiers() {
1215 let dialect = MySQLDialect;
1217 let config = dialect.generator_config();
1218 assert_eq!(config.identifier_quote, '`');
1219 }
1220
1221 #[test]
1222 fn test_create_procedure_signal_sqlstate_regression() {
1223 let sql = "CREATE PROCEDURE CheckSalary(IN p_salary DECIMAL(10,2))
1224BEGIN
1225 IF p_salary <= 0 THEN
1226 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salary must be positive';
1227 END IF;
1228 INSERT INTO employees (salary) VALUES (p_salary);
1229END;";
1230
1231 let dialect = Dialect::get(DialectType::MySQL);
1232 let ast = dialect.parse(sql).expect("Parse failed");
1233 let output = dialect.generate(&ast[0]).expect("Generate failed");
1234
1235 assert!(output.contains("CREATE PROCEDURE CheckSalary"));
1236 assert!(output.contains("IF p_salary <= 0 THEN"));
1237 assert!(
1238 output.contains("SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salary must be positive'")
1239 );
1240 assert!(output.contains("END IF"));
1241 assert!(output.contains("INSERT INTO employees (salary) VALUES (p_salary)"));
1242 }
1243
1244 fn mysql_identity(sql: &str, expected: &str) {
1245 let dialect = Dialect::get(DialectType::MySQL);
1246 let ast = dialect.parse(sql).expect("Parse failed");
1247 let transformed = dialect.transform(ast[0].clone()).expect("Transform failed");
1248 let result = dialect.generate(&transformed).expect("Generate failed");
1249 assert_eq!(result, expected, "SQL: {}", sql);
1250 }
1251
1252 #[test]
1253 fn test_ucase_to_upper() {
1254 mysql_identity("SELECT UCASE('foo')", "SELECT UPPER('foo')");
1255 }
1256
1257 #[test]
1258 fn test_lcase_to_lower() {
1259 mysql_identity("SELECT LCASE('foo')", "SELECT LOWER('foo')");
1260 }
1261
1262 #[test]
1263 fn test_day_of_month() {
1264 mysql_identity(
1265 "SELECT DAY_OF_MONTH('2023-01-01')",
1266 "SELECT DAYOFMONTH('2023-01-01')",
1267 );
1268 }
1269
1270 #[test]
1271 fn test_day_of_week() {
1272 mysql_identity(
1273 "SELECT DAY_OF_WEEK('2023-01-01')",
1274 "SELECT DAYOFWEEK('2023-01-01')",
1275 );
1276 }
1277
1278 #[test]
1279 fn test_day_of_year() {
1280 mysql_identity(
1281 "SELECT DAY_OF_YEAR('2023-01-01')",
1282 "SELECT DAYOFYEAR('2023-01-01')",
1283 );
1284 }
1285
1286 #[test]
1287 fn test_week_of_year() {
1288 mysql_identity(
1289 "SELECT WEEK_OF_YEAR('2023-01-01')",
1290 "SELECT WEEKOFYEAR('2023-01-01')",
1291 );
1292 }
1293
1294 #[test]
1295 fn test_mod_func_to_percent() {
1296 mysql_identity("MOD(x, y)", "x % y");
1298 }
1299
1300 #[test]
1301 fn test_database_to_schema() {
1302 mysql_identity("DATABASE()", "SCHEMA()");
1303 }
1304
1305 #[test]
1306 fn test_and_operator() {
1307 mysql_identity("SELECT 1 && 0", "SELECT 1 AND 0");
1308 }
1309
1310 #[test]
1311 fn test_or_operator() {
1312 mysql_identity("SELECT a || b", "SELECT a OR b");
1313 }
1314}