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" => RustType::with_import("Decimal", "use rust_decimal::Decimal;"),
53        "varchar" | "char" | "text" | "tinytext" | "mediumtext" | "longtext" | "enum" | "set" => {
54            RustType::simple("String")
55        }
56        "binary" | "varbinary" | "blob" | "tinyblob" | "mediumblob" | "longblob" => {
57            RustType::simple("Vec<u8>")
58        }
59        "date" => match time_crate {
60            TimeCrate::Chrono => RustType::with_import("NaiveDate", "use chrono::NaiveDate;"),
61            TimeCrate::Time => RustType::with_import("Date", "use time::Date;"),
62        },
63        "time" => match time_crate {
64            TimeCrate::Chrono => RustType::with_import("NaiveTime", "use chrono::NaiveTime;"),
65            TimeCrate::Time => RustType::with_import("Time", "use time::Time;"),
66        },
67        "datetime" => match time_crate {
68            TimeCrate::Chrono => {
69                RustType::with_import("NaiveDateTime", "use chrono::NaiveDateTime;")
70            }
71            TimeCrate::Time => {
72                RustType::with_import("PrimitiveDateTime", "use time::PrimitiveDateTime;")
73            }
74        },
75        "timestamp" => match time_crate {
76            TimeCrate::Chrono => {
77                RustType::with_import("DateTime<Utc>", "use chrono::{DateTime, Utc};")
78            }
79            TimeCrate::Time => RustType::with_import("OffsetDateTime", "use time::OffsetDateTime;"),
80        },
81        "json" => RustType::with_import("Value", "use serde_json::Value;"),
82        "year" => RustType::simple("i16"),
83        "bit" => {
84            // BIT(1) is the idiomatic MySQL boolean. Treat anything wider as raw bytes.
85            if ct == "bit(1)" {
86                RustType::simple("bool")
87            } else {
88                RustType::simple("Vec<u8>")
89            }
90        }
91        "boolean" | "bool" => RustType::simple("bool"),
92        _ => RustType::simple("String"),
93    }
94}
95
96/// Resolve an inline MySQL ENUM column to the correct generated enum type name.
97pub fn resolve_enum_type(table_name: &str, column_name: &str) -> String {
98    let enum_name = format!("{}_{}", table_name, column_name);
99    enum_name.to_upper_camel_case()
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use crate::cli::TimeCrate;
106
107    // --- tinyint ---
108
109    #[test]
110    fn test_tinyint1_is_bool() {
111        assert_eq!(
112            map_type("tinyint", "tinyint(1)", TimeCrate::Chrono).path,
113            "bool"
114        );
115    }
116
117    #[test]
118    fn test_tinyint_signed() {
119        assert_eq!(map_type("tinyint", "tinyint", TimeCrate::Chrono).path, "i8");
120    }
121
122    #[test]
123    fn test_tinyint_unsigned() {
124        assert_eq!(
125            map_type("tinyint", "tinyint unsigned", TimeCrate::Chrono).path,
126            "u8"
127        );
128    }
129
130    #[test]
131    fn test_tinyint3_signed() {
132        assert_eq!(
133            map_type("tinyint", "tinyint(3)", TimeCrate::Chrono).path,
134            "i8"
135        );
136    }
137
138    #[test]
139    fn test_tinyint3_unsigned() {
140        assert_eq!(
141            map_type("tinyint", "tinyint(3) unsigned", TimeCrate::Chrono).path,
142            "u8"
143        );
144    }
145
146    // --- smallint ---
147
148    #[test]
149    fn test_smallint_signed() {
150        assert_eq!(
151            map_type("smallint", "smallint", TimeCrate::Chrono).path,
152            "i16"
153        );
154    }
155
156    #[test]
157    fn test_smallint_unsigned() {
158        assert_eq!(
159            map_type("smallint", "smallint unsigned", TimeCrate::Chrono).path,
160            "u16"
161        );
162    }
163
164    // --- int/mediumint ---
165
166    #[test]
167    fn test_int_signed() {
168        assert_eq!(map_type("int", "int", TimeCrate::Chrono).path, "i32");
169    }
170
171    #[test]
172    fn test_int_unsigned() {
173        assert_eq!(
174            map_type("int", "int unsigned", TimeCrate::Chrono).path,
175            "u32"
176        );
177    }
178
179    #[test]
180    fn test_mediumint_signed() {
181        assert_eq!(
182            map_type("mediumint", "mediumint", TimeCrate::Chrono).path,
183            "i32"
184        );
185    }
186
187    #[test]
188    fn test_mediumint_unsigned() {
189        assert_eq!(
190            map_type("mediumint", "mediumint unsigned", TimeCrate::Chrono).path,
191            "u32"
192        );
193    }
194
195    #[test]
196    fn test_int11_signed() {
197        assert_eq!(map_type("int", "int(11)", TimeCrate::Chrono).path, "i32");
198    }
199
200    #[test]
201    fn test_int11_unsigned() {
202        assert_eq!(
203            map_type("int", "int(11) unsigned", TimeCrate::Chrono).path,
204            "u32"
205        );
206    }
207
208    // --- bigint ---
209
210    #[test]
211    fn test_bigint_signed() {
212        assert_eq!(map_type("bigint", "bigint", TimeCrate::Chrono).path, "i64");
213    }
214
215    #[test]
216    fn test_bigint_unsigned() {
217        assert_eq!(
218            map_type("bigint", "bigint unsigned", TimeCrate::Chrono).path,
219            "u64"
220        );
221    }
222
223    #[test]
224    fn test_bigint20_signed() {
225        assert_eq!(
226            map_type("bigint", "bigint(20)", TimeCrate::Chrono).path,
227            "i64"
228        );
229    }
230
231    // --- floats ---
232
233    #[test]
234    fn test_float() {
235        assert_eq!(map_type("float", "float", TimeCrate::Chrono).path, "f32");
236    }
237
238    #[test]
239    fn test_double() {
240        assert_eq!(map_type("double", "double", TimeCrate::Chrono).path, "f64");
241    }
242
243    // --- decimal ---
244
245    #[test]
246    fn test_decimal() {
247        let rt = map_type("decimal", "decimal(10,2)", TimeCrate::Chrono);
248        assert_eq!(rt.path, "Decimal");
249        assert!(rt.needs_import.as_ref().unwrap().contains("rust_decimal"));
250    }
251
252    #[test]
253    fn test_numeric() {
254        let rt = map_type("numeric", "numeric", TimeCrate::Chrono);
255        assert_eq!(rt.path, "Decimal");
256        assert!(rt.needs_import.is_some());
257    }
258
259    // --- strings ---
260
261    #[test]
262    fn test_varchar() {
263        assert_eq!(
264            map_type("varchar", "varchar(255)", TimeCrate::Chrono).path,
265            "String"
266        );
267    }
268
269    #[test]
270    fn test_char() {
271        assert_eq!(
272            map_type("char", "char(1)", TimeCrate::Chrono).path,
273            "String"
274        );
275    }
276
277    #[test]
278    fn test_text() {
279        assert_eq!(map_type("text", "text", TimeCrate::Chrono).path, "String");
280    }
281
282    #[test]
283    fn test_tinytext() {
284        assert_eq!(
285            map_type("tinytext", "tinytext", TimeCrate::Chrono).path,
286            "String"
287        );
288    }
289
290    #[test]
291    fn test_mediumtext() {
292        assert_eq!(
293            map_type("mediumtext", "mediumtext", TimeCrate::Chrono).path,
294            "String"
295        );
296    }
297
298    #[test]
299    fn test_longtext() {
300        assert_eq!(
301            map_type("longtext", "longtext", TimeCrate::Chrono).path,
302            "String"
303        );
304    }
305
306    #[test]
307    fn test_set() {
308        assert_eq!(
309            map_type("set", "set('a','b')", TimeCrate::Chrono).path,
310            "String"
311        );
312    }
313
314    // --- binary ---
315
316    #[test]
317    fn test_binary() {
318        assert_eq!(
319            map_type("binary", "binary(16)", TimeCrate::Chrono).path,
320            "Vec<u8>"
321        );
322    }
323
324    #[test]
325    fn test_varbinary() {
326        assert_eq!(
327            map_type("varbinary", "varbinary(255)", TimeCrate::Chrono).path,
328            "Vec<u8>"
329        );
330    }
331
332    #[test]
333    fn test_blob() {
334        assert_eq!(map_type("blob", "blob", TimeCrate::Chrono).path, "Vec<u8>");
335    }
336
337    #[test]
338    fn test_tinyblob() {
339        assert_eq!(
340            map_type("tinyblob", "tinyblob", TimeCrate::Chrono).path,
341            "Vec<u8>"
342        );
343    }
344
345    // --- dates ---
346
347    #[test]
348    fn test_date() {
349        let rt = map_type("date", "date", TimeCrate::Chrono);
350        assert_eq!(rt.path, "NaiveDate");
351        assert!(rt.needs_import.as_ref().unwrap().contains("chrono"));
352    }
353
354    #[test]
355    fn test_time() {
356        let rt = map_type("time", "time", TimeCrate::Chrono);
357        assert_eq!(rt.path, "NaiveTime");
358        assert!(rt.needs_import.as_ref().unwrap().contains("chrono"));
359    }
360
361    #[test]
362    fn test_datetime() {
363        let rt = map_type("datetime", "datetime", TimeCrate::Chrono);
364        assert_eq!(rt.path, "NaiveDateTime");
365        assert!(rt.needs_import.is_some());
366    }
367
368    #[test]
369    fn test_timestamp() {
370        let rt = map_type("timestamp", "timestamp", TimeCrate::Chrono);
371        assert_eq!(rt.path, "DateTime<Utc>");
372        assert!(rt.needs_import.as_ref().unwrap().contains("chrono"));
373    }
374
375    // --- misc ---
376
377    #[test]
378    fn test_json() {
379        let rt = map_type("json", "json", TimeCrate::Chrono);
380        assert_eq!(rt.path, "Value");
381        assert!(rt.needs_import.as_ref().unwrap().contains("serde_json"));
382    }
383
384    #[test]
385    fn test_year() {
386        assert_eq!(map_type("year", "year", TimeCrate::Chrono).path, "i16");
387    }
388
389    #[test]
390    fn test_bit1_is_bool() {
391        assert_eq!(map_type("bit", "bit(1)", TimeCrate::Chrono).path, "bool");
392    }
393
394    #[test]
395    fn test_bit8_is_bytes() {
396        assert_eq!(map_type("bit", "bit(8)", TimeCrate::Chrono).path, "Vec<u8>");
397    }
398
399    #[test]
400    fn test_boolean_alias_is_bool() {
401        assert_eq!(
402            map_type("boolean", "boolean", TimeCrate::Chrono).path,
403            "bool"
404        );
405    }
406
407    // --- enum placeholder ---
408
409    #[test]
410    fn test_enum_placeholder() {
411        assert_eq!(
412            map_type("enum", "enum('a','b','c')", TimeCrate::Chrono).path,
413            "String"
414        );
415    }
416
417    // --- case insensitive ---
418
419    #[test]
420    fn test_case_insensitive_int() {
421        assert_eq!(map_type("INT", "INT", TimeCrate::Chrono).path, "i32");
422    }
423
424    #[test]
425    fn test_case_insensitive_tinyint1() {
426        assert_eq!(
427            map_type("TINYINT", "TINYINT(1)", TimeCrate::Chrono).path,
428            "bool"
429        );
430    }
431
432    // --- fallback ---
433
434    #[test]
435    fn test_geometry_fallback() {
436        assert_eq!(
437            map_type("geometry", "geometry", TimeCrate::Chrono).path,
438            "String"
439        );
440    }
441
442    #[test]
443    fn test_point_fallback() {
444        assert_eq!(map_type("point", "point", TimeCrate::Chrono).path, "String");
445    }
446
447    // --- resolve_enum_type ---
448
449    #[test]
450    fn test_resolve_enum_users_status() {
451        assert_eq!(resolve_enum_type("users", "status"), "UsersStatus");
452    }
453
454    #[test]
455    fn test_resolve_enum_user_roles_role_type() {
456        assert_eq!(
457            resolve_enum_type("user_roles", "role_type"),
458            "UserRolesRoleType"
459        );
460    }
461
462    #[test]
463    fn test_resolve_enum_short_names() {
464        assert_eq!(resolve_enum_type("t", "c"), "TC");
465    }
466
467    #[test]
468    fn test_resolve_enum_order_items_size() {
469        assert_eq!(resolve_enum_type("order_items", "size"), "OrderItemsSize");
470    }
471
472    // --- time crate ---
473
474    #[test]
475    fn test_timestamp_time_crate() {
476        let rt = map_type("timestamp", "timestamp", TimeCrate::Time);
477        assert_eq!(rt.path, "OffsetDateTime");
478        assert!(rt
479            .needs_import
480            .as_ref()
481            .unwrap()
482            .contains("time::OffsetDateTime"));
483    }
484
485    #[test]
486    fn test_datetime_time_crate() {
487        let rt = map_type("datetime", "datetime", TimeCrate::Time);
488        assert_eq!(rt.path, "PrimitiveDateTime");
489        assert!(rt
490            .needs_import
491            .as_ref()
492            .unwrap()
493            .contains("time::PrimitiveDateTime"));
494    }
495
496    #[test]
497    fn test_date_time_crate() {
498        let rt = map_type("date", "date", TimeCrate::Time);
499        assert_eq!(rt.path, "Date");
500        assert!(rt.needs_import.as_ref().unwrap().contains("time::Date"));
501    }
502
503    #[test]
504    fn test_time_time_crate() {
505        let rt = map_type("time", "time", TimeCrate::Time);
506        assert_eq!(rt.path, "Time");
507        assert!(rt.needs_import.as_ref().unwrap().contains("time::Time"));
508    }
509}