1use std::collections::HashMap;
2
3use itermap::IterMap;
4use itertools::Itertools;
5use optempty::EmptyIntoNone;
6
7use super::Expression;
8use crate::{
9 condition::{
10 And, AttributeExists, AttributeNotExists, AttributeType, BeginsWith, Between, Comparison,
11 Condition, Contains, In, Not, Or, Parenthetical,
12 },
13 key::KeyCondition,
14 operand::{Operand, OperandType, Size},
15 path::{Element, Name, Path},
16 update::{Set, SetAction, Update},
17 value::{Ref, Value, ValueOrRef},
18};
19
20#[must_use = "Call `.build()` to create the `Expression`"]
26#[derive(Debug, Default, Clone)]
27pub struct Builder {
28 condition: Option<Condition>,
29 key_condition: Option<KeyCondition>,
30 update: Option<Update>,
31 filter: Option<Condition>,
32 projection: Option<Vec<Name>>,
33 names: HashMap<Name, String>,
34 values: HashMap<Value, Ref>,
35}
36
37impl Builder {
39 pub fn with_condition<T>(mut self, condition: T) -> Self
41 where
42 T: Into<Condition>,
43 {
44 self.condition = Some(self.process_condition(condition.into()));
45
46 self
47 }
48
49 pub fn with_key_condition<T>(mut self, key_condition: T) -> Self
70 where
71 T: Into<KeyCondition>,
72 {
73 self.key_condition = Some(KeyCondition {
74 condition: self.process_condition(key_condition.into().condition),
75 });
76
77 self
78 }
79
80 pub fn with_update<T>(mut self, update: T) -> Self
82 where
83 T: Into<Update>,
84 {
85 self.update = Some(self.process_update(update.into()));
86
87 self
88 }
89
90 pub fn with_filter<T>(mut self, filter: T) -> Self
92 where
93 T: Into<Condition>,
94 {
95 self.filter = Some(self.process_condition(filter.into()));
96
97 self
98 }
99
100 pub fn with_projection<I, T>(mut self, names: I) -> Self
155 where
156 I: IntoIterator<Item = T>,
157 T: Into<Name>,
158 {
159 self.projection = Some(
160 names
161 .into_iter()
162 .map(|name| self.process_name(name.into()))
163 .collect(),
164 )
165 .empty_into_none();
169
170 self
171 }
172
173 pub fn build(self) -> Expression {
175 let Self {
176 condition,
177 key_condition,
178 update,
179 filter,
180 projection,
181 names,
182 values,
183 } = self;
184
185 Expression {
186 condition_expression: condition.map(Into::into),
187 key_condition_expression: key_condition.map(Into::into),
188 update_expression: {
189 update.as_ref().map(ToString::to_string)
192 },
193 filter_expression: filter.map(Into::into),
194 projection_expression: projection.map(|attrs| {
195 attrs
196 .into_iter()
197 .map(|name| name.name)
198 .collect_vec()
199 .join(", ")
200 }),
201 expression_attribute_names: Some(
202 names
203 .into_iter()
204 .map_keys(|name| name.name)
205 .swap()
206 .collect(),
207 )
208 .empty_into_none(),
209 expression_attribute_values: Some(
210 values
211 .into_iter()
212 .swap()
213 .map_keys(String::from)
214 .map_values(Value::into_attribute_value)
215 .collect(),
216 )
217 .empty_into_none(),
218 }
219 }
220
221 fn process_condition(&mut self, condition: Condition) -> Condition {
222 match condition {
223 Condition::AttributeExists(AttributeExists { path }) => AttributeExists {
224 path: self.process_path(path),
225 }
226 .into(),
227 Condition::AttributeNotExists(AttributeNotExists { path }) => AttributeNotExists {
228 path: self.process_path(path),
229 }
230 .into(),
231 Condition::AttributeType(AttributeType {
232 path,
233 attribute_type,
234 }) => AttributeType {
235 path: self.process_path(path),
236 attribute_type,
237 }
238 .into(),
239 Condition::Contains(Contains { path, operand }) => Contains {
240 path: self.process_path(path),
241 operand: self.process_value(operand).into(),
242 }
243 .into(),
244 Condition::BeginsWith(BeginsWith { path, substr }) => BeginsWith {
245 path: self.process_path(path),
246 substr: self.process_value(substr).into(),
247 }
248 .into(),
249 Condition::Between(Between { op, lower, upper }) => Between {
250 op: self.process_operand(op),
251 lower: self.process_operand(lower),
252 upper: self.process_operand(upper),
253 }
254 .into(),
255 Condition::In(In { op, items }) => In {
256 op: self.process_operand(op),
257 items: items
258 .into_iter()
259 .map(|item| self.process_operand(item))
260 .collect(),
261 }
262 .into(),
263 Condition::Comparison(Comparison { left, cmp, right }) => Comparison {
264 left: self.process_operand(left),
265 cmp,
266 right: self.process_operand(right),
267 }
268 .into(),
269 Condition::And(And { left, right }) => And {
270 left: self.process_condition(*left).into(),
271 right: self.process_condition(*right).into(),
272 }
273 .into(),
274 Condition::Or(Or { left, right }) => Or {
275 left: self.process_condition(*left).into(),
276 right: self.process_condition(*right).into(),
277 }
278 .into(),
279 Condition::Not(Not { condition }) => Not {
280 condition: self.process_condition(*condition).into(),
281 }
282 .into(),
283 Condition::Parenthetical(Parenthetical { condition }) => Parenthetical {
284 condition: self.process_condition(*condition).into(),
285 }
286 .into(),
287 }
288 }
289
290 fn process_operand(&mut self, operand: Operand) -> Operand {
291 let Operand { op } = operand;
292
293 match op {
294 OperandType::Path(path) => self.process_path(path).into(),
295 OperandType::Size(Size { path: name }) => Size {
296 path: self.process_path(name),
297 }
298 .into(),
299 OperandType::Scalar(value) => Operand {
300 op: OperandType::Scalar(self.process_value(value).into()),
301 },
302 OperandType::Condition(condition) => self.process_condition(*condition).into(),
303 }
304 }
305
306 fn process_update(&mut self, update: Update) -> Update {
307 let Update {
308 mut set,
309 mut remove,
310 mut add,
311 mut delete,
312 } = update;
313
314 set = set.map(|set| self.process_set(set));
315
316 remove = remove.map(|mut remove| {
317 remove.paths = remove
318 .paths
319 .into_iter()
320 .map(|path| self.process_path(path))
321 .collect();
322
323 remove.paths.shrink_to_fit();
324
325 remove
326 });
327
328 add = add.map(|mut add| {
329 add.actions = add
330 .actions
331 .into_iter()
332 .map(|action| {
333 let mut action = action;
334
335 action.path = self.process_path(action.path);
336 action.value = self.process_value(action.value).into();
337
338 action
339 })
340 .collect();
341
342 add.actions.shrink_to_fit();
343
344 add
345 });
346
347 delete = delete.map(|mut delete| {
348 delete.actions = delete
349 .actions
350 .into_iter()
351 .map(|action| {
352 let mut action = action;
353
354 action.path = self.process_path(action.path);
355 action.subset = self.process_value(action.subset).into();
356
357 action
358 })
359 .collect();
360
361 delete.actions.shrink_to_fit();
362
363 delete
364 });
365
366 Update {
367 set,
368 remove,
369 add,
370 delete,
371 }
372 }
373
374 fn process_set(&mut self, set: Set) -> Set {
375 let Set { mut actions } = set;
376
377 actions = actions
378 .into_iter()
379 .map(|action| match action {
380 SetAction::Assign(mut action) => {
381 action.path = self.process_path(action.path);
382 action.value = self.process_value(action.value).into();
383
384 action.into()
385 }
386 SetAction::Math(mut action) => {
387 action.dst = self.process_path(action.dst);
388 action.src = action.src.map(|src| self.process_path(src));
389 action.num = self.process_value(action.num).into();
390
391 action.into()
392 }
393 SetAction::ListAppend(mut action) => {
394 action.dst = self.process_path(action.dst);
395 action.src = action.src.map(|src| self.process_path(src));
396 action.list = self.process_value(action.list).into();
397
398 action.into()
399 }
400 SetAction::IfNotExists(mut action) => {
401 action.dst = self.process_path(action.dst);
402 action.src = action.src.map(|src| self.process_path(src));
403 action.value = self.process_value(action.value).into();
404
405 action.into()
406 }
407 })
408 .collect();
409
410 actions.shrink_to_fit();
411
412 Set { actions }
413 }
414
415 fn process_path(&mut self, path: Path) -> Path {
416 let Path { mut elements } = path;
417
418 elements = elements
419 .into_iter()
420 .map(|elem| match elem {
421 Element::Name(name) => self.process_name(name).into(),
422 Element::IndexedField(mut new_indexed_field) => {
423 new_indexed_field.name = self.process_name(new_indexed_field.name);
424
425 new_indexed_field.into()
426 }
427 })
428 .collect();
429
430 Path { elements }
431 }
432
433 fn process_name(&mut self, name: Name) -> Name {
434 let count = self.names.len();
435
436 Name {
437 name: self
438 .names
439 .entry(name)
440 .or_insert(format!("#{count}"))
441 .clone(),
442 }
443 }
444
445 fn process_value(&mut self, value: ValueOrRef) -> Ref {
446 match value {
447 ValueOrRef::Value(value) => {
448 let count = self.values.len();
449
450 self.values
451 .entry(value)
452 .or_insert_with(|| count.to_string().into())
453 .clone()
454 }
455 ValueOrRef::Ref(value) => value,
456 }
457 }
458}
459
460#[cfg(test)]
461mod test {
462 use aws_sdk_dynamodb::operation::query::builders::QueryInputBuilder;
463 use pretty_assertions::assert_eq;
464
465 use crate::path::Name;
466
467 use super::Expression;
468
469 #[test]
470 fn empty_projection() {
471 let expression = Expression::builder()
472 .with_projection(Vec::<Name>::default())
473 .build();
474 assert_eq!(
475 Expression {
476 condition_expression: None,
477 filter_expression: None,
478 key_condition_expression: None,
479 projection_expression: None,
480 update_expression: None,
481 expression_attribute_names: None,
482 expression_attribute_values: None,
483 },
484 expression,
485 "An empty iterator should result in `None` for projection expression"
486 );
487
488 let query = expression.to_query_input_builder();
489 assert_eq!(QueryInputBuilder::default(), query);
490 }
491}
492
493#[cfg(test)]
494mod doc_examples {
495 #[test]
496 fn example_with_projection() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
497 use crate::{path::Name, Expression};
498 use pretty_assertions::assert_eq;
499
500 let expected = Expression {
501 condition_expression: None,
502 key_condition_expression: None,
503 update_expression: None,
504 filter_expression: None,
505 projection_expression: Some(String::from("#0, #1")),
506 expression_attribute_names: Some(
507 [("#0", "id"), ("#1", "name")]
508 .into_iter()
509 .map(|(k, v)| (String::from(k), String::from(v)))
510 .collect(),
511 ),
512 expression_attribute_values: None,
513 };
514
515 let expression = Expression::builder()
516 .with_projection(["id", "name"])
517 .build();
518 assert_eq!(expected, expression);
519
520 let expression = Expression::builder()
521 .with_projection([String::from("id"), String::from("name")])
522 .build();
523 assert_eq!(expected, expression);
524
525 let expression = Expression::builder()
526 .with_projection([Name::from("id"), Name::from("name")])
527 .build();
528 assert_eq!(expected, expression);
529
530 let expression = Expression::builder()
532 .with_projection(vec!["id", "name"])
533 .build();
534 assert_eq!(expected, expression);
535
536 let expression = Expression::builder()
538 .with_projection(["id", "name"].into_iter().map(Name::from))
539 .build();
540 assert_eq!(expected, expression);
541
542 Ok(())
543 }
544}