1use super::{DialectImpl, DialectType};
11use crate::error::Result;
12use crate::expressions::{BinaryFunc, BinaryOp, Cast, DataType, Expression, Function, JsonExtractFunc, LikeOp, Literal, Paren, UnaryFunc};
13use crate::generator::GeneratorConfig;
14use crate::tokens::TokenizerConfig;
15
16fn wrap_if_json_arrow(expr: Expression) -> Expression {
20 match &expr {
21 Expression::JsonExtract(f) if f.arrow_syntax => {
22 Expression::Paren(Box::new(Paren {
23 this: expr,
24 trailing_comments: Vec::new(),
25 }))
26 }
27 Expression::JsonExtractScalar(f) if f.arrow_syntax => {
28 Expression::Paren(Box::new(Paren {
29 this: expr,
30 trailing_comments: Vec::new(),
31 }))
32 }
33 _ => expr,
34 }
35}
36
37fn json_arrow_to_function(expr: Expression) -> Expression {
40 match expr {
41 Expression::JsonExtract(f) if f.arrow_syntax => {
42 Expression::Function(Box::new(Function::new(
43 "JSON_EXTRACT".to_string(),
44 vec![f.this, f.path],
45 )))
46 }
47 Expression::JsonExtractScalar(f) if f.arrow_syntax => {
48 let json_extract = Expression::Function(Box::new(Function::new(
51 "JSON_EXTRACT".to_string(),
52 vec![f.this, f.path],
53 )));
54 Expression::Function(Box::new(Function::new(
55 "JSON_UNQUOTE".to_string(),
56 vec![json_extract],
57 )))
58 }
59 other => other,
60 }
61}
62
63pub struct MySQLDialect;
65
66impl DialectImpl for MySQLDialect {
67 fn dialect_type(&self) -> DialectType {
68 DialectType::MySQL
69 }
70
71 fn tokenizer_config(&self) -> TokenizerConfig {
72 use crate::tokens::TokenType;
73 let mut config = TokenizerConfig::default();
74 config.identifiers.insert('`', '`');
76 config.identifiers.remove(&'"');
79 config.quotes.insert("\"".to_string(), "\"".to_string());
81 config.string_escapes.push('\\');
83 config.keywords.insert("XOR".to_string(), TokenType::Xor);
85 config.escape_follow_chars = vec!['0', 'b', 'n', 'r', 't', 'Z', '%', '_'];
88 config.identifiers_can_start_with_digit = true;
90 config
91 }
92
93 fn generator_config(&self) -> GeneratorConfig {
94 use crate::generator::IdentifierQuoteStyle;
95 GeneratorConfig {
96 identifier_quote: '`',
97 identifier_quote_style: IdentifierQuoteStyle::BACKTICK,
98 dialect: Some(DialectType::MySQL),
99 null_ordering_supported: false,
101 limit_only_literals: true,
103 semi_anti_join_with_side: false,
105 supports_table_alias_columns: false,
107 values_as_table: false,
109 tablesample_requires_parens: false,
111 tablesample_with_method: false,
112 aggregate_filter_supported: false,
114 try_supported: false,
116 supports_convert_timezone: false,
118 supports_uescape: false,
120 supports_between_flags: false,
122 query_hints: false,
124 parameter_token: "?",
126 supports_window_exclude: false,
128 supports_exploding_projections: false,
130 identifiers_can_start_with_digit: true,
131 locking_reads_supported: true,
133 ..Default::default()
134 }
135 }
136
137 fn transform_expr(&self, expr: Expression) -> Result<Expression> {
138 match expr {
139 Expression::DataType(dt) => self.transform_data_type(dt),
141
142 Expression::Nvl(f) => Ok(Expression::IfNull(f)),
144
145 Expression::TryCast(c) => self.transform_cast(*c),
151
152 Expression::SafeCast(c) => self.transform_cast(*c),
154
155 Expression::Cast(c) => self.transform_cast(*c),
158
159 Expression::ILike(op) => {
161 let lower_left = Expression::Lower(Box::new(UnaryFunc::new(op.left)));
163 let lower_right = Expression::Lower(Box::new(UnaryFunc::new(op.right)));
164 Ok(Expression::Like(Box::new(LikeOp {
165 left: lower_left,
166 right: lower_right,
167 escape: op.escape,
168 quantifier: op.quantifier,
169 })))
170 }
171
172 Expression::Concat(op) => {
176 Ok(Expression::Or(op))
177 }
178
179 Expression::Random(_) => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
181 seed: None, lower: None, upper: None,
182 }))),
183
184 Expression::ArrayAgg(f) => Ok(Expression::Function(Box::new(Function::new(
186 "GROUP_CONCAT".to_string(),
187 vec![f.this],
188 )))),
189
190 Expression::StringAgg(f) => {
192 let mut args = vec![f.this.clone()];
193 if let Some(separator) = &f.separator {
194 args.push(separator.clone());
195 }
196 Ok(Expression::Function(Box::new(Function::new(
197 "GROUP_CONCAT".to_string(),
198 args,
199 ))))
200 }
201
202 Expression::Unnest(f) => {
205 Ok(Expression::Function(Box::new(Function::new(
208 "JSON_TABLE".to_string(),
209 vec![f.this],
210 ))))
211 }
212
213 Expression::Substring(mut f) => {
215 f.from_for_syntax = false;
216 Ok(Expression::Substring(f))
217 }
218
219 Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(Function::new(
222 "BIT_AND".to_string(),
223 vec![f.this],
224 )))),
225
226 Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(Function::new(
228 "BIT_OR".to_string(),
229 vec![f.this],
230 )))),
231
232 Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(Function::new(
234 "BIT_XOR".to_string(),
235 vec![f.this],
236 )))),
237
238 Expression::BitwiseCount(f) => Ok(Expression::Function(Box::new(Function::new(
240 "BIT_COUNT".to_string(),
241 vec![f.this],
242 )))),
243
244 Expression::TimeFromParts(f) => {
246 let mut args = Vec::new();
247 if let Some(h) = f.hour {
248 args.push(*h);
249 }
250 if let Some(m) = f.min {
251 args.push(*m);
252 }
253 if let Some(s) = f.sec {
254 args.push(*s);
255 }
256 Ok(Expression::Function(Box::new(Function::new(
257 "MAKETIME".to_string(),
258 args,
259 ))))
260 }
261
262 Expression::LogicalAnd(f) => Ok(Expression::Function(Box::new(Function::new(
266 "MIN".to_string(),
267 vec![f.this],
268 )))),
269
270 Expression::LogicalOr(f) => Ok(Expression::Function(Box::new(Function::new(
272 "MAX".to_string(),
273 vec![f.this],
274 )))),
275
276 Expression::DayOfMonth(f) => Ok(Expression::Function(Box::new(Function::new(
279 "DAYOFMONTH".to_string(),
280 vec![f.this],
281 )))),
282
283 Expression::DayOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
285 "DAYOFWEEK".to_string(),
286 vec![f.this],
287 )))),
288
289 Expression::DayOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
291 "DAYOFYEAR".to_string(),
292 vec![f.this],
293 )))),
294
295 Expression::WeekOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
297 "WEEKOFYEAR".to_string(),
298 vec![f.this],
299 )))),
300
301 Expression::DateDiff(f) => Ok(Expression::Function(Box::new(Function::new(
303 "DATEDIFF".to_string(),
304 vec![f.this, f.expression],
305 )))),
306
307 Expression::TimeStrToUnix(f) => Ok(Expression::Function(Box::new(Function::new(
309 "UNIX_TIMESTAMP".to_string(),
310 vec![f.this],
311 )))),
312
313 Expression::TimestampDiff(f) => Ok(Expression::Function(Box::new(Function::new(
315 "TIMESTAMPDIFF".to_string(),
316 vec![*f.this, *f.expression],
317 )))),
318
319 Expression::StrPosition(f) => {
323 let mut args = vec![];
324 if let Some(substr) = f.substr {
325 args.push(*substr);
326 }
327 args.push(*f.this);
328 if let Some(pos) = f.position {
329 args.push(*pos);
330 }
331 Ok(Expression::Function(Box::new(Function::new(
332 "LOCATE".to_string(),
333 args,
334 ))))
335 }
336
337 Expression::Stuff(f) => {
339 let mut args = vec![*f.this];
340 if let Some(start) = f.start {
341 args.push(*start);
342 }
343 if let Some(length) = f.length {
344 args.push(Expression::number(length));
345 }
346 args.push(*f.expression);
347 Ok(Expression::Function(Box::new(Function::new(
348 "INSERT".to_string(),
349 args,
350 ))))
351 }
352
353 Expression::SessionUser(_) => Ok(Expression::Function(Box::new(Function::new(
356 "SESSION_USER".to_string(),
357 vec![],
358 )))),
359
360 Expression::CurrentDate(_) => Ok(Expression::CurrentDate(crate::expressions::CurrentDate)),
362
363 Expression::NullSafeNeq(op) => {
366 let null_safe_eq = Expression::NullSafeEq(Box::new(crate::expressions::BinaryOp {
368 left: op.left,
369 right: op.right,
370 left_comments: Vec::new(),
371 operator_comments: Vec::new(),
372 trailing_comments: Vec::new(),
373 }));
374 Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
375 this: null_safe_eq,
376 })))
377 }
378
379 Expression::JSONExtract(e) if e.variant_extract.is_some() => {
383 let path = match *e.expression {
384 Expression::Literal(Literal::String(s)) => {
385 let s = Self::convert_bracket_to_quoted_path(&s);
387 let normalized = if s.starts_with('$') {
388 s
389 } else if s.starts_with('[') {
390 format!("${}", s)
391 } else {
392 format!("$.{}", s)
393 };
394 Expression::Literal(Literal::String(normalized))
395 }
396 other => other,
397 };
398 Ok(Expression::Function(Box::new(Function::new(
399 "JSON_EXTRACT".to_string(),
400 vec![*e.this, path],
401 ))))
402 }
403
404 Expression::Function(f) => self.transform_function(*f),
406
407 Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
409
410 Expression::Eq(op) => Ok(Expression::Eq(Box::new(BinaryOp {
417 left: wrap_if_json_arrow(op.left),
418 right: wrap_if_json_arrow(op.right),
419 ..*op
420 }))),
421 Expression::Neq(op) => Ok(Expression::Neq(Box::new(BinaryOp {
422 left: wrap_if_json_arrow(op.left),
423 right: wrap_if_json_arrow(op.right),
424 ..*op
425 }))),
426 Expression::Lt(op) => Ok(Expression::Lt(Box::new(BinaryOp {
427 left: wrap_if_json_arrow(op.left),
428 right: wrap_if_json_arrow(op.right),
429 ..*op
430 }))),
431 Expression::Lte(op) => Ok(Expression::Lte(Box::new(BinaryOp {
432 left: wrap_if_json_arrow(op.left),
433 right: wrap_if_json_arrow(op.right),
434 ..*op
435 }))),
436 Expression::Gt(op) => Ok(Expression::Gt(Box::new(BinaryOp {
437 left: wrap_if_json_arrow(op.left),
438 right: wrap_if_json_arrow(op.right),
439 ..*op
440 }))),
441 Expression::Gte(op) => Ok(Expression::Gte(Box::new(BinaryOp {
442 left: wrap_if_json_arrow(op.left),
443 right: wrap_if_json_arrow(op.right),
444 ..*op
445 }))),
446
447 Expression::In(mut i) => {
449 i.this = wrap_if_json_arrow(i.this);
450 Ok(Expression::In(i))
451 }
452
453 Expression::Not(mut n) => {
455 n.this = wrap_if_json_arrow(n.this);
456 Ok(Expression::Not(n))
457 }
458
459 Expression::ArrayOverlaps(op) => Ok(Expression::And(op)),
462
463 Expression::ModFunc(f) => Ok(Expression::Mod(Box::new(BinaryOp {
465 left: f.this,
466 right: f.expression,
467 left_comments: Vec::new(),
468 operator_comments: Vec::new(),
469 trailing_comments: Vec::new(),
470 }))),
471
472 Expression::Show(mut s) => {
474 if s.this == "SLAVE STATUS" {
475 s.this = "REPLICA STATUS".to_string();
476 }
477 if matches!(s.this.as_str(), "INDEX" | "COLUMNS") && s.db.is_none() {
478 if let Some(Expression::Table(mut t)) = s.target.take() {
479 if let Some(db_ident) = t.schema.take().or(t.catalog.take()) {
480 s.db = Some(Expression::Identifier(db_ident));
481 s.target = Some(Expression::Identifier(t.name));
482 } else {
483 s.target = Some(Expression::Table(t));
484 }
485 }
486 }
487 Ok(Expression::Show(s))
488 }
489
490 Expression::AtTimeZone(atz) => {
493 let is_current = match &atz.this {
494 Expression::CurrentDate(_) | Expression::CurrentTimestamp(_) => true,
495 Expression::Function(f) => {
496 let n = f.name.to_uppercase();
497 (n == "CURRENT_DATE" || n == "CURRENT_TIMESTAMP") && f.no_parens
498 }
499 _ => false,
500 };
501 if is_current {
502 Ok(Expression::AtTimeZone(atz)) } else {
504 Ok(atz.this) }
506 }
507
508 Expression::MemberOf(mut op) => {
511 op.right = json_arrow_to_function(op.right);
512 Ok(Expression::MemberOf(op))
513 }
514
515 _ => Ok(expr),
517 }
518 }
519}
520
521impl MySQLDialect {
522 fn normalize_mysql_date_format(fmt: &str) -> String {
523 fmt
524 .replace("%H:%i:%s", "%T")
525 .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 { precision, timezone: _ } => DataType::Timestamp {
565 precision,
566 timezone: false,
567 },
568 DataType::Custom { name } if name.to_uppercase() == "TIMESTAMPTZ"
570 || name.to_uppercase() == "TIMESTAMPLTZ" => {
571 DataType::Timestamp {
572 precision: None,
573 timezone: false,
574 }
575 },
576 other => other,
579 };
580 Ok(Expression::DataType(transformed))
581 }
582
583 fn transform_cast(&self, cast: Cast) -> Result<Expression> {
586 match &cast.to {
588 DataType::Timestamp { .. } => {
589 Ok(Expression::Function(Box::new(Function::new(
590 "TIMESTAMP".to_string(),
591 vec![cast.this],
592 ))))
593 }
594 DataType::Custom { name } if name.to_uppercase() == "TIMESTAMPTZ"
595 || name.to_uppercase() == "TIMESTAMPLTZ" => {
596 Ok(Expression::Function(Box::new(Function::new(
597 "TIMESTAMP".to_string(),
598 vec![cast.this],
599 ))))
600 }
601 _ => Ok(Expression::Cast(Box::new(self.transform_cast_type(cast)))),
603 }
604 }
605
606 fn transform_cast_type(&self, cast: Cast) -> Cast {
610 let new_type = match &cast.to {
611 DataType::VarChar { .. } => DataType::Char { length: None },
613 DataType::Text => DataType::Char { length: None },
614
615 DataType::BigInt { .. } => DataType::Custom { name: "SIGNED".to_string() },
617 DataType::Int { .. } => DataType::Custom { name: "SIGNED".to_string() },
618 DataType::SmallInt { .. } => DataType::Custom { name: "SIGNED".to_string() },
619 DataType::TinyInt { .. } => DataType::Custom { name: "SIGNED".to_string() },
620 DataType::Boolean => DataType::Custom { name: "SIGNED".to_string() },
621
622 DataType::Custom { name } => {
624 let upper = name.to_uppercase();
625 match upper.as_str() {
626 "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => {
629 DataType::Custom { name: upper }
630 }
631 "MEDIUMINT" => DataType::Custom { name: "SIGNED".to_string() },
633 "UBIGINT" | "UINT" | "USMALLINT" | "UTINYINT" | "UMEDIUMINT" => {
635 DataType::Custom { name: "UNSIGNED".to_string() }
636 }
637 _ => cast.to.clone(),
639 }
640 }
641
642 _ => cast.to.clone(),
644 };
645
646 Cast {
647 this: cast.this,
648 to: new_type,
649 trailing_comments: cast.trailing_comments,
650 double_colon_syntax: cast.double_colon_syntax,
651 format: cast.format,
652 default: cast.default,
653 }
654 }
655
656 fn transform_function(&self, f: Function) -> Result<Expression> {
657 let name_upper = f.name.to_uppercase();
658 match name_upper.as_str() {
659 "DATE_FORMAT" if f.args.len() >= 2 => {
661 let mut f = f;
662 if let Some(Expression::Literal(Literal::String(fmt))) = f.args.get(1) {
663 let normalized = Self::normalize_mysql_date_format(fmt);
664 if normalized != *fmt {
665 f.args[1] = Expression::Literal(Literal::String(normalized));
666 }
667 }
668 Ok(Expression::Function(Box::new(f)))
669 }
670
671 "NVL" if f.args.len() == 2 => {
673 let mut args = f.args;
674 let second = args.pop().unwrap();
675 let first = args.pop().unwrap();
676 Ok(Expression::IfNull(Box::new(BinaryFunc { original_name: None,
677 this: first,
678 expression: second,
679 })))
680 }
681
682 "ARRAY_AGG" if f.args.len() == 1 => {
687 let mut args = f.args;
688 Ok(Expression::Function(Box::new(Function::new(
689 "GROUP_CONCAT".to_string(),
690 vec![args.pop().unwrap()],
691 ))))
692 }
693
694 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
696 "GROUP_CONCAT".to_string(),
697 f.args,
698 )))),
699
700 "RANDOM" => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
702 seed: None, lower: None, upper: None,
703 }))),
704
705 "CURRENT_TIMESTAMP" => {
708 let precision = if let Some(Expression::Literal(crate::expressions::Literal::Number(n))) = f.args.first() {
709 n.parse::<u32>().ok()
710 } else {
711 None
712 };
713 Ok(Expression::CurrentTimestamp(
714 crate::expressions::CurrentTimestamp { precision, sysdate: false },
715 ))
716 }
717
718 "POSITION" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
721 "LOCATE".to_string(),
722 f.args,
723 )))),
724
725 "LENGTH" => Ok(Expression::Function(Box::new(f))),
728
729 "CEIL" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
731 "CEILING".to_string(),
732 f.args,
733 )))),
734
735 "STDDEV" => Ok(Expression::Function(Box::new(Function::new(
737 "STD".to_string(),
738 f.args,
739 )))),
740
741 "STDDEV_SAMP" => Ok(Expression::Function(Box::new(Function::new(
743 "STDDEV".to_string(),
744 f.args,
745 )))),
746
747 "TO_DATE" => Ok(Expression::Function(Box::new(Function::new(
749 "STR_TO_DATE".to_string(),
750 f.args,
751 )))),
752
753 "TO_TIMESTAMP" => Ok(Expression::Function(Box::new(Function::new(
755 "STR_TO_DATE".to_string(),
756 f.args,
757 )))),
758
759 "DATE_TRUNC" if f.args.len() >= 2 => {
762 let mut args = f.args;
765 let _unit = args.remove(0);
766 let date = args.remove(0);
767 Ok(Expression::Function(Box::new(Function::new(
768 "DATE".to_string(),
769 vec![date],
770 ))))
771 }
772
773 "COALESCE" if f.args.len() > 2 => Ok(Expression::Function(Box::new(f))),
777
778 "DAY" => Ok(Expression::Function(Box::new(Function::new(
780 "DAYOFMONTH".to_string(),
781 f.args,
782 )))),
783
784 "DAYOFWEEK" => Ok(Expression::Function(Box::new(f))),
786
787 "DAYOFYEAR" => Ok(Expression::Function(Box::new(f))),
789
790 "WEEKOFYEAR" => Ok(Expression::Function(Box::new(f))),
792
793 "LAST_DAY" => Ok(Expression::Function(Box::new(f))),
795
796 "TIMESTAMPADD" => Ok(Expression::Function(Box::new(Function::new(
798 "DATE_ADD".to_string(),
799 f.args,
800 )))),
801
802 "TIMESTAMPDIFF" => Ok(Expression::Function(Box::new(f))),
804
805 "CONVERT_TIMEZONE" if f.args.len() == 3 => {
807 let mut args = f.args;
808 let from_tz = args.remove(0);
809 let to_tz = args.remove(0);
810 let timestamp = args.remove(0);
811 Ok(Expression::Function(Box::new(Function::new(
812 "CONVERT_TZ".to_string(),
813 vec![timestamp, from_tz, to_tz],
814 ))))
815 }
816
817 "UTC_TIMESTAMP" => Ok(Expression::Function(Box::new(f))),
819
820 "UTC_TIME" => Ok(Expression::Function(Box::new(f))),
822
823 "MAKETIME" => Ok(Expression::Function(Box::new(f))),
825
826 "TIME_FROM_PARTS" if f.args.len() == 3 => Ok(Expression::Function(Box::new(Function::new(
828 "MAKETIME".to_string(),
829 f.args,
830 )))),
831
832 "STUFF" if f.args.len() == 4 => Ok(Expression::Function(Box::new(Function::new(
834 "INSERT".to_string(),
835 f.args,
836 )))),
837
838 "LOCATE" => Ok(Expression::Function(Box::new(f))),
840
841 "FIND_IN_SET" => Ok(Expression::Function(Box::new(f))),
843
844 "FORMAT" => Ok(Expression::Function(Box::new(f))),
846
847 "JSON_EXTRACT" => Ok(Expression::Function(Box::new(f))),
849
850 "JSON_UNQUOTE" => Ok(Expression::Function(Box::new(f))),
852
853 "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
855 let extract = Expression::Function(Box::new(Function::new(
856 "JSON_EXTRACT".to_string(),
857 f.args,
858 )));
859 Ok(Expression::Function(Box::new(Function::new(
860 "JSON_UNQUOTE".to_string(),
861 vec![extract],
862 ))))
863 }
864
865 "GEN_RANDOM_UUID" | "GENERATE_UUID" => Ok(Expression::Function(Box::new(Function::new(
867 "UUID".to_string(),
868 vec![],
869 )))),
870
871 "DATABASE" => Ok(Expression::Function(Box::new(Function::new(
873 "SCHEMA".to_string(),
874 f.args,
875 )))),
876
877 "INSTR" if f.args.len() == 2 => {
880 let mut args = f.args;
881 let str_arg = args.remove(0);
882 let substr_arg = args.remove(0);
883 Ok(Expression::Function(Box::new(Function::new(
884 "LOCATE".to_string(),
885 vec![substr_arg, str_arg],
886 ))))
887 }
888
889 "TIME_STR_TO_UNIX" => Ok(Expression::Function(Box::new(Function::new(
891 "UNIX_TIMESTAMP".to_string(),
892 f.args,
893 )))),
894
895 "TIME_STR_TO_TIME" if f.args.len() >= 1 => {
897 let mut args = f.args.into_iter();
898 let arg = args.next().unwrap();
899
900 if args.next().is_some() {
902 return Ok(Expression::Function(Box::new(Function::new(
903 "TIMESTAMP".to_string(),
904 vec![arg],
905 ))));
906 }
907
908 let precision = if let Expression::Literal(crate::expressions::Literal::String(ref s)) = arg {
910 if let Some(dot_pos) = s.rfind('.') {
912 let after_dot = &s[dot_pos + 1..];
913 let frac_digits = after_dot.chars().take_while(|c| c.is_ascii_digit()).count();
915 if frac_digits > 0 {
916 if frac_digits <= 3 { Some(3) } else { Some(6) }
918 } else {
919 None
920 }
921 } else {
922 None
923 }
924 } else {
925 None
926 };
927
928 let type_name = match precision {
929 Some(p) => format!("DATETIME({})", p),
930 None => "DATETIME".to_string(),
931 };
932
933 Ok(Expression::Cast(Box::new(Cast {
934 this: arg,
935 to: DataType::Custom { name: type_name },
936 trailing_comments: Vec::new(),
937 double_colon_syntax: false,
938 format: None,
939 default: None,
940 })))
941 }
942
943 "UCASE" => Ok(Expression::Function(Box::new(Function::new(
945 "UPPER".to_string(),
946 f.args,
947 )))),
948
949 "LCASE" => Ok(Expression::Function(Box::new(Function::new(
951 "LOWER".to_string(),
952 f.args,
953 )))),
954
955 "DAY_OF_MONTH" => Ok(Expression::Function(Box::new(Function::new(
957 "DAYOFMONTH".to_string(),
958 f.args,
959 )))),
960
961 "DAY_OF_WEEK" => Ok(Expression::Function(Box::new(Function::new(
963 "DAYOFWEEK".to_string(),
964 f.args,
965 )))),
966
967 "DAY_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
969 "DAYOFYEAR".to_string(),
970 f.args,
971 )))),
972
973 "WEEK_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
975 "WEEKOFYEAR".to_string(),
976 f.args,
977 )))),
978
979 "MOD" if f.args.len() == 2 => {
981 let mut args = f.args;
982 let left = args.remove(0);
983 let right = args.remove(0);
984 Ok(Expression::Mod(Box::new(BinaryOp {
985 left,
986 right,
987 left_comments: Vec::new(),
988 operator_comments: Vec::new(),
989 trailing_comments: Vec::new(),
990 })))
991 }
992
993 "PARSE_JSON" if f.args.len() == 1 => Ok(f.args.into_iter().next().unwrap()),
995
996 "GET_PATH" if f.args.len() == 2 => {
998 let mut args = f.args;
999 let this = args.remove(0);
1000 let path = args.remove(0);
1001 let json_path = match &path {
1002 Expression::Literal(Literal::String(s)) => {
1003 let s = Self::convert_bracket_to_quoted_path(s);
1005 let normalized = if s.starts_with('$') {
1006 s
1007 } else if s.starts_with('[') {
1008 format!("${}", s)
1009 } else {
1010 format!("$.{}", s)
1011 };
1012 Expression::Literal(Literal::String(normalized))
1013 }
1014 _ => path,
1015 };
1016 Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
1017 this,
1018 path: json_path,
1019 returning: None,
1020 arrow_syntax: false,
1021 hash_arrow_syntax: false,
1022 wrapper_option: None,
1023 quotes_option: None,
1024 on_scalar_string: false,
1025 on_error: None,
1026 })))
1027 }
1028
1029 "REGEXP" if f.args.len() >= 2 => {
1031 Ok(Expression::Function(Box::new(Function::new("REGEXP_LIKE".to_string(), f.args))))
1032 }
1033
1034 _ => Ok(Expression::Function(Box::new(f))),
1036 }
1037 }
1038
1039 fn transform_aggregate_function(
1040 &self,
1041 f: Box<crate::expressions::AggregateFunction>,
1042 ) -> Result<Expression> {
1043 let name_upper = f.name.to_uppercase();
1044 match name_upper.as_str() {
1045 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
1047 "GROUP_CONCAT".to_string(),
1048 f.args,
1049 )))),
1050
1051 "ARRAY_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
1053 "GROUP_CONCAT".to_string(),
1054 f.args,
1055 )))),
1056
1057 _ => Ok(Expression::AggregateFunction(f)),
1059 }
1060 }
1061}
1062
1063#[cfg(test)]
1064mod tests {
1065 use super::*;
1066 use crate::dialects::Dialect;
1067
1068 fn transpile_to_mysql(sql: &str) -> String {
1069 let dialect = Dialect::get(DialectType::Generic);
1070 let result = dialect
1071 .transpile_to(sql, DialectType::MySQL)
1072 .expect("Transpile failed");
1073 result[0].clone()
1074 }
1075
1076 #[test]
1077 fn test_nvl_to_ifnull() {
1078 let result = transpile_to_mysql("SELECT NVL(a, b)");
1079 assert!(
1080 result.contains("IFNULL"),
1081 "Expected IFNULL, got: {}",
1082 result
1083 );
1084 }
1085
1086 #[test]
1087 fn test_coalesce_preserved() {
1088 let result = transpile_to_mysql("SELECT COALESCE(a, b)");
1090 assert!(
1091 result.contains("COALESCE"),
1092 "Expected COALESCE to be preserved, got: {}",
1093 result
1094 );
1095 }
1096
1097 #[test]
1098 fn test_random_to_rand() {
1099 let result = transpile_to_mysql("SELECT RANDOM()");
1100 assert!(result.contains("RAND"), "Expected RAND, got: {}", result);
1101 }
1102
1103 #[test]
1104 fn test_basic_select() {
1105 let result = transpile_to_mysql("SELECT a, b FROM users WHERE id = 1");
1106 assert!(result.contains("SELECT"));
1107 assert!(result.contains("FROM users"));
1108 }
1109
1110 #[test]
1111 fn test_string_agg_to_group_concat() {
1112 let result = transpile_to_mysql("SELECT STRING_AGG(name)");
1113 assert!(
1114 result.contains("GROUP_CONCAT"),
1115 "Expected GROUP_CONCAT, got: {}",
1116 result
1117 );
1118 }
1119
1120 #[test]
1121 fn test_array_agg_to_group_concat() {
1122 let result = transpile_to_mysql("SELECT ARRAY_AGG(name)");
1123 assert!(
1124 result.contains("GROUP_CONCAT"),
1125 "Expected GROUP_CONCAT, got: {}",
1126 result
1127 );
1128 }
1129
1130 #[test]
1131 fn test_to_date_to_str_to_date() {
1132 let result = transpile_to_mysql("SELECT TO_DATE('2023-01-01')");
1133 assert!(
1134 result.contains("STR_TO_DATE"),
1135 "Expected STR_TO_DATE, got: {}",
1136 result
1137 );
1138 }
1139
1140 #[test]
1141 fn test_backtick_identifiers() {
1142 let dialect = MySQLDialect;
1144 let config = dialect.generator_config();
1145 assert_eq!(config.identifier_quote, '`');
1146 }
1147
1148 fn mysql_identity(sql: &str, expected: &str) {
1149 let dialect = Dialect::get(DialectType::MySQL);
1150 let ast = dialect.parse(sql).expect("Parse failed");
1151 let transformed = dialect.transform(ast[0].clone()).expect("Transform failed");
1152 let result = dialect.generate(&transformed).expect("Generate failed");
1153 assert_eq!(result, expected, "SQL: {}", sql);
1154 }
1155
1156 #[test]
1157 fn test_ucase_to_upper() {
1158 mysql_identity("SELECT UCASE('foo')", "SELECT UPPER('foo')");
1159 }
1160
1161 #[test]
1162 fn test_lcase_to_lower() {
1163 mysql_identity("SELECT LCASE('foo')", "SELECT LOWER('foo')");
1164 }
1165
1166 #[test]
1167 fn test_day_of_month() {
1168 mysql_identity("SELECT DAY_OF_MONTH('2023-01-01')", "SELECT DAYOFMONTH('2023-01-01')");
1169 }
1170
1171 #[test]
1172 fn test_day_of_week() {
1173 mysql_identity("SELECT DAY_OF_WEEK('2023-01-01')", "SELECT DAYOFWEEK('2023-01-01')");
1174 }
1175
1176 #[test]
1177 fn test_day_of_year() {
1178 mysql_identity("SELECT DAY_OF_YEAR('2023-01-01')", "SELECT DAYOFYEAR('2023-01-01')");
1179 }
1180
1181 #[test]
1182 fn test_week_of_year() {
1183 mysql_identity("SELECT WEEK_OF_YEAR('2023-01-01')", "SELECT WEEKOFYEAR('2023-01-01')");
1184 }
1185
1186 #[test]
1187 fn test_mod_func_to_percent() {
1188 mysql_identity("MOD(x, y)", "x % y");
1190 }
1191
1192 #[test]
1193 fn test_database_to_schema() {
1194 mysql_identity("DATABASE()", "SCHEMA()");
1195 }
1196
1197 #[test]
1198 fn test_and_operator() {
1199 mysql_identity("SELECT 1 && 0", "SELECT 1 AND 0");
1200 }
1201
1202 #[test]
1203 fn test_or_operator() {
1204 mysql_identity("SELECT a || b", "SELECT a OR b");
1205 }
1206
1207}