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