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
87 }
88
89 fn generator_config(&self) -> GeneratorConfig {
90 use crate::generator::IdentifierQuoteStyle;
91 GeneratorConfig {
92 identifier_quote: '`',
93 identifier_quote_style: IdentifierQuoteStyle::BACKTICK,
94 dialect: Some(DialectType::MySQL),
95 null_ordering_supported: false,
97 limit_only_literals: true,
99 semi_anti_join_with_side: false,
101 supports_table_alias_columns: false,
103 values_as_table: false,
105 tablesample_requires_parens: false,
107 tablesample_with_method: false,
108 aggregate_filter_supported: false,
110 try_supported: false,
112 supports_convert_timezone: false,
114 supports_uescape: false,
116 supports_between_flags: false,
118 query_hints: false,
120 parameter_token: "?",
122 supports_window_exclude: false,
124 supports_exploding_projections: false,
126 identifiers_can_start_with_digit: true,
127 locking_reads_supported: true,
129 ..Default::default()
130 }
131 }
132
133 fn transform_expr(&self, expr: Expression) -> Result<Expression> {
134 match expr {
135 Expression::DataType(dt) => self.transform_data_type(dt),
137
138 Expression::Nvl(f) => Ok(Expression::IfNull(f)),
140
141 Expression::TryCast(c) => self.transform_cast(*c),
147
148 Expression::SafeCast(c) => self.transform_cast(*c),
150
151 Expression::Cast(c) => self.transform_cast(*c),
154
155 Expression::ILike(op) => {
157 let lower_left = Expression::Lower(Box::new(UnaryFunc::new(op.left)));
159 let lower_right = Expression::Lower(Box::new(UnaryFunc::new(op.right)));
160 Ok(Expression::Like(Box::new(LikeOp {
161 left: lower_left,
162 right: lower_right,
163 escape: op.escape,
164 quantifier: op.quantifier,
165 inferred_type: None,
166 })))
167 }
168
169 Expression::Concat(op) => Ok(Expression::Concat(op)),
172
173 Expression::Random(_) => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
175 seed: None,
176 lower: None,
177 upper: None,
178 }))),
179
180 Expression::ArrayAgg(f) => Ok(Expression::Function(Box::new(Function::new(
182 "GROUP_CONCAT".to_string(),
183 vec![f.this],
184 )))),
185
186 Expression::StringAgg(f) => {
188 let mut args = vec![f.this.clone()];
189 if let Some(separator) = &f.separator {
190 args.push(separator.clone());
191 }
192 Ok(Expression::Function(Box::new(Function::new(
193 "GROUP_CONCAT".to_string(),
194 args,
195 ))))
196 }
197
198 Expression::Unnest(f) => {
201 Ok(Expression::Function(Box::new(Function::new(
204 "JSON_TABLE".to_string(),
205 vec![f.this],
206 ))))
207 }
208
209 Expression::Substring(mut f) => {
211 f.from_for_syntax = false;
212 Ok(Expression::Substring(f))
213 }
214
215 Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(Function::new(
218 "BIT_AND".to_string(),
219 vec![f.this],
220 )))),
221
222 Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(Function::new(
224 "BIT_OR".to_string(),
225 vec![f.this],
226 )))),
227
228 Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(Function::new(
230 "BIT_XOR".to_string(),
231 vec![f.this],
232 )))),
233
234 Expression::BitwiseCount(f) => Ok(Expression::Function(Box::new(Function::new(
236 "BIT_COUNT".to_string(),
237 vec![f.this],
238 )))),
239
240 Expression::TimeFromParts(f) => {
242 let mut args = Vec::new();
243 if let Some(h) = f.hour {
244 args.push(*h);
245 }
246 if let Some(m) = f.min {
247 args.push(*m);
248 }
249 if let Some(s) = f.sec {
250 args.push(*s);
251 }
252 Ok(Expression::Function(Box::new(Function::new(
253 "MAKETIME".to_string(),
254 args,
255 ))))
256 }
257
258 Expression::LogicalAnd(f) => Ok(Expression::Function(Box::new(Function::new(
262 "MIN".to_string(),
263 vec![f.this],
264 )))),
265
266 Expression::LogicalOr(f) => Ok(Expression::Function(Box::new(Function::new(
268 "MAX".to_string(),
269 vec![f.this],
270 )))),
271
272 Expression::DayOfMonth(f) => Ok(Expression::Function(Box::new(Function::new(
275 "DAYOFMONTH".to_string(),
276 vec![f.this],
277 )))),
278
279 Expression::DayOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
281 "DAYOFWEEK".to_string(),
282 vec![f.this],
283 )))),
284
285 Expression::DayOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
287 "DAYOFYEAR".to_string(),
288 vec![f.this],
289 )))),
290
291 Expression::WeekOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
293 "WEEKOFYEAR".to_string(),
294 vec![f.this],
295 )))),
296
297 Expression::DateDiff(f) => Ok(Expression::Function(Box::new(Function::new(
299 "DATEDIFF".to_string(),
300 vec![f.this, f.expression],
301 )))),
302
303 Expression::TimeStrToUnix(f) => Ok(Expression::Function(Box::new(Function::new(
305 "UNIX_TIMESTAMP".to_string(),
306 vec![f.this],
307 )))),
308
309 Expression::TimestampDiff(f) => Ok(Expression::Function(Box::new(Function::new(
311 "TIMESTAMPDIFF".to_string(),
312 vec![*f.this, *f.expression],
313 )))),
314
315 Expression::StrPosition(f) => {
319 let mut args = vec![];
320 if let Some(substr) = f.substr {
321 args.push(*substr);
322 }
323 args.push(*f.this);
324 if let Some(pos) = f.position {
325 args.push(*pos);
326 }
327 Ok(Expression::Function(Box::new(Function::new(
328 "LOCATE".to_string(),
329 args,
330 ))))
331 }
332
333 Expression::Stuff(f) => {
335 let mut args = vec![*f.this];
336 if let Some(start) = f.start {
337 args.push(*start);
338 }
339 if let Some(length) = f.length {
340 args.push(Expression::number(length));
341 }
342 args.push(*f.expression);
343 Ok(Expression::Function(Box::new(Function::new(
344 "INSERT".to_string(),
345 args,
346 ))))
347 }
348
349 Expression::SessionUser(_) => Ok(Expression::Function(Box::new(Function::new(
352 "SESSION_USER".to_string(),
353 vec![],
354 )))),
355
356 Expression::CurrentDate(_) => {
358 Ok(Expression::CurrentDate(crate::expressions::CurrentDate))
359 }
360
361 Expression::NullSafeNeq(op) => {
364 let null_safe_eq = Expression::NullSafeEq(Box::new(crate::expressions::BinaryOp {
366 left: op.left,
367 right: op.right,
368 left_comments: Vec::new(),
369 operator_comments: Vec::new(),
370 trailing_comments: Vec::new(),
371 inferred_type: None,
372 }));
373 Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
374 this: null_safe_eq,
375 inferred_type: None,
376 })))
377 }
378
379 Expression::JSONExtract(e) if e.variant_extract.is_some() => {
383 let path = match *e.expression {
384 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
385 let Literal::String(s) = lit.as_ref() else {
386 unreachable!()
387 };
388 let s = Self::convert_bracket_to_quoted_path(&s);
390 let normalized = if s.starts_with('$') {
391 s
392 } else if s.starts_with('[') {
393 format!("${}", s)
394 } else {
395 format!("$.{}", s)
396 };
397 Expression::Literal(Box::new(Literal::String(normalized)))
398 }
399 other => other,
400 };
401 Ok(Expression::Function(Box::new(Function::new(
402 "JSON_EXTRACT".to_string(),
403 vec![*e.this, path],
404 ))))
405 }
406
407 Expression::Function(f) => self.transform_function(*f),
409
410 Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
412
413 Expression::Eq(op) => Ok(Expression::Eq(Box::new(BinaryOp {
420 left: wrap_if_json_arrow(op.left),
421 right: wrap_if_json_arrow(op.right),
422 ..*op
423 }))),
424 Expression::Neq(op) => Ok(Expression::Neq(Box::new(BinaryOp {
425 left: wrap_if_json_arrow(op.left),
426 right: wrap_if_json_arrow(op.right),
427 ..*op
428 }))),
429 Expression::Lt(op) => Ok(Expression::Lt(Box::new(BinaryOp {
430 left: wrap_if_json_arrow(op.left),
431 right: wrap_if_json_arrow(op.right),
432 ..*op
433 }))),
434 Expression::Lte(op) => Ok(Expression::Lte(Box::new(BinaryOp {
435 left: wrap_if_json_arrow(op.left),
436 right: wrap_if_json_arrow(op.right),
437 ..*op
438 }))),
439 Expression::Gt(op) => Ok(Expression::Gt(Box::new(BinaryOp {
440 left: wrap_if_json_arrow(op.left),
441 right: wrap_if_json_arrow(op.right),
442 ..*op
443 }))),
444 Expression::Gte(op) => Ok(Expression::Gte(Box::new(BinaryOp {
445 left: wrap_if_json_arrow(op.left),
446 right: wrap_if_json_arrow(op.right),
447 ..*op
448 }))),
449
450 Expression::In(mut i) => {
452 i.this = wrap_if_json_arrow(i.this);
453 Ok(Expression::In(i))
454 }
455
456 Expression::Not(mut n) => {
458 n.this = wrap_if_json_arrow(n.this);
459 Ok(Expression::Not(n))
460 }
461
462 Expression::ArrayOverlaps(op) => Ok(Expression::And(op)),
465
466 Expression::ModFunc(f) => Ok(Expression::Mod(Box::new(BinaryOp {
468 left: f.this,
469 right: f.expression,
470 left_comments: Vec::new(),
471 operator_comments: Vec::new(),
472 trailing_comments: Vec::new(),
473 inferred_type: None,
474 }))),
475
476 Expression::Show(mut s) => {
478 if s.this == "SLAVE STATUS" {
479 s.this = "REPLICA STATUS".to_string();
480 }
481 if matches!(s.this.as_str(), "INDEX" | "COLUMNS") && s.db.is_none() {
482 if let Some(Expression::Table(mut t)) = s.target.take() {
483 if let Some(db_ident) = t.schema.take().or(t.catalog.take()) {
484 s.db = Some(Expression::Identifier(db_ident));
485 s.target = Some(Expression::Identifier(t.name));
486 } else {
487 s.target = Some(Expression::Table(t));
488 }
489 }
490 }
491 Ok(Expression::Show(s))
492 }
493
494 Expression::AtTimeZone(atz) => {
497 let is_current = match &atz.this {
498 Expression::CurrentDate(_) | Expression::CurrentTimestamp(_) => true,
499 Expression::Function(f) => {
500 let n = f.name.to_uppercase();
501 (n == "CURRENT_DATE" || n == "CURRENT_TIMESTAMP") && f.no_parens
502 }
503 _ => false,
504 };
505 if is_current {
506 Ok(Expression::AtTimeZone(atz)) } else {
508 Ok(atz.this) }
510 }
511
512 Expression::MemberOf(mut op) => {
515 op.right = json_arrow_to_function(op.right);
516 Ok(Expression::MemberOf(op))
517 }
518
519 _ => Ok(expr),
521 }
522 }
523}
524
525impl MySQLDialect {
526 fn normalize_mysql_date_format(fmt: &str) -> String {
527 fmt.replace("%H:%i:%s", "%T").replace("%H:%i:%S", "%T")
528 }
529
530 fn convert_bracket_to_quoted_path(path: &str) -> String {
533 let mut result = String::new();
534 let mut chars = path.chars().peekable();
535 while let Some(c) = chars.next() {
536 if c == '[' && chars.peek() == Some(&'"') {
537 chars.next(); let mut key = String::new();
539 while let Some(kc) = chars.next() {
540 if kc == '"' && chars.peek() == Some(&']') {
541 chars.next(); break;
543 }
544 key.push(kc);
545 }
546 if !result.is_empty() && !result.ends_with('.') {
547 result.push('.');
548 }
549 result.push('"');
550 result.push_str(&key);
551 result.push('"');
552 } else {
553 result.push(c);
554 }
555 }
556 result
557 }
558
559 fn transform_data_type(&self, dt: crate::expressions::DataType) -> Result<Expression> {
563 use crate::expressions::DataType;
564 let transformed = match dt {
565 DataType::Timestamp {
567 precision,
568 timezone: _,
569 } => DataType::Timestamp {
570 precision,
571 timezone: false,
572 },
573 DataType::Custom { name }
575 if name.to_uppercase() == "TIMESTAMPTZ"
576 || name.to_uppercase() == "TIMESTAMPLTZ" =>
577 {
578 DataType::Timestamp {
579 precision: None,
580 timezone: false,
581 }
582 }
583 other => other,
586 };
587 Ok(Expression::DataType(transformed))
588 }
589
590 fn transform_cast(&self, cast: Cast) -> Result<Expression> {
594 match &cast.to {
596 DataType::Timestamp { .. } => Ok(Expression::Function(Box::new(Function::new(
597 "TIMESTAMP".to_string(),
598 vec![cast.this],
599 )))),
600 DataType::Custom { name }
601 if name.to_uppercase() == "TIMESTAMPTZ"
602 || name.to_uppercase() == "TIMESTAMPLTZ" =>
603 {
604 Ok(Expression::Function(Box::new(Function::new(
605 "TIMESTAMP".to_string(),
606 vec![cast.this],
607 ))))
608 }
609 _ => Ok(Expression::Cast(Box::new(self.transform_cast_type(cast)))),
611 }
612 }
613
614 fn transform_cast_type(&self, cast: Cast) -> Cast {
618 let new_type = match &cast.to {
619 DataType::VarChar { length, .. } => DataType::Char { length: *length },
621 DataType::Text => DataType::Char { length: None },
622
623 DataType::BigInt { .. } => DataType::Custom {
625 name: "SIGNED".to_string(),
626 },
627 DataType::Int { .. } => DataType::Custom {
628 name: "SIGNED".to_string(),
629 },
630 DataType::SmallInt { .. } => DataType::Custom {
631 name: "SIGNED".to_string(),
632 },
633 DataType::TinyInt { .. } => DataType::Custom {
634 name: "SIGNED".to_string(),
635 },
636 DataType::Boolean => DataType::Custom {
637 name: "SIGNED".to_string(),
638 },
639
640 DataType::Custom { name } => {
642 let upper = name.to_uppercase();
643 match upper.as_str() {
644 "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
647 | "TINYBLOB" => DataType::Custom { name: upper },
648 "MEDIUMINT" => DataType::Custom {
650 name: "SIGNED".to_string(),
651 },
652 "UBIGINT" | "UINT" | "USMALLINT" | "UTINYINT" | "UMEDIUMINT" => {
654 DataType::Custom {
655 name: "UNSIGNED".to_string(),
656 }
657 }
658 _ => cast.to.clone(),
660 }
661 }
662
663 DataType::Binary { .. } => cast.to.clone(),
665 DataType::VarBinary { .. } => cast.to.clone(),
666 DataType::Date => cast.to.clone(),
667 DataType::Time { .. } => cast.to.clone(),
668 DataType::Decimal { .. } => cast.to.clone(),
669 DataType::Json => cast.to.clone(),
670 DataType::Float { .. } => cast.to.clone(),
671 DataType::Double { .. } => cast.to.clone(),
672 DataType::Char { .. } => cast.to.clone(),
673 DataType::CharacterSet { .. } => cast.to.clone(),
674 DataType::Enum { .. } => cast.to.clone(),
675 DataType::Set { .. } => cast.to.clone(),
676 DataType::Timestamp { .. } => cast.to.clone(),
677
678 _ => DataType::Char { length: None },
680 };
681
682 Cast {
683 this: cast.this,
684 to: new_type,
685 trailing_comments: cast.trailing_comments,
686 double_colon_syntax: cast.double_colon_syntax,
687 format: cast.format,
688 default: cast.default,
689 inferred_type: None,
690 }
691 }
692
693 fn transform_function(&self, f: Function) -> Result<Expression> {
694 let name_upper = f.name.to_uppercase();
695 match name_upper.as_str() {
696 "DATE_FORMAT" if f.args.len() >= 2 => {
698 let mut f = f;
699 if let Some(Expression::Literal(lit)) = f.args.get(1) {
700 if let Literal::String(fmt) = lit.as_ref() {
701 let normalized = Self::normalize_mysql_date_format(fmt);
702 if normalized != *fmt {
703 f.args[1] = Expression::Literal(Box::new(Literal::String(normalized)));
704 }
705 }
706 }
707 Ok(Expression::Function(Box::new(f)))
708 }
709
710 "NVL" if f.args.len() == 2 => {
712 let mut args = f.args;
713 let second = args.pop().unwrap();
714 let first = args.pop().unwrap();
715 Ok(Expression::IfNull(Box::new(BinaryFunc {
716 original_name: None,
717 this: first,
718 expression: second,
719 inferred_type: None,
720 })))
721 }
722
723 "ARRAY_AGG" if f.args.len() == 1 => {
728 let mut args = f.args;
729 Ok(Expression::Function(Box::new(Function::new(
730 "GROUP_CONCAT".to_string(),
731 vec![args.pop().unwrap()],
732 ))))
733 }
734
735 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
737 Function::new("GROUP_CONCAT".to_string(), f.args),
738 ))),
739
740 "RANDOM" => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
742 seed: None,
743 lower: None,
744 upper: None,
745 }))),
746
747 "CURRENT_TIMESTAMP" => {
750 let precision = if let Some(Expression::Literal(lit)) = f.args.first() {
751 if let crate::expressions::Literal::Number(n) = lit.as_ref() {
752 n.parse::<u32>().ok()
753 } else {
754 None
755 }
756 } else {
757 None
758 };
759 Ok(Expression::CurrentTimestamp(
760 crate::expressions::CurrentTimestamp {
761 precision,
762 sysdate: false,
763 },
764 ))
765 }
766
767 "POSITION" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
770 "LOCATE".to_string(),
771 f.args,
772 )))),
773
774 "LENGTH" => Ok(Expression::Function(Box::new(f))),
777
778 "CEIL" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
780 "CEILING".to_string(),
781 f.args,
782 )))),
783
784 "STDDEV" => Ok(Expression::Function(Box::new(Function::new(
786 "STD".to_string(),
787 f.args,
788 )))),
789
790 "STDDEV_SAMP" => Ok(Expression::Function(Box::new(Function::new(
792 "STDDEV".to_string(),
793 f.args,
794 )))),
795
796 "TO_DATE" => Ok(Expression::Function(Box::new(Function::new(
798 "STR_TO_DATE".to_string(),
799 f.args,
800 )))),
801
802 "TO_TIMESTAMP" => Ok(Expression::Function(Box::new(Function::new(
804 "STR_TO_DATE".to_string(),
805 f.args,
806 )))),
807
808 "DATE_TRUNC" if f.args.len() >= 2 => {
811 let mut args = f.args;
814 let _unit = args.remove(0);
815 let date = args.remove(0);
816 Ok(Expression::Function(Box::new(Function::new(
817 "DATE".to_string(),
818 vec![date],
819 ))))
820 }
821
822 "COALESCE" if f.args.len() > 2 => Ok(Expression::Function(Box::new(f))),
826
827 "DAY" => Ok(Expression::Function(Box::new(Function::new(
829 "DAYOFMONTH".to_string(),
830 f.args,
831 )))),
832
833 "DAYOFWEEK" => Ok(Expression::Function(Box::new(f))),
835
836 "DAYOFYEAR" => Ok(Expression::Function(Box::new(f))),
838
839 "WEEKOFYEAR" => Ok(Expression::Function(Box::new(f))),
841
842 "LAST_DAY" => Ok(Expression::Function(Box::new(f))),
844
845 "TIMESTAMPADD" => Ok(Expression::Function(Box::new(Function::new(
847 "DATE_ADD".to_string(),
848 f.args,
849 )))),
850
851 "TIMESTAMPDIFF" => Ok(Expression::Function(Box::new(f))),
853
854 "CONVERT_TIMEZONE" if f.args.len() == 3 => {
856 let mut args = f.args;
857 let from_tz = args.remove(0);
858 let to_tz = args.remove(0);
859 let timestamp = args.remove(0);
860 Ok(Expression::Function(Box::new(Function::new(
861 "CONVERT_TZ".to_string(),
862 vec![timestamp, from_tz, to_tz],
863 ))))
864 }
865
866 "UTC_TIMESTAMP" => Ok(Expression::Function(Box::new(f))),
868
869 "UTC_TIME" => Ok(Expression::Function(Box::new(f))),
871
872 "MAKETIME" => Ok(Expression::Function(Box::new(f))),
874
875 "TIME_FROM_PARTS" if f.args.len() == 3 => Ok(Expression::Function(Box::new(
877 Function::new("MAKETIME".to_string(), f.args),
878 ))),
879
880 "STUFF" if f.args.len() == 4 => Ok(Expression::Function(Box::new(Function::new(
882 "INSERT".to_string(),
883 f.args,
884 )))),
885
886 "LOCATE" => Ok(Expression::Function(Box::new(f))),
888
889 "FIND_IN_SET" => Ok(Expression::Function(Box::new(f))),
891
892 "FORMAT" => Ok(Expression::Function(Box::new(f))),
894
895 "JSON_EXTRACT" => Ok(Expression::Function(Box::new(f))),
897
898 "JSON_UNQUOTE" => Ok(Expression::Function(Box::new(f))),
900
901 "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
903 let extract = Expression::Function(Box::new(Function::new(
904 "JSON_EXTRACT".to_string(),
905 f.args,
906 )));
907 Ok(Expression::Function(Box::new(Function::new(
908 "JSON_UNQUOTE".to_string(),
909 vec![extract],
910 ))))
911 }
912
913 "GEN_RANDOM_UUID" | "GENERATE_UUID" => Ok(Expression::Function(Box::new(
915 Function::new("UUID".to_string(), vec![]),
916 ))),
917
918 "DATABASE" => Ok(Expression::Function(Box::new(Function::new(
920 "SCHEMA".to_string(),
921 f.args,
922 )))),
923
924 "INSTR" if f.args.len() == 2 => {
927 let mut args = f.args;
928 let str_arg = args.remove(0);
929 let substr_arg = args.remove(0);
930 Ok(Expression::Function(Box::new(Function::new(
931 "LOCATE".to_string(),
932 vec![substr_arg, str_arg],
933 ))))
934 }
935
936 "TIME_STR_TO_UNIX" => Ok(Expression::Function(Box::new(Function::new(
938 "UNIX_TIMESTAMP".to_string(),
939 f.args,
940 )))),
941
942 "TIME_STR_TO_TIME" if f.args.len() >= 1 => {
944 let mut args = f.args.into_iter();
945 let arg = args.next().unwrap();
946
947 if args.next().is_some() {
949 return Ok(Expression::Function(Box::new(Function::new(
950 "TIMESTAMP".to_string(),
951 vec![arg],
952 ))));
953 }
954
955 let precision = if let Expression::Literal(ref lit) = arg {
957 if let crate::expressions::Literal::String(ref s) = lit.as_ref() {
958 if let Some(dot_pos) = s.rfind('.') {
960 let after_dot = &s[dot_pos + 1..];
961 let frac_digits =
963 after_dot.chars().take_while(|c| c.is_ascii_digit()).count();
964 if frac_digits > 0 {
965 if frac_digits <= 3 {
967 Some(3)
968 } else {
969 Some(6)
970 }
971 } else {
972 None
973 }
974 } else {
975 None
976 }
977 } else {
978 None
979 }
980 } else {
981 None
982 };
983
984 let type_name = match precision {
985 Some(p) => format!("DATETIME({})", p),
986 None => "DATETIME".to_string(),
987 };
988
989 Ok(Expression::Cast(Box::new(Cast {
990 this: arg,
991 to: DataType::Custom { name: type_name },
992 trailing_comments: Vec::new(),
993 double_colon_syntax: false,
994 format: None,
995 default: None,
996 inferred_type: None,
997 })))
998 }
999
1000 "UCASE" => Ok(Expression::Function(Box::new(Function::new(
1002 "UPPER".to_string(),
1003 f.args,
1004 )))),
1005
1006 "LCASE" => Ok(Expression::Function(Box::new(Function::new(
1008 "LOWER".to_string(),
1009 f.args,
1010 )))),
1011
1012 "DAY_OF_MONTH" => Ok(Expression::Function(Box::new(Function::new(
1014 "DAYOFMONTH".to_string(),
1015 f.args,
1016 )))),
1017
1018 "DAY_OF_WEEK" => Ok(Expression::Function(Box::new(Function::new(
1020 "DAYOFWEEK".to_string(),
1021 f.args,
1022 )))),
1023
1024 "DAY_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
1026 "DAYOFYEAR".to_string(),
1027 f.args,
1028 )))),
1029
1030 "WEEK_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
1032 "WEEKOFYEAR".to_string(),
1033 f.args,
1034 )))),
1035
1036 "MOD" if f.args.len() == 2 => {
1038 let mut args = f.args;
1039 let left = args.remove(0);
1040 let right = args.remove(0);
1041 Ok(Expression::Mod(Box::new(BinaryOp {
1042 left,
1043 right,
1044 left_comments: Vec::new(),
1045 operator_comments: Vec::new(),
1046 trailing_comments: Vec::new(),
1047 inferred_type: None,
1048 })))
1049 }
1050
1051 "PARSE_JSON" if f.args.len() == 1 => Ok(f.args.into_iter().next().unwrap()),
1053
1054 "GET_PATH" if f.args.len() == 2 => {
1056 let mut args = f.args;
1057 let this = args.remove(0);
1058 let path = args.remove(0);
1059 let json_path = match &path {
1060 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
1061 let Literal::String(s) = lit.as_ref() else {
1062 unreachable!()
1063 };
1064 let s = Self::convert_bracket_to_quoted_path(s);
1066 let normalized = if s.starts_with('$') {
1067 s
1068 } else if s.starts_with('[') {
1069 format!("${}", s)
1070 } else {
1071 format!("$.{}", s)
1072 };
1073 Expression::Literal(Box::new(Literal::String(normalized)))
1074 }
1075 _ => path,
1076 };
1077 Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
1078 this,
1079 path: json_path,
1080 returning: None,
1081 arrow_syntax: false,
1082 hash_arrow_syntax: false,
1083 wrapper_option: None,
1084 quotes_option: None,
1085 on_scalar_string: false,
1086 on_error: None,
1087 })))
1088 }
1089
1090 "REGEXP" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
1092 "REGEXP_LIKE".to_string(),
1093 f.args,
1094 )))),
1095
1096 "CURTIME" => Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
1098 precision: None,
1099 })),
1100
1101 "TRUNC" => Ok(Expression::Function(Box::new(Function::new(
1103 "TRUNCATE".to_string(),
1104 f.args,
1105 )))),
1106
1107 _ => Ok(Expression::Function(Box::new(f))),
1109 }
1110 }
1111
1112 fn transform_aggregate_function(
1113 &self,
1114 f: Box<crate::expressions::AggregateFunction>,
1115 ) -> Result<Expression> {
1116 let name_upper = f.name.to_uppercase();
1117 match name_upper.as_str() {
1118 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1120 Function::new("GROUP_CONCAT".to_string(), f.args),
1121 ))),
1122
1123 "ARRAY_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
1125 "GROUP_CONCAT".to_string(),
1126 f.args,
1127 )))),
1128
1129 _ => Ok(Expression::AggregateFunction(f)),
1131 }
1132 }
1133}
1134
1135#[cfg(test)]
1136mod tests {
1137 use super::*;
1138 use crate::dialects::Dialect;
1139
1140 fn transpile_to_mysql(sql: &str) -> String {
1141 let dialect = Dialect::get(DialectType::Generic);
1142 let result = dialect
1143 .transpile_to(sql, DialectType::MySQL)
1144 .expect("Transpile failed");
1145 result[0].clone()
1146 }
1147
1148 #[test]
1149 fn test_nvl_to_ifnull() {
1150 let result = transpile_to_mysql("SELECT NVL(a, b)");
1151 assert!(
1152 result.contains("IFNULL"),
1153 "Expected IFNULL, got: {}",
1154 result
1155 );
1156 }
1157
1158 #[test]
1159 fn test_coalesce_preserved() {
1160 let result = transpile_to_mysql("SELECT COALESCE(a, b)");
1162 assert!(
1163 result.contains("COALESCE"),
1164 "Expected COALESCE to be preserved, got: {}",
1165 result
1166 );
1167 }
1168
1169 #[test]
1170 fn test_random_to_rand() {
1171 let result = transpile_to_mysql("SELECT RANDOM()");
1172 assert!(result.contains("RAND"), "Expected RAND, got: {}", result);
1173 }
1174
1175 #[test]
1176 fn test_basic_select() {
1177 let result = transpile_to_mysql("SELECT a, b FROM users WHERE id = 1");
1178 assert!(result.contains("SELECT"));
1179 assert!(result.contains("FROM users"));
1180 }
1181
1182 #[test]
1183 fn test_string_agg_to_group_concat() {
1184 let result = transpile_to_mysql("SELECT STRING_AGG(name)");
1185 assert!(
1186 result.contains("GROUP_CONCAT"),
1187 "Expected GROUP_CONCAT, got: {}",
1188 result
1189 );
1190 }
1191
1192 #[test]
1193 fn test_array_agg_to_group_concat() {
1194 let result = transpile_to_mysql("SELECT ARRAY_AGG(name)");
1195 assert!(
1196 result.contains("GROUP_CONCAT"),
1197 "Expected GROUP_CONCAT, got: {}",
1198 result
1199 );
1200 }
1201
1202 #[test]
1203 fn test_to_date_to_str_to_date() {
1204 let result = transpile_to_mysql("SELECT TO_DATE('2023-01-01')");
1205 assert!(
1206 result.contains("STR_TO_DATE"),
1207 "Expected STR_TO_DATE, got: {}",
1208 result
1209 );
1210 }
1211
1212 #[test]
1213 fn test_backtick_identifiers() {
1214 let dialect = MySQLDialect;
1216 let config = dialect.generator_config();
1217 assert_eq!(config.identifier_quote, '`');
1218 }
1219
1220 fn mysql_identity(sql: &str, expected: &str) {
1221 let dialect = Dialect::get(DialectType::MySQL);
1222 let ast = dialect.parse(sql).expect("Parse failed");
1223 let transformed = dialect.transform(ast[0].clone()).expect("Transform failed");
1224 let result = dialect.generate(&transformed).expect("Generate failed");
1225 assert_eq!(result, expected, "SQL: {}", sql);
1226 }
1227
1228 #[test]
1229 fn test_ucase_to_upper() {
1230 mysql_identity("SELECT UCASE('foo')", "SELECT UPPER('foo')");
1231 }
1232
1233 #[test]
1234 fn test_lcase_to_lower() {
1235 mysql_identity("SELECT LCASE('foo')", "SELECT LOWER('foo')");
1236 }
1237
1238 #[test]
1239 fn test_day_of_month() {
1240 mysql_identity(
1241 "SELECT DAY_OF_MONTH('2023-01-01')",
1242 "SELECT DAYOFMONTH('2023-01-01')",
1243 );
1244 }
1245
1246 #[test]
1247 fn test_day_of_week() {
1248 mysql_identity(
1249 "SELECT DAY_OF_WEEK('2023-01-01')",
1250 "SELECT DAYOFWEEK('2023-01-01')",
1251 );
1252 }
1253
1254 #[test]
1255 fn test_day_of_year() {
1256 mysql_identity(
1257 "SELECT DAY_OF_YEAR('2023-01-01')",
1258 "SELECT DAYOFYEAR('2023-01-01')",
1259 );
1260 }
1261
1262 #[test]
1263 fn test_week_of_year() {
1264 mysql_identity(
1265 "SELECT WEEK_OF_YEAR('2023-01-01')",
1266 "SELECT WEEKOFYEAR('2023-01-01')",
1267 );
1268 }
1269
1270 #[test]
1271 fn test_mod_func_to_percent() {
1272 mysql_identity("MOD(x, y)", "x % y");
1274 }
1275
1276 #[test]
1277 fn test_database_to_schema() {
1278 mysql_identity("DATABASE()", "SCHEMA()");
1279 }
1280
1281 #[test]
1282 fn test_and_operator() {
1283 mysql_identity("SELECT 1 && 0", "SELECT 1 AND 0");
1284 }
1285
1286 #[test]
1287 fn test_or_operator() {
1288 mysql_identity("SELECT a || b", "SELECT a OR b");
1289 }
1290}