Skip to main content

toasty_driver_integration_suite/tests/
embed_enum_collection.rs

1//! `Vec<unit-enum>` collections. The element is a scalar discriminant, so the
2//! column is a native scalar array where the backend has one — `int8[]` for
3//! integer discriminants, `ink[]` for a native enum — never a document.
4
5use crate::helpers::column;
6use crate::prelude::*;
7
8use toasty_core::{schema::db, stmt};
9
10#[derive(Clone, Copy, Debug, PartialEq, toasty::Embed)]
11enum Color {
12    #[column(variant = 1)]
13    Red,
14    #[column(variant = 2)]
15    Green,
16    #[column(variant = 3)]
17    Blue,
18}
19
20#[derive(Debug, toasty::Model)]
21struct Palette {
22    #[key]
23    #[auto]
24    id: uuid::Uuid,
25    colors: Vec<Color>,
26}
27
28/// A `Vec<unit-enum>` round-trips through INSERT and a fresh fetch.
29#[driver_test(requires(document_collections))]
30pub async fn vec_enum_create_get(t: &mut Test) -> Result<(), BoxError> {
31    let mut db = t.setup_db(models!(Palette)).await;
32
33    let colors = [Color::Red, Color::Blue];
34    let palette = toasty::create!(Palette { colors }).exec(&mut db).await?;
35
36    let reloaded = Palette::get_by_id(&mut db, &palette.id).await?;
37    assert_eq!(reloaded.colors, colors);
38
39    Ok(())
40}
41
42/// The scalar-collection operators unlocked by the emitted `Scalar` impl:
43/// `contains(variant)` matches on discriminant membership and `len()` filters
44/// on cardinality.
45#[driver_test(requires(document_collections))]
46pub async fn vec_enum_contains_and_len(t: &mut Test) -> Result<(), BoxError> {
47    let mut db = t.setup_db(models!(Palette)).await;
48
49    toasty::create!(Palette::[
50        { colors: [Color::Red, Color::Green] },
51        { colors: [Color::Blue] },
52        { colors: [Color::Red, Color::Green, Color::Blue] },
53    ])
54    .exec(&mut db)
55    .await?;
56
57    let reds = Palette::filter(Palette::fields().colors().contains(Color::Red))
58        .exec(&mut db)
59        .await?;
60    assert_eq!(reds.len(), 2);
61
62    let singletons = Palette::filter(Palette::fields().colors().len().eq(1))
63        .exec(&mut db)
64        .await?;
65    assert_eq!(singletons.len(), 1);
66
67    let triples = Palette::filter(Palette::fields().colors().len().eq(3))
68        .exec(&mut db)
69        .await?;
70    assert_eq!(triples.len(), 1);
71
72    Ok(())
73}
74
75/// Enum-level storage applies to collection elements, while a field-level
76/// type overrides that default. Both paths bridge and round-trip each element.
77#[driver_test(requires(document_collections))]
78pub async fn vec_enum_uses_discriminant_storage(t: &mut Test) -> Result<(), BoxError> {
79    #[derive(Clone, Copy, Debug, PartialEq, toasty::Embed)]
80    #[column(type = u16)]
81    enum SmallColor {
82        #[column(variant = 1)]
83        Red,
84        #[column(variant = 2)]
85        Green,
86    }
87
88    #[derive(Debug, toasty::Model)]
89    struct SmallPalette {
90        #[key]
91        #[auto]
92        id: uuid::Uuid,
93        colors: Vec<SmallColor>,
94        #[column(type = u8)]
95        compact_colors: Vec<SmallColor>,
96    }
97
98    let mut db = t.setup_db(models!(SmallPalette)).await;
99
100    assert_eq!(
101        column_storage_ty(&db, "small_palettes", "colors"),
102        db::Type::list(db::Type::UnsignedInteger(2))
103    );
104    assert_eq!(
105        column_storage_ty(&db, "small_palettes", "compact_colors"),
106        db::Type::list(db::Type::UnsignedInteger(1))
107    );
108
109    let colors_column = db
110        .schema()
111        .db
112        .column(column(&db, "small_palettes", "colors"));
113    assert_eq!(
114        colors_column.ty,
115        stmt::Type::List(Box::new(stmt::Type::U16))
116    );
117    let compact_column = db
118        .schema()
119        .db
120        .column(column(&db, "small_palettes", "compact_colors"));
121    assert_eq!(
122        compact_column.ty,
123        stmt::Type::List(Box::new(stmt::Type::U8))
124    );
125
126    let colors = [SmallColor::Red, SmallColor::Green];
127    let palette = toasty::create!(SmallPalette {
128        colors,
129        compact_colors: colors,
130    })
131    .exec(&mut db)
132    .await?;
133
134    let reloaded = SmallPalette::get_by_id(&mut db, &palette.id).await?;
135    assert_eq!(reloaded.colors, colors);
136    assert_eq!(reloaded.compact_colors, colors);
137
138    Ok(())
139}
140
141/// Transparent field wrappers preserve enum-level storage, and a field-level
142/// override still wins after passing through the wrapper.
143#[driver_test]
144pub async fn enum_storage_propagates_through_wrappers(t: &mut Test) -> Result<(), BoxError> {
145    #[derive(Clone, Copy, Debug, PartialEq, toasty::Embed)]
146    #[column(type = u16)]
147    enum Status {
148        #[column(variant = 1)]
149        Draft,
150        #[column(variant = 2)]
151        Published,
152    }
153
154    #[derive(Debug, toasty::Model)]
155    struct WrappedStatus {
156        #[key]
157        #[auto]
158        id: uuid::Uuid,
159        optional: Option<Status>,
160        deferred: toasty::Deferred<Status>,
161        boxed: Box<Status>,
162        arced: std::sync::Arc<Status>,
163        #[column(type = u8)]
164        rced: std::rc::Rc<Status>,
165    }
166
167    let mut db = t.setup_db(models!(WrappedStatus)).await;
168
169    for name in ["optional", "deferred", "boxed", "arced"] {
170        assert_eq!(
171            column_storage_ty(&db, "wrapped_statuses", name),
172            db::Type::UnsignedInteger(2)
173        );
174    }
175    assert_eq!(
176        column_storage_ty(&db, "wrapped_statuses", "rced"),
177        db::Type::UnsignedInteger(1)
178    );
179
180    let wrapped = toasty::create!(WrappedStatus {
181        optional: Some(Status::Draft),
182        deferred: Status::Published,
183        boxed: Status::Draft,
184        arced: Status::Published,
185        rced: Status::Draft,
186    })
187    .exec(&mut db)
188    .await?;
189
190    let reloaded = WrappedStatus::filter_by_id(wrapped.id)
191        .include(WrappedStatus::fields().deferred())
192        .get(&mut db)
193        .await?;
194    assert_eq!(reloaded.optional, Some(Status::Draft));
195    assert_eq!(*reloaded.deferred.get(), Status::Published);
196    assert_eq!(*reloaded.boxed, Status::Draft);
197    assert_eq!(*reloaded.arced, Status::Published);
198    assert_eq!(*reloaded.rced, Status::Draft);
199
200    Ok(())
201}
202
203/// Enum storage also follows an enum nested through a flattened embed.
204#[driver_test]
205pub async fn enum_storage_propagates_through_nested_embed(t: &mut Test) {
206    #[derive(Debug, toasty::Embed)]
207    #[column(type = u8)]
208    enum Status {
209        #[column(variant = 1)]
210        Active,
211        #[column(variant = 2)]
212        Archived,
213    }
214
215    #[derive(Debug, toasty::Embed)]
216    struct Metadata {
217        status: Status,
218    }
219
220    #[derive(Debug, toasty::Model)]
221    struct Item {
222        #[key]
223        #[auto]
224        id: uuid::Uuid,
225        metadata: Metadata,
226    }
227
228    let db = t.setup_db(models!(Item)).await;
229    assert_eq!(
230        column_storage_ty(&db, "items", "metadata_status"),
231        db::Type::UnsignedInteger(1)
232    );
233}
234
235#[derive(Clone, Copy, Debug, PartialEq, toasty::Embed)]
236enum Ink {
237    Cyan,
238    Magenta,
239    Yellow,
240}
241
242#[derive(Debug, toasty::Model)]
243struct Printer {
244    #[key]
245    #[auto]
246    id: uuid::Uuid,
247    inks: Vec<Ink>,
248}
249
250/// A `Vec<native-enum>` stores as a native enum array (`ink[]`), not `text[]`.
251#[driver_test(requires(and(native_enum, native_array)))]
252pub async fn native_enum_vec_stores_as_enum_array(t: &mut Test) -> Result<(), BoxError> {
253    let db = t.setup_db(models!(Printer)).await;
254
255    let storage_ty = column_storage_ty(&db, "printers", "inks");
256    let db::Type::List(elem) = &storage_ty else {
257        panic!("expected List(Enum), got {storage_ty:?}")
258    };
259    let db::Type::Enum(type_enum) = &**elem else {
260        panic!("expected List(Enum), got {storage_ty:?}")
261    };
262
263    // Type name is test-prefixed, so assert on the variant labels.
264    let variants: Vec<&str> = type_enum.variants.iter().map(|v| v.name.as_str()).collect();
265    assert_eq!(variants, ["cyan", "magenta", "yellow"]);
266
267    Ok(())
268}
269
270/// A `Vec<native-enum>` round-trips through INSERT and a fresh fetch,
271/// exercising the enum-array bind and decode wire paths.
272#[driver_test(requires(and(native_enum, vec_scalar)))]
273pub async fn native_enum_vec_create_get(t: &mut Test) -> Result<(), BoxError> {
274    let mut db = t.setup_db(models!(Printer)).await;
275
276    let inks = [Ink::Cyan, Ink::Yellow];
277    let printer = toasty::create!(Printer { inks }).exec(&mut db).await?;
278
279    let reloaded = Printer::get_by_id(&mut db, &printer.id).await?;
280    assert_eq!(reloaded.inks, inks);
281
282    Ok(())
283}