Skip to main content

sqlx_gen/typemap/
mysql.rs

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