Skip to main content

toasty_driver_integration_suite/tests/
crud_partitioned.rs

1use crate::prelude::*;
2
3use toasty_core::{
4    driver::{Operation, Rows},
5    stmt::{Assignment, Expr, 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(Expr::Arg({ position: 0 }))},
65            }),
66            params: [{ value: == "updated" }, ..],
67            ret: None,
68        }));
69
70        assert_struct!(resp, {
71            values: Rows::Count(_),
72        });
73    } else {
74        // NoSQL: first a QueryPk to collect all matching PKs, then one
75        // single-key UpdateByKey per matched record (the engine shreds
76        // multi-key updates so each key is adjudicated independently).
77        let (op, _) = test.log().pop();
78
79        assert_struct!(op, Operation::QueryPk({
80            table: == todo_table_id,
81            select.len(): 2,
82            filter: None,
83        }));
84
85        // Column index 2 = title (id=0, user_id=1, title=2).
86        for _ in 0..2 {
87            let (op, resp) = test.log().pop();
88
89            assert_struct!(op, Operation::UpdateByKey({
90                table: == todo_table_id,
91                keys.len(): 1,
92                assignments: #{ [2]: Assignment::Set(== "updated")},
93                filter: None,
94                returning: None,
95            }));
96
97            assert_struct!(resp, {
98                values: Rows::Count(1),
99            });
100        }
101    }
102
103    assert!(test.log().is_empty(), "log should be empty after update");
104
105    test.log().clear();
106    let reloaded1 = Todo::get_by_user_id_and_id(&mut db, &todo1.user_id, todo1.id)
107        .await
108        .unwrap();
109    assert_eq!(reloaded1.title, "updated");
110
111    test.log().clear();
112    let reloaded2 = Todo::get_by_user_id_and_id(&mut db, &todo2.user_id, todo2.id)
113        .await
114        .unwrap();
115    assert_eq!(reloaded2.title, "updated");
116}
117
118/// Test delete on a model with a partitioned composite primary key using the
119/// partition-key-only filter.
120///
121/// `Todo::filter_by_user_id(user_id).delete()` must delete all matching records,
122/// not silently skip the deletion by issuing only a read-only `QueryPk`.
123#[driver_test]
124pub async fn delete_by_partition_key(test: &mut Test) {
125    #[derive(Debug, toasty::Model)]
126    #[key(partition = user_id, local = id)]
127    struct Todo {
128        #[auto]
129        id: uuid::Uuid,
130
131        user_id: String,
132
133        title: String,
134    }
135
136    let mut db = test.setup_db(models!(Todo)).await;
137
138    let todo_table_id = table_id(&db, "todos");
139    let is_sql = test.capability().sql;
140
141    let todo1 = Todo::create()
142        .user_id("alice")
143        .title("todo1")
144        .exec(&mut db)
145        .await
146        .unwrap();
147
148    let todo2 = Todo::create()
149        .user_id("alice")
150        .title("todo2")
151        .exec(&mut db)
152        .await
153        .unwrap();
154
155    let user_id = todo1.user_id.clone();
156    let id1 = todo1.id;
157    let id2 = todo2.id;
158
159    test.log().clear();
160
161    // Delete all todos for "alice" using only the partition key filter.
162    Todo::filter_by_user_id("alice")
163        .delete()
164        .exec(&mut db)
165        .await
166        .unwrap();
167
168    if is_sql {
169        let (op, resp) = test.log().pop();
170
171        assert_struct!(op, Operation::QuerySql({
172            stmt: Statement::Delete({
173                from: Source::Table({
174                    tables: [== todo_table_id, ..],
175                }),
176            }),
177        }));
178
179        assert_struct!(resp, {
180            values: Rows::Count(_),
181        });
182    } else {
183        // NoSQL: first a QueryPk to collect all matching PKs, then one
184        // DeleteByKey per matched record (the engine fans out individually).
185        let (op, _) = test.log().pop();
186
187        assert_struct!(op, Operation::QueryPk({
188            table: == todo_table_id,
189            select.len(): 2,
190            filter: None,
191        }));
192
193        for _ in 0..2 {
194            let (op, resp) = test.log().pop();
195
196            assert_struct!(op, Operation::DeleteByKey({
197                table: == todo_table_id,
198                keys.len(): 1,
199                filter: None,
200            }));
201
202            assert_struct!(resp, {
203                values: Rows::Count(1),
204            });
205        }
206    }
207
208    assert!(test.log().is_empty(), "log should be empty after delete");
209
210    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user_id, id1).await);
211    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user_id, id2).await);
212}