Skip to main content

toasty_driver_integration_suite/tests/
index_custom_name.rs

1use crate::prelude::*;
2
3/// `#[index(name = "...", ...)]` overrides the auto-generated index name
4/// in the DB schema, and the index is still usable for queries.
5#[driver_test]
6pub async fn index_custom_name_overrides_default(t: &mut Test) -> Result<()> {
7    #[derive(Debug, toasty::Model)]
8    #[index(name = "tournament_region_idx", tournament_id, region)]
9    struct Match {
10        #[key]
11        id: String,
12        tournament_id: String,
13        region: String,
14    }
15
16    let mut db = t.setup_db(models!(Match)).await;
17
18    // Schema carries the user-provided name (not the auto-generated form).
19    let table = &db.schema().db.tables[0];
20    let custom_idx = table
21        .indices
22        .iter()
23        .find(|i| !i.primary_key)
24        .expect("non-PK index should exist");
25    assert_eq!(custom_idx.name, "tournament_region_idx");
26
27    // The auto-generated form must NOT be present.
28    assert!(
29        !table
30            .indices
31            .iter()
32            .any(|i| i.name == "index_matches_by_tournament_id_and_region"),
33        "auto-generated name should not coexist with the custom name"
34    );
35
36    toasty::create!(Match::[
37        { id: "m1", tournament_id: "WINTER2024", region: "NA-EAST" },
38        { id: "m2", tournament_id: "WINTER2024", region: "EU-WEST" },
39    ])
40    .exec(&mut db)
41    .await?;
42
43    let matches: Vec<Match> = Match::filter_by_tournament_id("WINTER2024")
44        .exec(&mut db)
45        .await?;
46    assert_eq!(matches.len(), 2);
47
48    Ok(())
49}
50
51/// Without `name = "..."`, the schema builder still produces the
52/// auto-generated `index_<table>_by_<cols>` form. Sanity check that the
53/// custom-name path doesn't accidentally suppress all auto-naming.
54#[driver_test]
55pub async fn index_custom_name_default_unchanged(t: &mut Test) -> Result<()> {
56    #[derive(Debug, toasty::Model)]
57    #[index(category)]
58    struct Product {
59        #[key]
60        id: String,
61        category: String,
62    }
63
64    let db = t.setup_db(models!(Product)).await;
65    let table = &db.schema().db.tables[0];
66
67    let auto_idx = table
68        .indices
69        .iter()
70        .find(|i| !i.primary_key)
71        .expect("non-PK index should exist");
72    // Suite prefixes the table name; assert the structural form, not the literal.
73    assert!(
74        auto_idx.name.starts_with("index_") && auto_idx.name.ends_with("_by_category"),
75        "expected auto-generated `index_<table>_by_category`, got: {}",
76        auto_idx.name
77    );
78
79    Ok(())
80}
81
82/// Auto-generated index names that exceed the backend's identifier limit are
83/// truncated and given a stable 5-character hash suffix (`_XXXX`). The table
84/// can still be created and the index is usable for queries.
85///
86/// The bare auto-generated name for this model is:
87/// `index_organization_memberships_by_organization_id_and_member_user_id`
88/// (69 chars), which exceeds MySQL's 64-char and PostgreSQL's 63-char limits.
89/// With the test harness table prefix it is longer still.
90#[driver_test]
91pub async fn index_long_name_is_truncated(t: &mut Test) -> Result<()> {
92    #[derive(Debug, toasty::Model)]
93    #[index(organization_id, member_user_id)]
94    struct OrganizationMembership {
95        // String key avoids auto-increment, which DynamoDB does not support.
96        #[key]
97        id: String,
98        organization_id: i64,
99        member_user_id: i64,
100    }
101
102    let mut db = t.setup_db(models!(OrganizationMembership)).await;
103
104    let limit = db.capability().max_identifier_length;
105    let table = &db.schema().db.tables[0];
106    let auto_idx = table
107        .indices
108        .iter()
109        .find(|i| !i.primary_key)
110        .expect("non-PK index should exist");
111
112    if let Some(limit) = limit {
113        assert!(
114            auto_idx.name.len() <= limit,
115            "index name `{}` ({} chars) exceeds limit {}",
116            auto_idx.name,
117            auto_idx.name.len(),
118            limit
119        );
120    }
121
122    // The index must be usable regardless of name length.
123    toasty::create!(OrganizationMembership::[
124        { id: "m1", organization_id: 1_i64, member_user_id: 10_i64 },
125        { id: "m2", organization_id: 1_i64, member_user_id: 20_i64 },
126        { id: "m3", organization_id: 2_i64, member_user_id: 10_i64 },
127    ])
128    .exec(&mut db)
129    .await?;
130
131    let members: Vec<OrganizationMembership> =
132        OrganizationMembership::filter_by_organization_id(1_i64)
133            .exec(&mut db)
134            .await?;
135    assert_eq!(members.len(), 2);
136
137    Ok(())
138}
139
140/// `#[key(name = "...", ...)]` records the custom name on the primary-key
141/// index in the DB schema. SQL backends emit primary keys inline today, so
142/// this verifies the schema-internal wiring rather than DDL output.
143#[driver_test]
144pub async fn key_custom_name_recorded_on_pk_index(t: &mut Test) -> Result<()> {
145    #[derive(Debug, toasty::Model)]
146    #[key(name = "player_pk", partition = team, local = name)]
147    struct Player {
148        team: String,
149        name: String,
150    }
151
152    let db = t.setup_db(models!(Player)).await;
153    let table = &db.schema().db.tables[0];
154
155    let pk_index = table
156        .indices
157        .iter()
158        .find(|i| i.primary_key)
159        .expect("PK index should exist");
160    assert_eq!(pk_index.name, "player_pk");
161
162    Ok(())
163}