Skip to main content

sea_orm_codegen/entity/
column.rs

1use crate::{BigIntegerType, DateTimeCrate, util::escape_rust_keyword};
2use heck::{ToSnakeCase, ToUpperCamelCase};
3use proc_macro2::{Ident, TokenStream};
4use quote::{format_ident, quote};
5use sea_query::{ColumnDef, ColumnType, StringLen};
6use std::fmt::Write as FmtWrite;
7
8#[derive(Debug, Clone)]
9pub struct Column {
10    pub(crate) name: String,
11    pub(crate) col_type: ColumnType,
12    pub(crate) auto_increment: bool,
13    pub(crate) not_null: bool,
14    pub(crate) unique: bool,
15    pub(crate) unique_key: Option<String>,
16}
17
18#[derive(Debug, Default, Copy, Clone)]
19pub struct ColumnOption {
20    pub(crate) date_time_crate: DateTimeCrate,
21    pub(crate) big_integer_type: BigIntegerType,
22}
23
24impl Column {
25    pub fn get_name_snake_case(&self) -> Ident {
26        format_ident!("{}", escape_rust_keyword(self.name.to_snake_case()))
27    }
28
29    pub fn get_name_camel_case(&self) -> Ident {
30        format_ident!("{}", escape_rust_keyword(self.name.to_upper_camel_case()))
31    }
32
33    pub fn is_snake_case_name(&self) -> bool {
34        self.name.to_snake_case() == self.name
35    }
36
37    pub fn get_rs_type(&self, opt: &ColumnOption) -> TokenStream {
38        fn write_rs_type(col_type: &ColumnType, opt: &ColumnOption) -> String {
39            #[allow(unreachable_patterns)]
40            match col_type {
41                ColumnType::Char(_)
42                | ColumnType::String(_)
43                | ColumnType::Text
44                | ColumnType::Custom(_) => "String".to_owned(),
45                ColumnType::TinyInteger => "i8".to_owned(),
46                ColumnType::SmallInteger => "i16".to_owned(),
47                ColumnType::Integer => "i32".to_owned(),
48                ColumnType::BigInteger => match opt.big_integer_type {
49                    BigIntegerType::I64 => "i64",
50                    BigIntegerType::I32 => "i32",
51                }
52                .to_owned(),
53                ColumnType::TinyUnsigned => "u8".to_owned(),
54                ColumnType::SmallUnsigned => "u16".to_owned(),
55                ColumnType::Unsigned => "u32".to_owned(),
56                ColumnType::BigUnsigned => "u64".to_owned(),
57                ColumnType::Float => "f32".to_owned(),
58                ColumnType::Double => "f64".to_owned(),
59                ColumnType::Json | ColumnType::JsonBinary => "Json".to_owned(),
60                ColumnType::Date => match opt.date_time_crate {
61                    DateTimeCrate::Chrono => "Date".to_owned(),
62                    DateTimeCrate::Time => "TimeDate".to_owned(),
63                },
64                ColumnType::Time => match opt.date_time_crate {
65                    DateTimeCrate::Chrono => "Time".to_owned(),
66                    DateTimeCrate::Time => "TimeTime".to_owned(),
67                },
68                ColumnType::DateTime => match opt.date_time_crate {
69                    DateTimeCrate::Chrono => "DateTime".to_owned(),
70                    DateTimeCrate::Time => "TimeDateTime".to_owned(),
71                },
72                ColumnType::Timestamp => match opt.date_time_crate {
73                    DateTimeCrate::Chrono => "DateTimeUtc".to_owned(),
74                    DateTimeCrate::Time => "TimeDateTime".to_owned(),
75                },
76                ColumnType::TimestampWithTimeZone => match opt.date_time_crate {
77                    DateTimeCrate::Chrono => "DateTimeWithTimeZone".to_owned(),
78                    DateTimeCrate::Time => "TimeDateTimeWithTimeZone".to_owned(),
79                },
80                ColumnType::Decimal(_) | ColumnType::Money(_) => "Decimal".to_owned(),
81                ColumnType::Uuid => "Uuid".to_owned(),
82                ColumnType::Binary(_) | ColumnType::VarBinary(_) | ColumnType::Blob => {
83                    "Vec<u8>".to_owned()
84                }
85                ColumnType::Boolean => "bool".to_owned(),
86                ColumnType::Enum { name, .. } => name.to_string().to_upper_camel_case(),
87                ColumnType::Array(column_type) => {
88                    format!("Vec<{}>", write_rs_type(column_type, opt))
89                }
90                ColumnType::Vector(_) => "PgVector".to_owned(),
91                ColumnType::Bit(None | Some(1)) => "bool".to_owned(),
92                ColumnType::Bit(_) | ColumnType::VarBit(_) => "Vec<u8>".to_owned(),
93                ColumnType::Year => "i32".to_owned(),
94                ColumnType::Cidr | ColumnType::Inet => "IpNetwork".to_owned(),
95                ColumnType::Interval(_, _) | ColumnType::MacAddr | ColumnType::LTree => {
96                    "String".to_owned()
97                }
98                _ => unimplemented!(),
99            }
100        }
101        let ident: TokenStream = write_rs_type(&self.col_type, opt).parse().unwrap();
102        match self.not_null {
103            true => quote! { #ident },
104            false => quote! { Option<#ident> },
105        }
106    }
107
108    pub fn get_col_type_attrs(&self) -> Option<TokenStream> {
109        let col_type = match &self.col_type {
110            ColumnType::Float => Some("Float".to_owned()),
111            ColumnType::Double => Some("Double".to_owned()),
112            ColumnType::Decimal(Some((p, s))) => Some(format!("Decimal(Some(({p}, {s})))")),
113            ColumnType::Money(Some((p, s))) => Some(format!("Money(Some(({p}, {s})))")),
114            ColumnType::Text => Some("Text".to_owned()),
115            ColumnType::JsonBinary => Some("JsonBinary".to_owned()),
116            ColumnType::Custom(iden) => {
117                let ty = format!("custom(\"{iden}\")");
118                return Some(quote! ( ignore, column_type = #ty, select_as = "text" ));
119            }
120            ColumnType::Binary(s) => Some(format!("Binary({s})")),
121            ColumnType::VarBinary(s) => match s {
122                StringLen::N(s) => Some(format!("VarBinary(StringLen::N({s}))")),
123                StringLen::None => Some("VarBinary(StringLen::None)".to_owned()),
124                StringLen::Max => Some("VarBinary(StringLen::Max)".to_owned()),
125            },
126            ColumnType::Blob => Some("Blob".to_owned()),
127            ColumnType::Cidr => Some("Cidr".to_owned()),
128            _ => None,
129        };
130        col_type.map(|ty| quote! { column_type = #ty })
131    }
132
133    fn get_def_inner(&self, enum_type_ident: Option<&Ident>) -> TokenStream {
134        fn write_col_def(col_type: &ColumnType, enum_type_ident: Option<&Ident>) -> TokenStream {
135            match col_type {
136                ColumnType::Char(s) => match s {
137                    Some(s) => quote! { ColumnType::Char(Some(#s)) },
138                    None => quote! { ColumnType::Char(None) },
139                },
140                ColumnType::String(s) => match s {
141                    StringLen::N(s) => quote! { ColumnType::String(StringLen::N(#s)) },
142                    StringLen::None => quote! { ColumnType::String(StringLen::None) },
143                    StringLen::Max => quote! { ColumnType::String(StringLen::Max) },
144                },
145                ColumnType::Text => quote! { ColumnType::Text },
146                ColumnType::TinyInteger => quote! { ColumnType::TinyInteger },
147                ColumnType::SmallInteger => quote! { ColumnType::SmallInteger },
148                ColumnType::Integer => quote! { ColumnType::Integer },
149                ColumnType::BigInteger => quote! { ColumnType::BigInteger },
150                ColumnType::TinyUnsigned => quote! { ColumnType::TinyUnsigned },
151                ColumnType::SmallUnsigned => quote! { ColumnType::SmallUnsigned },
152                ColumnType::Unsigned => quote! { ColumnType::Unsigned },
153                ColumnType::BigUnsigned => quote! { ColumnType::BigUnsigned },
154                ColumnType::Float => quote! { ColumnType::Float },
155                ColumnType::Double => quote! { ColumnType::Double },
156                ColumnType::Decimal(s) => match s {
157                    Some((s1, s2)) => quote! { ColumnType::Decimal(Some((#s1, #s2))) },
158                    None => quote! { ColumnType::Decimal(None) },
159                },
160                ColumnType::DateTime => quote! { ColumnType::DateTime },
161                ColumnType::Timestamp => quote! { ColumnType::Timestamp },
162                ColumnType::TimestampWithTimeZone => {
163                    quote! { ColumnType::TimestampWithTimeZone }
164                }
165                ColumnType::Time => quote! { ColumnType::Time },
166                ColumnType::Date => quote! { ColumnType::Date },
167                ColumnType::Binary(s) => {
168                    quote! { ColumnType::Binary(#s) }
169                }
170                ColumnType::VarBinary(s) => match s {
171                    StringLen::N(s) => quote! { ColumnType::VarBinary(StringLen::N(#s)) },
172                    StringLen::None => quote! { ColumnType::VarBinary(StringLen::None) },
173                    StringLen::Max => quote! { ColumnType::VarBinary(StringLen::Max) },
174                },
175                ColumnType::Blob => quote! { ColumnType::Blob },
176                ColumnType::Boolean => quote! { ColumnType::Boolean },
177                ColumnType::Money(s) => match s {
178                    Some((s1, s2)) => quote! { ColumnType::Money(Some((#s1, #s2))) },
179                    None => quote! { ColumnType::Money(None) },
180                },
181                ColumnType::Json => quote! { ColumnType::Json },
182                ColumnType::JsonBinary => quote! { ColumnType::JsonBinary },
183                ColumnType::Uuid => quote! { ColumnType::Uuid },
184                ColumnType::Cidr => quote! { ColumnType::Cidr },
185                ColumnType::Inet => quote! { ColumnType::Inet },
186                ColumnType::Custom(s) => {
187                    let s = s.to_string();
188                    quote! { ColumnType::custom(#s) }
189                }
190                ColumnType::Enum { name, .. } => {
191                    let enum_ident = enum_type_ident.cloned().unwrap_or_else(|| {
192                        format_ident!("{}", name.to_string().to_upper_camel_case())
193                    });
194                    quote! {
195                        #enum_ident::db_type()
196                            .get_column_type()
197                            .to_owned()
198                    }
199                }
200                ColumnType::Array(column_type) => {
201                    let column_type = write_col_def(column_type, enum_type_ident);
202                    quote! { ColumnType::Array(RcOrArc::new(#column_type)) }
203                }
204                ColumnType::Vector(size) => match size {
205                    Some(size) => quote! { ColumnType::Vector(Some(#size)) },
206                    None => quote! { ColumnType::Vector(None) },
207                },
208                ColumnType::Year => quote! { ColumnType::Year },
209                ColumnType::Bit(s) => match s {
210                    Some(s) => quote! { ColumnType::Bit(Some(#s)) },
211                    None => quote! { ColumnType::Bit(None) },
212                },
213                ColumnType::VarBit(s) => quote! { ColumnType::VarBit(#s) },
214                ColumnType::MacAddr => quote! { ColumnType::MacAddr },
215                ColumnType::LTree => quote! { ColumnType::LTree },
216                ColumnType::Interval(_, _) => quote! { ColumnType::Interval(None, None) },
217                #[allow(unreachable_patterns)]
218                _ => unimplemented!(),
219            }
220        }
221        let mut col_def = write_col_def(&self.col_type, enum_type_ident);
222        col_def.extend(quote! {
223            .def()
224        });
225        if !self.not_null {
226            col_def.extend(quote! {
227                .null()
228            });
229        }
230        if self.unique {
231            col_def.extend(quote! {
232                .unique()
233            });
234        }
235        col_def
236    }
237
238    pub fn get_def(&self) -> TokenStream {
239        self.get_def_inner(None)
240    }
241
242    pub fn get_def_with_enum_type_ident(&self, enum_type_ident: &Ident) -> TokenStream {
243        self.get_def_inner(Some(enum_type_ident))
244    }
245
246    pub fn get_info(&self, opt: &ColumnOption) -> String {
247        let mut info = String::new();
248        let type_info = self.get_rs_type(opt).to_string().replace(' ', "");
249        let col_info = self.col_info();
250        write!(
251            &mut info,
252            "Column `{}`: {}{}",
253            self.name, type_info, col_info
254        )
255        .unwrap();
256        info
257    }
258
259    fn col_info(&self) -> String {
260        let mut info = String::new();
261        if self.auto_increment {
262            write!(&mut info, ", auto_increment").unwrap();
263        }
264        if self.not_null {
265            write!(&mut info, ", not_null").unwrap();
266        }
267        if self.unique {
268            write!(&mut info, ", unique").unwrap();
269        }
270        info
271    }
272
273    pub fn get_serde_attribute(
274        &self,
275        is_primary_key: bool,
276        serde_skip_deserializing_primary_key: bool,
277        serde_skip_hidden_column: bool,
278    ) -> TokenStream {
279        if self.name.starts_with('_') && serde_skip_hidden_column {
280            quote! {
281                #[serde(skip)]
282            }
283        } else if serde_skip_deserializing_primary_key && is_primary_key {
284            quote! {
285                #[serde(skip_deserializing)]
286            }
287        } else {
288            quote! {}
289        }
290    }
291
292    pub fn get_inner_col_type(&self) -> &ColumnType {
293        match &self.col_type {
294            ColumnType::Array(inner_col_type) => inner_col_type.as_ref(),
295            _ => &self.col_type,
296        }
297    }
298}
299
300impl From<ColumnDef> for Column {
301    fn from(col_def: ColumnDef) -> Self {
302        (&col_def).into()
303    }
304}
305
306impl From<&ColumnDef> for Column {
307    fn from(col_def: &ColumnDef) -> Self {
308        let name = col_def.get_column_name();
309        let col_type = match col_def.get_column_type() {
310            Some(ty) => ty.clone(),
311            None => panic!("ColumnType should not be empty"),
312        };
313        let auto_increment = col_def.get_column_spec().auto_increment;
314        let not_null = match col_def.get_column_spec().nullable {
315            Some(nullable) => !nullable,
316            None => false,
317        };
318        let unique = col_def.get_column_spec().unique;
319        Self {
320            name,
321            col_type,
322            auto_increment,
323            not_null,
324            unique,
325            unique_key: None,
326        }
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use crate::{Column, ColumnOption, DateTimeCrate};
333    use proc_macro2::TokenStream;
334    use quote::quote;
335    use sea_query::{Alias, ColumnDef, ColumnType, SeaRc, StringLen};
336
337    fn date_time_crate_chrono() -> ColumnOption {
338        ColumnOption {
339            date_time_crate: DateTimeCrate::Chrono,
340            big_integer_type: Default::default(),
341        }
342    }
343
344    fn date_time_crate_time() -> ColumnOption {
345        ColumnOption {
346            date_time_crate: DateTimeCrate::Time,
347            big_integer_type: Default::default(),
348        }
349    }
350
351    fn setup() -> Vec<Column> {
352        macro_rules! make_col {
353            ($name:expr, $col_type:expr) => {
354                Column {
355                    name: $name.to_owned(),
356                    col_type: $col_type,
357                    auto_increment: false,
358                    not_null: false,
359                    unique: false,
360                    unique_key: None,
361                }
362            };
363        }
364        vec![
365            make_col!("id", ColumnType::String(StringLen::N(255))),
366            make_col!("id", ColumnType::String(StringLen::None)),
367            make_col!(
368                "cake_id",
369                ColumnType::Custom(SeaRc::new(Alias::new("cus_col")))
370            ),
371            make_col!("CakeId", ColumnType::TinyInteger),
372            make_col!("CakeId", ColumnType::TinyUnsigned),
373            make_col!("CakeId", ColumnType::SmallInteger),
374            make_col!("CakeId", ColumnType::SmallUnsigned),
375            make_col!("CakeId", ColumnType::Integer),
376            make_col!("CakeId", ColumnType::Unsigned),
377            make_col!("CakeFillingId", ColumnType::BigInteger),
378            make_col!("CakeFillingId", ColumnType::BigUnsigned),
379            make_col!("cake-filling-id", ColumnType::Float),
380            make_col!("CAKE_FILLING_ID", ColumnType::Double),
381            make_col!("CAKE-FILLING-ID", ColumnType::Binary(10)),
382            make_col!("CAKE-FILLING-ID", ColumnType::VarBinary(StringLen::None)),
383            make_col!("CAKE-FILLING-ID", ColumnType::VarBinary(StringLen::N(10))),
384            make_col!("CAKE-FILLING-ID", ColumnType::VarBinary(StringLen::Max)),
385            make_col!("CAKE", ColumnType::Boolean),
386            make_col!("date", ColumnType::Date),
387            make_col!("time", ColumnType::Time),
388            make_col!("date_time", ColumnType::DateTime),
389            make_col!("timestamp", ColumnType::Timestamp),
390            make_col!("timestamp_tz", ColumnType::TimestampWithTimeZone),
391        ]
392    }
393
394    #[test]
395    fn test_get_name_snake_case() {
396        let columns = setup();
397        let snack_cases = vec![
398            "id",
399            "id",
400            "cake_id",
401            "cake_id",
402            "cake_id",
403            "cake_id",
404            "cake_id",
405            "cake_id",
406            "cake_id",
407            "cake_filling_id",
408            "cake_filling_id",
409            "cake_filling_id",
410            "cake_filling_id",
411            "cake_filling_id",
412            "cake_filling_id",
413            "cake_filling_id",
414            "cake_filling_id",
415            "cake",
416            "date",
417            "time",
418            "date_time",
419            "timestamp",
420            "timestamp_tz",
421        ];
422        for (col, snack_case) in columns.into_iter().zip(snack_cases) {
423            assert_eq!(col.get_name_snake_case().to_string(), snack_case);
424        }
425    }
426
427    #[test]
428    fn test_get_name_camel_case() {
429        let columns = setup();
430        let camel_cases = vec![
431            "Id",
432            "Id",
433            "CakeId",
434            "CakeId",
435            "CakeId",
436            "CakeId",
437            "CakeId",
438            "CakeId",
439            "CakeId",
440            "CakeFillingId",
441            "CakeFillingId",
442            "CakeFillingId",
443            "CakeFillingId",
444            "CakeFillingId",
445            "CakeFillingId",
446            "CakeFillingId",
447            "CakeFillingId",
448            "Cake",
449            "Date",
450            "Time",
451            "DateTime",
452            "Timestamp",
453            "TimestampTz",
454        ];
455        for (col, camel_case) in columns.into_iter().zip(camel_cases) {
456            assert_eq!(col.get_name_camel_case().to_string(), camel_case);
457        }
458    }
459
460    #[test]
461    fn test_get_rs_type_with_chrono() {
462        let columns = setup();
463        let rs_types = vec![
464            "String",
465            "String",
466            "String",
467            "i8",
468            "u8",
469            "i16",
470            "u16",
471            "i32",
472            "u32",
473            "i64",
474            "u64",
475            "f32",
476            "f64",
477            "Vec<u8>",
478            "Vec<u8>",
479            "Vec<u8>",
480            "Vec<u8>",
481            "bool",
482            "Date",
483            "Time",
484            "DateTime",
485            "DateTimeUtc",
486            "DateTimeWithTimeZone",
487        ];
488        for (mut col, rs_type) in columns.into_iter().zip(rs_types) {
489            let rs_type: TokenStream = rs_type.parse().unwrap();
490
491            col.not_null = true;
492            assert_eq!(
493                col.get_rs_type(&date_time_crate_chrono()).to_string(),
494                quote!(#rs_type).to_string()
495            );
496
497            col.not_null = false;
498            assert_eq!(
499                col.get_rs_type(&date_time_crate_chrono()).to_string(),
500                quote!(Option<#rs_type>).to_string()
501            );
502        }
503    }
504
505    #[test]
506    fn test_get_rs_type_with_time() {
507        let columns = setup();
508        let rs_types = vec![
509            "String",
510            "String",
511            "String",
512            "i8",
513            "u8",
514            "i16",
515            "u16",
516            "i32",
517            "u32",
518            "i64",
519            "u64",
520            "f32",
521            "f64",
522            "Vec<u8>",
523            "Vec<u8>",
524            "Vec<u8>",
525            "Vec<u8>",
526            "bool",
527            "TimeDate",
528            "TimeTime",
529            "TimeDateTime",
530            "TimeDateTime",
531            "TimeDateTimeWithTimeZone",
532        ];
533        for (mut col, rs_type) in columns.into_iter().zip(rs_types) {
534            let rs_type: TokenStream = rs_type.parse().unwrap();
535
536            col.not_null = true;
537            assert_eq!(
538                col.get_rs_type(&date_time_crate_time()).to_string(),
539                quote!(#rs_type).to_string()
540            );
541
542            col.not_null = false;
543            assert_eq!(
544                col.get_rs_type(&date_time_crate_time()).to_string(),
545                quote!(Option<#rs_type>).to_string()
546            );
547        }
548    }
549
550    #[test]
551    fn test_get_def() {
552        let columns = setup();
553        let col_defs = vec![
554            "ColumnType::String(StringLen::N(255u32)).def()",
555            "ColumnType::String(StringLen::None).def()",
556            "ColumnType::custom(\"cus_col\").def()",
557            "ColumnType::TinyInteger.def()",
558            "ColumnType::TinyUnsigned.def()",
559            "ColumnType::SmallInteger.def()",
560            "ColumnType::SmallUnsigned.def()",
561            "ColumnType::Integer.def()",
562            "ColumnType::Unsigned.def()",
563            "ColumnType::BigInteger.def()",
564            "ColumnType::BigUnsigned.def()",
565            "ColumnType::Float.def()",
566            "ColumnType::Double.def()",
567            "ColumnType::Binary(10u32).def()",
568            "ColumnType::VarBinary(StringLen::None).def()",
569            "ColumnType::VarBinary(StringLen::N(10u32)).def()",
570            "ColumnType::VarBinary(StringLen::Max).def()",
571            "ColumnType::Boolean.def()",
572            "ColumnType::Date.def()",
573            "ColumnType::Time.def()",
574            "ColumnType::DateTime.def()",
575            "ColumnType::Timestamp.def()",
576            "ColumnType::TimestampWithTimeZone.def()",
577        ];
578        for (mut col, col_def) in columns.into_iter().zip(col_defs) {
579            let mut col_def: TokenStream = col_def.parse().unwrap();
580
581            col.not_null = true;
582            assert_eq!(col.get_def().to_string(), col_def.to_string());
583
584            col.not_null = false;
585            col_def.extend(quote!(.null()));
586            assert_eq!(col.get_def().to_string(), col_def.to_string());
587
588            col.unique = true;
589            col_def.extend(quote!(.unique()));
590            assert_eq!(col.get_def().to_string(), col_def.to_string());
591        }
592    }
593
594    #[test]
595    fn test_get_col_type_attrs_money_and_decimal() {
596        let make_col = |col_type| Column {
597            name: "amount".to_owned(),
598            col_type,
599            auto_increment: false,
600            not_null: true,
601            unique: false,
602            unique_key: None,
603        };
604
605        // Money and Decimal both carry an `Option<(precision, scale)>`, so the
606        // generated `column_type` string must keep the inner tuple parentheses.
607        // Without them the emitted `ColumnType::Money(Some(10, 2))` is `Some`
608        // called with two arguments, which does not compile.
609        assert_eq!(
610            make_col(ColumnType::Money(Some((10, 2))))
611                .get_col_type_attrs()
612                .unwrap()
613                .to_string(),
614            quote! { column_type = "Money(Some((10, 2)))" }.to_string()
615        );
616        assert_eq!(
617            make_col(ColumnType::Decimal(Some((10, 2))))
618                .get_col_type_attrs()
619                .unwrap()
620                .to_string(),
621            quote! { column_type = "Decimal(Some((10, 2)))" }.to_string()
622        );
623    }
624
625    #[test]
626    fn test_get_info() {
627        let column: Column = ColumnDef::new(Alias::new("id")).string().to_owned().into();
628        assert_eq!(
629            column.get_info(&date_time_crate_chrono()).as_str(),
630            "Column `id`: Option<String>"
631        );
632
633        let column: Column = ColumnDef::new(Alias::new("id"))
634            .string()
635            .not_null()
636            .to_owned()
637            .into();
638        assert_eq!(
639            column.get_info(&date_time_crate_chrono()).as_str(),
640            "Column `id`: String, not_null"
641        );
642
643        let column: Column = ColumnDef::new(Alias::new("id"))
644            .string()
645            .not_null()
646            .unique_key()
647            .to_owned()
648            .into();
649        assert_eq!(
650            column.get_info(&date_time_crate_chrono()).as_str(),
651            "Column `id`: String, not_null, unique"
652        );
653
654        let column: Column = ColumnDef::new(Alias::new("id"))
655            .string()
656            .not_null()
657            .unique_key()
658            .auto_increment()
659            .to_owned()
660            .into();
661        assert_eq!(
662            column.get_info(&date_time_crate_chrono()).as_str(),
663            "Column `id`: String, auto_increment, not_null, unique"
664        );
665
666        let column: Column = ColumnDef::new(Alias::new("date_field"))
667            .date()
668            .not_null()
669            .to_owned()
670            .into();
671        assert_eq!(
672            column.get_info(&date_time_crate_chrono()).as_str(),
673            "Column `date_field`: Date, not_null"
674        );
675
676        let column: Column = ColumnDef::new(Alias::new("date_field"))
677            .date()
678            .not_null()
679            .to_owned()
680            .into();
681        assert_eq!(
682            column.get_info(&date_time_crate_time()).as_str(),
683            "Column `date_field`: TimeDate, not_null"
684        );
685
686        let column: Column = ColumnDef::new(Alias::new("time_field"))
687            .time()
688            .not_null()
689            .to_owned()
690            .into();
691        assert_eq!(
692            column.get_info(&date_time_crate_chrono()).as_str(),
693            "Column `time_field`: Time, not_null"
694        );
695
696        let column: Column = ColumnDef::new(Alias::new("time_field"))
697            .time()
698            .not_null()
699            .to_owned()
700            .into();
701        assert_eq!(
702            column.get_info(&date_time_crate_time()).as_str(),
703            "Column `time_field`: TimeTime, not_null"
704        );
705
706        let column: Column = ColumnDef::new(Alias::new("date_time_field"))
707            .date_time()
708            .not_null()
709            .to_owned()
710            .into();
711        assert_eq!(
712            column.get_info(&date_time_crate_chrono()).as_str(),
713            "Column `date_time_field`: DateTime, not_null"
714        );
715
716        let column: Column = ColumnDef::new(Alias::new("date_time_field"))
717            .date_time()
718            .not_null()
719            .to_owned()
720            .into();
721        assert_eq!(
722            column.get_info(&date_time_crate_time()).as_str(),
723            "Column `date_time_field`: TimeDateTime, not_null"
724        );
725
726        let column: Column = ColumnDef::new(Alias::new("timestamp_field"))
727            .timestamp()
728            .not_null()
729            .to_owned()
730            .into();
731        assert_eq!(
732            column.get_info(&date_time_crate_chrono()).as_str(),
733            "Column `timestamp_field`: DateTimeUtc, not_null"
734        );
735
736        let column: Column = ColumnDef::new(Alias::new("timestamp_field"))
737            .timestamp()
738            .not_null()
739            .to_owned()
740            .into();
741        assert_eq!(
742            column.get_info(&date_time_crate_time()).as_str(),
743            "Column `timestamp_field`: TimeDateTime, not_null"
744        );
745
746        let column: Column = ColumnDef::new(Alias::new("timestamp_with_timezone_field"))
747            .timestamp_with_time_zone()
748            .not_null()
749            .to_owned()
750            .into();
751        assert_eq!(
752            column.get_info(&date_time_crate_chrono()).as_str(),
753            "Column `timestamp_with_timezone_field`: DateTimeWithTimeZone, not_null"
754        );
755
756        let column: Column = ColumnDef::new(Alias::new("timestamp_with_timezone_field"))
757            .timestamp_with_time_zone()
758            .not_null()
759            .to_owned()
760            .into();
761        assert_eq!(
762            column.get_info(&date_time_crate_time()).as_str(),
763            "Column `timestamp_with_timezone_field`: TimeDateTimeWithTimeZone, not_null"
764        );
765    }
766
767    #[test]
768    fn test_from_column_def() {
769        let column: Column = ColumnDef::new(Alias::new("id")).string().to_owned().into();
770        assert_eq!(
771            column.get_def().to_string(),
772            quote! {
773                ColumnType::String(StringLen::None).def().null()
774            }
775            .to_string()
776        );
777
778        let column: Column = ColumnDef::new(Alias::new("id"))
779            .string()
780            .not_null()
781            .to_owned()
782            .into();
783        assert!(column.not_null);
784
785        let column: Column = ColumnDef::new(Alias::new("id"))
786            .string()
787            .unique_key()
788            .not_null()
789            .to_owned()
790            .into();
791        assert!(column.unique);
792        assert!(column.not_null);
793
794        let column: Column = ColumnDef::new(Alias::new("id"))
795            .string()
796            .auto_increment()
797            .unique_key()
798            .not_null()
799            .to_owned()
800            .into();
801        assert!(column.auto_increment);
802        assert!(column.unique);
803        assert!(column.not_null);
804    }
805
806    // Regression test for #3092: these column types are handled by the Rust-type
807    // mapping but used to hit `unimplemented!()` in `write_col_def`, panicking
808    // `generate entity --expanded-format`.
809    #[test]
810    fn test_get_def_extended_column_types() {
811        let col = |col_type| Column {
812            name: "c".to_owned(),
813            col_type,
814            auto_increment: false,
815            not_null: true,
816            unique: false,
817            unique_key: None,
818        };
819        let cases = [
820            (ColumnType::Year, "ColumnType::Year.def()"),
821            (
822                ColumnType::Bit(Some(8)),
823                "ColumnType::Bit(Some(8u32)).def()",
824            ),
825            (ColumnType::Bit(None), "ColumnType::Bit(None).def()"),
826            (ColumnType::VarBit(16), "ColumnType::VarBit(16u32).def()"),
827            (ColumnType::MacAddr, "ColumnType::MacAddr.def()"),
828            (ColumnType::LTree, "ColumnType::LTree.def()"),
829            (
830                ColumnType::Interval(None, None),
831                "ColumnType::Interval(None, None).def()",
832            ),
833        ];
834        for (col_type, expected) in cases {
835            let expected: TokenStream = expected.parse().unwrap();
836            assert_eq!(col(col_type).get_def().to_string(), expected.to_string());
837        }
838    }
839}