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};
16#[cfg(feature = "generate")]
17use crate::generator::GeneratorConfig;
18use crate::tokens::TokenizerConfig;
19
20fn wrap_if_json_arrow(expr: Expression) -> Expression {
24 match &expr {
25 Expression::JsonExtract(f) if f.arrow_syntax => Expression::Paren(Box::new(Paren {
26 this: expr,
27 trailing_comments: Vec::new(),
28 })),
29 Expression::JsonExtractScalar(f) if f.arrow_syntax => Expression::Paren(Box::new(Paren {
30 this: expr,
31 trailing_comments: Vec::new(),
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 => Expression::Function(Box::new(
42 Function::new("JSON_EXTRACT".to_string(), vec![f.this, f.path]),
43 )),
44 Expression::JsonExtractScalar(f) if f.arrow_syntax => {
45 let json_extract = Expression::Function(Box::new(Function::new(
48 "JSON_EXTRACT".to_string(),
49 vec![f.this, f.path],
50 )));
51 Expression::Function(Box::new(Function::new(
52 "JSON_UNQUOTE".to_string(),
53 vec![json_extract],
54 )))
55 }
56 other => other,
57 }
58}
59
60pub struct MySQLDialect;
62
63impl DialectImpl for MySQLDialect {
64 fn dialect_type(&self) -> DialectType {
65 DialectType::MySQL
66 }
67
68 fn tokenizer_config(&self) -> TokenizerConfig {
69 use crate::tokens::TokenType;
70 let mut config = TokenizerConfig::default();
71 config.identifiers.insert('`', '`');
73 config.identifiers.remove(&'"');
76 config.quotes.insert("\"".to_string(), "\"".to_string());
78 config.string_escapes.push('\\');
80 config.keywords.insert("XOR".to_string(), TokenType::Xor);
82 config.escape_follow_chars = vec!['0', 'b', 'n', 'r', 't', 'Z', '%', '_'];
85 config.identifiers_can_start_with_digit = true;
87 config.hex_number_strings = true;
88 config
89 }
90
91 #[cfg(feature = "generate")]
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 #[cfg(feature = "transpile")]
138
139 fn transform_expr(&self, expr: Expression) -> Result<Expression> {
140 match expr {
141 Expression::DataType(dt) => self.transform_data_type(dt),
143
144 Expression::Nvl(f) => Ok(Expression::IfNull(f)),
146
147 Expression::TryCast(c) => self.transform_cast(*c),
153
154 Expression::SafeCast(c) => self.transform_cast(*c),
156
157 Expression::Cast(c) => self.transform_cast(*c),
160
161 Expression::ILike(op) => {
163 let lower_left = Expression::Lower(Box::new(UnaryFunc::new(op.left)));
165 let lower_right = Expression::Lower(Box::new(UnaryFunc::new(op.right)));
166 Ok(Expression::Like(Box::new(LikeOp {
167 left: lower_left,
168 right: lower_right,
169 escape: op.escape,
170 quantifier: op.quantifier,
171 inferred_type: None,
172 })))
173 }
174
175 Expression::Concat(op) => Ok(Expression::Concat(op)),
178
179 Expression::Random(_) => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
181 seed: None,
182 lower: None,
183 upper: None,
184 }))),
185
186 Expression::ArrayAgg(f) => Ok(Expression::Function(Box::new(Function::new(
188 "GROUP_CONCAT".to_string(),
189 vec![f.this],
190 )))),
191
192 Expression::StringAgg(f) => {
194 let mut args = vec![f.this.clone()];
195 if let Some(separator) = &f.separator {
196 args.push(separator.clone());
197 }
198 Ok(Expression::Function(Box::new(Function::new(
199 "GROUP_CONCAT".to_string(),
200 args,
201 ))))
202 }
203
204 Expression::Unnest(f) => {
207 Ok(Expression::Function(Box::new(Function::new(
210 "JSON_TABLE".to_string(),
211 vec![f.this],
212 ))))
213 }
214
215 Expression::Substring(mut f) => {
217 f.from_for_syntax = false;
218 Ok(Expression::Substring(f))
219 }
220
221 Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(Function::new(
224 "BIT_AND".to_string(),
225 vec![f.this],
226 )))),
227
228 Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(Function::new(
230 "BIT_OR".to_string(),
231 vec![f.this],
232 )))),
233
234 Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(Function::new(
236 "BIT_XOR".to_string(),
237 vec![f.this],
238 )))),
239
240 Expression::BitwiseCount(f) => Ok(Expression::Function(Box::new(Function::new(
242 "BIT_COUNT".to_string(),
243 vec![f.this],
244 )))),
245
246 Expression::TimeFromParts(f) => {
248 let mut args = Vec::new();
249 if let Some(h) = f.hour {
250 args.push(*h);
251 }
252 if let Some(m) = f.min {
253 args.push(*m);
254 }
255 if let Some(s) = f.sec {
256 args.push(*s);
257 }
258 Ok(Expression::Function(Box::new(Function::new(
259 "MAKETIME".to_string(),
260 args,
261 ))))
262 }
263
264 Expression::LogicalAnd(f) => Ok(Expression::Function(Box::new(Function::new(
268 "MIN".to_string(),
269 vec![f.this],
270 )))),
271
272 Expression::LogicalOr(f) => Ok(Expression::Function(Box::new(Function::new(
274 "MAX".to_string(),
275 vec![f.this],
276 )))),
277
278 Expression::DayOfMonth(f) => Ok(Expression::Function(Box::new(Function::new(
281 "DAYOFMONTH".to_string(),
282 vec![f.this],
283 )))),
284
285 Expression::DayOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
287 "DAYOFWEEK".to_string(),
288 vec![f.this],
289 )))),
290
291 Expression::DayOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
293 "DAYOFYEAR".to_string(),
294 vec![f.this],
295 )))),
296
297 Expression::WeekOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
299 "WEEKOFYEAR".to_string(),
300 vec![f.this],
301 )))),
302
303 Expression::DateDiff(f) => Ok(Expression::Function(Box::new(Function::new(
305 "DATEDIFF".to_string(),
306 vec![f.this, f.expression],
307 )))),
308
309 Expression::TimeStrToUnix(f) => Ok(Expression::Function(Box::new(Function::new(
311 "UNIX_TIMESTAMP".to_string(),
312 vec![f.this],
313 )))),
314
315 Expression::TimestampDiff(f) => Ok(Expression::Function(Box::new(Function::new(
317 "TIMESTAMPDIFF".to_string(),
318 vec![*f.this, *f.expression],
319 )))),
320
321 Expression::StrPosition(f) => {
325 let mut args = vec![];
326 if let Some(substr) = f.substr {
327 args.push(*substr);
328 }
329 args.push(*f.this);
330 if let Some(pos) = f.position {
331 args.push(*pos);
332 }
333 Ok(Expression::Function(Box::new(Function::new(
334 "LOCATE".to_string(),
335 args,
336 ))))
337 }
338
339 Expression::Stuff(f) => {
341 let mut args = vec![*f.this];
342 if let Some(start) = f.start {
343 args.push(*start);
344 }
345 if let Some(length) = f.length {
346 args.push(Expression::number(length));
347 }
348 args.push(*f.expression);
349 Ok(Expression::Function(Box::new(Function::new(
350 "INSERT".to_string(),
351 args,
352 ))))
353 }
354
355 Expression::SessionUser(_) => Ok(Expression::Function(Box::new(Function::new(
358 "SESSION_USER".to_string(),
359 vec![],
360 )))),
361
362 Expression::CurrentDate(_) => {
364 Ok(Expression::CurrentDate(crate::expressions::CurrentDate))
365 }
366
367 Expression::NullSafeNeq(op) => {
370 let null_safe_eq = Expression::NullSafeEq(Box::new(crate::expressions::BinaryOp {
372 left: op.left,
373 right: op.right,
374 left_comments: Vec::new(),
375 operator_comments: Vec::new(),
376 trailing_comments: Vec::new(),
377 inferred_type: None,
378 }));
379 Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
380 this: null_safe_eq,
381 inferred_type: None,
382 })))
383 }
384
385 Expression::JSONExtract(e) if e.variant_extract.is_some() => {
389 let path = match *e.expression {
390 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
391 let Literal::String(s) = lit.as_ref() else {
392 unreachable!()
393 };
394 let s = Self::convert_bracket_to_quoted_path(&s);
396 let normalized = if s.starts_with('$') {
397 s
398 } else if s.starts_with('[') {
399 format!("${}", s)
400 } else {
401 format!("$.{}", s)
402 };
403 Expression::Literal(Box::new(Literal::String(normalized)))
404 }
405 other => other,
406 };
407 Ok(Expression::Function(Box::new(Function::new(
408 "JSON_EXTRACT".to_string(),
409 vec![*e.this, path],
410 ))))
411 }
412
413 Expression::Function(f) => self.transform_function(*f),
415
416 Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
418
419 Expression::Eq(op) => Ok(Expression::Eq(Box::new(BinaryOp {
426 left: wrap_if_json_arrow(op.left),
427 right: wrap_if_json_arrow(op.right),
428 ..*op
429 }))),
430 Expression::Neq(op) => Ok(Expression::Neq(Box::new(BinaryOp {
431 left: wrap_if_json_arrow(op.left),
432 right: wrap_if_json_arrow(op.right),
433 ..*op
434 }))),
435 Expression::Lt(op) => Ok(Expression::Lt(Box::new(BinaryOp {
436 left: wrap_if_json_arrow(op.left),
437 right: wrap_if_json_arrow(op.right),
438 ..*op
439 }))),
440 Expression::Lte(op) => Ok(Expression::Lte(Box::new(BinaryOp {
441 left: wrap_if_json_arrow(op.left),
442 right: wrap_if_json_arrow(op.right),
443 ..*op
444 }))),
445 Expression::Gt(op) => Ok(Expression::Gt(Box::new(BinaryOp {
446 left: wrap_if_json_arrow(op.left),
447 right: wrap_if_json_arrow(op.right),
448 ..*op
449 }))),
450 Expression::Gte(op) => Ok(Expression::Gte(Box::new(BinaryOp {
451 left: wrap_if_json_arrow(op.left),
452 right: wrap_if_json_arrow(op.right),
453 ..*op
454 }))),
455
456 Expression::In(mut i) => {
458 i.this = wrap_if_json_arrow(i.this);
459 Ok(Expression::In(i))
460 }
461
462 Expression::Not(mut n) => {
464 n.this = wrap_if_json_arrow(n.this);
465 Ok(Expression::Not(n))
466 }
467
468 Expression::ArrayOverlaps(op) => Ok(Expression::And(op)),
471
472 Expression::ModFunc(f) => Ok(Expression::Mod(Box::new(BinaryOp {
474 left: f.this,
475 right: f.expression,
476 left_comments: Vec::new(),
477 operator_comments: Vec::new(),
478 trailing_comments: Vec::new(),
479 inferred_type: None,
480 }))),
481
482 Expression::Show(mut s) => {
484 if s.this == "SLAVE STATUS" {
485 s.this = "REPLICA STATUS".to_string();
486 }
487 if matches!(s.this.as_str(), "INDEX" | "COLUMNS") && s.db.is_none() {
488 if let Some(Expression::Table(mut t)) = s.target.take() {
489 if let Some(db_ident) = t.schema.take().or(t.catalog.take()) {
490 s.db = Some(Expression::Identifier(db_ident));
491 s.target = Some(Expression::Identifier(t.name));
492 } else {
493 s.target = Some(Expression::Table(t));
494 }
495 }
496 }
497 Ok(Expression::Show(s))
498 }
499
500 Expression::AtTimeZone(atz) => {
503 let is_current = match &atz.this {
504 Expression::CurrentDate(_) | Expression::CurrentTimestamp(_) => true,
505 Expression::Function(f) => {
506 let n = f.name.to_uppercase();
507 (n == "CURRENT_DATE" || n == "CURRENT_TIMESTAMP") && f.no_parens
508 }
509 _ => false,
510 };
511 if is_current {
512 Ok(Expression::AtTimeZone(atz)) } else {
514 Ok(atz.this) }
516 }
517
518 Expression::MemberOf(mut op) => {
521 op.right = json_arrow_to_function(op.right);
522 Ok(Expression::MemberOf(op))
523 }
524
525 _ => Ok(expr),
527 }
528 }
529}
530
531#[cfg(feature = "transpile")]
532impl MySQLDialect {
533 fn normalize_mysql_date_format(fmt: &str) -> String {
534 fmt.replace("%H:%i:%s", "%T").replace("%H:%i:%S", "%T")
535 }
536
537 fn convert_bracket_to_quoted_path(path: &str) -> String {
540 let mut result = String::new();
541 let mut chars = path.chars().peekable();
542 while let Some(c) = chars.next() {
543 if c == '[' && chars.peek() == Some(&'"') {
544 chars.next(); let mut key = String::new();
546 while let Some(kc) = chars.next() {
547 if kc == '"' && chars.peek() == Some(&']') {
548 chars.next(); break;
550 }
551 key.push(kc);
552 }
553 if !result.is_empty() && !result.ends_with('.') {
554 result.push('.');
555 }
556 result.push('"');
557 result.push_str(&key);
558 result.push('"');
559 } else {
560 result.push(c);
561 }
562 }
563 result
564 }
565
566 fn transform_data_type(&self, dt: crate::expressions::DataType) -> Result<Expression> {
570 use crate::expressions::DataType;
571 let transformed = match dt {
572 DataType::Timestamp {
574 precision,
575 timezone: _,
576 } => DataType::Timestamp {
577 precision,
578 timezone: false,
579 },
580 DataType::Custom { name }
582 if name.to_uppercase() == "TIMESTAMPTZ"
583 || name.to_uppercase() == "TIMESTAMPLTZ" =>
584 {
585 DataType::Timestamp {
586 precision: None,
587 timezone: false,
588 }
589 }
590 other => other,
593 };
594 Ok(Expression::DataType(transformed))
595 }
596
597 fn transform_cast(&self, cast: Cast) -> Result<Expression> {
601 match &cast.to {
603 DataType::Timestamp { .. } => Ok(Expression::Function(Box::new(Function::new(
604 "TIMESTAMP".to_string(),
605 vec![cast.this],
606 )))),
607 DataType::Custom { name }
608 if name.to_uppercase() == "TIMESTAMPTZ"
609 || name.to_uppercase() == "TIMESTAMPLTZ" =>
610 {
611 Ok(Expression::Function(Box::new(Function::new(
612 "TIMESTAMP".to_string(),
613 vec![cast.this],
614 ))))
615 }
616 _ => Ok(Expression::Cast(Box::new(self.transform_cast_type(cast)))),
618 }
619 }
620
621 fn transform_cast_type(&self, cast: Cast) -> Cast {
625 let new_type = match &cast.to {
626 DataType::VarChar { length, .. } => DataType::Char { length: *length },
628 DataType::Text => DataType::Char { length: None },
629
630 DataType::BigInt { .. } => DataType::Custom {
632 name: "SIGNED".to_string(),
633 },
634 DataType::Int { .. } => DataType::Custom {
635 name: "SIGNED".to_string(),
636 },
637 DataType::SmallInt { .. } => DataType::Custom {
638 name: "SIGNED".to_string(),
639 },
640 DataType::TinyInt { .. } => DataType::Custom {
641 name: "SIGNED".to_string(),
642 },
643 DataType::Boolean => DataType::Custom {
644 name: "SIGNED".to_string(),
645 },
646
647 DataType::Custom { name } => {
649 let upper = name.to_uppercase();
650 match upper.as_str() {
651 "LONGTEXT" | "MEDIUMTEXT" | "TINYTEXT" | "LONGBLOB" | "MEDIUMBLOB"
654 | "TINYBLOB" => DataType::Custom { name: upper },
655 "MEDIUMINT" => DataType::Custom {
657 name: "SIGNED".to_string(),
658 },
659 "UBIGINT" | "UINT" | "USMALLINT" | "UTINYINT" | "UMEDIUMINT" => {
661 DataType::Custom {
662 name: "UNSIGNED".to_string(),
663 }
664 }
665 _ => cast.to.clone(),
667 }
668 }
669
670 DataType::Binary { .. } => cast.to.clone(),
672 DataType::VarBinary { .. } => cast.to.clone(),
673 DataType::Date => cast.to.clone(),
674 DataType::Time { .. } => cast.to.clone(),
675 DataType::Decimal { .. } => cast.to.clone(),
676 DataType::Json => cast.to.clone(),
677 DataType::Float { .. } => cast.to.clone(),
678 DataType::Double { .. } => cast.to.clone(),
679 DataType::Char { .. } => cast.to.clone(),
680 DataType::CharacterSet { .. } => cast.to.clone(),
681 DataType::Enum { .. } => cast.to.clone(),
682 DataType::Set { .. } => cast.to.clone(),
683 DataType::Timestamp { .. } => cast.to.clone(),
684
685 _ => DataType::Char { length: None },
687 };
688
689 Cast {
690 this: cast.this,
691 to: new_type,
692 trailing_comments: cast.trailing_comments,
693 double_colon_syntax: cast.double_colon_syntax,
694 format: cast.format,
695 default: cast.default,
696 inferred_type: None,
697 }
698 }
699
700 fn transform_function(&self, f: Function) -> Result<Expression> {
701 let name_upper = f.name.to_uppercase();
702 match name_upper.as_str() {
703 "DATE_FORMAT" if f.args.len() >= 2 => {
705 let mut f = f;
706 if let Some(Expression::Literal(lit)) = f.args.get(1) {
707 if let Literal::String(fmt) = lit.as_ref() {
708 let normalized = Self::normalize_mysql_date_format(fmt);
709 if normalized != *fmt {
710 f.args[1] = Expression::Literal(Box::new(Literal::String(normalized)));
711 }
712 }
713 }
714 Ok(Expression::Function(Box::new(f)))
715 }
716
717 "NVL" if f.args.len() == 2 => {
719 let mut args = f.args;
720 let second = args.pop().unwrap();
721 let first = args.pop().unwrap();
722 Ok(Expression::IfNull(Box::new(BinaryFunc {
723 original_name: None,
724 this: first,
725 expression: second,
726 inferred_type: None,
727 })))
728 }
729
730 "ARRAY_AGG" if f.args.len() == 1 => {
735 let mut args = f.args;
736 Ok(Expression::Function(Box::new(Function::new(
737 "GROUP_CONCAT".to_string(),
738 vec![args.pop().unwrap()],
739 ))))
740 }
741
742 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
744 Function::new("GROUP_CONCAT".to_string(), f.args),
745 ))),
746
747 "RANDOM" => Ok(Expression::Rand(Box::new(crate::expressions::Rand {
749 seed: None,
750 lower: None,
751 upper: None,
752 }))),
753
754 "CURRENT_TIMESTAMP" => {
757 let precision = if let Some(Expression::Literal(lit)) = f.args.first() {
758 if let crate::expressions::Literal::Number(n) = lit.as_ref() {
759 n.parse::<u32>().ok()
760 } else {
761 None
762 }
763 } else {
764 None
765 };
766 Ok(Expression::CurrentTimestamp(
767 crate::expressions::CurrentTimestamp {
768 precision,
769 sysdate: false,
770 },
771 ))
772 }
773
774 "POSITION" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
777 "LOCATE".to_string(),
778 f.args,
779 )))),
780
781 "LENGTH" => Ok(Expression::Function(Box::new(f))),
784
785 "CEIL" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
787 "CEILING".to_string(),
788 f.args,
789 )))),
790
791 "STDDEV" => Ok(Expression::Function(Box::new(Function::new(
793 "STD".to_string(),
794 f.args,
795 )))),
796
797 "STDDEV_SAMP" => Ok(Expression::Function(Box::new(Function::new(
799 "STDDEV".to_string(),
800 f.args,
801 )))),
802
803 "TO_DATE" => Ok(Expression::Function(Box::new(Function::new(
805 "STR_TO_DATE".to_string(),
806 f.args,
807 )))),
808
809 "TO_TIMESTAMP" => Ok(Expression::Function(Box::new(Function::new(
811 "STR_TO_DATE".to_string(),
812 f.args,
813 )))),
814
815 "DATE_TRUNC" if f.args.len() >= 2 => {
818 let mut args = f.args;
821 let _unit = args.remove(0);
822 let date = args.remove(0);
823 Ok(Expression::Function(Box::new(Function::new(
824 "DATE".to_string(),
825 vec![date],
826 ))))
827 }
828
829 "COALESCE" if f.args.len() > 2 => Ok(Expression::Function(Box::new(f))),
833
834 "DAY" => Ok(Expression::Function(Box::new(Function::new(
836 "DAYOFMONTH".to_string(),
837 f.args,
838 )))),
839
840 "DAYOFWEEK" => Ok(Expression::Function(Box::new(f))),
842
843 "DAYOFYEAR" => Ok(Expression::Function(Box::new(f))),
845
846 "WEEKOFYEAR" => Ok(Expression::Function(Box::new(f))),
848
849 "LAST_DAY" => Ok(Expression::Function(Box::new(f))),
851
852 "TIMESTAMPADD" => Ok(Expression::Function(Box::new(Function::new(
854 "DATE_ADD".to_string(),
855 f.args,
856 )))),
857
858 "TIMESTAMPDIFF" => Ok(Expression::Function(Box::new(f))),
860
861 "CONVERT_TIMEZONE" if f.args.len() == 3 => {
863 let mut args = f.args;
864 let from_tz = args.remove(0);
865 let to_tz = args.remove(0);
866 let timestamp = args.remove(0);
867 Ok(Expression::Function(Box::new(Function::new(
868 "CONVERT_TZ".to_string(),
869 vec![timestamp, from_tz, to_tz],
870 ))))
871 }
872
873 "UTC_TIMESTAMP" => Ok(Expression::Function(Box::new(f))),
875
876 "UTC_TIME" => Ok(Expression::Function(Box::new(f))),
878
879 "MAKETIME" => Ok(Expression::Function(Box::new(f))),
881
882 "TIME_FROM_PARTS" if f.args.len() == 3 => Ok(Expression::Function(Box::new(
884 Function::new("MAKETIME".to_string(), f.args),
885 ))),
886
887 "STUFF" if f.args.len() == 4 => Ok(Expression::Function(Box::new(Function::new(
889 "INSERT".to_string(),
890 f.args,
891 )))),
892
893 "LOCATE" => Ok(Expression::Function(Box::new(f))),
895
896 "FIND_IN_SET" => Ok(Expression::Function(Box::new(f))),
898
899 "FORMAT" => Ok(Expression::Function(Box::new(f))),
901
902 "JSON_EXTRACT" => Ok(Expression::Function(Box::new(f))),
904
905 "JSON_UNQUOTE" => Ok(Expression::Function(Box::new(f))),
907
908 "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
910 let extract = Expression::Function(Box::new(Function::new(
911 "JSON_EXTRACT".to_string(),
912 f.args,
913 )));
914 Ok(Expression::Function(Box::new(Function::new(
915 "JSON_UNQUOTE".to_string(),
916 vec![extract],
917 ))))
918 }
919
920 "GEN_RANDOM_UUID" | "GENERATE_UUID" => Ok(Expression::Function(Box::new(
922 Function::new("UUID".to_string(), vec![]),
923 ))),
924
925 "DATABASE" => Ok(Expression::Function(Box::new(Function::new(
927 "SCHEMA".to_string(),
928 f.args,
929 )))),
930
931 "INSTR" if f.args.len() == 2 => {
934 let mut args = f.args;
935 let str_arg = args.remove(0);
936 let substr_arg = args.remove(0);
937 Ok(Expression::Function(Box::new(Function::new(
938 "LOCATE".to_string(),
939 vec![substr_arg, str_arg],
940 ))))
941 }
942
943 "TIME_STR_TO_UNIX" => Ok(Expression::Function(Box::new(Function::new(
945 "UNIX_TIMESTAMP".to_string(),
946 f.args,
947 )))),
948
949 "TIME_STR_TO_TIME" if f.args.len() >= 1 => {
951 let mut args = f.args.into_iter();
952 let arg = args.next().unwrap();
953
954 if args.next().is_some() {
956 return Ok(Expression::Function(Box::new(Function::new(
957 "TIMESTAMP".to_string(),
958 vec![arg],
959 ))));
960 }
961
962 let precision = if let Expression::Literal(ref lit) = arg {
964 if let crate::expressions::Literal::String(ref s) = lit.as_ref() {
965 if let Some(dot_pos) = s.rfind('.') {
967 let after_dot = &s[dot_pos + 1..];
968 let frac_digits =
970 after_dot.chars().take_while(|c| c.is_ascii_digit()).count();
971 if frac_digits > 0 {
972 if frac_digits <= 3 {
974 Some(3)
975 } else {
976 Some(6)
977 }
978 } else {
979 None
980 }
981 } else {
982 None
983 }
984 } else {
985 None
986 }
987 } else {
988 None
989 };
990
991 let type_name = match precision {
992 Some(p) => format!("DATETIME({})", p),
993 None => "DATETIME".to_string(),
994 };
995
996 Ok(Expression::Cast(Box::new(Cast {
997 this: arg,
998 to: DataType::Custom { name: type_name },
999 trailing_comments: Vec::new(),
1000 double_colon_syntax: false,
1001 format: None,
1002 default: None,
1003 inferred_type: None,
1004 })))
1005 }
1006
1007 "UCASE" => Ok(Expression::Function(Box::new(Function::new(
1009 "UPPER".to_string(),
1010 f.args,
1011 )))),
1012
1013 "LCASE" => Ok(Expression::Function(Box::new(Function::new(
1015 "LOWER".to_string(),
1016 f.args,
1017 )))),
1018
1019 "DAY_OF_MONTH" => Ok(Expression::Function(Box::new(Function::new(
1021 "DAYOFMONTH".to_string(),
1022 f.args,
1023 )))),
1024
1025 "DAY_OF_WEEK" => Ok(Expression::Function(Box::new(Function::new(
1027 "DAYOFWEEK".to_string(),
1028 f.args,
1029 )))),
1030
1031 "DAY_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
1033 "DAYOFYEAR".to_string(),
1034 f.args,
1035 )))),
1036
1037 "WEEK_OF_YEAR" => Ok(Expression::Function(Box::new(Function::new(
1039 "WEEKOFYEAR".to_string(),
1040 f.args,
1041 )))),
1042
1043 "MOD" if f.args.len() == 2 => {
1045 let mut args = f.args;
1046 let left = args.remove(0);
1047 let right = args.remove(0);
1048 Ok(Expression::Mod(Box::new(BinaryOp {
1049 left,
1050 right,
1051 left_comments: Vec::new(),
1052 operator_comments: Vec::new(),
1053 trailing_comments: Vec::new(),
1054 inferred_type: None,
1055 })))
1056 }
1057
1058 "PARSE_JSON" if f.args.len() == 1 => Ok(f.args.into_iter().next().unwrap()),
1060
1061 "GET_PATH" if f.args.len() == 2 => {
1063 let mut args = f.args;
1064 let this = args.remove(0);
1065 let path = args.remove(0);
1066 let json_path = match &path {
1067 Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
1068 let Literal::String(s) = lit.as_ref() else {
1069 unreachable!()
1070 };
1071 let s = Self::convert_bracket_to_quoted_path(s);
1073 let normalized = if s.starts_with('$') {
1074 s
1075 } else if s.starts_with('[') {
1076 format!("${}", s)
1077 } else {
1078 format!("$.{}", s)
1079 };
1080 Expression::Literal(Box::new(Literal::String(normalized)))
1081 }
1082 _ => path,
1083 };
1084 Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
1085 this,
1086 path: json_path,
1087 returning: None,
1088 arrow_syntax: false,
1089 hash_arrow_syntax: false,
1090 wrapper_option: None,
1091 quotes_option: None,
1092 on_scalar_string: false,
1093 on_error: None,
1094 })))
1095 }
1096
1097 "REGEXP" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
1099 "REGEXP_LIKE".to_string(),
1100 f.args,
1101 )))),
1102
1103 "CURTIME" => Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
1105 precision: None,
1106 })),
1107
1108 "TRUNC" => Ok(Expression::Function(Box::new(Function::new(
1110 "TRUNCATE".to_string(),
1111 f.args,
1112 )))),
1113
1114 _ => Ok(Expression::Function(Box::new(f))),
1116 }
1117 }
1118
1119 fn transform_aggregate_function(
1120 &self,
1121 f: Box<crate::expressions::AggregateFunction>,
1122 ) -> Result<Expression> {
1123 let name_upper = f.name.to_uppercase();
1124 match name_upper.as_str() {
1125 "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1127 Function::new("GROUP_CONCAT".to_string(), f.args),
1128 ))),
1129
1130 "ARRAY_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
1132 "GROUP_CONCAT".to_string(),
1133 f.args,
1134 )))),
1135
1136 _ => Ok(Expression::AggregateFunction(f)),
1138 }
1139 }
1140}
1141
1142#[cfg(test)]
1143mod tests {
1144 use super::*;
1145 use crate::dialects::Dialect;
1146
1147 fn transpile_to_mysql(sql: &str) -> String {
1148 let dialect = Dialect::get(DialectType::Generic);
1149 let result = dialect
1150 .transpile(sql, DialectType::MySQL)
1151 .expect("Transpile failed");
1152 result[0].clone()
1153 }
1154
1155 #[test]
1156 fn test_nvl_to_ifnull() {
1157 let result = transpile_to_mysql("SELECT NVL(a, b)");
1158 assert!(
1159 result.contains("IFNULL"),
1160 "Expected IFNULL, got: {}",
1161 result
1162 );
1163 }
1164
1165 #[test]
1166 fn test_coalesce_preserved() {
1167 let result = transpile_to_mysql("SELECT COALESCE(a, b)");
1169 assert!(
1170 result.contains("COALESCE"),
1171 "Expected COALESCE to be preserved, got: {}",
1172 result
1173 );
1174 }
1175
1176 #[test]
1177 fn test_random_to_rand() {
1178 let result = transpile_to_mysql("SELECT RANDOM()");
1179 assert!(result.contains("RAND"), "Expected RAND, got: {}", result);
1180 }
1181
1182 #[test]
1183 fn test_basic_select() {
1184 let result = transpile_to_mysql("SELECT a, b FROM users WHERE id = 1");
1185 assert!(result.contains("SELECT"));
1186 assert!(result.contains("FROM users"));
1187 }
1188
1189 #[test]
1190 fn test_string_agg_to_group_concat() {
1191 let result = transpile_to_mysql("SELECT STRING_AGG(name)");
1192 assert!(
1193 result.contains("GROUP_CONCAT"),
1194 "Expected GROUP_CONCAT, got: {}",
1195 result
1196 );
1197 }
1198
1199 #[test]
1200 fn test_array_agg_to_group_concat() {
1201 let result = transpile_to_mysql("SELECT ARRAY_AGG(name)");
1202 assert!(
1203 result.contains("GROUP_CONCAT"),
1204 "Expected GROUP_CONCAT, got: {}",
1205 result
1206 );
1207 }
1208
1209 #[test]
1210 fn test_to_date_to_str_to_date() {
1211 let result = transpile_to_mysql("SELECT TO_DATE('2023-01-01')");
1212 assert!(
1213 result.contains("STR_TO_DATE"),
1214 "Expected STR_TO_DATE, got: {}",
1215 result
1216 );
1217 }
1218
1219 #[test]
1220 fn test_backtick_identifiers() {
1221 let dialect = MySQLDialect;
1223 let config = dialect.generator_config();
1224 assert_eq!(config.identifier_quote, '`');
1225 }
1226
1227 #[test]
1228 fn test_create_procedure_signal_sqlstate_regression() {
1229 let sql = "CREATE PROCEDURE CheckSalary(IN p_salary DECIMAL(10,2))
1230BEGIN
1231 IF p_salary <= 0 THEN
1232 SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salary must be positive';
1233 END IF;
1234 INSERT INTO employees (salary) VALUES (p_salary);
1235END;";
1236
1237 let dialect = Dialect::get(DialectType::MySQL);
1238 let ast = dialect.parse(sql).expect("Parse failed");
1239 let output = dialect.generate(&ast[0]).expect("Generate failed");
1240
1241 assert!(output.contains("CREATE PROCEDURE CheckSalary"));
1242 assert!(output.contains("IF p_salary <= 0 THEN"));
1243 assert!(
1244 output.contains("SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Salary must be positive'")
1245 );
1246 assert!(output.contains("END IF"));
1247 assert!(output.contains("INSERT INTO employees (salary) VALUES (p_salary)"));
1248 }
1249
1250 fn mysql_identity(sql: &str, expected: &str) {
1251 let dialect = Dialect::get(DialectType::MySQL);
1252 let ast = dialect.parse(sql).expect("Parse failed");
1253 let transformed = dialect.transform(ast[0].clone()).expect("Transform failed");
1254 let result = dialect.generate(&transformed).expect("Generate failed");
1255 assert_eq!(result, expected, "SQL: {}", sql);
1256 }
1257
1258 #[test]
1259 fn test_ucase_to_upper() {
1260 mysql_identity("SELECT UCASE('foo')", "SELECT UPPER('foo')");
1261 }
1262
1263 #[test]
1264 fn test_lcase_to_lower() {
1265 mysql_identity("SELECT LCASE('foo')", "SELECT LOWER('foo')");
1266 }
1267
1268 #[test]
1269 fn test_day_of_month() {
1270 mysql_identity(
1271 "SELECT DAY_OF_MONTH('2023-01-01')",
1272 "SELECT DAYOFMONTH('2023-01-01')",
1273 );
1274 }
1275
1276 #[test]
1277 fn test_day_of_week() {
1278 mysql_identity(
1279 "SELECT DAY_OF_WEEK('2023-01-01')",
1280 "SELECT DAYOFWEEK('2023-01-01')",
1281 );
1282 }
1283
1284 #[test]
1285 fn test_day_of_year() {
1286 mysql_identity(
1287 "SELECT DAY_OF_YEAR('2023-01-01')",
1288 "SELECT DAYOFYEAR('2023-01-01')",
1289 );
1290 }
1291
1292 #[test]
1293 fn test_week_of_year() {
1294 mysql_identity(
1295 "SELECT WEEK_OF_YEAR('2023-01-01')",
1296 "SELECT WEEKOFYEAR('2023-01-01')",
1297 );
1298 }
1299
1300 #[test]
1301 fn test_mod_func_to_percent() {
1302 mysql_identity("MOD(x, y)", "x % y");
1304 }
1305
1306 #[test]
1307 fn test_database_to_schema() {
1308 mysql_identity("DATABASE()", "SCHEMA()");
1309 }
1310
1311 #[test]
1312 fn test_and_operator() {
1313 mysql_identity("SELECT 1 && 0", "SELECT 1 AND 0");
1314 }
1315
1316 #[test]
1317 fn test_or_operator() {
1318 mysql_identity("SELECT a || b", "SELECT a OR b");
1319 }
1320}