toasty_driver_integration_suite/tests/
embedded_struct_index.rs

1use crate::prelude::*;
2
3/// Tests that `#[unique]` and `#[index]` on embedded struct fields produce
4/// physical DB indices on the flattened columns.
5#[driver_test]
6pub async fn embedded_struct_index_schema(test: &mut Test) {
7    #[derive(Debug, toasty::Embed)]
8    struct Contact {
9        #[unique]
10        email: String,
11        #[index]
12        country: String,
13    }
14
15    #[derive(Debug, toasty::Model)]
16    struct User {
17        #[key]
18        id: String,
19        name: String,
20        #[allow(dead_code)]
21        contact: Contact,
22    }
23
24    let db = test.setup_db(models!(User, Contact)).await;
25    let schema = db.schema();
26
27    // The embedded struct should carry its indices in the app schema
28    assert_struct!(schema.app.models, #{
29        Contact::id(): toasty::schema::app::Model::EmbeddedStruct(_ {
30            indices.len(): 2,
31            ..
32        }),
33        ..
34    });
35
36    // The DB table should have indices on the flattened embedded columns.
37    // Index 0: primary key (id)
38    // Index 1: unique on contact_email
39    // Index 2: non-unique on contact_country
40    let table = &schema.db.tables[0];
41    let contact_email_col = table
42        .columns
43        .iter()
44        .find(|c| c.name == "contact_email")
45        .expect("contact_email column should exist");
46    let contact_country_col = table
47        .columns
48        .iter()
49        .find(|c| c.name == "contact_country")
50        .expect("contact_country column should exist");
51
52    // Should have 3 indices total: PK + unique email + non-unique country
53    assert_eq!(table.indices.len(), 3);
54
55    // Unique index on contact_email
56    let email_index = &table.indices[1];
57    assert!(email_index.unique);
58    assert!(!email_index.primary_key);
59    assert_eq!(email_index.columns.len(), 1);
60    assert_eq!(email_index.columns[0].column, contact_email_col.id);
61
62    // Non-unique index on contact_country
63    let country_index = &table.indices[2];
64    assert!(!country_index.unique);
65    assert!(!country_index.primary_key);
66    assert_eq!(country_index.columns.len(), 1);
67    assert_eq!(country_index.columns[0].column, contact_country_col.id);
68}
69
70/// Tests that unique constraint on embedded struct field is enforced at the
71/// database level and that filtering by indexed embedded fields works.
72#[driver_test]
73pub async fn embedded_struct_unique_index_enforced(test: &mut Test) -> Result<()> {
74    #[derive(Debug, toasty::Embed)]
75    struct Contact {
76        #[unique]
77        email: String,
78    }
79
80    #[derive(Debug, toasty::Model)]
81    struct User {
82        #[key]
83        id: String,
84        name: String,
85        contact: Contact,
86    }
87
88    let mut db = test.setup_db(models!(User, Contact)).await;
89
90    // Create a user with a contact email
91    User::create()
92        .id("1")
93        .name("Alice")
94        .contact(Contact {
95            email: "alice@example.com".to_string(),
96        })
97        .exec(&mut db)
98        .await?;
99
100    // Creating another user with the same contact email should fail
101    assert_err!(
102        User::create()
103            .id("2")
104            .name("Bob")
105            .contact(Contact {
106                email: "alice@example.com".to_string(),
107            })
108            .exec(&mut db)
109            .await
110    );
111
112    // Creating a user with a different email works
113    User::create()
114        .id("3")
115        .name("Charlie")
116        .contact(Contact {
117            email: "charlie@example.com".to_string(),
118        })
119        .exec(&mut db)
120        .await?;
121
122    // Filter by the indexed embedded field
123    let users = User::filter(User::fields().contact().email().eq("alice@example.com"))
124        .exec(&mut db)
125        .await?;
126
127    assert_eq!(users.len(), 1);
128    assert_eq!(users[0].name, "Alice");
129
130    Ok(())
131}
132
133/// Tests that `#[index]` on a field inside a nested embedded struct (embed
134/// within embed) produces a physical DB index on the deeply-flattened column.
135#[driver_test]
136pub async fn nested_embedded_struct_index(test: &mut Test) {
137    #[derive(Debug, toasty::Embed)]
138    struct Geo {
139        #[index]
140        city: String,
141        zip: String,
142    }
143
144    #[derive(Debug, toasty::Embed)]
145    struct Address {
146        street: String,
147        #[allow(dead_code)]
148        geo: Geo,
149    }
150
151    #[derive(Debug, toasty::Model)]
152    struct User {
153        #[key]
154        id: String,
155        #[allow(dead_code)]
156        address: Address,
157    }
158
159    let db = test.setup_db(models!(User, Address, Geo)).await;
160    let schema = db.schema();
161
162    let table = &schema.db.tables[0];
163
164    // The nested embedded field should be flattened to address_geo_city
165    let city_col = table
166        .columns
167        .iter()
168        .find(|c| c.name == "address_geo_city")
169        .expect("address_geo_city column should exist");
170
171    // Should have 2 indices: PK + non-unique on address_geo_city
172    assert_eq!(table.indices.len(), 2);
173
174    let city_index = &table.indices[1];
175    assert!(!city_index.unique);
176    assert_eq!(city_index.columns.len(), 1);
177    assert_eq!(city_index.columns[0].column, city_col.id);
178}