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