1use serde::{Deserialize, Serialize};
2
3use crate::schema::{
4 foreign_key::ForeignKeySyntax,
5 names::ColumnName,
6 primary_key::PrimaryKeySyntax,
7 str_or_bool::{StrOrBoolOrArray, StringOrBool},
8};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12#[serde(rename_all = "snake_case")]
13pub struct ColumnDef {
14 pub name: ColumnName,
15 pub r#type: ColumnType,
16 pub nullable: bool,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub default: Option<StringOrBool>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub comment: Option<String>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub primary_key: Option<PrimaryKeySyntax>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub unique: Option<StrOrBoolOrArray>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub index: Option<StrOrBoolOrArray>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub foreign_key: Option<ForeignKeySyntax>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
33#[serde(rename_all = "snake_case", untagged)]
34pub enum ColumnType {
35 Simple(SimpleColumnType),
36 Complex(ComplexColumnType),
37}
38
39impl ColumnType {
40 pub fn supports_auto_increment(&self) -> bool {
42 match self {
43 ColumnType::Simple(ty) => ty.supports_auto_increment(),
44 ColumnType::Complex(_) => false,
45 }
46 }
47
48 pub fn requires_migration(&self, other: &ColumnType) -> bool {
52 match (self, other) {
53 (
54 ColumnType::Complex(ComplexColumnType::Enum {
55 values: values1, ..
56 }),
57 ColumnType::Complex(ComplexColumnType::Enum {
58 values: values2, ..
59 }),
60 ) => {
61 if values1.is_integer() && values2.is_integer() {
63 false
64 } else {
65 values1 != values2
70 }
71 }
72 _ => self != other,
73 }
74 }
75
76 pub fn to_rust_type(&self, nullable: bool) -> String {
78 let base = match self {
79 ColumnType::Simple(ty) => match ty {
80 SimpleColumnType::SmallInt => "i16".to_string(),
81 SimpleColumnType::Integer => "i32".to_string(),
82 SimpleColumnType::BigInt => "i64".to_string(),
83 SimpleColumnType::Real => "f32".to_string(),
84 SimpleColumnType::DoublePrecision => "f64".to_string(),
85 SimpleColumnType::Text => "String".to_string(),
86 SimpleColumnType::Boolean => "bool".to_string(),
87 SimpleColumnType::Date => "Date".to_string(),
88 SimpleColumnType::Time => "Time".to_string(),
89 SimpleColumnType::Timestamp => "DateTime".to_string(),
90 SimpleColumnType::Timestamptz => "DateTimeWithTimeZone".to_string(),
91 SimpleColumnType::Interval => "String".to_string(),
92 SimpleColumnType::Bytea => "Vec<u8>".to_string(),
93 SimpleColumnType::Uuid => "Uuid".to_string(),
94 SimpleColumnType::Json => "Json".to_string(),
95 SimpleColumnType::Inet | SimpleColumnType::Cidr => "String".to_string(),
97 SimpleColumnType::Macaddr => "String".to_string(),
98 SimpleColumnType::Xml => "String".to_string(),
99 },
100 ColumnType::Complex(ty) => match ty {
101 ComplexColumnType::Varchar { .. } => "String".to_string(),
102 ComplexColumnType::Numeric { .. } => "Decimal".to_string(),
103 ComplexColumnType::Char { .. } => "String".to_string(),
104 ComplexColumnType::Custom { .. } => "String".to_string(), ComplexColumnType::Enum { .. } => "String".to_string(),
106 },
107 };
108
109 if nullable {
110 format!("Option<{}>", base)
111 } else {
112 base
113 }
114 }
115
116 pub fn to_display_string(&self) -> String {
119 match self {
120 ColumnType::Simple(ty) => ty.to_display_string(),
121 ColumnType::Complex(ty) => ty.to_display_string(),
122 }
123 }
124
125 pub fn default_fill_value(&self) -> &'static str {
128 match self {
129 ColumnType::Simple(ty) => ty.default_fill_value(),
130 ColumnType::Complex(ty) => ty.default_fill_value(),
131 }
132 }
133
134 pub fn enum_variant_names(&self) -> Option<Vec<String>> {
137 match self {
138 ColumnType::Complex(ComplexColumnType::Enum { values, .. }) => Some(
139 values
140 .variant_names()
141 .into_iter()
142 .map(String::from)
143 .collect(),
144 ),
145 _ => None,
146 }
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
151#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
152#[serde(rename_all = "snake_case")]
153pub enum SimpleColumnType {
154 SmallInt,
155 Integer,
156 BigInt,
157 Real,
158 DoublePrecision,
159
160 Text,
162
163 Boolean,
165
166 Date,
168 Time,
169 Timestamp,
170 Timestamptz,
171 Interval,
172
173 Bytea,
175
176 Uuid,
178
179 Json,
181 Inet,
185 Cidr,
186 Macaddr,
187
188 Xml,
190}
191
192impl SimpleColumnType {
193 pub fn supports_auto_increment(&self) -> bool {
195 matches!(
196 self,
197 SimpleColumnType::SmallInt | SimpleColumnType::Integer | SimpleColumnType::BigInt
198 )
199 }
200
201 pub fn to_display_string(&self) -> String {
203 match self {
204 SimpleColumnType::SmallInt => "smallint".to_string(),
205 SimpleColumnType::Integer => "integer".to_string(),
206 SimpleColumnType::BigInt => "bigint".to_string(),
207 SimpleColumnType::Real => "real".to_string(),
208 SimpleColumnType::DoublePrecision => "double precision".to_string(),
209 SimpleColumnType::Text => "text".to_string(),
210 SimpleColumnType::Boolean => "boolean".to_string(),
211 SimpleColumnType::Date => "date".to_string(),
212 SimpleColumnType::Time => "time".to_string(),
213 SimpleColumnType::Timestamp => "timestamp".to_string(),
214 SimpleColumnType::Timestamptz => "timestamptz".to_string(),
215 SimpleColumnType::Interval => "interval".to_string(),
216 SimpleColumnType::Bytea => "bytea".to_string(),
217 SimpleColumnType::Uuid => "uuid".to_string(),
218 SimpleColumnType::Json => "json".to_string(),
219 SimpleColumnType::Inet => "inet".to_string(),
220 SimpleColumnType::Cidr => "cidr".to_string(),
221 SimpleColumnType::Macaddr => "macaddr".to_string(),
222 SimpleColumnType::Xml => "xml".to_string(),
223 }
224 }
225
226 pub fn default_fill_value(&self) -> &'static str {
229 match self {
230 SimpleColumnType::SmallInt | SimpleColumnType::Integer | SimpleColumnType::BigInt => {
231 "0"
232 }
233 SimpleColumnType::Real | SimpleColumnType::DoublePrecision => "0.0",
234 SimpleColumnType::Boolean => "false",
235 SimpleColumnType::Text => "''",
236 SimpleColumnType::Date => "'1970-01-01'",
237 SimpleColumnType::Time => "'00:00:00'",
238 SimpleColumnType::Timestamp | SimpleColumnType::Timestamptz => "CURRENT_TIMESTAMP",
239 SimpleColumnType::Interval => "'0'",
240 SimpleColumnType::Uuid => "'00000000-0000-0000-0000-000000000000'",
241 SimpleColumnType::Json => "'{}'",
242 SimpleColumnType::Bytea => "''",
243 SimpleColumnType::Inet | SimpleColumnType::Cidr => "'0.0.0.0'",
244 SimpleColumnType::Macaddr => "'00:00:00:00:00:00'",
245 SimpleColumnType::Xml => "'<xml/>'",
246 }
247 }
248}
249
250#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
252#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
253pub struct NumValue {
254 pub name: String,
255 pub value: i32,
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
260#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
261#[serde(untagged)]
262pub enum EnumValues {
263 String(Vec<String>),
264 Integer(Vec<NumValue>),
265}
266
267impl EnumValues {
268 pub fn is_string(&self) -> bool {
270 matches!(self, EnumValues::String(_))
271 }
272
273 pub fn is_integer(&self) -> bool {
275 matches!(self, EnumValues::Integer(_))
276 }
277
278 pub fn variant_names(&self) -> Vec<&str> {
280 match self {
281 EnumValues::String(values) => values.iter().map(|s| s.as_str()).collect(),
282 EnumValues::Integer(values) => values.iter().map(|v| v.name.as_str()).collect(),
283 }
284 }
285
286 pub fn len(&self) -> usize {
288 match self {
289 EnumValues::String(values) => values.len(),
290 EnumValues::Integer(values) => values.len(),
291 }
292 }
293
294 pub fn is_empty(&self) -> bool {
296 self.len() == 0
297 }
298
299 pub fn to_sql_values(&self) -> Vec<String> {
302 match self {
303 EnumValues::String(values) => values
304 .iter()
305 .map(|s| format!("'{}'", s.replace('\'', "''")))
306 .collect(),
307 EnumValues::Integer(values) => values.iter().map(|v| v.value.to_string()).collect(),
308 }
309 }
310}
311
312impl From<Vec<String>> for EnumValues {
313 fn from(values: Vec<String>) -> Self {
314 EnumValues::String(values)
315 }
316}
317
318impl From<Vec<&str>> for EnumValues {
319 fn from(values: Vec<&str>) -> Self {
320 EnumValues::String(values.into_iter().map(|s| s.to_string()).collect())
321 }
322}
323
324#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
325#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
326#[serde(rename_all = "snake_case", tag = "kind")]
327pub enum ComplexColumnType {
328 Varchar { length: u32 },
329 Numeric { precision: u32, scale: u32 },
330 Char { length: u32 },
331 Custom { custom_type: String },
332 Enum { name: String, values: EnumValues },
333}
334
335impl ComplexColumnType {
336 pub fn to_display_string(&self) -> String {
338 match self {
339 ComplexColumnType::Varchar { length } => format!("varchar({})", length),
340 ComplexColumnType::Numeric { precision, scale } => {
341 format!("numeric({},{})", precision, scale)
342 }
343 ComplexColumnType::Char { length } => format!("char({})", length),
344 ComplexColumnType::Custom { custom_type } => custom_type.to_lowercase(),
345 ComplexColumnType::Enum { name, values } => {
346 if values.is_integer() {
347 format!("enum<{}> (integer)", name)
348 } else {
349 format!("enum<{}>", name)
350 }
351 }
352 }
353 }
354
355 pub fn default_fill_value(&self) -> &'static str {
357 match self {
358 ComplexColumnType::Varchar { .. } | ComplexColumnType::Char { .. } => "''",
359 ComplexColumnType::Numeric { .. } => "0",
360 ComplexColumnType::Custom { .. } => "''",
361 ComplexColumnType::Enum { .. } => "''",
362 }
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use rstest::rstest;
370
371 #[rstest]
372 #[case(SimpleColumnType::SmallInt, "i16")]
373 #[case(SimpleColumnType::Integer, "i32")]
374 #[case(SimpleColumnType::BigInt, "i64")]
375 #[case(SimpleColumnType::Real, "f32")]
376 #[case(SimpleColumnType::DoublePrecision, "f64")]
377 #[case(SimpleColumnType::Text, "String")]
378 #[case(SimpleColumnType::Boolean, "bool")]
379 #[case(SimpleColumnType::Date, "Date")]
380 #[case(SimpleColumnType::Time, "Time")]
381 #[case(SimpleColumnType::Timestamp, "DateTime")]
382 #[case(SimpleColumnType::Timestamptz, "DateTimeWithTimeZone")]
383 #[case(SimpleColumnType::Interval, "String")]
384 #[case(SimpleColumnType::Bytea, "Vec<u8>")]
385 #[case(SimpleColumnType::Uuid, "Uuid")]
386 #[case(SimpleColumnType::Json, "Json")]
387 #[case(SimpleColumnType::Inet, "String")]
389 #[case(SimpleColumnType::Cidr, "String")]
390 #[case(SimpleColumnType::Macaddr, "String")]
391 #[case(SimpleColumnType::Xml, "String")]
392 fn test_simple_column_type_to_rust_type_not_nullable(
393 #[case] column_type: SimpleColumnType,
394 #[case] expected: &str,
395 ) {
396 assert_eq!(
397 ColumnType::Simple(column_type).to_rust_type(false),
398 expected
399 );
400 }
401
402 #[rstest]
403 #[case(SimpleColumnType::SmallInt, "Option<i16>")]
404 #[case(SimpleColumnType::Integer, "Option<i32>")]
405 #[case(SimpleColumnType::BigInt, "Option<i64>")]
406 #[case(SimpleColumnType::Real, "Option<f32>")]
407 #[case(SimpleColumnType::DoublePrecision, "Option<f64>")]
408 #[case(SimpleColumnType::Text, "Option<String>")]
409 #[case(SimpleColumnType::Boolean, "Option<bool>")]
410 #[case(SimpleColumnType::Date, "Option<Date>")]
411 #[case(SimpleColumnType::Time, "Option<Time>")]
412 #[case(SimpleColumnType::Timestamp, "Option<DateTime>")]
413 #[case(SimpleColumnType::Timestamptz, "Option<DateTimeWithTimeZone>")]
414 #[case(SimpleColumnType::Interval, "Option<String>")]
415 #[case(SimpleColumnType::Bytea, "Option<Vec<u8>>")]
416 #[case(SimpleColumnType::Uuid, "Option<Uuid>")]
417 #[case(SimpleColumnType::Json, "Option<Json>")]
418 #[case(SimpleColumnType::Inet, "Option<String>")]
420 #[case(SimpleColumnType::Cidr, "Option<String>")]
421 #[case(SimpleColumnType::Macaddr, "Option<String>")]
422 #[case(SimpleColumnType::Xml, "Option<String>")]
423 fn test_simple_column_type_to_rust_type_nullable(
424 #[case] column_type: SimpleColumnType,
425 #[case] expected: &str,
426 ) {
427 assert_eq!(ColumnType::Simple(column_type).to_rust_type(true), expected);
428 }
429
430 #[rstest]
431 #[case(ComplexColumnType::Varchar { length: 255 }, false, "String")]
432 #[case(ComplexColumnType::Varchar { length: 50 }, false, "String")]
433 #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, false, "Decimal")]
434 #[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, false, "Decimal")]
435 #[case(ComplexColumnType::Char { length: 10 }, false, "String")]
436 #[case(ComplexColumnType::Char { length: 1 }, false, "String")]
437 #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, false, "String")]
438 #[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, false, "String")]
439 #[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec!["active".into(), "inactive".into()]) }, false, "String")]
440 fn test_complex_column_type_to_rust_type_not_nullable(
441 #[case] column_type: ComplexColumnType,
442 #[case] nullable: bool,
443 #[case] expected: &str,
444 ) {
445 assert_eq!(
446 ColumnType::Complex(column_type).to_rust_type(nullable),
447 expected
448 );
449 }
450
451 #[rstest]
452 #[case(ComplexColumnType::Varchar { length: 255 }, "Option<String>")]
453 #[case(ComplexColumnType::Varchar { length: 50 }, "Option<String>")]
454 #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, "Option<Decimal>")]
455 #[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, "Option<Decimal>")]
456 #[case(ComplexColumnType::Char { length: 10 }, "Option<String>")]
457 #[case(ComplexColumnType::Char { length: 1 }, "Option<String>")]
458 #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, "Option<String>")]
459 #[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, "Option<String>")]
460 #[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec!["active".into(), "inactive".into()]) }, "Option<String>")]
461 fn test_complex_column_type_to_rust_type_nullable(
462 #[case] column_type: ComplexColumnType,
463 #[case] expected: &str,
464 ) {
465 assert_eq!(
466 ColumnType::Complex(column_type).to_rust_type(true),
467 expected
468 );
469 }
470
471 #[rstest]
472 #[case(ComplexColumnType::Varchar { length: 255 })]
473 #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 })]
474 #[case(ComplexColumnType::Char { length: 1 })]
475 #[case(ComplexColumnType::Custom { custom_type: "SERIAL".into() })]
476 #[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec![]) })]
477 fn test_complex_column_type_does_not_support_auto_increment(
478 #[case] column_type: ComplexColumnType,
479 ) {
480 assert!(!ColumnType::Complex(column_type).supports_auto_increment());
482 }
483
484 #[test]
485 fn test_enum_values_is_string() {
486 let string_vals = EnumValues::String(vec!["active".into()]);
487 let int_vals = EnumValues::Integer(vec![NumValue {
488 name: "Active".into(),
489 value: 1,
490 }]);
491 assert!(string_vals.is_string());
492 assert!(!int_vals.is_string());
493 }
494
495 #[test]
496 fn test_enum_values_is_integer() {
497 let string_vals = EnumValues::String(vec!["active".into()]);
498 let int_vals = EnumValues::Integer(vec![NumValue {
499 name: "Active".into(),
500 value: 1,
501 }]);
502 assert!(!string_vals.is_integer());
503 assert!(int_vals.is_integer());
504 }
505
506 #[test]
507 fn test_enum_values_variant_names_string() {
508 let vals = EnumValues::String(vec!["pending".into(), "active".into()]);
509 assert_eq!(vals.variant_names(), vec!["pending", "active"]);
510 }
511
512 #[test]
513 fn test_enum_values_variant_names_integer() {
514 let vals = EnumValues::Integer(vec![
515 NumValue {
516 name: "Low".into(),
517 value: 0,
518 },
519 NumValue {
520 name: "High".into(),
521 value: 10,
522 },
523 ]);
524 assert_eq!(vals.variant_names(), vec!["Low", "High"]);
525 }
526
527 #[test]
528 fn test_enum_values_len_and_is_empty() {
529 let empty = EnumValues::String(vec![]);
531 let non_empty = EnumValues::String(vec!["a".into()]);
532 assert!(empty.is_empty());
533 assert_eq!(empty.len(), 0);
534 assert!(!non_empty.is_empty());
535 assert_eq!(non_empty.len(), 1);
536
537 let empty_int = EnumValues::Integer(vec![]);
539 let non_empty_int = EnumValues::Integer(vec![
540 NumValue {
541 name: "A".into(),
542 value: 0,
543 },
544 NumValue {
545 name: "B".into(),
546 value: 1,
547 },
548 ]);
549 assert!(empty_int.is_empty());
550 assert_eq!(empty_int.len(), 0);
551 assert!(!non_empty_int.is_empty());
552 assert_eq!(non_empty_int.len(), 2);
553 }
554
555 #[test]
556 fn test_enum_values_to_sql_values_string() {
557 let vals = EnumValues::String(vec!["active".into(), "pending".into()]);
558 assert_eq!(vals.to_sql_values(), vec!["'active'", "'pending'"]);
559 }
560
561 #[test]
562 fn test_enum_values_to_sql_values_integer() {
563 let vals = EnumValues::Integer(vec![
564 NumValue {
565 name: "Low".into(),
566 value: 0,
567 },
568 NumValue {
569 name: "High".into(),
570 value: 10,
571 },
572 ]);
573 assert_eq!(vals.to_sql_values(), vec!["0", "10"]);
574 }
575
576 #[test]
577 fn test_enum_values_from_vec_string() {
578 let vals: EnumValues = vec!["a".to_string(), "b".to_string()].into();
579 assert!(matches!(vals, EnumValues::String(_)));
580 }
581
582 #[test]
583 fn test_enum_values_from_vec_str() {
584 let vals: EnumValues = vec!["a", "b"].into();
585 assert!(matches!(vals, EnumValues::String(_)));
586 }
587
588 #[rstest]
589 #[case(SimpleColumnType::SmallInt, true)]
590 #[case(SimpleColumnType::Integer, true)]
591 #[case(SimpleColumnType::BigInt, true)]
592 #[case(SimpleColumnType::Text, false)]
593 #[case(SimpleColumnType::Boolean, false)]
594 fn test_simple_column_type_supports_auto_increment(
595 #[case] ty: SimpleColumnType,
596 #[case] expected: bool,
597 ) {
598 assert_eq!(ty.supports_auto_increment(), expected);
599 }
600
601 #[rstest]
602 #[case(SimpleColumnType::Integer, true)]
603 #[case(SimpleColumnType::Text, false)]
604 fn test_column_type_simple_supports_auto_increment(
605 #[case] ty: SimpleColumnType,
606 #[case] expected: bool,
607 ) {
608 assert_eq!(ColumnType::Simple(ty).supports_auto_increment(), expected);
609 }
610
611 #[test]
612 fn test_requires_migration_integer_enum_values_changed() {
613 let from = ColumnType::Complex(ComplexColumnType::Enum {
615 name: "status".into(),
616 values: EnumValues::Integer(vec![
617 NumValue {
618 name: "Pending".into(),
619 value: 0,
620 },
621 NumValue {
622 name: "Active".into(),
623 value: 1,
624 },
625 ]),
626 });
627 let to = ColumnType::Complex(ComplexColumnType::Enum {
628 name: "status".into(),
629 values: EnumValues::Integer(vec![
630 NumValue {
631 name: "Pending".into(),
632 value: 0,
633 },
634 NumValue {
635 name: "Active".into(),
636 value: 1,
637 },
638 NumValue {
639 name: "Completed".into(),
640 value: 100,
641 },
642 ]),
643 });
644 assert!(!from.requires_migration(&to));
645 }
646
647 #[test]
648 fn test_requires_migration_integer_enum_name_changed() {
649 let from = ColumnType::Complex(ComplexColumnType::Enum {
651 name: "old_status".into(),
652 values: EnumValues::Integer(vec![NumValue {
653 name: "Pending".into(),
654 value: 0,
655 }]),
656 });
657 let to = ColumnType::Complex(ComplexColumnType::Enum {
658 name: "new_status".into(),
659 values: EnumValues::Integer(vec![NumValue {
660 name: "Pending".into(),
661 value: 0,
662 }]),
663 });
664 assert!(!from.requires_migration(&to));
665 }
666
667 #[test]
668 fn test_requires_migration_string_enum_values_changed() {
669 let from = ColumnType::Complex(ComplexColumnType::Enum {
671 name: "status".into(),
672 values: EnumValues::String(vec!["pending".into(), "active".into()]),
673 });
674 let to = ColumnType::Complex(ComplexColumnType::Enum {
675 name: "status".into(),
676 values: EnumValues::String(vec!["pending".into(), "active".into(), "completed".into()]),
677 });
678 assert!(from.requires_migration(&to));
679 }
680
681 #[test]
682 fn test_requires_migration_simple_types() {
683 let int = ColumnType::Simple(SimpleColumnType::Integer);
684 let text = ColumnType::Simple(SimpleColumnType::Text);
685 assert!(int.requires_migration(&text));
686 assert!(!int.requires_migration(&int));
687 }
688
689 #[test]
690 fn test_requires_migration_mixed_enum_types() {
691 let string_enum = ColumnType::Complex(ComplexColumnType::Enum {
693 name: "status".into(),
694 values: EnumValues::String(vec!["pending".into()]),
695 });
696 let int_enum = ColumnType::Complex(ComplexColumnType::Enum {
697 name: "status".into(),
698 values: EnumValues::Integer(vec![NumValue {
699 name: "Pending".into(),
700 value: 0,
701 }]),
702 });
703 assert!(string_enum.requires_migration(&int_enum));
704 }
705
706 #[rstest]
708 #[case(SimpleColumnType::SmallInt, "smallint")]
709 #[case(SimpleColumnType::Integer, "integer")]
710 #[case(SimpleColumnType::BigInt, "bigint")]
711 #[case(SimpleColumnType::Real, "real")]
712 #[case(SimpleColumnType::DoublePrecision, "double precision")]
713 #[case(SimpleColumnType::Text, "text")]
714 #[case(SimpleColumnType::Boolean, "boolean")]
715 #[case(SimpleColumnType::Date, "date")]
716 #[case(SimpleColumnType::Time, "time")]
717 #[case(SimpleColumnType::Timestamp, "timestamp")]
718 #[case(SimpleColumnType::Timestamptz, "timestamptz")]
719 #[case(SimpleColumnType::Interval, "interval")]
720 #[case(SimpleColumnType::Bytea, "bytea")]
721 #[case(SimpleColumnType::Uuid, "uuid")]
722 #[case(SimpleColumnType::Json, "json")]
723 #[case(SimpleColumnType::Inet, "inet")]
724 #[case(SimpleColumnType::Cidr, "cidr")]
725 #[case(SimpleColumnType::Macaddr, "macaddr")]
726 #[case(SimpleColumnType::Xml, "xml")]
727 fn test_simple_column_type_to_display_string(
728 #[case] column_type: SimpleColumnType,
729 #[case] expected: &str,
730 ) {
731 assert_eq!(column_type.to_display_string(), expected);
732 }
733
734 #[test]
735 fn test_complex_column_type_to_display_string_varchar() {
736 let ty = ComplexColumnType::Varchar { length: 255 };
737 assert_eq!(ty.to_display_string(), "varchar(255)");
738 }
739
740 #[test]
741 fn test_complex_column_type_to_display_string_numeric() {
742 let ty = ComplexColumnType::Numeric {
743 precision: 10,
744 scale: 2,
745 };
746 assert_eq!(ty.to_display_string(), "numeric(10,2)");
747 }
748
749 #[test]
750 fn test_complex_column_type_to_display_string_char() {
751 let ty = ComplexColumnType::Char { length: 5 };
752 assert_eq!(ty.to_display_string(), "char(5)");
753 }
754
755 #[test]
756 fn test_complex_column_type_to_display_string_custom() {
757 let ty = ComplexColumnType::Custom {
758 custom_type: "TSVECTOR".into(),
759 };
760 assert_eq!(ty.to_display_string(), "tsvector");
761 }
762
763 #[test]
764 fn test_complex_column_type_to_display_string_string_enum() {
765 let ty = ComplexColumnType::Enum {
766 name: "user_status".into(),
767 values: EnumValues::String(vec!["active".into(), "inactive".into()]),
768 };
769 assert_eq!(ty.to_display_string(), "enum<user_status>");
770 }
771
772 #[test]
773 fn test_complex_column_type_to_display_string_integer_enum() {
774 let ty = ComplexColumnType::Enum {
775 name: "priority".into(),
776 values: EnumValues::Integer(vec![
777 NumValue {
778 name: "Low".into(),
779 value: 0,
780 },
781 NumValue {
782 name: "High".into(),
783 value: 10,
784 },
785 ]),
786 };
787 assert_eq!(ty.to_display_string(), "enum<priority> (integer)");
788 }
789
790 #[test]
791 fn test_column_type_to_display_string_simple() {
792 let ty = ColumnType::Simple(SimpleColumnType::Integer);
793 assert_eq!(ty.to_display_string(), "integer");
794 }
795
796 #[test]
797 fn test_column_type_to_display_string_complex() {
798 let ty = ColumnType::Complex(ComplexColumnType::Varchar { length: 100 });
799 assert_eq!(ty.to_display_string(), "varchar(100)");
800 }
801
802 #[rstest]
804 #[case(SimpleColumnType::SmallInt, "0")]
805 #[case(SimpleColumnType::Integer, "0")]
806 #[case(SimpleColumnType::BigInt, "0")]
807 #[case(SimpleColumnType::Real, "0.0")]
808 #[case(SimpleColumnType::DoublePrecision, "0.0")]
809 #[case(SimpleColumnType::Boolean, "false")]
810 #[case(SimpleColumnType::Text, "''")]
811 #[case(SimpleColumnType::Date, "'1970-01-01'")]
812 #[case(SimpleColumnType::Time, "'00:00:00'")]
813 #[case(SimpleColumnType::Timestamp, "CURRENT_TIMESTAMP")]
814 #[case(SimpleColumnType::Timestamptz, "CURRENT_TIMESTAMP")]
815 #[case(SimpleColumnType::Interval, "'0'")]
816 #[case(SimpleColumnType::Bytea, "''")]
817 #[case(SimpleColumnType::Uuid, "'00000000-0000-0000-0000-000000000000'")]
818 #[case(SimpleColumnType::Json, "'{}'")]
819 #[case(SimpleColumnType::Inet, "'0.0.0.0'")]
820 #[case(SimpleColumnType::Cidr, "'0.0.0.0'")]
821 #[case(SimpleColumnType::Macaddr, "'00:00:00:00:00:00'")]
822 #[case(SimpleColumnType::Xml, "'<xml/>'")]
823 fn test_simple_column_type_default_fill_value(
824 #[case] column_type: SimpleColumnType,
825 #[case] expected: &str,
826 ) {
827 assert_eq!(column_type.default_fill_value(), expected);
828 }
829
830 #[test]
831 fn test_complex_column_type_default_fill_value_varchar() {
832 let ty = ComplexColumnType::Varchar { length: 255 };
833 assert_eq!(ty.default_fill_value(), "''");
834 }
835
836 #[test]
837 fn test_complex_column_type_default_fill_value_char() {
838 let ty = ComplexColumnType::Char { length: 1 };
839 assert_eq!(ty.default_fill_value(), "''");
840 }
841
842 #[test]
843 fn test_complex_column_type_default_fill_value_numeric() {
844 let ty = ComplexColumnType::Numeric {
845 precision: 10,
846 scale: 2,
847 };
848 assert_eq!(ty.default_fill_value(), "0");
849 }
850
851 #[test]
852 fn test_complex_column_type_default_fill_value_custom() {
853 let ty = ComplexColumnType::Custom {
854 custom_type: "MONEY".into(),
855 };
856 assert_eq!(ty.default_fill_value(), "''");
857 }
858
859 #[test]
860 fn test_complex_column_type_default_fill_value_enum() {
861 let ty = ComplexColumnType::Enum {
862 name: "status".into(),
863 values: EnumValues::String(vec!["active".into()]),
864 };
865 assert_eq!(ty.default_fill_value(), "''");
866 }
867
868 #[test]
869 fn test_column_type_default_fill_value_simple() {
870 let ty = ColumnType::Simple(SimpleColumnType::Integer);
871 assert_eq!(ty.default_fill_value(), "0");
872 }
873
874 #[test]
875 fn test_column_type_default_fill_value_complex() {
876 let ty = ColumnType::Complex(ComplexColumnType::Varchar { length: 100 });
877 assert_eq!(ty.default_fill_value(), "''");
878 }
879
880 #[test]
882 fn test_enum_variant_names_simple_type_returns_none() {
883 let ty = ColumnType::Simple(SimpleColumnType::Integer);
884 assert_eq!(ty.enum_variant_names(), None);
885 }
886
887 #[test]
888 fn test_enum_variant_names_complex_non_enum_returns_none() {
889 let ty = ColumnType::Complex(ComplexColumnType::Varchar { length: 255 });
890 assert_eq!(ty.enum_variant_names(), None);
891 }
892
893 #[test]
894 fn test_enum_variant_names_complex_numeric_returns_none() {
895 let ty = ColumnType::Complex(ComplexColumnType::Numeric {
896 precision: 10,
897 scale: 2,
898 });
899 assert_eq!(ty.enum_variant_names(), None);
900 }
901
902 #[test]
903 fn test_enum_variant_names_complex_char_returns_none() {
904 let ty = ColumnType::Complex(ComplexColumnType::Char { length: 1 });
905 assert_eq!(ty.enum_variant_names(), None);
906 }
907
908 #[test]
909 fn test_enum_variant_names_complex_custom_returns_none() {
910 let ty = ColumnType::Complex(ComplexColumnType::Custom {
911 custom_type: "TSVECTOR".into(),
912 });
913 assert_eq!(ty.enum_variant_names(), None);
914 }
915
916 #[test]
917 fn test_enum_variant_names_string_enum() {
918 let ty = ColumnType::Complex(ComplexColumnType::Enum {
919 name: "status".into(),
920 values: EnumValues::String(vec!["active".into(), "inactive".into(), "pending".into()]),
921 });
922 assert_eq!(
923 ty.enum_variant_names(),
924 Some(vec![
925 "active".to_string(),
926 "inactive".to_string(),
927 "pending".to_string()
928 ])
929 );
930 }
931
932 #[test]
933 fn test_enum_variant_names_integer_enum() {
934 let ty = ColumnType::Complex(ComplexColumnType::Enum {
935 name: "priority".into(),
936 values: EnumValues::Integer(vec![
937 NumValue {
938 name: "Low".into(),
939 value: 0,
940 },
941 NumValue {
942 name: "Medium".into(),
943 value: 5,
944 },
945 NumValue {
946 name: "High".into(),
947 value: 10,
948 },
949 ]),
950 });
951 assert_eq!(
952 ty.enum_variant_names(),
953 Some(vec![
954 "Low".to_string(),
955 "Medium".to_string(),
956 "High".to_string()
957 ])
958 );
959 }
960
961 #[test]
962 fn test_enum_variant_names_empty_string_enum() {
963 let ty = ColumnType::Complex(ComplexColumnType::Enum {
964 name: "empty".into(),
965 values: EnumValues::String(vec![]),
966 });
967 assert_eq!(ty.enum_variant_names(), Some(vec![]));
968 }
969
970 #[test]
971 fn test_enum_variant_names_empty_integer_enum() {
972 let ty = ColumnType::Complex(ComplexColumnType::Enum {
973 name: "empty".into(),
974 values: EnumValues::Integer(vec![]),
975 });
976 assert_eq!(ty.enum_variant_names(), Some(vec![]));
977 }
978}