toasty_driver_integration_suite/tests/
one_model_partitioned_crud.rs

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