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