Skip to main content

toasty_core/stmt/
assignments.rs

1use crate::stmt::{Node, Statement, Visit, VisitMut};
2
3use super::{Expr, Projection};
4
5use std::{collections::BTreeMap, ops};
6
7/// An ordered map of field assignments for an [`Update`](super::Update) statement.
8///
9/// Each entry maps a field projection (identifying which field to change) to an
10/// [`Assignment`] (how to change it). The entries are ordered by projection,
11/// allowing range queries over prefixes.
12///
13/// # Examples
14///
15/// ```ignore
16/// use toasty_core::stmt::{Assignments, Expr, Projection};
17///
18/// let mut assignments = Assignments::default();
19/// assert!(assignments.is_empty());
20///
21/// assignments.set(Projection::single(0), Expr::null());
22/// assert_eq!(assignments.len(), 1);
23/// ```
24#[derive(Clone, Debug, Default, PartialEq)]
25pub struct Assignments {
26    /// Map from field projection to assignment. The projection may reference an
27    /// application-level model field or a lowered table column. Supports both
28    /// single-step (e.g., `[0]`) and multi-step projections (e.g., `[0, 1]`
29    /// for nested fields).
30    assignments: BTreeMap<Projection, Assignment>,
31}
32
33/// A field assignment within an [`Update`](super::Update) statement.
34///
35/// Each variant carries the expression providing the value for the operation.
36/// Multiple operations on the same field are represented via [`Batch`](Assignment::Batch).
37///
38/// # Examples
39///
40/// ```ignore
41/// use toasty_core::stmt::{Assignment, Expr};
42///
43/// let assignment = Assignment::Set(Expr::null());
44/// assert!(assignment.is_set());
45/// ```
46#[derive(Debug, Clone, PartialEq)]
47pub enum Assignment {
48    /// Set a field, replacing the current value.
49    Set(Expr),
50
51    /// Insert one or more values into a set field.
52    Insert(Expr),
53
54    /// Remove one or more values from a set field.
55    Remove(Expr),
56
57    /// Append every element of a list expression to the end of an ordered
58    /// collection field (e.g. `Vec<scalar>`). The expression must evaluate
59    /// to a list whose element type matches the target column.
60    Append(Expr),
61
62    /// Drop the last element of an ordered collection field. Drives
63    /// [`stmt::pop`](crate::stmt::Assignment::Pop) on `Vec<scalar>` fields.
64    /// No-op on an empty collection.
65    Pop,
66
67    /// Drop the element at the given index from an ordered collection field.
68    /// The expression must evaluate to a `usize`-shaped value. Out-of-bounds
69    /// indices are a no-op rather than an error — per-row failure semantics
70    /// on a bulk update are rarely useful.
71    RemoveAt(Expr),
72
73    /// Add the expression to the current numeric column value (`col = col + expr`).
74    /// Atomic against the existing column value on every backend.
75    Add(Expr),
76
77    /// Subtract the expression from the current numeric column value
78    /// (`col = col - expr`). Atomic against the existing column value on
79    /// every backend.
80    Subtract(Expr),
81
82    /// Multiple assignments on the same field.
83    Batch(Vec<Assignment>),
84}
85
86impl Statement {
87    /// Returns this statement's assignments if it is an `Update`.
88    pub fn assignments(&self) -> Option<&Assignments> {
89        match self {
90            Statement::Update(update) => Some(&update.assignments),
91            _ => None,
92        }
93    }
94}
95
96impl Assignments {
97    /// Creates an empty `Assignments`.
98    pub fn new() -> Self {
99        Self {
100            assignments: BTreeMap::new(),
101        }
102    }
103
104    /// Returns `true` if there are no assignments.
105    pub fn is_empty(&self) -> bool {
106        self.assignments.is_empty()
107    }
108
109    /// Returns the number of assigned projections (keys).
110    pub fn len(&self) -> usize {
111        self.assignments.len()
112    }
113
114    /// Returns `true` if an assignment exists for the given projection.
115    ///
116    /// The `key` accepts any type that implements `AsRef<[usize]>`:
117    /// - [`Projection`] — look up by projection directly
118    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
119    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
120    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
121    ///   so `&[1]` works without a suffix.
122    pub fn contains<Q>(&self, key: &Q) -> bool
123    where
124        Q: ?Sized + AsRef<[usize]>,
125    {
126        self.assignments.contains_key(key.as_ref())
127    }
128
129    /// Returns a reference to the assignment for the given projection, if any.
130    ///
131    /// The `key` accepts any type that implements `AsRef<[usize]>`:
132    /// - [`Projection`] — look up by projection directly
133    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
134    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
135    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
136    ///   so `&[1]` works without a suffix.
137    pub fn get<Q>(&self, key: &Q) -> Option<&Assignment>
138    where
139        Q: ?Sized + AsRef<[usize]>,
140    {
141        self.assignments.get(key.as_ref())
142    }
143
144    /// Returns a mutable reference to the assignment for the given projection.
145    ///
146    /// The `key` accepts any type that implements `AsRef<[usize]>`:
147    /// - [`Projection`] — look up by projection directly
148    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
149    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
150    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
151    ///   so `&[1]` works without a suffix.
152    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Assignment>
153    where
154        Q: ?Sized + AsRef<[usize]>,
155    {
156        self.assignments.get_mut(key.as_ref())
157    }
158
159    /// Sets a field to the given expression value, replacing any existing
160    /// assignment for that projection.
161    pub fn set<Q>(&mut self, key: Q, expr: impl Into<Expr>)
162    where
163        Q: Into<Projection>,
164    {
165        let key = key.into();
166        self.assignments.insert(key, Assignment::Set(expr.into()));
167    }
168
169    /// Removes the assignment for the given projection, if any.
170    ///
171    /// The `key` accepts any type that implements `AsRef<[usize]>`:
172    /// - [`Projection`] — look up by projection directly
173    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
174    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
175    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
176    ///   so `&[1]` works without a suffix.
177    pub fn unset<Q>(&mut self, key: &Q)
178    where
179        Q: ?Sized + AsRef<[usize]>,
180    {
181        self.assignments.remove(key.as_ref());
182    }
183
184    /// Insert a value into a set. The expression should evaluate to a single
185    /// value that is inserted into the set.
186    pub fn insert<Q>(&mut self, key: Q, expr: impl Into<Expr>)
187    where
188        Q: Into<Projection>,
189    {
190        let key = key.into();
191        let new = Assignment::Insert(expr.into());
192        self.assignments
193            .entry(key)
194            .and_modify(|existing| existing.push(new.clone()))
195            .or_insert(new);
196    }
197
198    /// Adds a `Remove` assignment for the given projection.
199    pub fn remove<Q>(&mut self, key: Q, expr: impl Into<Expr>)
200    where
201        Q: Into<Projection>,
202    {
203        let key = key.into();
204        let new = Assignment::Remove(expr.into());
205        self.assignments
206            .entry(key)
207            .and_modify(|existing| existing.push(new.clone()))
208            .or_insert(new);
209    }
210
211    /// Adds an `Append` assignment for the given projection. The expression
212    /// should evaluate to a list of elements to append to an ordered
213    /// collection field; multiple appends on the same projection batch.
214    pub fn append<Q>(&mut self, key: Q, expr: impl Into<Expr>)
215    where
216        Q: Into<Projection>,
217    {
218        let key = key.into();
219        let new = Assignment::Append(expr.into());
220        self.assignments
221            .entry(key)
222            .and_modify(|existing| existing.push(new.clone()))
223            .or_insert(new);
224    }
225
226    /// Adds a `Pop` assignment for the given projection. Multiple pops on the
227    /// same projection batch.
228    pub fn pop<Q>(&mut self, key: Q)
229    where
230        Q: Into<Projection>,
231    {
232        let key = key.into();
233        let new = Assignment::Pop;
234        self.assignments
235            .entry(key)
236            .and_modify(|existing| existing.push(new.clone()))
237            .or_insert(new);
238    }
239
240    /// Adds a `RemoveAt` assignment for the given projection. The expression
241    /// should evaluate to the element index to drop. Multiple removals on the
242    /// same projection batch.
243    pub fn remove_at<Q>(&mut self, key: Q, expr: impl Into<Expr>)
244    where
245        Q: Into<Projection>,
246    {
247        let key = key.into();
248        let new = Assignment::RemoveAt(expr.into());
249        self.assignments
250            .entry(key)
251            .and_modify(|existing| existing.push(new.clone()))
252            .or_insert(new);
253    }
254
255    /// Adds an `Add` assignment for the given projection. The expression
256    /// should evaluate to a numeric value to add to the current column value.
257    /// Multiple ops on the same projection batch.
258    pub fn add<Q>(&mut self, key: Q, expr: impl Into<Expr>)
259    where
260        Q: Into<Projection>,
261    {
262        let key = key.into();
263        let new = Assignment::Add(expr.into());
264        self.assignments
265            .entry(key)
266            .and_modify(|existing| existing.push(new.clone()))
267            .or_insert(new);
268    }
269
270    /// Adds a `Subtract` assignment for the given projection. The expression
271    /// should evaluate to a numeric value to subtract from the current column
272    /// value. Multiple ops on the same projection batch.
273    pub fn subtract<Q>(&mut self, key: Q, expr: impl Into<Expr>)
274    where
275        Q: Into<Projection>,
276    {
277        let key = key.into();
278        let new = Assignment::Subtract(expr.into());
279        self.assignments
280            .entry(key)
281            .and_modify(|existing| existing.push(new.clone()))
282            .or_insert(new);
283    }
284
285    /// Removes and returns the assignment for the given projection.
286    ///
287    /// The `key` accepts any type that implements `AsRef<[usize]>`:
288    /// - [`Projection`] — look up by projection directly
289    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
290    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
291    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
292    ///   so `&[1]` works without a suffix.
293    pub fn take<Q>(&mut self, key: &Q) -> Option<Assignment>
294    where
295        Q: ?Sized + AsRef<[usize]>,
296    {
297        self.assignments.remove(key.as_ref())
298    }
299
300    /// Returns an iterator over the assignment projections (keys).
301    pub fn keys(&self) -> impl Iterator<Item = &Projection> + '_ {
302        self.assignments.keys()
303    }
304
305    /// Returns an iterator over `(projection, assignment)` pairs.
306    pub fn iter(&self) -> impl Iterator<Item = (&Projection, &Assignment)> + '_ {
307        self.assignments.iter()
308    }
309
310    /// Returns a mutable iterator over `(projection, assignment)` pairs.
311    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Projection, &mut Assignment)> + '_ {
312        self.assignments.iter_mut()
313    }
314}
315
316impl IntoIterator for Assignments {
317    type Item = (Projection, Assignment);
318    type IntoIter = std::collections::btree_map::IntoIter<Projection, Assignment>;
319
320    fn into_iter(self) -> Self::IntoIter {
321        self.assignments.into_iter()
322    }
323}
324
325impl<'a> IntoIterator for &'a Assignments {
326    type Item = (&'a Projection, &'a Assignment);
327    type IntoIter = std::collections::btree_map::Iter<'a, Projection, Assignment>;
328
329    fn into_iter(self) -> Self::IntoIter {
330        self.assignments.iter()
331    }
332}
333
334impl<'a> IntoIterator for &'a mut Assignments {
335    type Item = (&'a Projection, &'a mut Assignment);
336    type IntoIter = std::collections::btree_map::IterMut<'a, Projection, Assignment>;
337
338    fn into_iter(self) -> Self::IntoIter {
339        self.assignments.iter_mut()
340    }
341}
342
343/// Indexes into the assignments by projection. Panics if no assignment exists
344/// for the given key.
345///
346/// The index accepts any type that implements `AsRef<[usize]>`:
347/// [`Projection`], `&[usize]`, or `[usize; N]` arrays.
348impl<Q> ops::Index<Q> for Assignments
349where
350    Q: AsRef<[usize]>,
351{
352    type Output = Assignment;
353
354    fn index(&self, index: Q) -> &Self::Output {
355        match self.assignments.get(index.as_ref()) {
356            Some(ret) => ret,
357            None => panic!("no assignment for projection"),
358        }
359    }
360}
361
362/// Mutably indexes into the assignments by projection. Panics if no assignment
363/// exists for the given key.
364///
365/// The index accepts any type that implements `AsRef<[usize]>`:
366/// [`Projection`], `&[usize]`, or `[usize; N]` arrays.
367impl<Q> ops::IndexMut<Q> for Assignments
368where
369    Q: AsRef<[usize]>,
370{
371    fn index_mut(&mut self, index: Q) -> &mut Self::Output {
372        match self.assignments.get_mut(index.as_ref()) {
373            Some(ret) => ret,
374            None => panic!("no assignment for projection"),
375        }
376    }
377}
378
379impl Assignment {
380    /// Returns `true` if this is the `Set` variant.
381    pub fn is_set(&self) -> bool {
382        matches!(self, Self::Set(_))
383    }
384
385    /// Returns `true` if this is the `Remove` variant.
386    pub fn is_remove(&self) -> bool {
387        matches!(self, Self::Remove(_))
388    }
389
390    /// Appends another assignment, converting to `Batch` if needed.
391    pub fn push(&mut self, other: Assignment) {
392        match self {
393            Self::Batch(entries) => entries.push(other),
394            _ => {
395                let prev = std::mem::replace(self, Assignment::Batch(Vec::new()));
396                if let Assignment::Batch(entries) = self {
397                    entries.push(prev);
398                    entries.push(other);
399                }
400            }
401        }
402    }
403}
404
405impl Node for Assignment {
406    fn visit<V: Visit>(&self, mut visit: V) {
407        visit.visit_assignment(self);
408    }
409
410    fn visit_mut<V: VisitMut>(&mut self, mut visit: V) {
411        visit.visit_assignment_mut(self);
412    }
413}