Skip to main content

vespertide_core/schema/
mod.rs

1pub mod check_violation_strategy;
2pub mod column;
3pub mod constraint;
4pub mod fk_orphan_strategy;
5pub mod foreign_key;
6pub mod index;
7pub mod names;
8pub mod pk_addition_strategy;
9pub mod primary_key;
10pub mod reference;
11pub mod str_or_bool;
12pub mod table;
13pub mod unique_strategy;
14
15pub use check_violation_strategy::CheckViolationStrategy;
16pub use column::{
17    ColumnDef, ColumnType, ComplexColumnType, EnumValues, NumValue, SimpleColumnType,
18};
19pub use constraint::{ConstraintKind, TableConstraint};
20pub use fk_orphan_strategy::ForeignKeyOrphanStrategy;
21pub use index::IndexDef;
22pub use names::{ColumnName, IndexName, TableName};
23pub use pk_addition_strategy::PrimaryKeyAdditionStrategy;
24pub use primary_key::PrimaryKeyDef;
25pub use reference::ReferenceAction;
26pub use str_or_bool::{DefaultValue, StrOrBoolOrArray, StringOrBool};
27pub use table::{TableDef, TableValidationError};
28pub use unique_strategy::{KeepPolicy, UniqueConstraintStrategy};
29
30#[cfg(test)]
31mod tests {
32    mod column {
33        use crate::schema::column::*;
34        use crate::schema::primary_key::PrimaryKeySyntax;
35        use crate::schema::str_or_bool::StrOrBoolOrArray;
36        use rstest::rstest;
37
38        #[test]
39        fn new_creates_minimal_column() {
40            let c = ColumnDef::new("id", ColumnType::Simple(SimpleColumnType::Integer), false);
41            assert_eq!(c.name.as_str(), "id");
42            assert!(!c.nullable);
43            assert!(c.primary_key.is_none());
44            assert!(c.unique.is_none());
45            assert!(c.index.is_none());
46            assert!(c.foreign_key.is_none());
47            assert!(c.default.is_none());
48            assert!(c.comment.is_none());
49        }
50
51        #[test]
52        fn builder_chain_sets_fields() {
53            let c = ColumnDef::new("email", ColumnType::Simple(SimpleColumnType::Text), false)
54                .unique(StrOrBoolOrArray::Bool(true))
55                .index(StrOrBoolOrArray::Bool(true))
56                .comment("user email");
57            assert!(c.unique.is_some());
58            assert!(c.index.is_some());
59            assert_eq!(c.comment.as_deref(), Some("user email"));
60        }
61
62        #[test]
63        fn builder_preserves_round_trip_with_existing_literal() {
64            // Equivalence: new(...) + setters == literal { ... } construction
65            let from_builder =
66                ColumnDef::new("name", ColumnType::Simple(SimpleColumnType::Text), false);
67            let from_literal = ColumnDef {
68                name: "name".into(),
69                r#type: ColumnType::Simple(SimpleColumnType::Text),
70                nullable: false,
71                default: None,
72                comment: None,
73                primary_key: None,
74                unique: None,
75                index: None,
76                foreign_key: None,
77            };
78            assert_eq!(from_builder, from_literal);
79        }
80
81        #[test]
82        fn primary_key_builder_accepts_existing_syntax_type() {
83            let c = ColumnDef::new("id", ColumnType::Simple(SimpleColumnType::Integer), false)
84                .primary_key(PrimaryKeySyntax::Bool(true));
85            assert_eq!(c.primary_key, Some(PrimaryKeySyntax::Bool(true)));
86        }
87
88        #[rstest]
89        #[case(SimpleColumnType::SmallInt, "i16")]
90        #[case(SimpleColumnType::Integer, "i32")]
91        #[case(SimpleColumnType::BigInt, "i64")]
92        #[case(SimpleColumnType::Real, "f32")]
93        #[case(SimpleColumnType::DoublePrecision, "f64")]
94        #[case(SimpleColumnType::Text, "String")]
95        #[case(SimpleColumnType::Boolean, "bool")]
96        #[case(SimpleColumnType::Date, "Date")]
97        #[case(SimpleColumnType::Time, "Time")]
98        #[case(SimpleColumnType::Timestamp, "DateTime")]
99        #[case(SimpleColumnType::Timestamptz, "DateTimeWithTimeZone")]
100        #[case(SimpleColumnType::Interval, "String")]
101        #[case(SimpleColumnType::Bytea, "Vec<u8>")]
102        #[case(SimpleColumnType::Uuid, "Uuid")]
103        #[case(SimpleColumnType::Json, "Json")]
104        // #[case(SimpleColumnType::Jsonb, "Json")]
105        #[case(SimpleColumnType::Inet, "String")]
106        #[case(SimpleColumnType::Cidr, "String")]
107        #[case(SimpleColumnType::Macaddr, "String")]
108        #[case(SimpleColumnType::Xml, "String")]
109        fn test_simple_column_type_to_rust_type_not_nullable(
110            #[case] column_type: SimpleColumnType,
111            #[case] expected: &str,
112        ) {
113            assert_eq!(
114                ColumnType::Simple(column_type).to_rust_type(false),
115                expected
116            );
117        }
118
119        #[rstest]
120        #[case(SimpleColumnType::SmallInt, "Option<i16>")]
121        #[case(SimpleColumnType::Integer, "Option<i32>")]
122        #[case(SimpleColumnType::BigInt, "Option<i64>")]
123        #[case(SimpleColumnType::Real, "Option<f32>")]
124        #[case(SimpleColumnType::DoublePrecision, "Option<f64>")]
125        #[case(SimpleColumnType::Text, "Option<String>")]
126        #[case(SimpleColumnType::Boolean, "Option<bool>")]
127        #[case(SimpleColumnType::Date, "Option<Date>")]
128        #[case(SimpleColumnType::Time, "Option<Time>")]
129        #[case(SimpleColumnType::Timestamp, "Option<DateTime>")]
130        #[case(SimpleColumnType::Timestamptz, "Option<DateTimeWithTimeZone>")]
131        #[case(SimpleColumnType::Interval, "Option<String>")]
132        #[case(SimpleColumnType::Bytea, "Option<Vec<u8>>")]
133        #[case(SimpleColumnType::Uuid, "Option<Uuid>")]
134        #[case(SimpleColumnType::Json, "Option<Json>")]
135        // #[case(SimpleColumnType::Jsonb, "Option<Json>")]
136        #[case(SimpleColumnType::Inet, "Option<String>")]
137        #[case(SimpleColumnType::Cidr, "Option<String>")]
138        #[case(SimpleColumnType::Macaddr, "Option<String>")]
139        #[case(SimpleColumnType::Xml, "Option<String>")]
140        fn test_simple_column_type_to_rust_type_nullable(
141            #[case] column_type: SimpleColumnType,
142            #[case] expected: &str,
143        ) {
144            assert_eq!(ColumnType::Simple(column_type).to_rust_type(true), expected);
145        }
146
147        #[rstest]
148        #[case(ComplexColumnType::Varchar { length: 255 }, false, "String")]
149        #[case(ComplexColumnType::Varchar { length: 50 }, false, "String")]
150        #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, false, "Decimal")]
151        #[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, false, "Decimal")]
152        #[case(ComplexColumnType::Char { length: 10 }, false, "String")]
153        #[case(ComplexColumnType::Char { length: 1 }, false, "String")]
154        #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, false, "String")]
155        #[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, false, "String")]
156        #[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec!["active".into(), "inactive".into()]) }, false, "String")]
157        fn test_complex_column_type_to_rust_type_not_nullable(
158            #[case] column_type: ComplexColumnType,
159            #[case] nullable: bool,
160            #[case] expected: &str,
161        ) {
162            assert_eq!(
163                ColumnType::Complex(column_type).to_rust_type(nullable),
164                expected
165            );
166        }
167
168        #[rstest]
169        #[case(ComplexColumnType::Varchar { length: 255 }, "Option<String>")]
170        #[case(ComplexColumnType::Varchar { length: 50 }, "Option<String>")]
171        #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, "Option<Decimal>")]
172        #[case(ComplexColumnType::Numeric { precision: 5, scale: 0 }, "Option<Decimal>")]
173        #[case(ComplexColumnType::Char { length: 10 }, "Option<String>")]
174        #[case(ComplexColumnType::Char { length: 1 }, "Option<String>")]
175        #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, "Option<String>")]
176        #[case(ComplexColumnType::Custom { custom_type: "JSONB".into() }, "Option<String>")]
177        #[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec!["active".into(), "inactive".into()]) }, "Option<String>")]
178        fn test_complex_column_type_to_rust_type_nullable(
179            #[case] column_type: ComplexColumnType,
180            #[case] expected: &str,
181        ) {
182            assert_eq!(
183                ColumnType::Complex(column_type).to_rust_type(true),
184                expected
185            );
186        }
187
188        #[rstest]
189        #[case(ComplexColumnType::Varchar { length: 255 })]
190        #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 })]
191        #[case(ComplexColumnType::Char { length: 1 })]
192        #[case(ComplexColumnType::Custom { custom_type: "SERIAL".into() })]
193        #[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec![]) })]
194        fn test_complex_column_type_does_not_support_auto_increment(
195            #[case] column_type: ComplexColumnType,
196        ) {
197            // Complex types never support auto_increment
198            assert!(!ColumnType::Complex(column_type).supports_auto_increment());
199        }
200
201        #[test]
202        fn test_enum_values_is_string() {
203            let string_vals = EnumValues::String(vec!["active".into()]);
204            let int_vals = EnumValues::Integer(vec![NumValue {
205                name: "Active".into(),
206                value: 1,
207            }]);
208            assert!(string_vals.is_string());
209            assert!(!int_vals.is_string());
210        }
211
212        #[test]
213        fn test_enum_values_is_integer() {
214            let string_vals = EnumValues::String(vec!["active".into()]);
215            let int_vals = EnumValues::Integer(vec![NumValue {
216                name: "Active".into(),
217                value: 1,
218            }]);
219            assert!(!string_vals.is_integer());
220            assert!(int_vals.is_integer());
221        }
222
223        #[test]
224        fn test_enum_values_variant_names_string() {
225            let vals = EnumValues::String(vec!["pending".into(), "active".into()]);
226            assert_eq!(vals.variant_names(), vec!["pending", "active"]);
227        }
228
229        #[test]
230        fn test_enum_values_variant_names_integer() {
231            let vals = EnumValues::Integer(vec![
232                NumValue {
233                    name: "Low".into(),
234                    value: 0,
235                },
236                NumValue {
237                    name: "High".into(),
238                    value: 10,
239                },
240            ]);
241            assert_eq!(vals.variant_names(), vec!["Low", "High"]);
242        }
243
244        #[test]
245        fn test_enum_values_len_and_is_empty() {
246            // String variant
247            let empty = EnumValues::String(vec![]);
248            let non_empty = EnumValues::String(vec!["a".into()]);
249            assert!(empty.is_empty());
250            assert_eq!(empty.len(), 0);
251            assert!(!non_empty.is_empty());
252            assert_eq!(non_empty.len(), 1);
253
254            // Integer variant
255            let empty_int = EnumValues::Integer(vec![]);
256            let non_empty_int = EnumValues::Integer(vec![
257                NumValue {
258                    name: "A".into(),
259                    value: 0,
260                },
261                NumValue {
262                    name: "B".into(),
263                    value: 1,
264                },
265            ]);
266            assert!(empty_int.is_empty());
267            assert_eq!(empty_int.len(), 0);
268            assert!(!non_empty_int.is_empty());
269            assert_eq!(non_empty_int.len(), 2);
270        }
271
272        #[test]
273        fn test_enum_values_to_sql_values_string() {
274            let vals = EnumValues::String(vec!["active".into(), "pending".into()]);
275            assert_eq!(vals.to_sql_values(), vec!["'active'", "'pending'"]);
276        }
277
278        #[test]
279        fn to_sql_values_escapes_single_quotes() {
280            let vals =
281                EnumValues::String(vec!["O'Brien".into(), "Smith".into(), "'leading".into()]);
282            let sql = vals.to_sql_values();
283
284            assert!(
285                sql.iter().any(|s| s == "'O''Brien'"),
286                "single quote inside value must be doubled"
287            );
288            assert!(
289                sql.iter().any(|s| s == "'''leading'"),
290                "leading single quote must be doubled"
291            );
292            assert!(
293                sql.iter().any(|s| s == "'Smith'"),
294                "values without quotes are unchanged"
295            );
296        }
297
298        #[test]
299        fn test_enum_values_to_sql_values_integer() {
300            let vals = EnumValues::Integer(vec![
301                NumValue {
302                    name: "Low".into(),
303                    value: 0,
304                },
305                NumValue {
306                    name: "High".into(),
307                    value: 10,
308                },
309            ]);
310            assert_eq!(vals.to_sql_values(), vec!["0", "10"]);
311        }
312
313        #[test]
314        fn test_enum_values_from_vec_string() {
315            let vals: EnumValues = vec!["a".to_string(), "b".to_string()].into();
316            assert!(matches!(vals, EnumValues::String(_)));
317        }
318
319        #[test]
320        fn test_enum_values_from_vec_str() {
321            let vals: EnumValues = vec!["a", "b"].into();
322            assert!(matches!(vals, EnumValues::String(_)));
323        }
324
325        #[rstest]
326        #[case(SimpleColumnType::SmallInt, true)]
327        #[case(SimpleColumnType::Integer, true)]
328        #[case(SimpleColumnType::BigInt, true)]
329        #[case(SimpleColumnType::Text, false)]
330        #[case(SimpleColumnType::Boolean, false)]
331        fn test_simple_column_type_supports_auto_increment(
332            #[case] ty: SimpleColumnType,
333            #[case] expected: bool,
334        ) {
335            assert_eq!(ty.supports_auto_increment(), expected);
336        }
337
338        #[rstest]
339        #[case(SimpleColumnType::Integer, true)]
340        #[case(SimpleColumnType::Text, false)]
341        fn test_column_type_simple_supports_auto_increment(
342            #[case] ty: SimpleColumnType,
343            #[case] expected: bool,
344        ) {
345            assert_eq!(ColumnType::Simple(ty).supports_auto_increment(), expected);
346        }
347
348        #[test]
349        fn test_requires_migration_integer_enum_values_changed() {
350            // Integer enum values changed - should NOT require migration
351            let from = ColumnType::Complex(ComplexColumnType::Enum {
352                name: "status".into(),
353                values: EnumValues::Integer(vec![
354                    NumValue {
355                        name: "Pending".into(),
356                        value: 0,
357                    },
358                    NumValue {
359                        name: "Active".into(),
360                        value: 1,
361                    },
362                ]),
363            });
364            let to = ColumnType::Complex(ComplexColumnType::Enum {
365                name: "status".into(),
366                values: EnumValues::Integer(vec![
367                    NumValue {
368                        name: "Pending".into(),
369                        value: 0,
370                    },
371                    NumValue {
372                        name: "Active".into(),
373                        value: 1,
374                    },
375                    NumValue {
376                        name: "Completed".into(),
377                        value: 100,
378                    },
379                ]),
380            });
381            assert!(!from.requires_migration(&to));
382        }
383
384        #[test]
385        fn requires_migration_integer_enum_name_change_is_false() {
386            let e1 = ColumnType::Complex(ComplexColumnType::Enum {
387                name: "old_status".into(),
388                values: EnumValues::Integer(vec![
389                    NumValue {
390                        name: "active".into(),
391                        value: 0,
392                    },
393                    NumValue {
394                        name: "inactive".into(),
395                        value: 1,
396                    },
397                ]),
398            });
399            let e2 = ColumnType::Complex(ComplexColumnType::Enum {
400                name: "new_status".into(),
401                values: EnumValues::Integer(vec![
402                    NumValue {
403                        name: "active".into(),
404                        value: 0,
405                    },
406                    NumValue {
407                        name: "inactive".into(),
408                        value: 1,
409                    },
410                ]),
411            });
412
413            assert!(
414                !e1.requires_migration(&e2),
415                "integer enum name change should NOT require migration (stored as INTEGER, no DB schema change)"
416            );
417        }
418
419        #[test]
420        fn test_requires_migration_integer_enum_name_changed() {
421            // Integer enum name changed - should NOT require migration (DB type is always INTEGER)
422            let from = ColumnType::Complex(ComplexColumnType::Enum {
423                name: "old_status".into(),
424                values: EnumValues::Integer(vec![NumValue {
425                    name: "Pending".into(),
426                    value: 0,
427                }]),
428            });
429            let to = ColumnType::Complex(ComplexColumnType::Enum {
430                name: "new_status".into(),
431                values: EnumValues::Integer(vec![NumValue {
432                    name: "Pending".into(),
433                    value: 0,
434                }]),
435            });
436            assert!(!from.requires_migration(&to));
437        }
438
439        #[test]
440        fn test_requires_migration_string_enum_values_changed() {
441            // String enum values changed - SHOULD require migration
442            let from = ColumnType::Complex(ComplexColumnType::Enum {
443                name: "status".into(),
444                values: EnumValues::String(vec!["pending".into(), "active".into()]),
445            });
446            let to = ColumnType::Complex(ComplexColumnType::Enum {
447                name: "status".into(),
448                values: EnumValues::String(vec![
449                    "pending".into(),
450                    "active".into(),
451                    "completed".into(),
452                ]),
453            });
454            assert!(from.requires_migration(&to));
455        }
456
457        #[test]
458        fn test_requires_migration_simple_types() {
459            let int = ColumnType::Simple(SimpleColumnType::Integer);
460            let text = ColumnType::Simple(SimpleColumnType::Text);
461            assert!(int.requires_migration(&text));
462            assert!(!int.requires_migration(&int));
463        }
464
465        #[test]
466        fn test_requires_migration_mixed_enum_types() {
467            // String enum to integer enum - SHOULD require migration
468            let string_enum = ColumnType::Complex(ComplexColumnType::Enum {
469                name: "status".into(),
470                values: EnumValues::String(vec!["pending".into()]),
471            });
472            let int_enum = ColumnType::Complex(ComplexColumnType::Enum {
473                name: "status".into(),
474                values: EnumValues::Integer(vec![NumValue {
475                    name: "Pending".into(),
476                    value: 0,
477                }]),
478            });
479            assert!(string_enum.requires_migration(&int_enum));
480        }
481
482        // Tests for to_display_string
483        #[rstest]
484        #[case(SimpleColumnType::SmallInt, "smallint")]
485        #[case(SimpleColumnType::Integer, "integer")]
486        #[case(SimpleColumnType::BigInt, "bigint")]
487        #[case(SimpleColumnType::Real, "real")]
488        #[case(SimpleColumnType::DoublePrecision, "double precision")]
489        #[case(SimpleColumnType::Text, "text")]
490        #[case(SimpleColumnType::Boolean, "boolean")]
491        #[case(SimpleColumnType::Date, "date")]
492        #[case(SimpleColumnType::Time, "time")]
493        #[case(SimpleColumnType::Timestamp, "timestamp")]
494        #[case(SimpleColumnType::Timestamptz, "timestamptz")]
495        #[case(SimpleColumnType::Interval, "interval")]
496        #[case(SimpleColumnType::Bytea, "bytea")]
497        #[case(SimpleColumnType::Uuid, "uuid")]
498        #[case(SimpleColumnType::Json, "json")]
499        #[case(SimpleColumnType::Inet, "inet")]
500        #[case(SimpleColumnType::Cidr, "cidr")]
501        #[case(SimpleColumnType::Macaddr, "macaddr")]
502        #[case(SimpleColumnType::Xml, "xml")]
503        fn test_simple_column_type_to_display_string(
504            #[case] column_type: SimpleColumnType,
505            #[case] expected: &str,
506        ) {
507            assert_eq!(column_type.to_display_string(), expected);
508        }
509
510        #[rstest]
511        #[case(SimpleColumnType::SmallInt, "SMALLINT")]
512        #[case(SimpleColumnType::Integer, "INTEGER")]
513        #[case(SimpleColumnType::BigInt, "BIGINT")]
514        #[case(SimpleColumnType::Real, "REAL")]
515        #[case(SimpleColumnType::DoublePrecision, "DOUBLE PRECISION")]
516        #[case(SimpleColumnType::Text, "TEXT")]
517        #[case(SimpleColumnType::Boolean, "BOOLEAN")]
518        #[case(SimpleColumnType::Date, "DATE")]
519        #[case(SimpleColumnType::Time, "TIME")]
520        #[case(SimpleColumnType::Timestamp, "TIMESTAMP")]
521        #[case(SimpleColumnType::Timestamptz, "TIMESTAMPTZ")]
522        #[case(SimpleColumnType::Interval, "INTERVAL")]
523        #[case(SimpleColumnType::Bytea, "BYTEA")]
524        #[case(SimpleColumnType::Uuid, "UUID")]
525        #[case(SimpleColumnType::Json, "JSON")]
526        #[case(SimpleColumnType::Inet, "INET")]
527        #[case(SimpleColumnType::Cidr, "CIDR")]
528        #[case(SimpleColumnType::Macaddr, "MACADDR")]
529        #[case(SimpleColumnType::Xml, "XML")]
530        fn test_simple_column_type_sql_type(
531            #[case] column_type: SimpleColumnType,
532            #[case] expected: &str,
533        ) {
534            assert_eq!(column_type.sql_type(), expected);
535        }
536
537        #[rstest]
538        #[case(ComplexColumnType::Varchar { length: 255 }, "VARCHAR")]
539        #[case(ComplexColumnType::Numeric { precision: 10, scale: 2 }, "NUMERIC")]
540        #[case(ComplexColumnType::Char { length: 5 }, "CHAR")]
541        #[case(ComplexColumnType::Custom { custom_type: "MONEY".into() }, "CUSTOM")]
542        #[case(ComplexColumnType::Enum { name: "status".into(), values: EnumValues::String(vec!["active".into()]) }, "ENUM")]
543        fn test_complex_column_type_sql_type(
544            #[case] column_type: ComplexColumnType,
545            #[case] expected: &str,
546        ) {
547            assert_eq!(column_type.sql_type(), expected);
548        }
549
550        #[test]
551        fn test_complex_column_type_to_display_string_varchar() {
552            let ty = ComplexColumnType::Varchar { length: 255 };
553            assert_eq!(ty.to_display_string(), "varchar(255)");
554        }
555
556        #[test]
557        fn test_complex_column_type_to_display_string_numeric() {
558            let ty = ComplexColumnType::Numeric {
559                precision: 10,
560                scale: 2,
561            };
562            assert_eq!(ty.to_display_string(), "numeric(10,2)");
563        }
564
565        #[test]
566        fn test_complex_column_type_to_display_string_char() {
567            let ty = ComplexColumnType::Char { length: 5 };
568            assert_eq!(ty.to_display_string(), "char(5)");
569        }
570
571        #[test]
572        fn test_complex_column_type_to_display_string_custom() {
573            let ty = ComplexColumnType::Custom {
574                custom_type: "TSVECTOR".into(),
575            };
576            assert_eq!(ty.to_display_string(), "tsvector");
577        }
578
579        #[test]
580        fn test_complex_column_type_to_display_string_string_enum() {
581            let ty = ComplexColumnType::Enum {
582                name: "user_status".into(),
583                values: EnumValues::String(vec!["active".into(), "inactive".into()]),
584            };
585            assert_eq!(ty.to_display_string(), "enum<user_status>");
586        }
587
588        #[test]
589        fn test_complex_column_type_to_display_string_integer_enum() {
590            let ty = ComplexColumnType::Enum {
591                name: "priority".into(),
592                values: EnumValues::Integer(vec![
593                    NumValue {
594                        name: "Low".into(),
595                        value: 0,
596                    },
597                    NumValue {
598                        name: "High".into(),
599                        value: 10,
600                    },
601                ]),
602            };
603            assert_eq!(ty.to_display_string(), "enum<priority> (integer)");
604        }
605
606        #[test]
607        fn test_column_type_to_display_string_simple() {
608            let ty = ColumnType::Simple(SimpleColumnType::Integer);
609            assert_eq!(ty.to_display_string(), "integer");
610        }
611
612        #[test]
613        fn test_column_type_to_display_string_complex() {
614            let ty = ColumnType::Complex(ComplexColumnType::Varchar { length: 100 });
615            assert_eq!(ty.to_display_string(), "varchar(100)");
616        }
617
618        // Tests for default_fill_value
619        #[rstest]
620        #[case(SimpleColumnType::SmallInt, "0")]
621        #[case(SimpleColumnType::Integer, "0")]
622        #[case(SimpleColumnType::BigInt, "0")]
623        #[case(SimpleColumnType::Real, "0.0")]
624        #[case(SimpleColumnType::DoublePrecision, "0.0")]
625        #[case(SimpleColumnType::Boolean, "false")]
626        #[case(SimpleColumnType::Text, "''")]
627        #[case(SimpleColumnType::Date, "'1970-01-01'")]
628        #[case(SimpleColumnType::Time, "'00:00:00'")]
629        #[case(SimpleColumnType::Timestamp, "CURRENT_TIMESTAMP")]
630        #[case(SimpleColumnType::Timestamptz, "CURRENT_TIMESTAMP")]
631        #[case(SimpleColumnType::Interval, "'0'")]
632        #[case(SimpleColumnType::Bytea, "''")]
633        #[case(SimpleColumnType::Uuid, "'00000000-0000-0000-0000-000000000000'")]
634        #[case(SimpleColumnType::Json, "'{}'")]
635        #[case(SimpleColumnType::Inet, "'0.0.0.0'")]
636        #[case(SimpleColumnType::Cidr, "'0.0.0.0'")]
637        #[case(SimpleColumnType::Macaddr, "'00:00:00:00:00:00'")]
638        #[case(SimpleColumnType::Xml, "'<xml/>'")]
639        fn test_simple_column_type_default_fill_value(
640            #[case] column_type: SimpleColumnType,
641            #[case] expected: &str,
642        ) {
643            assert_eq!(column_type.default_fill_value(), expected);
644        }
645
646        #[test]
647        fn test_complex_column_type_default_fill_value_varchar() {
648            let ty = ComplexColumnType::Varchar { length: 255 };
649            assert_eq!(ty.default_fill_value(), "''");
650        }
651
652        #[test]
653        fn test_complex_column_type_default_fill_value_char() {
654            let ty = ComplexColumnType::Char { length: 1 };
655            assert_eq!(ty.default_fill_value(), "''");
656        }
657
658        #[test]
659        fn test_complex_column_type_default_fill_value_numeric() {
660            let ty = ComplexColumnType::Numeric {
661                precision: 10,
662                scale: 2,
663            };
664            assert_eq!(ty.default_fill_value(), "0");
665        }
666
667        #[test]
668        fn test_complex_column_type_default_fill_value_custom() {
669            let ty = ComplexColumnType::Custom {
670                custom_type: "MONEY".into(),
671            };
672            assert_eq!(ty.default_fill_value(), "''");
673        }
674
675        #[test]
676        fn test_complex_column_type_default_fill_value_enum() {
677            let ty = ComplexColumnType::Enum {
678                name: "status".into(),
679                values: EnumValues::String(vec!["active".into()]),
680            };
681            assert_eq!(ty.default_fill_value(), "''");
682        }
683
684        #[test]
685        fn test_column_type_default_fill_value_simple() {
686            let ty = ColumnType::Simple(SimpleColumnType::Integer);
687            assert_eq!(ty.default_fill_value(), "0");
688        }
689
690        #[test]
691        fn test_column_type_default_fill_value_complex() {
692            let ty = ColumnType::Complex(ComplexColumnType::Varchar { length: 100 });
693            assert_eq!(ty.default_fill_value(), "''");
694        }
695
696        // Tests for enum_variant_names
697        #[test]
698        fn test_enum_variant_names_simple_type_returns_none() {
699            let ty = ColumnType::Simple(SimpleColumnType::Integer);
700            assert_eq!(ty.enum_variant_names(), None);
701        }
702
703        #[test]
704        fn test_enum_variant_names_complex_non_enum_returns_none() {
705            let ty = ColumnType::Complex(ComplexColumnType::Varchar { length: 255 });
706            assert_eq!(ty.enum_variant_names(), None);
707        }
708
709        #[test]
710        fn test_enum_variant_names_complex_numeric_returns_none() {
711            let ty = ColumnType::Complex(ComplexColumnType::Numeric {
712                precision: 10,
713                scale: 2,
714            });
715            assert_eq!(ty.enum_variant_names(), None);
716        }
717
718        #[test]
719        fn test_enum_variant_names_complex_char_returns_none() {
720            let ty = ColumnType::Complex(ComplexColumnType::Char { length: 1 });
721            assert_eq!(ty.enum_variant_names(), None);
722        }
723
724        #[test]
725        fn test_enum_variant_names_complex_custom_returns_none() {
726            let ty = ColumnType::Complex(ComplexColumnType::Custom {
727                custom_type: "TSVECTOR".into(),
728            });
729            assert_eq!(ty.enum_variant_names(), None);
730        }
731
732        #[test]
733        fn test_enum_variant_names_string_enum() {
734            let ty = ColumnType::Complex(ComplexColumnType::Enum {
735                name: "status".into(),
736                values: EnumValues::String(vec![
737                    "active".into(),
738                    "inactive".into(),
739                    "pending".into(),
740                ]),
741            });
742            assert_eq!(
743                ty.enum_variant_names(),
744                Some(vec![
745                    "active".to_string(),
746                    "inactive".to_string(),
747                    "pending".to_string()
748                ])
749            );
750        }
751
752        #[test]
753        fn test_enum_variant_names_integer_enum() {
754            let ty = ColumnType::Complex(ComplexColumnType::Enum {
755                name: "priority".into(),
756                values: EnumValues::Integer(vec![
757                    NumValue {
758                        name: "Low".into(),
759                        value: 0,
760                    },
761                    NumValue {
762                        name: "Medium".into(),
763                        value: 5,
764                    },
765                    NumValue {
766                        name: "High".into(),
767                        value: 10,
768                    },
769                ]),
770            });
771            assert_eq!(
772                ty.enum_variant_names(),
773                Some(vec![
774                    "Low".to_string(),
775                    "Medium".to_string(),
776                    "High".to_string()
777                ])
778            );
779        }
780
781        #[test]
782        fn test_enum_variant_names_empty_string_enum() {
783            let ty = ColumnType::Complex(ComplexColumnType::Enum {
784                name: "empty".into(),
785                values: EnumValues::String(vec![]),
786            });
787            assert_eq!(ty.enum_variant_names(), Some(vec![]));
788        }
789
790        #[test]
791        fn test_enum_variant_names_empty_integer_enum() {
792            let ty = ColumnType::Complex(ComplexColumnType::Enum {
793                name: "empty".into(),
794                values: EnumValues::Integer(vec![]),
795            });
796            assert_eq!(ty.enum_variant_names(), Some(vec![]));
797        }
798    }
799}