pg2any_lib/destinations/dialects/
mysql.rs1use crate::destinations::dialect::SqlDialect;
2use pg_walstream::ColumnValue;
3
4#[derive(Default, Debug, Clone, Copy)]
5pub struct MySqlDialect;
6
7impl SqlDialect for MySqlDialect {
8 fn quote_identifier(&self, ident: &str, out: &mut String) {
9 out.reserve(ident.len() + 2);
10 out.push('`');
11 if ident.contains('`') {
12 for ch in ident.chars() {
13 if ch == '`' {
14 out.push('`');
15 }
16 out.push(ch);
17 }
18 } else {
19 out.push_str(ident);
20 }
21 out.push('`');
22 }
23
24 fn qualify_table(&self, schema: &str, table: &str, out: &mut String) {
25 self.quote_identifier(schema, out);
26 out.push('.');
27 self.quote_identifier(table, out);
28 }
29
30 fn render_hex_literal(&self, bytes: &[u8], out: &mut String) {
31 out.push_str("X'");
32 crate::destinations::dialect::push_hex_ascii(out, bytes);
33 out.push('\'');
34 }
35
36 fn render_value(&self, value: &ColumnValue, out: &mut String) {
37 match value {
38 ColumnValue::Null => out.push_str("NULL"),
39 ColumnValue::Text(_) => match value.as_str() {
40 Some(s) => {
41 if s == "t" {
42 out.push('1');
43 return;
44 }
45 if s == "f" {
46 out.push('0');
47 return;
48 }
49 out.reserve(s.len() + 2);
50 out.push('\'');
51 let needs_escape = s.contains(['\'', '\\']);
52 if needs_escape {
53 for ch in s.chars() {
54 match ch {
55 '\'' => out.push_str("''"),
56 '\\' => out.push_str("\\\\"),
57 _ => out.push(ch),
58 }
59 }
60 } else {
61 out.push_str(s);
62 }
63 out.push('\'');
64 }
65 None => self.render_hex_literal(value.as_bytes(), out),
66 },
67 ColumnValue::Binary(_) => self.render_hex_literal(value.as_bytes(), out),
68 }
69 }
70
71 fn truncate_table_sql(&self, schema: &str, table: &str) -> Option<String> {
72 let mut sql = String::new();
73 sql.push_str("TRUNCATE TABLE ");
74 self.qualify_table(schema, table, &mut sql);
75 sql.push(';');
76 Some(sql)
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 fn s(f: impl Fn(&mut String)) -> String {
85 let mut o = String::new();
86 f(&mut o);
87 o
88 }
89
90 #[test]
91 fn quote_identifier() {
92 assert_eq!(s(|o| MySqlDialect.quote_identifier("users", o)), "`users`");
93 assert_eq!(
94 s(|o| MySqlDialect.quote_identifier("back`tick", o)),
95 "`back``tick`"
96 );
97 assert_eq!(
98 s(|o| MySqlDialect.quote_identifier("bra]cket", o)),
99 "`bra]cket`"
100 );
101 assert_eq!(
102 s(|o| MySqlDialect.quote_identifier("double\"quote", o)),
103 "`double\"quote`"
104 );
105 }
106
107 #[test]
108 fn qualify_table() {
109 assert_eq!(
110 s(|o| MySqlDialect.qualify_table("public", "users", o)),
111 "`public`.`users`"
112 );
113 assert_eq!(
114 s(|o| MySqlDialect.qualify_table("custom", "items", o)),
115 "`custom`.`items`"
116 );
117 }
118
119 #[test]
120 fn render_hex_literal() {
121 assert_eq!(s(|o| MySqlDialect.render_hex_literal(&[], o)), "X''");
122 assert_eq!(
123 s(|o| MySqlDialect.render_hex_literal(&[0xde, 0xad, 0xbe, 0xef], o)),
124 "X'deadbeef'"
125 );
126 }
127
128 #[test]
129 fn render_value() {
130 use bytes::Bytes;
131 assert_eq!(
132 s(|o| MySqlDialect.render_value(&ColumnValue::Null, o)),
133 "NULL"
134 );
135 assert_eq!(
136 s(|o| MySqlDialect.render_value(&ColumnValue::text("t"), o)),
137 "1"
138 );
139 assert_eq!(
140 s(|o| MySqlDialect.render_value(&ColumnValue::text("f"), o)),
141 "0"
142 );
143 assert_eq!(
144 s(|o| MySqlDialect.render_value(&ColumnValue::text("hello"), o)),
145 "'hello'"
146 );
147 assert_eq!(
148 s(|o| MySqlDialect.render_value(&ColumnValue::text("o'reilly"), o)),
149 "'o''reilly'"
150 );
151 assert_eq!(
152 s(|o| MySqlDialect.render_value(&ColumnValue::text("back\\slash"), o)),
153 "'back\\\\slash'"
154 );
155 assert_eq!(
156 s(|o| MySqlDialect.render_value(
157 &ColumnValue::Binary(Bytes::from_static(&[0x00, 0xff, 0xab])),
158 o
159 )),
160 "X'00ffab'"
161 );
162 }
163
164 #[test]
165 fn truncate_table_sql() {
166 assert_eq!(
167 MySqlDialect
168 .truncate_table_sql("public", "users")
169 .as_deref(),
170 Some("TRUNCATE TABLE `public`.`users`;")
171 );
172 }
173}