toasty_driver_integration_suite/tests/
embed_enum_shared_index.rs1use crate::prelude::*;
2
3#[driver_test]
6pub async fn shared_field_unique_index_schema(test: &mut Test) {
7 #[derive(Debug, toasty::Embed)]
8 #[unique(name)]
9 enum Creature {
10 #[column(variant = 1)]
11 Human {
12 #[shared(name)]
13 name: String,
14 #[allow(dead_code)]
15 profession: String,
16 },
17 #[column(variant = 2)]
18 Animal {
19 #[shared(name)]
20 name: String,
21 #[allow(dead_code)]
22 species: String,
23 },
24 }
25
26 #[derive(Debug, toasty::Model)]
27 struct Character {
28 #[key]
29 id: String,
30 #[allow(dead_code)]
31 creature: Creature,
32 }
33
34 let db = test.setup_db(models!(Character)).await;
35 let schema = db.schema();
36
37 let table = &schema.db.tables[0];
38 let name_col = columns(&db, "characters", &["creature_name"])[0];
39
40 assert_struct!(table.indices, [
41 { primary_key: true },
42 { unique: true, primary_key: false, columns: [{ column: == name_col }] },
43 ]);
44}
45
46#[driver_test]
50pub async fn shared_field_unique_enforced_cross_variant(test: &mut Test) -> Result<()> {
51 #[derive(Debug, PartialEq, toasty::Embed)]
52 #[unique(name)]
53 enum Creature {
54 #[column(variant = 1)]
55 Human {
56 #[shared(name)]
57 name: String,
58 profession: String,
59 },
60 #[column(variant = 2)]
61 Animal {
62 #[shared(name)]
63 name: String,
64 species: String,
65 },
66 }
67
68 #[derive(Debug, toasty::Model)]
69 struct Character {
70 #[key]
71 id: String,
72 creature: Creature,
73 }
74
75 let mut db = test.setup_db(models!(Character)).await;
76
77 toasty::create!(Character {
78 id: "1",
79 creature: Creature::Human {
80 name: "Bob".to_string(),
81 profession: "builder".to_string(),
82 }
83 })
84 .exec(&mut db)
85 .await?;
86
87 assert_err!(
89 toasty::create!(Character {
90 id: "2",
91 creature: Creature::Animal {
92 name: "Bob".to_string(),
93 species: "dog".to_string(),
94 }
95 })
96 .exec(&mut db)
97 .await
98 );
99
100 toasty::create!(Character {
102 id: "3",
103 creature: Creature::Animal {
104 name: "Rex".to_string(),
105 species: "dog".to_string(),
106 }
107 })
108 .exec(&mut db)
109 .await?;
110
111 toasty::create!(Character {
112 id: "4",
113 creature: Creature::Human {
114 name: "Alice".to_string(),
115 profession: "artist".to_string(),
116 }
117 })
118 .exec(&mut db)
119 .await?;
120
121 Ok(())
122}
123
124#[driver_test]
127pub async fn shared_field_non_unique_index(test: &mut Test) -> Result<()> {
128 #[derive(Debug, PartialEq, toasty::Embed)]
129 #[index(name)]
130 enum Creature {
131 #[column(variant = 1)]
132 Human {
133 #[shared(name)]
134 name: String,
135 },
136 #[column(variant = 2)]
137 Animal {
138 #[shared(name)]
139 name: String,
140 },
141 }
142
143 #[derive(Debug, toasty::Model)]
144 struct Character {
145 #[key]
146 id: String,
147 creature: Creature,
148 }
149
150 let mut db = test.setup_db(models!(Character)).await;
151
152 let name_col = columns(&db, "characters", &["creature_name"])[0];
153 assert_struct!(db.schema().db.tables[0].indices, [
154 { primary_key: true },
155 { unique: false, primary_key: false, columns: [{ column: == name_col }] },
156 ]);
157
158 toasty::create!(Character {
160 id: "1",
161 creature: Creature::Human {
162 name: "Bob".to_string()
163 }
164 })
165 .exec(&mut db)
166 .await?;
167 toasty::create!(Character {
168 id: "2",
169 creature: Creature::Animal {
170 name: "Bob".to_string()
171 }
172 })
173 .exec(&mut db)
174 .await?;
175
176 Ok(())
177}
178
179#[driver_test(requires(sql))]
184pub async fn composite_index_shared_and_variant_field(test: &mut Test) -> Result<()> {
185 #[derive(Debug, PartialEq, toasty::Embed)]
186 #[unique(name, human::profession)]
187 enum Creature {
188 #[column(variant = 1)]
189 Human {
190 #[shared(name)]
191 name: String,
192 profession: String,
193 },
194 #[column(variant = 2)]
195 Animal {
196 #[shared(name)]
197 name: String,
198 species: String,
199 },
200 }
201
202 #[derive(Debug, toasty::Model)]
203 struct Character {
204 #[key]
205 id: String,
206 creature: Creature,
207 }
208
209 let mut db = test.setup_db(models!(Character)).await;
210
211 let cols = columns(&db, "characters", &["creature_name", "creature_profession"]);
212 assert_struct!(db.schema().db.tables[0].indices, [
213 { primary_key: true },
214 {
215 unique: true,
216 primary_key: false,
217 columns: [{ column: == cols[0] }, { column: == cols[1] }],
218 },
219 ]);
220
221 toasty::create!(Character {
222 id: "1",
223 creature: Creature::Human {
224 name: "Bob".to_string(),
225 profession: "builder".to_string(),
226 }
227 })
228 .exec(&mut db)
229 .await?;
230
231 assert_err!(
233 toasty::create!(Character {
234 id: "2",
235 creature: Creature::Human {
236 name: "Bob".to_string(),
237 profession: "builder".to_string(),
238 }
239 })
240 .exec(&mut db)
241 .await
242 );
243
244 toasty::create!(Character {
246 id: "3",
247 creature: Creature::Human {
248 name: "Bob".to_string(),
249 profession: "architect".to_string(),
250 }
251 })
252 .exec(&mut db)
253 .await?;
254
255 Ok(())
256}
257
258#[driver_test]
263pub async fn duplicate_column_without_shared_rejected(test: &mut Test) -> Result<()> {
264 #[derive(Debug, toasty::Embed)]
265 enum Value {
266 #[column(variant = 1)]
267 Text {
268 #[allow(dead_code)]
269 #[column("value")]
270 text: String,
271 },
272 #[column(variant = 2)]
273 Number {
274 #[allow(dead_code)]
275 #[column("value")]
276 number: String,
277 },
278 }
279
280 #[derive(Debug, toasty::Model)]
281 struct Holder {
282 #[key]
283 id: String,
284 #[allow(dead_code)]
285 value: Value,
286 }
287
288 let err = assert_err!(test.try_setup_db(models!(Holder)).await);
289 assert!(
290 err.to_string().contains("without declaring a shared field"),
291 "unexpected error: {err}"
292 );
293
294 Ok(())
295}
296
297#[driver_test]
302pub async fn nested_enum_column_collision_rejected(test: &mut Test) -> Result<()> {
303 #[derive(Debug, PartialEq, toasty::Embed)]
304 enum Inner {
305 #[column(variant = 1)]
306 A {
307 #[allow(dead_code)]
308 x: String,
309 },
310 }
311
312 #[derive(Debug, PartialEq, toasty::Embed)]
313 enum Outer {
314 #[column(variant = 1)]
315 V1 {
316 #[allow(dead_code)]
317 inner: Inner,
318 },
319 #[column(variant = 2)]
322 V2 {
323 #[allow(dead_code)]
324 inner_x: String,
325 },
326 }
327
328 #[derive(Debug, toasty::Model)]
329 struct Holder {
330 #[key]
331 id: String,
332 #[allow(dead_code)]
333 value: Outer,
334 }
335
336 let err = assert_err!(test.try_setup_db(models!(Holder)).await);
337 assert!(
338 err.to_string().contains("without declaring a shared field"),
339 "unexpected error: {err}"
340 );
341
342 Ok(())
343}
344
345#[driver_test]
349pub async fn nested_enum_column_collision_rejected_reverse_order(test: &mut Test) -> Result<()> {
350 #[derive(Debug, PartialEq, toasty::Embed)]
351 enum Inner {
352 #[column(variant = 1)]
353 A {
354 #[allow(dead_code)]
355 x: String,
356 },
357 }
358
359 #[derive(Debug, PartialEq, toasty::Embed)]
360 enum Outer {
361 #[column(variant = 1)]
364 V1 {
365 #[allow(dead_code)]
366 inner_x: String,
367 },
368 #[column(variant = 2)]
369 V2 {
370 #[allow(dead_code)]
371 inner: Inner,
372 },
373 }
374
375 #[derive(Debug, toasty::Model)]
376 struct Holder {
377 #[key]
378 id: String,
379 #[allow(dead_code)]
380 value: Outer,
381 }
382
383 let err = assert_err!(test.try_setup_db(models!(Holder)).await);
384 assert!(
385 err.to_string().contains("without declaring a shared field"),
386 "unexpected error: {err}"
387 );
388
389 Ok(())
390}
391
392#[driver_test]
396pub async fn embedded_prefix_collision_is_not_duplicate(test: &mut Test) -> Result<()> {
397 #[derive(Debug, PartialEq, toasty::Embed)]
398 struct Geo {
399 lat: String,
400 }
401
402 #[derive(Debug, PartialEq, toasty::Embed)]
403 struct Postal {
404 zip: String,
405 }
406
407 #[derive(Debug, PartialEq, toasty::Embed)]
408 enum Location {
409 #[column(variant = 1)]
410 Coords {
411 #[column("common")]
412 geo: Geo,
413 },
414 #[column(variant = 2)]
415 Mail {
416 #[column("common")]
417 postal: Postal,
418 },
419 }
420
421 #[derive(Debug, toasty::Model)]
422 struct Place {
423 #[key]
424 id: String,
425 location: Location,
426 }
427
428 let mut db = test.setup_db(models!(Place)).await;
429
430 let table = &db.schema().db.tables[0];
432 assert!(
433 table
434 .columns
435 .iter()
436 .any(|c| c.name == "location_common_lat")
437 );
438 assert!(
439 table
440 .columns
441 .iter()
442 .any(|c| c.name == "location_common_zip")
443 );
444
445 toasty::create!(Place {
446 id: "1",
447 location: Location::Coords {
448 geo: Geo {
449 lat: "47.6".to_string()
450 }
451 }
452 })
453 .exec(&mut db)
454 .await?;
455
456 let place = Place::get_by_id(&mut db, &"1".to_string()).await?;
457 assert!(matches!(place.location, Location::Coords { .. }));
458
459 Ok(())
460}
461
462#[driver_test]
467pub async fn shared_field_column_override(test: &mut Test) -> Result<()> {
468 #[derive(Debug, PartialEq, toasty::Embed)]
469 #[unique(name)]
470 enum Creature {
471 #[column(variant = 1)]
472 Human {
473 #[shared(name)]
474 #[column("legacy_name")]
475 name: String,
476 },
477 #[column(variant = 2)]
478 Animal {
479 #[shared(name)]
480 name: String,
481 },
482 }
483
484 #[derive(Debug, toasty::Model)]
485 struct Character {
486 #[key]
487 id: String,
488 creature: Creature,
489 }
490
491 let mut db = test.setup_db(models!(Character)).await;
492
493 let table = &db.schema().db.tables[0];
495 assert!(
496 table
497 .columns
498 .iter()
499 .any(|c| c.name == "creature_legacy_name")
500 );
501 assert!(!table.columns.iter().any(|c| c.name == "creature_name"));
502
503 let legacy_col = columns(&db, "characters", &["creature_legacy_name"])[0];
504 assert_struct!(table.indices, [
505 { primary_key: true },
506 { unique: true, primary_key: false, columns: [{ column: == legacy_col }] },
507 ]);
508
509 toasty::create!(Character {
511 id: "1",
512 creature: Creature::Human {
513 name: "Bob".to_string()
514 }
515 })
516 .exec(&mut db)
517 .await?;
518
519 assert_struct!(
520 Character::get_by_id(&mut db, &"1".to_string()).await?,
521 _ { creature: Creature::Human { name: "Bob", .. }, .. }
522 );
523
524 Ok(())
525}