toasty_driver_integration_suite/tests/
crud_partitioned.rs

1use crate::prelude::*;
2
3use toasty_core::{
4    driver::{Operation, Rows},
5    stmt::{Assignment, Source, Statement, UpdateTarget},
6};
7
8/// Test update on a model with a partitioned composite primary key using the
9/// partition-key-only filter.
10///
11/// `Todo::filter_by_user_id(user_id).update()` uses only the partition key in the
12/// filter expression. For NoSQL (DynamoDB), this requires a `QueryPk` to find all
13/// matching records and then an `UpdateItem` for each — not just a bare `QueryPk`
14/// that silently discards the mutation.
15#[driver_test]
16pub async fn update_by_partition_key(test: &mut Test) {
17    #[derive(Debug, toasty::Model)]
18    #[key(partition = user_id, local = id)]
19    struct Todo {
20        #[auto]
21        id: uuid::Uuid,
22
23        user_id: String,
24
25        title: String,
26    }
27
28    let mut db = test.setup_db(models!(Todo)).await;
29
30    let todo_table_id = table_id(&db, "todos");
31    let is_sql = test.capability().sql;
32
33    let todo1 = Todo::create()
34        .user_id("alice")
35        .title("original1")
36        .exec(&mut db)
37        .await
38        .unwrap();
39
40    let todo2 = Todo::create()
41        .user_id("alice")
42        .title("original2")
43        .exec(&mut db)
44        .await
45        .unwrap();
46
47    test.log().clear();
48
49    // Update all todos for "alice" using only the partition key filter.
50    Todo::filter_by_user_id("alice")
51        .update()
52        .title("updated")
53        .exec(&mut db)
54        .await
55        .unwrap();
56
57    if is_sql {
58        let (op, resp) = test.log().pop();
59
60        // Column index 2 = title (id=0, user_id=1, title=2).
61        assert_struct!(op, Operation::QuerySql({
62            stmt: Statement::Update({
63                target: UpdateTarget::Table(== todo_table_id),
64                assignments: #{ [2]: Assignment::Set(== "updated")},
65            }),
66            ret: None,
67        }));
68
69        assert_struct!(resp, {
70            values: Rows::Count(_),
71        });
72    } else {
73        // NoSQL: first a QueryPk to collect all matching PKs, then UpdateByKey
74        // for every matched record.
75        let (op, _) = test.log().pop();
76
77        assert_struct!(op, Operation::QueryPk({
78            table: == todo_table_id,
79            select.len(): 2,
80            filter: None,
81        }));
82
83        let (op, resp) = test.log().pop();
84
85        // Column index 2 = title (id=0, user_id=1, title=2).
86        assert_struct!(op, Operation::UpdateByKey({
87            table: == todo_table_id,
88            keys.len(): 2,
89            assignments: #{ [2]: Assignment::Set(== "updated")},
90            filter: None,
91            returning: false,
92        }));
93
94        assert_struct!(resp, {
95            values: Rows::Count(2),
96        });
97    }
98
99    assert!(test.log().is_empty(), "log should be empty after update");
100
101    test.log().clear();
102    let reloaded1 = Todo::get_by_user_id_and_id(&mut db, &todo1.user_id, todo1.id)
103        .await
104        .unwrap();
105    assert_eq!(reloaded1.title, "updated");
106
107    test.log().clear();
108    let reloaded2 = Todo::get_by_user_id_and_id(&mut db, &todo2.user_id, todo2.id)
109        .await
110        .unwrap();
111    assert_eq!(reloaded2.title, "updated");
112}
113
114/// Test delete on a model with a partitioned composite primary key using the
115/// partition-key-only filter.
116///
117/// `Todo::filter_by_user_id(user_id).delete()` must delete all matching records,
118/// not silently skip the deletion by issuing only a read-only `QueryPk`.
119#[driver_test]
120pub async fn delete_by_partition_key(test: &mut Test) {
121    #[derive(Debug, toasty::Model)]
122    #[key(partition = user_id, local = id)]
123    struct Todo {
124        #[auto]
125        id: uuid::Uuid,
126
127        user_id: String,
128
129        title: String,
130    }
131
132    let mut db = test.setup_db(models!(Todo)).await;
133
134    let todo_table_id = table_id(&db, "todos");
135    let is_sql = test.capability().sql;
136
137    let todo1 = Todo::create()
138        .user_id("alice")
139        .title("todo1")
140        .exec(&mut db)
141        .await
142        .unwrap();
143
144    let todo2 = Todo::create()
145        .user_id("alice")
146        .title("todo2")
147        .exec(&mut db)
148        .await
149        .unwrap();
150
151    let user_id = todo1.user_id.clone();
152    let id1 = todo1.id;
153    let id2 = todo2.id;
154
155    test.log().clear();
156
157    // Delete all todos for "alice" using only the partition key filter.
158    Todo::filter_by_user_id("alice")
159        .delete()
160        .exec(&mut db)
161        .await
162        .unwrap();
163
164    if is_sql {
165        let (op, resp) = test.log().pop();
166
167        assert_struct!(op, Operation::QuerySql({
168            stmt: Statement::Delete({
169                from: Source::Table({
170                    tables: [== todo_table_id, ..],
171                }),
172            }),
173        }));
174
175        assert_struct!(resp, {
176            values: Rows::Count(_),
177        });
178    } else {
179        // NoSQL: first a QueryPk to collect all matching PKs, then one
180        // DeleteByKey per matched record (the engine fans out individually).
181        let (op, _) = test.log().pop();
182
183        assert_struct!(op, Operation::QueryPk({
184            table: == todo_table_id,
185            select.len(): 2,
186            filter: None,
187        }));
188
189        for _ in 0..2 {
190            let (op, resp) = test.log().pop();
191
192            assert_struct!(op, Operation::DeleteByKey({
193                table: == todo_table_id,
194                keys.len(): 1,
195                filter: None,
196            }));
197
198            assert_struct!(resp, {
199                values: Rows::Count(1),
200            });
201        }
202    }
203
204    assert!(test.log().is_empty(), "log should be empty after delete");
205
206    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user_id, id1).await);
207    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user_id, id2).await);
208}