dynamodb_expression/expression/
builder.rs

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/// For building an [`Expression`]. Finish with [`.build()`].
21///
22/// See: [`Expression::builder`]
23///
24/// [`.build()`]: Self::build
25#[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
37/// Functions and methods for building an `Expression`.
38impl Builder {
39    /// Sets the condition for this [`Expression`], overwriting any previously set.
40    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    /// Sets the key condition for this [`Expression`], overwriting any previously set.
50    ///
51    /// See also: [`Path::key`]
52    ///
53    /// ```
54    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
55    /// use dynamodb_expression::{Expression, Num, Path};
56    ///
57    /// let key_condition = "id"
58    ///     .parse::<Path>()?
59    ///     .key()
60    ///     .equal(Num::new(42))
61    ///     .and("category".parse::<Path>()?.key().begins_with("hardware."));
62    ///
63    /// let expression = Expression::builder().with_key_condition(key_condition).build();
64    /// # _ = expression;
65    /// #
66    /// # Ok(())
67    /// # }
68    /// ```
69    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    /// Sets the update expression, overwriting any previously set.
81    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    /// Sets the filter for this [`Expression`], overwriting any previously set.
91    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    /// Sets the projection for this [`Expression`], overwriting any previously set.
101    ///
102    /// Each of these examples produce the same projection expression.
103    ///
104    /// ```
105    /// # fn example_with_projection() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
106    /// # use pretty_assertions::assert_eq;
107    /// # use dynamodb_expression::{path::Name, Expression};
108    /// #
109    /// let expected = Expression {
110    ///     condition_expression: None,
111    ///     key_condition_expression: None,
112    ///     update_expression: None,
113    ///     filter_expression: None,
114    ///     projection_expression: Some(String::from("#0, #1")),
115    ///     expression_attribute_names: Some(
116    ///         [("#0", "id"), ("#1", "name")]
117    ///             .into_iter()
118    ///             .map(|(k, v)| (String::from(k), String::from(v)))
119    ///             .collect(),
120    ///     ),
121    ///     expression_attribute_values: None,
122    /// };
123    ///
124    /// let expression = Expression::builder()
125    ///     .with_projection(["id", "name"])
126    ///     .build();
127    /// assert_eq!(expected, expression);
128    ///
129    /// let expression = Expression::builder()
130    ///     .with_projection([String::from("id"), String::from("name")])
131    ///     .build();
132    /// assert_eq!(expected, expression);
133    ///
134    /// let expression = Expression::builder()
135    ///     .with_projection([Name::from("id"), Name::from("name")])
136    ///     .build();
137    /// assert_eq!(expected, expression);
138    ///
139    /// // Anything that's `IntoIterator` will work. A `Vec`, for example.
140    /// let expression = Expression::builder()
141    ///     .with_projection(vec!["id", "name"])
142    ///     .build();
143    /// assert_eq!(expected, expression);
144    ///
145    /// // Or an `Iterator`.
146    /// let expression = Expression::builder()
147    ///     .with_projection(["id", "name"].into_iter().map(Name::from))
148    ///     .build();
149    /// assert_eq!(expected, expression);
150    /// #
151    /// # Ok(())
152    /// # }
153    /// ```
154    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` because DynamoDB doesn't allow empty projection
166        // expressions, and will return:
167        // `Invalid ProjectionExpression: The expression can not be empty;`
168        .empty_into_none();
169
170        self
171    }
172
173    /// Builds the [`Expression`].
174    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                // Is there a more efficient way when all the `Update` strings
190                // require formatting?
191                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        // Anything that's `IntoIterator` will work. A `Vec`, for example.
531        let expression = Expression::builder()
532            .with_projection(vec!["id", "name"])
533            .build();
534        assert_eq!(expected, expression);
535
536        // Or an `Iterator`.
537        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}