Skip to main content

sqlx_gen/typemap/
mysql.rs

1use heck::ToUpperCamelCase;
2
3use super::RustType;
4
5pub fn map_type(data_type: &str, column_type: &str) -> RustType {
6    let dt = data_type.to_lowercase();
7    let ct = column_type.to_lowercase();
8    let is_unsigned = ct.contains("unsigned");
9
10    // Handle ENUM columns → generate a Rust enum reference
11    if ct.starts_with("enum(") {
12        // The enum name will be derived from table_name + column_name in codegen
13        // For now, we can't know the full name here. Return a placeholder.
14        // The actual type will be resolved in codegen.
15        return RustType::simple("String");
16    }
17
18    match dt.as_str() {
19        "tinyint" => {
20            if ct == "tinyint(1)" {
21                RustType::simple("bool")
22            } else if is_unsigned {
23                RustType::simple("u8")
24            } else {
25                RustType::simple("i8")
26            }
27        }
28        "smallint" => {
29            if is_unsigned {
30                RustType::simple("u16")
31            } else {
32                RustType::simple("i16")
33            }
34        }
35        "mediumint" | "int" => {
36            if is_unsigned {
37                RustType::simple("u32")
38            } else {
39                RustType::simple("i32")
40            }
41        }
42        "bigint" => {
43            if is_unsigned {
44                RustType::simple("u64")
45            } else {
46                RustType::simple("i64")
47            }
48        }
49        "float" => RustType::simple("f32"),
50        "double" => RustType::simple("f64"),
51        "decimal" | "numeric" => {
52            RustType::with_import("Decimal", "use rust_decimal::Decimal;")
53        }
54        "varchar" | "char" | "text" | "tinytext" | "mediumtext" | "longtext" | "enum" | "set" => {
55            RustType::simple("String")
56        }
57        "binary" | "varbinary" | "blob" | "tinyblob" | "mediumblob" | "longblob" => {
58            RustType::simple("Vec<u8>")
59        }
60        "date" => RustType::with_import("NaiveDate", "use chrono::NaiveDate;"),
61        "time" => RustType::with_import("NaiveTime", "use chrono::NaiveTime;"),
62        "datetime" => {
63            RustType::with_import("NaiveDateTime", "use chrono::NaiveDateTime;")
64        }
65        "timestamp" => {
66            RustType::with_import("DateTime<Utc>", "use chrono::{DateTime, Utc};")
67        }
68        "json" => RustType::with_import("Value", "use serde_json::Value;"),
69        "year" => RustType::simple("i16"),
70        "bit" => RustType::simple("Vec<u8>"),
71        _ => RustType::simple("String"),
72    }
73}
74
75/// Resolve an inline MySQL ENUM column to the correct generated enum type name.
76pub fn resolve_enum_type(table_name: &str, column_name: &str) -> String {
77    let enum_name = format!("{}_{}", table_name, column_name);
78    enum_name.to_upper_camel_case()
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    // --- tinyint ---
86
87    #[test]
88    fn test_tinyint1_is_bool() {
89        assert_eq!(map_type("tinyint", "tinyint(1)").path, "bool");
90    }
91
92    #[test]
93    fn test_tinyint_signed() {
94        assert_eq!(map_type("tinyint", "tinyint").path, "i8");
95    }
96
97    #[test]
98    fn test_tinyint_unsigned() {
99        assert_eq!(map_type("tinyint", "tinyint unsigned").path, "u8");
100    }
101
102    #[test]
103    fn test_tinyint3_signed() {
104        assert_eq!(map_type("tinyint", "tinyint(3)").path, "i8");
105    }
106
107    #[test]
108    fn test_tinyint3_unsigned() {
109        assert_eq!(map_type("tinyint", "tinyint(3) unsigned").path, "u8");
110    }
111
112    // --- smallint ---
113
114    #[test]
115    fn test_smallint_signed() {
116        assert_eq!(map_type("smallint", "smallint").path, "i16");
117    }
118
119    #[test]
120    fn test_smallint_unsigned() {
121        assert_eq!(map_type("smallint", "smallint unsigned").path, "u16");
122    }
123
124    // --- int/mediumint ---
125
126    #[test]
127    fn test_int_signed() {
128        assert_eq!(map_type("int", "int").path, "i32");
129    }
130
131    #[test]
132    fn test_int_unsigned() {
133        assert_eq!(map_type("int", "int unsigned").path, "u32");
134    }
135
136    #[test]
137    fn test_mediumint_signed() {
138        assert_eq!(map_type("mediumint", "mediumint").path, "i32");
139    }
140
141    #[test]
142    fn test_mediumint_unsigned() {
143        assert_eq!(map_type("mediumint", "mediumint unsigned").path, "u32");
144    }
145
146    #[test]
147    fn test_int11_signed() {
148        assert_eq!(map_type("int", "int(11)").path, "i32");
149    }
150
151    #[test]
152    fn test_int11_unsigned() {
153        assert_eq!(map_type("int", "int(11) unsigned").path, "u32");
154    }
155
156    // --- bigint ---
157
158    #[test]
159    fn test_bigint_signed() {
160        assert_eq!(map_type("bigint", "bigint").path, "i64");
161    }
162
163    #[test]
164    fn test_bigint_unsigned() {
165        assert_eq!(map_type("bigint", "bigint unsigned").path, "u64");
166    }
167
168    #[test]
169    fn test_bigint20_signed() {
170        assert_eq!(map_type("bigint", "bigint(20)").path, "i64");
171    }
172
173    // --- floats ---
174
175    #[test]
176    fn test_float() {
177        assert_eq!(map_type("float", "float").path, "f32");
178    }
179
180    #[test]
181    fn test_double() {
182        assert_eq!(map_type("double", "double").path, "f64");
183    }
184
185    // --- decimal ---
186
187    #[test]
188    fn test_decimal() {
189        let rt = map_type("decimal", "decimal(10,2)");
190        assert_eq!(rt.path, "Decimal");
191        assert!(rt.needs_import.as_ref().unwrap().contains("rust_decimal"));
192    }
193
194    #[test]
195    fn test_numeric() {
196        let rt = map_type("numeric", "numeric");
197        assert_eq!(rt.path, "Decimal");
198        assert!(rt.needs_import.is_some());
199    }
200
201    // --- strings ---
202
203    #[test]
204    fn test_varchar() {
205        assert_eq!(map_type("varchar", "varchar(255)").path, "String");
206    }
207
208    #[test]
209    fn test_char() {
210        assert_eq!(map_type("char", "char(1)").path, "String");
211    }
212
213    #[test]
214    fn test_text() {
215        assert_eq!(map_type("text", "text").path, "String");
216    }
217
218    #[test]
219    fn test_tinytext() {
220        assert_eq!(map_type("tinytext", "tinytext").path, "String");
221    }
222
223    #[test]
224    fn test_mediumtext() {
225        assert_eq!(map_type("mediumtext", "mediumtext").path, "String");
226    }
227
228    #[test]
229    fn test_longtext() {
230        assert_eq!(map_type("longtext", "longtext").path, "String");
231    }
232
233    #[test]
234    fn test_set() {
235        assert_eq!(map_type("set", "set('a','b')").path, "String");
236    }
237
238    // --- binary ---
239
240    #[test]
241    fn test_binary() {
242        assert_eq!(map_type("binary", "binary(16)").path, "Vec<u8>");
243    }
244
245    #[test]
246    fn test_varbinary() {
247        assert_eq!(map_type("varbinary", "varbinary(255)").path, "Vec<u8>");
248    }
249
250    #[test]
251    fn test_blob() {
252        assert_eq!(map_type("blob", "blob").path, "Vec<u8>");
253    }
254
255    #[test]
256    fn test_tinyblob() {
257        assert_eq!(map_type("tinyblob", "tinyblob").path, "Vec<u8>");
258    }
259
260    // --- dates ---
261
262    #[test]
263    fn test_date() {
264        let rt = map_type("date", "date");
265        assert_eq!(rt.path, "NaiveDate");
266        assert!(rt.needs_import.as_ref().unwrap().contains("chrono"));
267    }
268
269    #[test]
270    fn test_time() {
271        let rt = map_type("time", "time");
272        assert_eq!(rt.path, "NaiveTime");
273        assert!(rt.needs_import.as_ref().unwrap().contains("chrono"));
274    }
275
276    #[test]
277    fn test_datetime() {
278        let rt = map_type("datetime", "datetime");
279        assert_eq!(rt.path, "NaiveDateTime");
280        assert!(rt.needs_import.is_some());
281    }
282
283    #[test]
284    fn test_timestamp() {
285        let rt = map_type("timestamp", "timestamp");
286        assert_eq!(rt.path, "DateTime<Utc>");
287        assert!(rt.needs_import.as_ref().unwrap().contains("chrono"));
288    }
289
290    // --- misc ---
291
292    #[test]
293    fn test_json() {
294        let rt = map_type("json", "json");
295        assert_eq!(rt.path, "Value");
296        assert!(rt.needs_import.as_ref().unwrap().contains("serde_json"));
297    }
298
299    #[test]
300    fn test_year() {
301        assert_eq!(map_type("year", "year").path, "i16");
302    }
303
304    #[test]
305    fn test_bit() {
306        assert_eq!(map_type("bit", "bit(1)").path, "Vec<u8>");
307    }
308
309    // --- enum placeholder ---
310
311    #[test]
312    fn test_enum_placeholder() {
313        assert_eq!(map_type("enum", "enum('a','b','c')").path, "String");
314    }
315
316    // --- case insensitive ---
317
318    #[test]
319    fn test_case_insensitive_int() {
320        assert_eq!(map_type("INT", "INT").path, "i32");
321    }
322
323    #[test]
324    fn test_case_insensitive_tinyint1() {
325        assert_eq!(map_type("TINYINT", "TINYINT(1)").path, "bool");
326    }
327
328    // --- fallback ---
329
330    #[test]
331    fn test_geometry_fallback() {
332        assert_eq!(map_type("geometry", "geometry").path, "String");
333    }
334
335    #[test]
336    fn test_point_fallback() {
337        assert_eq!(map_type("point", "point").path, "String");
338    }
339
340    // --- resolve_enum_type ---
341
342    #[test]
343    fn test_resolve_enum_users_status() {
344        assert_eq!(resolve_enum_type("users", "status"), "UsersStatus");
345    }
346
347    #[test]
348    fn test_resolve_enum_user_roles_role_type() {
349        assert_eq!(resolve_enum_type("user_roles", "role_type"), "UserRolesRoleType");
350    }
351
352    #[test]
353    fn test_resolve_enum_short_names() {
354        assert_eq!(resolve_enum_type("t", "c"), "TC");
355    }
356
357    #[test]
358    fn test_resolve_enum_order_items_size() {
359        assert_eq!(resolve_enum_type("order_items", "size"), "OrderItemsSize");
360    }
361}