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    /// Multiple assignments on the same field.
74    Batch(Vec<Assignment>),
75}
76
77impl Statement {
78    /// Returns this statement's assignments if it is an `Update`.
79    pub fn assignments(&self) -> Option<&Assignments> {
80        match self {
81            Statement::Update(update) => Some(&update.assignments),
82            _ => None,
83        }
84    }
85}
86
87impl Assignments {
88    /// Creates an empty `Assignments`.
89    pub fn new() -> Self {
90        Self {
91            assignments: BTreeMap::new(),
92        }
93    }
94
95    /// Returns `true` if there are no assignments.
96    pub fn is_empty(&self) -> bool {
97        self.assignments.is_empty()
98    }
99
100    /// Returns the number of assigned projections (keys).
101    pub fn len(&self) -> usize {
102        self.assignments.len()
103    }
104
105    /// Returns `true` if an assignment exists for the given projection.
106    ///
107    /// The `key` accepts any type that implements `AsRef<[usize]>`:
108    /// - [`Projection`] — look up by projection directly
109    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
110    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
111    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
112    ///   so `&[1]` works without a suffix.
113    pub fn contains<Q>(&self, key: &Q) -> bool
114    where
115        Q: ?Sized + AsRef<[usize]>,
116    {
117        self.assignments.contains_key(key.as_ref())
118    }
119
120    /// Returns a reference to the assignment for the given projection, if any.
121    ///
122    /// The `key` accepts any type that implements `AsRef<[usize]>`:
123    /// - [`Projection`] — look up by projection directly
124    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
125    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
126    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
127    ///   so `&[1]` works without a suffix.
128    pub fn get<Q>(&self, key: &Q) -> Option<&Assignment>
129    where
130        Q: ?Sized + AsRef<[usize]>,
131    {
132        self.assignments.get(key.as_ref())
133    }
134
135    /// Returns a mutable reference to the assignment for the given projection.
136    ///
137    /// The `key` accepts any type that implements `AsRef<[usize]>`:
138    /// - [`Projection`] — look up by projection directly
139    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
140    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
141    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
142    ///   so `&[1]` works without a suffix.
143    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Assignment>
144    where
145        Q: ?Sized + AsRef<[usize]>,
146    {
147        self.assignments.get_mut(key.as_ref())
148    }
149
150    /// Sets a field to the given expression value, replacing any existing
151    /// assignment for that projection.
152    pub fn set<Q>(&mut self, key: Q, expr: impl Into<Expr>)
153    where
154        Q: Into<Projection>,
155    {
156        let key = key.into();
157        self.assignments.insert(key, Assignment::Set(expr.into()));
158    }
159
160    /// Removes the assignment for the given projection, if any.
161    ///
162    /// The `key` accepts any type that implements `AsRef<[usize]>`:
163    /// - [`Projection`] — look up by projection directly
164    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
165    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
166    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
167    ///   so `&[1]` works without a suffix.
168    pub fn unset<Q>(&mut self, key: &Q)
169    where
170        Q: ?Sized + AsRef<[usize]>,
171    {
172        self.assignments.remove(key.as_ref());
173    }
174
175    /// Insert a value into a set. The expression should evaluate to a single
176    /// value that is inserted into the set.
177    pub fn insert<Q>(&mut self, key: Q, expr: impl Into<Expr>)
178    where
179        Q: Into<Projection>,
180    {
181        let key = key.into();
182        let new = Assignment::Insert(expr.into());
183        self.assignments
184            .entry(key)
185            .and_modify(|existing| existing.push(new.clone()))
186            .or_insert(new);
187    }
188
189    /// Adds a `Remove` assignment for the given projection.
190    pub fn remove<Q>(&mut self, key: Q, expr: impl Into<Expr>)
191    where
192        Q: Into<Projection>,
193    {
194        let key = key.into();
195        let new = Assignment::Remove(expr.into());
196        self.assignments
197            .entry(key)
198            .and_modify(|existing| existing.push(new.clone()))
199            .or_insert(new);
200    }
201
202    /// Adds an `Append` assignment for the given projection. The expression
203    /// should evaluate to a list of elements to append to an ordered
204    /// collection field; multiple appends on the same projection batch.
205    pub fn append<Q>(&mut self, key: Q, expr: impl Into<Expr>)
206    where
207        Q: Into<Projection>,
208    {
209        let key = key.into();
210        let new = Assignment::Append(expr.into());
211        self.assignments
212            .entry(key)
213            .and_modify(|existing| existing.push(new.clone()))
214            .or_insert(new);
215    }
216
217    /// Adds a `Pop` assignment for the given projection. Multiple pops on the
218    /// same projection batch.
219    pub fn pop<Q>(&mut self, key: Q)
220    where
221        Q: Into<Projection>,
222    {
223        let key = key.into();
224        let new = Assignment::Pop;
225        self.assignments
226            .entry(key)
227            .and_modify(|existing| existing.push(new.clone()))
228            .or_insert(new);
229    }
230
231    /// Adds a `RemoveAt` assignment for the given projection. The expression
232    /// should evaluate to the element index to drop. Multiple removals on the
233    /// same projection batch.
234    pub fn remove_at<Q>(&mut self, key: Q, expr: impl Into<Expr>)
235    where
236        Q: Into<Projection>,
237    {
238        let key = key.into();
239        let new = Assignment::RemoveAt(expr.into());
240        self.assignments
241            .entry(key)
242            .and_modify(|existing| existing.push(new.clone()))
243            .or_insert(new);
244    }
245
246    /// Removes and returns the assignment for the given projection.
247    ///
248    /// The `key` accepts any type that implements `AsRef<[usize]>`:
249    /// - [`Projection`] — look up by projection directly
250    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
251    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
252    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
253    ///   so `&[1]` works without a suffix.
254    pub fn take<Q>(&mut self, key: &Q) -> Option<Assignment>
255    where
256        Q: ?Sized + AsRef<[usize]>,
257    {
258        self.assignments.remove(key.as_ref())
259    }
260
261    /// Returns an iterator over the assignment projections (keys).
262    pub fn keys(&self) -> impl Iterator<Item = &Projection> + '_ {
263        self.assignments.keys()
264    }
265
266    /// Returns an iterator over `(projection, assignment)` pairs.
267    pub fn iter(&self) -> impl Iterator<Item = (&Projection, &Assignment)> + '_ {
268        self.assignments.iter()
269    }
270
271    /// Returns a mutable iterator over `(projection, assignment)` pairs.
272    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Projection, &mut Assignment)> + '_ {
273        self.assignments.iter_mut()
274    }
275}
276
277impl IntoIterator for Assignments {
278    type Item = (Projection, Assignment);
279    type IntoIter = std::collections::btree_map::IntoIter<Projection, Assignment>;
280
281    fn into_iter(self) -> Self::IntoIter {
282        self.assignments.into_iter()
283    }
284}
285
286impl<'a> IntoIterator for &'a Assignments {
287    type Item = (&'a Projection, &'a Assignment);
288    type IntoIter = std::collections::btree_map::Iter<'a, Projection, Assignment>;
289
290    fn into_iter(self) -> Self::IntoIter {
291        self.assignments.iter()
292    }
293}
294
295impl<'a> IntoIterator for &'a mut Assignments {
296    type Item = (&'a Projection, &'a mut Assignment);
297    type IntoIter = std::collections::btree_map::IterMut<'a, Projection, Assignment>;
298
299    fn into_iter(self) -> Self::IntoIter {
300        self.assignments.iter_mut()
301    }
302}
303
304/// Indexes into the assignments by projection. Panics if no assignment exists
305/// for the given key.
306///
307/// The index accepts any type that implements `AsRef<[usize]>`:
308/// [`Projection`], `&[usize]`, or `[usize; N]` arrays.
309impl<Q> ops::Index<Q> for Assignments
310where
311    Q: AsRef<[usize]>,
312{
313    type Output = Assignment;
314
315    fn index(&self, index: Q) -> &Self::Output {
316        match self.assignments.get(index.as_ref()) {
317            Some(ret) => ret,
318            None => panic!("no assignment for projection"),
319        }
320    }
321}
322
323/// Mutably indexes into the assignments by projection. Panics if no assignment
324/// exists for the given key.
325///
326/// The index accepts any type that implements `AsRef<[usize]>`:
327/// [`Projection`], `&[usize]`, or `[usize; N]` arrays.
328impl<Q> ops::IndexMut<Q> for Assignments
329where
330    Q: AsRef<[usize]>,
331{
332    fn index_mut(&mut self, index: Q) -> &mut Self::Output {
333        match self.assignments.get_mut(index.as_ref()) {
334            Some(ret) => ret,
335            None => panic!("no assignment for projection"),
336        }
337    }
338}
339
340impl Assignment {
341    /// Returns `true` if this is the `Set` variant.
342    pub fn is_set(&self) -> bool {
343        matches!(self, Self::Set(_))
344    }
345
346    /// Returns `true` if this is the `Remove` variant.
347    pub fn is_remove(&self) -> bool {
348        matches!(self, Self::Remove(_))
349    }
350
351    /// Appends another assignment, converting to `Batch` if needed.
352    pub fn push(&mut self, other: Assignment) {
353        match self {
354            Self::Batch(entries) => entries.push(other),
355            _ => {
356                let prev = std::mem::replace(self, Assignment::Batch(Vec::new()));
357                if let Assignment::Batch(entries) = self {
358                    entries.push(prev);
359                    entries.push(other);
360                }
361            }
362        }
363    }
364}
365
366impl Node for Assignment {
367    fn visit<V: Visit>(&self, mut visit: V) {
368        visit.visit_assignment(self);
369    }
370
371    fn visit_mut<V: VisitMut>(&mut self, mut visit: V) {
372        visit.visit_assignment_mut(self);
373    }
374}