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 { unreachable!() };
386 let s = Self::convert_bracket_to_quoted_path(&s);
388 let normalized = if s.starts_with('$') {
389 s
390 } else if s.starts_with('[') {
391 format!("${}", s)
392 } else {
393 format!("$.{}", s)
394 };
395 Expression::Literal(Box::new(Literal::String(normalized)))
396 }
397 other => other,
398 };
399 Ok(Expression::Function(Box::new(Function::new(
400 "JSON_EXTRACT".to_string(),
401 vec![*e.this, path],
402 ))))
403 }
404
405 Expression::Function(f) => self.transform_function(*f),
407
408 Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
410
411 Expression::Eq(op) => Ok(Expression::Eq(Box::new(BinaryOp {
418 left: wrap_if_json_arrow(op.left),
419 right: wrap_if_json_arrow(op.right),
420 ..*op
421 }))),
422 Expression::Neq(op) => Ok(Expression::Neq(Box::new(BinaryOp {
423 left: wrap_if_json_arrow(op.left),
424 right: wrap_if_json_arrow(op.right),
425 ..*op
426 }))),
427 Expression::Lt(op) => Ok(Expression::Lt(Box::new(BinaryOp {
428 left: wrap_if_json_arrow(op.left),
429 right: wrap_if_json_arrow(op.right),
430 ..*op
431 }))),
432 Expression::Lte(op) => Ok(Expression::Lte(Box::new(BinaryOp {
433 left: wrap_if_json_arrow(op.left),
434 right: wrap_if_json_arrow(op.right),
435 ..*op
436 }))),
437 Expression::Gt(op) => Ok(Expression::Gt(Box::new(BinaryOp {
438 left: wrap_if_json_arrow(op.left),
439 right: wrap_if_json_arrow(op.right),
440 ..*op
441 }))),
442 Expression::Gte(op) => Ok(Expression::Gte(Box::new(BinaryOp {
443 left: wrap_if_json_arrow(op.left),
444 right: wrap_if_json_arrow(op.right),
445 ..*op
446 }))),
447
448 Expression::In(mut i) => {
450 i.this = wrap_if_json_arrow(i.this);
451 Ok(Expression::In(i))
452 }
453
454 Expression::Not(mut n) => {
456 n.this = wrap_if_json_arrow(n.this);
457 Ok(Expression::Not(n))
458 }
459
460 Expression::ArrayOverlaps(op) => Ok(Expression::And(op)),
463
464 Expression::ModFunc(f) => Ok(Expression::Mod(Box::new(BinaryOp {
466 left: f.this,
467 right: f.expression,
468 left_comments: Vec::new(),
469 operator_comments: Vec::new(),
470 trailing_comments: Vec::new(),
471 inferred_type: None,
472 }))),
473
474 Expression::Show(mut s) => {
476 if s.this == "SLAVE STATUS" {
477 s.this = "REPLICA STATUS".to_string();
478 }
479 if matches!(s.this.as_str(), "INDEX" | "COLUMNS") && s.db.is_none() {
480 if let Some(Expression::Table(mut t)) = s.target.take() {
481 if let Some(db_ident) = t.schema.take().or(t.catalog.take()) {
482 s.db = Some(Expression::Identifier(db_ident));
483 s.target = Some(Expression::Identifier(t.name));
484 } else {
485 s.target = Some(Expression::Table(t));
486 }
487 }
488 }
489 Ok(Expression::Show(s))
490 }
491
492 Expression::AtTimeZone(atz) => {
495 let is_current = match &atz.this {
496 Expression::CurrentDate(_) | Expression::CurrentTimestamp(_) => true,
497 Expression::Function(f) => {
498 let n = f.name.to_uppercase();
499 (n == "CURRENT_DATE" || n == "CURRENT_TIMESTAMP") && f.no_parens
500 }
501 _ => false,
502 };
503 if is_current {
504 Ok(Expression::AtTimeZone(atz)) } else {
506 Ok(atz.this) }
508 }
509
510 Expression::MemberOf(mut op) => {
513 op.right = json_arrow_to_function(op.right);
514 Ok(Expression::MemberOf(op))
515 }
516
517 _ => Ok(expr),
519 }
520 }
521}
522
523impl MySQLDialect {
524 fn normalize_mysql_date_format(fmt: &str) -> String {
525 fmt.replace("%H:%i:%s", "%T").replace("%H:%i:%S", "%T")
526 }
527
528 fn convert_bracket_to_quoted_path(path: &str) -> String {
531 let mut result = String::new();
532 let mut chars = path.chars().peekable();
533 while let Some(c) = chars.next() {
534 if c == '[' && chars.peek() == Some(&'"') {
535 chars.next(); let mut key = String::new();
537 while let Some(kc) = chars.next() {
538 if kc == '"' && chars.peek() == Some(&']') {
539 chars.next(); break;
541 }
542 key.push(kc);
543 }
544 if !result.is_empty() && !result.ends_with('.') {
545 result.push('.');
546 }
547 result.push('"');
548 result.push_str(&key);
549 result.push('"');
550 } else {
551 result.push(c);
552 }
553 }
554 result
555 }
556
557 fn transform_data_type(&self, dt: crate::expressions::DataType) -> Result<Expression> {
561 use crate::expressions::DataType;
562 let transformed = match dt {
563 DataType::Timestamp {
565 precision,
566 timezone: _,
567 } => DataType::Timestamp {
568 precision,
569 timezone: false,
570 },
571 DataType::Custom { name }
573 if name.to_uppercase() == "TIMESTAMPTZ"
574 || name.to_uppercase() == "TIMESTAMPLTZ" =>
575 {
576 DataType::Timestamp {
577 precision: None,
578 timezone: false,
579 }
580 }
581 other => other,
584 };
585 Ok(Expression::DataType(transformed))
586 }
587
588 fn transform_cast(&self, cast: Cast) -> Result<Expression> {
592 match &cast.to {
594 DataType::Timestamp { .. } => Ok(Expression::Function(Box::new(Function::new(
595 "TIMESTAMP".to_string(),
596 vec![cast.this],
597 )))),
598 DataType::Custom { name }
599 if name.to_uppercase() == "TIMESTAMPTZ"
600 || name.to_uppercase() == "TIMESTAMPLTZ" =>
601 {
602 Ok(Expression::Function(Box::new(Function::new(
603 "TIMESTAMP".to_string(),
604 vec![cast.this],
605 ))))
606 }
607 _ => Ok(Expression::Cast(Box::new(self.transform_cast_type(cast)))),
609 }
610 }
611
612 fn transform_cast_type(&self, cast: Cast) -> Cast {
616 let new_type = match &cast.to {
617 DataType::VarChar { length, .. } => DataType::Char { length: *length },
619 DataType::Text => DataType::Char { length: None },
620
621 DataType::BigInt { .. } => DataType::Custom {
623 name: "SIGNED".to_string(),
624 },
625 DataType::Int { .. } => DataType::Custom {
626 name: "SIGNED".to_string(),
627 },
628 DataType::SmallInt { .. } => DataType::Custom {
629 name: "SIGNED".to_string(),
630 },
631 DataType::TinyInt { .. } => DataType::Custom {
632 name: "SIGNED".to_string(),
633 },
634 DataType::Boolean => DataType::Custom {
635 name: "SIGNED".to_string(),
636 },
637
638 DataType::Custom { name } => {
640 let upper = name.to_uppercase();
641 match upper.as_str() {
642 "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
645 | "TINYBLOB" => DataType::Custom { name: upper },
646 "MEDIUMINT" => DataType::Custom {
648 name: "SIGNED".to_string(),
649 },
650 "UBIGINT" | "UINT" | "USMALLINT" | "UTINYINT" | "UMEDIUMINT" => {
652 DataType::Custom {
653 name: "UNSIGNED".to_string(),
654 }
655 }
656 _ => cast.to.clone(),
658 }
659 }
660
661 DataType::Binary { .. } => cast.to.clone(),
663 DataType::VarBinary { .. } => cast.to.clone(),
664 DataType::Date => cast.to.clone(),
665 DataType::Time { .. } => cast.to.clone(),
666 DataType::Decimal { .. } => cast.to.clone(),
667 DataType::Json => cast.to.clone(),
668 DataType::Float { .. } => cast.to.clone(),
669 DataType::Double { .. } => cast.to.clone(),
670 DataType::Char { .. } => cast.to.clone(),
671 DataType::CharacterSet { .. } => cast.to.clone(),
672 DataType::Enum { .. } => cast.to.clone(),
673 DataType::Set { .. } => cast.to.clone(),
674 DataType::Timestamp { .. } => cast.to.clone(),
675
676 _ => DataType::Char { length: None },
678 };
679
680 Cast {
681 this: cast.this,
682 to: new_type,
683 trailing_comments: cast.trailing_comments,
684 double_colon_syntax: cast.double_colon_syntax,
685 format: cast.format,
686 default: cast.default,
687 inferred_type: None,
688 }
689 }
690
691 fn transform_function(&self, f: Function) -> Result<Expression> {
692 let name_upper = f.name.to_uppercase();
693 match name_upper.as_str() {
694 "DATE_FORMAT" if f.args.len() >= 2 => {
696 let mut f = f;
697 if let Some(Expression::Literal(lit)) = f.args.get(1) {
698 if let Literal::String(fmt) = lit.as_ref() {
699 let normalized = Self::normalize_mysql_date_format(fmt);
700 if normalized != *fmt {
701 f.args[1] = Expression::Literal(Box::new(Literal::String(normalized)));
702 }
703 }
704 }
705 Ok(Expression::Function(Box::new(f)))
706 }
707
708 "NVL" if f.args.len() == 2 => {
710 let mut args = f.args;
711 let second = args.pop().unwrap();
712 let first = args.pop().unwrap();
713 Ok(Expression::IfNull(Box::new(BinaryFunc {
714 original_name: None,
715 this: first,
716 expression: second,
717 inferred_type: None,
718 })))
719 }
720
721 "ARRAY_AGG" if f.args.len() == 1 => {
726 let mut args = f.args;
727 Ok(Expression::Function(Box::new(Function::new(
728 "GROUP_CONCAT".to_string(),
729 vec![args.pop().unwrap()],
730 ))))
731 }
732
733 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
735 Function::new("GROUP_CONCAT".to_string(), f.args),
736 ))),
737
738 "RANDOM" => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
740 seed: None,
741 lower: None,
742 upper: None,
743 }))),
744
745 "CURRENT_TIMESTAMP" => {
748 let precision =
749 if let Some(Expression::Literal(lit)) =
750 f.args.first()
751 {
752 if let crate::expressions::Literal::Number(n) = lit.as_ref() {
753 n.parse::<u32>().ok()
754 } else { None }
755 } else {
756 None
757 };
758 Ok(Expression::CurrentTimestamp(
759 crate::expressions::CurrentTimestamp {
760 precision,
761 sysdate: false,
762 },
763 ))
764 }
765
766 "POSITION" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
769 "LOCATE".to_string(),
770 f.args,
771 )))),
772
773 "LENGTH" => Ok(Expression::Function(Box::new(f))),
776
777 "CEIL" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
779 "CEILING".to_string(),
780 f.args,
781 )))),
782
783 "STDDEV" => Ok(Expression::Function(Box::new(Function::new(
785 "STD".to_string(),
786 f.args,
787 )))),
788
789 "STDDEV_SAMP" => Ok(Expression::Function(Box::new(Function::new(
791 "STDDEV".to_string(),
792 f.args,
793 )))),
794
795 "TO_DATE" => Ok(Expression::Function(Box::new(Function::new(
797 "STR_TO_DATE".to_string(),
798 f.args,
799 )))),
800
801 "TO_TIMESTAMP" => Ok(Expression::Function(Box::new(Function::new(
803 "STR_TO_DATE".to_string(),
804 f.args,
805 )))),
806
807 "DATE_TRUNC" if f.args.len() >= 2 => {
810 let mut args = f.args;
813 let _unit = args.remove(0);
814 let date = args.remove(0);
815 Ok(Expression::Function(Box::new(Function::new(
816 "DATE".to_string(),
817 vec![date],
818 ))))
819 }
820
821 "COALESCE" if f.args.len() > 2 => Ok(Expression::Function(Box::new(f))),
825
826 "DAY" => Ok(Expression::Function(Box::new(Function::new(
828 "DAYOFMONTH".to_string(),
829 f.args,
830 )))),
831
832 "DAYOFWEEK" => Ok(Expression::Function(Box::new(f))),
834
835 "DAYOFYEAR" => Ok(Expression::Function(Box::new(f))),
837
838 "WEEKOFYEAR" => Ok(Expression::Function(Box::new(f))),
840
841 "LAST_DAY" => Ok(Expression::Function(Box::new(f))),
843
844 "TIMESTAMPADD" => Ok(Expression::Function(Box::new(Function::new(
846 "DATE_ADD".to_string(),
847 f.args,
848 )))),
849
850 "TIMESTAMPDIFF" => Ok(Expression::Function(Box::new(f))),
852
853 "CONVERT_TIMEZONE" if f.args.len() == 3 => {
855 let mut args = f.args;
856 let from_tz = args.remove(0);
857 let to_tz = args.remove(0);
858 let timestamp = args.remove(0);
859 Ok(Expression::Function(Box::new(Function::new(
860 "CONVERT_TZ".to_string(),
861 vec![timestamp, from_tz, to_tz],
862 ))))
863 }
864
865 "UTC_TIMESTAMP" => Ok(Expression::Function(Box::new(f))),
867
868 "UTC_TIME" => Ok(Expression::Function(Box::new(f))),
870
871 "MAKETIME" => Ok(Expression::Function(Box::new(f))),
873
874 "TIME_FROM_PARTS" if f.args.len() == 3 => Ok(Expression::Function(Box::new(
876 Function::new("MAKETIME".to_string(), f.args),
877 ))),
878
879 "STUFF" if f.args.len() == 4 => Ok(Expression::Function(Box::new(Function::new(
881 "INSERT".to_string(),
882 f.args,
883 )))),
884
885 "LOCATE" => Ok(Expression::Function(Box::new(f))),
887
888 "FIND_IN_SET" => Ok(Expression::Function(Box::new(f))),
890
891 "FORMAT" => Ok(Expression::Function(Box::new(f))),
893
894 "JSON_EXTRACT" => Ok(Expression::Function(Box::new(f))),
896
897 "JSON_UNQUOTE" => Ok(Expression::Function(Box::new(f))),
899
900 "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
902 let extract = Expression::Function(Box::new(Function::new(
903 "JSON_EXTRACT".to_string(),
904 f.args,
905 )));
906 Ok(Expression::Function(Box::new(Function::new(
907 "JSON_UNQUOTE".to_string(),
908 vec![extract],
909 ))))
910 }
911
912 "GEN_RANDOM_UUID" | "GENERATE_UUID" => Ok(Expression::Function(Box::new(
914 Function::new("UUID".to_string(), vec![]),
915 ))),
916
917 "DATABASE" => Ok(Expression::Function(Box::new(Function::new(
919 "SCHEMA".to_string(),
920 f.args,
921 )))),
922
923 "INSTR" if f.args.len() == 2 => {
926 let mut args = f.args;
927 let str_arg = args.remove(0);
928 let substr_arg = args.remove(0);
929 Ok(Expression::Function(Box::new(Function::new(
930 "LOCATE".to_string(),
931 vec![substr_arg, str_arg],
932 ))))
933 }
934
935 "TIME_STR_TO_UNIX" => Ok(Expression::Function(Box::new(Function::new(
937 "UNIX_TIMESTAMP".to_string(),
938 f.args,
939 )))),
940
941 "TIME_STR_TO_TIME" if f.args.len() >= 1 => {
943 let mut args = f.args.into_iter();
944 let arg = args.next().unwrap();
945
946 if args.next().is_some() {
948 return Ok(Expression::Function(Box::new(Function::new(
949 "TIMESTAMP".to_string(),
950 vec![arg],
951 ))));
952 }
953
954 let precision =
956 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 { None }
978 } else {
979 None
980 };
981
982 let type_name = match precision {
983 Some(p) => format!("DATETIME({})", p),
984 None => "DATETIME".to_string(),
985 };
986
987 Ok(Expression::Cast(Box::new(Cast {
988 this: arg,
989 to: DataType::Custom { name: type_name },
990 trailing_comments: Vec::new(),
991 double_colon_syntax: false,
992 format: None,
993 default: None,
994 inferred_type: None,
995 })))
996 }
997
998 "UCASE" => Ok(Expression::Function(Box::new(Function::new(
1000 "UPPER".to_string(),
1001 f.args,
1002 )))),
1003
1004 "LCASE" => Ok(Expression::Function(Box::new(Function::new(
1006 "LOWER".to_string(),
1007 f.args,
1008 )))),
1009
1010 "DAY_OF_MONTH" => Ok(Expression::Function(Box::new(Function::new(
1012 "DAYOFMONTH".to_string(),
1013 f.args,
1014 )))),
1015
1016 "DAY_OF_WEEK" => Ok(Expression::Function(Box::new(Function::new(
1018 "DAYOFWEEK".to_string(),
1019 f.args,
1020 )))),
1021
1022 "DAY_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
1024 "DAYOFYEAR".to_string(),
1025 f.args,
1026 )))),
1027
1028 "WEEK_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
1030 "WEEKOFYEAR".to_string(),
1031 f.args,
1032 )))),
1033
1034 "MOD" if f.args.len() == 2 => {
1036 let mut args = f.args;
1037 let left = args.remove(0);
1038 let right = args.remove(0);
1039 Ok(Expression::Mod(Box::new(BinaryOp {
1040 left,
1041 right,
1042 left_comments: Vec::new(),
1043 operator_comments: Vec::new(),
1044 trailing_comments: Vec::new(),
1045 inferred_type: None,
1046 })))
1047 }
1048
1049 "PARSE_JSON" if f.args.len() == 1 => Ok(f.args.into_iter().next().unwrap()),
1051
1052 "GET_PATH" if f.args.len() == 2 => {
1054 let mut args = f.args;
1055 let this = args.remove(0);
1056 let path = args.remove(0);
1057 let json_path = match &path {
1058 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
1059 let Literal::String(s) = lit.as_ref() else { unreachable!() };
1060 let s = Self::convert_bracket_to_quoted_path(s);
1062 let normalized = if s.starts_with('$') {
1063 s
1064 } else if s.starts_with('[') {
1065 format!("${}", s)
1066 } else {
1067 format!("$.{}", s)
1068 };
1069 Expression::Literal(Box::new(Literal::String(normalized)))
1070 }
1071 _ => path,
1072 };
1073 Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
1074 this,
1075 path: json_path,
1076 returning: None,
1077 arrow_syntax: false,
1078 hash_arrow_syntax: false,
1079 wrapper_option: None,
1080 quotes_option: None,
1081 on_scalar_string: false,
1082 on_error: None,
1083 })))
1084 }
1085
1086 "REGEXP" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
1088 "REGEXP_LIKE".to_string(),
1089 f.args,
1090 )))),
1091
1092 "CURTIME" => Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
1094 precision: None,
1095 })),
1096
1097 "TRUNC" => Ok(Expression::Function(Box::new(Function::new(
1099 "TRUNCATE".to_string(),
1100 f.args,
1101 )))),
1102
1103 _ => Ok(Expression::Function(Box::new(f))),
1105 }
1106 }
1107
1108 fn transform_aggregate_function(
1109 &self,
1110 f: Box<crate::expressions::AggregateFunction>,
1111 ) -> Result<Expression> {
1112 let name_upper = f.name.to_uppercase();
1113 match name_upper.as_str() {
1114 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1116 Function::new("GROUP_CONCAT".to_string(), f.args),
1117 ))),
1118
1119 "ARRAY_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
1121 "GROUP_CONCAT".to_string(),
1122 f.args,
1123 )))),
1124
1125 _ => Ok(Expression::AggregateFunction(f)),
1127 }
1128 }
1129}
1130
1131#[cfg(test)]
1132mod tests {
1133 use super::*;
1134 use crate::dialects::Dialect;
1135
1136 fn transpile_to_mysql(sql: &str) -> String {
1137 let dialect = Dialect::get(DialectType::Generic);
1138 let result = dialect
1139 .transpile_to(sql, DialectType::MySQL)
1140 .expect("Transpile failed");
1141 result[0].clone()
1142 }
1143
1144 #[test]
1145 fn test_nvl_to_ifnull() {
1146 let result = transpile_to_mysql("SELECT NVL(a, b)");
1147 assert!(
1148 result.contains("IFNULL"),
1149 "Expected IFNULL, got: {}",
1150 result
1151 );
1152 }
1153
1154 #[test]
1155 fn test_coalesce_preserved() {
1156 let result = transpile_to_mysql("SELECT COALESCE(a, b)");
1158 assert!(
1159 result.contains("COALESCE"),
1160 "Expected COALESCE to be preserved, got: {}",
1161 result
1162 );
1163 }
1164
1165 #[test]
1166 fn test_random_to_rand() {
1167 let result = transpile_to_mysql("SELECT RANDOM()");
1168 assert!(result.contains("RAND"), "Expected RAND, got: {}", result);
1169 }
1170
1171 #[test]
1172 fn test_basic_select() {
1173 let result = transpile_to_mysql("SELECT a, b FROM users WHERE id = 1");
1174 assert!(result.contains("SELECT"));
1175 assert!(result.contains("FROM users"));
1176 }
1177
1178 #[test]
1179 fn test_string_agg_to_group_concat() {
1180 let result = transpile_to_mysql("SELECT STRING_AGG(name)");
1181 assert!(
1182 result.contains("GROUP_CONCAT"),
1183 "Expected GROUP_CONCAT, got: {}",
1184 result
1185 );
1186 }
1187
1188 #[test]
1189 fn test_array_agg_to_group_concat() {
1190 let result = transpile_to_mysql("SELECT ARRAY_AGG(name)");
1191 assert!(
1192 result.contains("GROUP_CONCAT"),
1193 "Expected GROUP_CONCAT, got: {}",
1194 result
1195 );
1196 }
1197
1198 #[test]
1199 fn test_to_date_to_str_to_date() {
1200 let result = transpile_to_mysql("SELECT TO_DATE('2023-01-01')");
1201 assert!(
1202 result.contains("STR_TO_DATE"),
1203 "Expected STR_TO_DATE, got: {}",
1204 result
1205 );
1206 }
1207
1208 #[test]
1209 fn test_backtick_identifiers() {
1210 let dialect = MySQLDialect;
1212 let config = dialect.generator_config();
1213 assert_eq!(config.identifier_quote, '`');
1214 }
1215
1216 fn mysql_identity(sql: &str, expected: &str) {
1217 let dialect = Dialect::get(DialectType::MySQL);
1218 let ast = dialect.parse(sql).expect("Parse failed");
1219 let transformed = dialect.transform(ast[0].clone()).expect("Transform failed");
1220 let result = dialect.generate(&transformed).expect("Generate failed");
1221 assert_eq!(result, expected, "SQL: {}", sql);
1222 }
1223
1224 #[test]
1225 fn test_ucase_to_upper() {
1226 mysql_identity("SELECT UCASE('foo')", "SELECT UPPER('foo')");
1227 }
1228
1229 #[test]
1230 fn test_lcase_to_lower() {
1231 mysql_identity("SELECT LCASE('foo')", "SELECT LOWER('foo')");
1232 }
1233
1234 #[test]
1235 fn test_day_of_month() {
1236 mysql_identity(
1237 "SELECT DAY_OF_MONTH('2023-01-01')",
1238 "SELECT DAYOFMONTH('2023-01-01')",
1239 );
1240 }
1241
1242 #[test]
1243 fn test_day_of_week() {
1244 mysql_identity(
1245 "SELECT DAY_OF_WEEK('2023-01-01')",
1246 "SELECT DAYOFWEEK('2023-01-01')",
1247 );
1248 }
1249
1250 #[test]
1251 fn test_day_of_year() {
1252 mysql_identity(
1253 "SELECT DAY_OF_YEAR('2023-01-01')",
1254 "SELECT DAYOFYEAR('2023-01-01')",
1255 );
1256 }
1257
1258 #[test]
1259 fn test_week_of_year() {
1260 mysql_identity(
1261 "SELECT WEEK_OF_YEAR('2023-01-01')",
1262 "SELECT WEEKOFYEAR('2023-01-01')",
1263 );
1264 }
1265
1266 #[test]
1267 fn test_mod_func_to_percent() {
1268 mysql_identity("MOD(x, y)", "x % y");
1270 }
1271
1272 #[test]
1273 fn test_database_to_schema() {
1274 mysql_identity("DATABASE()", "SCHEMA()");
1275 }
1276
1277 #[test]
1278 fn test_and_operator() {
1279 mysql_identity("SELECT 1 && 0", "SELECT 1 AND 0");
1280 }
1281
1282 #[test]
1283 fn test_or_operator() {
1284 mysql_identity("SELECT a || b", "SELECT a OR b");
1285 }
1286}