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