toasty_driver_integration_suite/tests/
starts_with.rs1use crate::prelude::*;
2
3#[derive(Debug, toasty::Model)]
6#[key(partition = partition_id, local = sort_key)]
7struct Item {
8 partition_id: i64,
9 sort_key: String,
10 name: String,
11}
12
13async fn setup(test: &mut Test) -> toasty::Db {
14 let mut db = test.setup_db(models!(Item)).await;
15
16 toasty::create!(Item::[
17 { partition_id: 1_i64, sort_key: "alpha-1", name: "Alice" },
18 { partition_id: 1_i64, sort_key: "alpha-2", name: "Alicia" },
19 { partition_id: 1_i64, sort_key: "beta-1", name: "Bob" },
20 { partition_id: 1_i64, sort_key: "beta-2", name: "Barry" },
21 { partition_id: 2_i64, sort_key: "alpha-1", name: "Carol" },
22 ])
23 .exec(&mut db)
24 .await
25 .unwrap();
26
27 db
28}
29
30#[driver_test]
33pub async fn starts_with_sort_key(test: &mut Test) -> Result<()> {
34 let mut db = setup(test).await;
35
36 let mut items: Vec<Item> = Item::filter(
37 Item::fields()
38 .partition_id()
39 .eq(1_i64)
40 .and(Item::fields().sort_key().starts_with("alpha".to_string())),
41 )
42 .exec(&mut db)
43 .await?;
44
45 items.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
46
47 assert_eq!(items.len(), 2);
48 assert_eq!(items[0].sort_key, "alpha-1");
49 assert_eq!(items[1].sort_key, "alpha-2");
50
51 Ok(())
52}
53
54#[driver_test]
57pub async fn starts_with_non_key_attr(test: &mut Test) -> Result<()> {
58 let mut db = setup(test).await;
59
60 let mut items: Vec<Item> = Item::filter(
61 Item::fields()
62 .partition_id()
63 .eq(1_i64)
64 .and(Item::fields().name().starts_with("Al".to_string())),
65 )
66 .exec(&mut db)
67 .await?;
68
69 items.sort_by(|a, b| a.name.cmp(&b.name));
70
71 assert_eq!(items.len(), 2);
72 assert_eq!(items[0].name, "Alice");
73 assert_eq!(items[1].name, "Alicia");
74
75 Ok(())
76}
77
78#[driver_test]
80pub async fn starts_with_no_match(test: &mut Test) -> Result<()> {
81 let mut db = setup(test).await;
82
83 let items: Vec<Item> = Item::filter(
84 Item::fields()
85 .partition_id()
86 .eq(1_i64)
87 .and(Item::fields().sort_key().starts_with("gamma".to_string())),
88 )
89 .exec(&mut db)
90 .await?;
91
92 assert_eq!(items.len(), 0);
93
94 Ok(())
95}
96
97#[driver_test(requires(not(sql)))]
99pub async fn starts_with_empty_prefix(test: &mut Test) -> Result<()> {
100 let mut db = setup(test).await;
101
102 let result: toasty::Result<Vec<Item>> = Item::filter(
103 Item::fields()
104 .partition_id()
105 .eq(1_i64)
106 .and(Item::fields().sort_key().starts_with("".to_string())),
107 )
108 .exec(&mut db)
109 .await;
110
111 assert!(
112 result.is_err(),
113 "expected error when using starts_with with empty prefix on DynamoDB"
114 );
115
116 Ok(())
117}
118
119#[driver_test(requires(sql))]
121pub async fn starts_with_empty_prefix_sql(test: &mut Test) -> Result<()> {
122 let mut db = setup(test).await;
123
124 let items: Vec<Item> = Item::filter(
125 Item::fields()
126 .partition_id()
127 .eq(1_i64)
128 .and(Item::fields().sort_key().starts_with("".to_string())),
129 )
130 .exec(&mut db)
131 .await?;
132
133 assert_eq!(items.len(), 4, "empty prefix should match all rows on SQL");
134
135 Ok(())
136}
137
138#[driver_test]
141pub async fn starts_with_special_chars(test: &mut Test) -> Result<()> {
142 #[derive(Debug, toasty::Model)]
143 #[key(partition = partition_id, local = sort_key)]
144 struct StringItem {
145 partition_id: i64,
146 sort_key: String,
147 }
148
149 let mut db = test.setup_db(models!(StringItem)).await;
150
151 toasty::create!(StringItem::[
152 { partition_id: 1_i64, sort_key: "100%-discount" },
153 { partition_id: 1_i64, sort_key: "100xdiscount" },
154 { partition_id: 1_i64, sort_key: "1009" },
155 { partition_id: 1_i64, sort_key: "a_b-literal" },
156 { partition_id: 1_i64, sort_key: "axb-wildcard" },
157 { partition_id: 1_i64, sort_key: "!bang-literal" },
158 { partition_id: 1_i64, sort_key: "x!bang" },
159 ])
160 .exec(&mut db)
161 .await
162 .unwrap();
163
164 let mut items: Vec<StringItem> = StringItem::filter(
166 StringItem::fields().partition_id().eq(1_i64).and(
167 StringItem::fields()
168 .sort_key()
169 .starts_with("100%".to_string()),
170 ),
171 )
172 .exec(&mut db)
173 .await?;
174 items.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
175 assert_eq!(items.len(), 1);
176 assert_eq!(items[0].sort_key, "100%-discount");
177
178 let mut items: Vec<StringItem> = StringItem::filter(
180 StringItem::fields().partition_id().eq(1_i64).and(
181 StringItem::fields()
182 .sort_key()
183 .starts_with("a_b".to_string()),
184 ),
185 )
186 .exec(&mut db)
187 .await?;
188 items.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
189 assert_eq!(items.len(), 1);
190 assert_eq!(items[0].sort_key, "a_b-literal");
191
192 let mut items: Vec<StringItem> = StringItem::filter(
195 StringItem::fields().partition_id().eq(1_i64).and(
196 StringItem::fields()
197 .sort_key()
198 .starts_with("!bang".to_string()),
199 ),
200 )
201 .exec(&mut db)
202 .await?;
203 items.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
204 assert_eq!(items.len(), 1);
205 assert_eq!(items[0].sort_key, "!bang-literal");
206
207 Ok(())
208}
209
210#[driver_test]
213pub async fn starts_with_optional_field(test: &mut Test) -> Result<()> {
214 #[derive(Debug, toasty::Model)]
215 #[key(partition = partition_id, local = id)]
216 struct OptItem {
217 partition_id: i64,
218 id: i64,
219 nickname: Option<String>,
220 }
221
222 let mut db = test.setup_db(models!(OptItem)).await;
223
224 toasty::create!(OptItem::[
225 { partition_id: 1_i64, id: 1_i64, nickname: Some("Ali".to_string()) },
226 { partition_id: 1_i64, id: 2_i64, nickname: Some("Alicia".to_string()) },
227 { partition_id: 1_i64, id: 3_i64, nickname: Some("Bob".to_string()) },
228 { partition_id: 1_i64, id: 4_i64, nickname: None },
229 ])
230 .exec(&mut db)
231 .await?;
232
233 let mut items: Vec<OptItem> = OptItem::filter(
234 OptItem::fields()
235 .partition_id()
236 .eq(1_i64)
237 .and(OptItem::fields().nickname().starts_with("Al".to_string())),
238 )
239 .exec(&mut db)
240 .await?;
241
242 items.sort_by_key(|i| i.id);
243
244 assert_eq!(items.len(), 2);
245 assert_eq!(items[0].nickname.as_deref(), Some("Ali"));
246 assert_eq!(items[1].nickname.as_deref(), Some("Alicia"));
247
248 Ok(())
249}
250
251#[driver_test]
254pub async fn starts_with_case_sensitive(test: &mut Test) -> Result<()> {
255 #[derive(Debug, toasty::Model)]
256 #[key(partition = partition_id, local = sort_key)]
257 struct CaseItem {
258 partition_id: i64,
259 sort_key: String,
260 name: String,
261 }
262
263 let mut db = test.setup_db(models!(CaseItem)).await;
264
265 toasty::create!(CaseItem::[
266 { partition_id: 1_i64, sort_key: "1", name: "Alice" },
267 { partition_id: 1_i64, sort_key: "2", name: "alice" },
268 { partition_id: 1_i64, sort_key: "3", name: "ALICE" },
269 ])
270 .exec(&mut db)
271 .await?;
272
273 let mut lower: Vec<CaseItem> = CaseItem::filter(
275 CaseItem::fields()
276 .partition_id()
277 .eq(1_i64)
278 .and(CaseItem::fields().name().starts_with("al".to_string())),
279 )
280 .exec(&mut db)
281 .await?;
282 lower.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
283 assert_eq!(
284 lower.len(),
285 1,
286 "lowercase prefix should match only lowercase record"
287 );
288 assert_eq!(lower[0].name, "alice");
289
290 let mut upper: Vec<CaseItem> = CaseItem::filter(
292 CaseItem::fields()
293 .partition_id()
294 .eq(1_i64)
295 .and(CaseItem::fields().name().starts_with("AL".to_string())),
296 )
297 .exec(&mut db)
298 .await?;
299 upper.sort_by(|a, b| a.sort_key.cmp(&b.sort_key));
300 assert_eq!(
301 upper.len(),
302 1,
303 "uppercase prefix should match only uppercase record"
304 );
305 assert_eq!(upper[0].name, "ALICE");
306
307 Ok(())
308}
309
310#[driver_test(requires(not(sql)))]
314pub async fn starts_with_partition_key_error(test: &mut Test) -> Result<()> {
315 #[derive(Debug, toasty::Model)]
316 #[key(partition = partition_id, local = sort_key)]
317 struct StringKeyItem {
318 partition_id: String,
319 sort_key: String,
320 }
321
322 let mut db = test.setup_db(models!(StringKeyItem)).await;
323
324 StringKeyItem::create()
325 .partition_id("hello")
326 .sort_key("world")
327 .exec(&mut db)
328 .await?;
329
330 let result = StringKeyItem::filter(
331 StringKeyItem::fields()
332 .partition_id()
333 .starts_with("hel".to_string()),
334 )
335 .exec(&mut db)
336 .await;
337
338 if test.capability().scan {
339 let items = result?;
340 assert_eq!(1, items.len());
341 assert_eq!("hello", items[0].partition_id);
342 } else {
343 assert!(
344 result.is_err(),
345 "expected error when using starts_with on partition key"
346 );
347 }
348
349 Ok(())
350}