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    /// Multiple assignments on the same field.
58    Batch(Vec<Assignment>),
59}
60
61impl Statement {
62    /// Returns this statement's assignments if it is an `Update`.
63    pub fn assignments(&self) -> Option<&Assignments> {
64        match self {
65            Statement::Update(update) => Some(&update.assignments),
66            _ => None,
67        }
68    }
69}
70
71impl Assignments {
72    /// Creates an empty `Assignments`.
73    pub fn new() -> Self {
74        Self {
75            assignments: BTreeMap::new(),
76        }
77    }
78
79    /// Returns `true` if there are no assignments.
80    pub fn is_empty(&self) -> bool {
81        self.assignments.is_empty()
82    }
83
84    /// Returns the number of assigned projections (keys).
85    pub fn len(&self) -> usize {
86        self.assignments.len()
87    }
88
89    /// Returns `true` if an assignment exists for the given projection.
90    ///
91    /// The `key` accepts any type that implements `AsRef<[usize]>`:
92    /// - [`Projection`] — look up by projection directly
93    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
94    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
95    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
96    ///   so `&[1]` works without a suffix.
97    pub fn contains<Q>(&self, key: &Q) -> bool
98    where
99        Q: ?Sized + AsRef<[usize]>,
100    {
101        self.assignments.contains_key(key.as_ref())
102    }
103
104    /// Returns a reference to the assignment for the given projection, if any.
105    ///
106    /// The `key` accepts any type that implements `AsRef<[usize]>`:
107    /// - [`Projection`] — look up by projection directly
108    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
109    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
110    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
111    ///   so `&[1]` works without a suffix.
112    pub fn get<Q>(&self, key: &Q) -> Option<&Assignment>
113    where
114        Q: ?Sized + AsRef<[usize]>,
115    {
116        self.assignments.get(key.as_ref())
117    }
118
119    /// Returns a mutable reference to the assignment for the given projection.
120    ///
121    /// The `key` accepts any type that implements `AsRef<[usize]>`:
122    /// - [`Projection`] — look up by projection directly
123    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
124    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
125    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
126    ///   so `&[1]` works without a suffix.
127    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Assignment>
128    where
129        Q: ?Sized + AsRef<[usize]>,
130    {
131        self.assignments.get_mut(key.as_ref())
132    }
133
134    /// Sets a field to the given expression value, replacing any existing
135    /// assignment for that projection.
136    pub fn set<Q>(&mut self, key: Q, expr: impl Into<Expr>)
137    where
138        Q: Into<Projection>,
139    {
140        let key = key.into();
141        self.assignments.insert(key, Assignment::Set(expr.into()));
142    }
143
144    /// Removes the assignment for the given projection, if any.
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 unset<Q>(&mut self, key: &Q)
153    where
154        Q: ?Sized + AsRef<[usize]>,
155    {
156        self.assignments.remove(key.as_ref());
157    }
158
159    /// Insert a value into a set. The expression should evaluate to a single
160    /// value that is inserted into the set.
161    pub fn insert<Q>(&mut self, key: Q, expr: impl Into<Expr>)
162    where
163        Q: Into<Projection>,
164    {
165        let key = key.into();
166        let new = Assignment::Insert(expr.into());
167        self.assignments
168            .entry(key)
169            .and_modify(|existing| existing.push(new.clone()))
170            .or_insert(new);
171    }
172
173    /// Adds a `Remove` assignment for the given projection.
174    pub fn remove<Q>(&mut self, key: Q, expr: impl Into<Expr>)
175    where
176        Q: Into<Projection>,
177    {
178        let key = key.into();
179        let new = Assignment::Remove(expr.into());
180        self.assignments
181            .entry(key)
182            .and_modify(|existing| existing.push(new.clone()))
183            .or_insert(new);
184    }
185
186    /// Removes and returns the assignment for the given projection.
187    ///
188    /// The `key` accepts any type that implements `AsRef<[usize]>`:
189    /// - [`Projection`] — look up by projection directly
190    /// - `&[usize]` — a slice of field indices (e.g., `&[1, 2]`)
191    /// - `[usize; N]` — a fixed-size array (e.g., `[1]`, `[1, 2]`).
192    ///   Integer literals infer as `usize` from the `AsRef<[usize]>` bound,
193    ///   so `&[1]` works without a suffix.
194    pub fn take<Q>(&mut self, key: &Q) -> Option<Assignment>
195    where
196        Q: ?Sized + AsRef<[usize]>,
197    {
198        self.assignments.remove(key.as_ref())
199    }
200
201    /// Returns an iterator over the assignment projections (keys).
202    pub fn keys(&self) -> impl Iterator<Item = &Projection> + '_ {
203        self.assignments.keys()
204    }
205
206    /// Returns an iterator over `(projection, assignment)` pairs.
207    pub fn iter(&self) -> impl Iterator<Item = (&Projection, &Assignment)> + '_ {
208        self.assignments.iter()
209    }
210
211    /// Returns a mutable iterator over `(projection, assignment)` pairs.
212    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Projection, &mut Assignment)> + '_ {
213        self.assignments.iter_mut()
214    }
215}
216
217impl IntoIterator for Assignments {
218    type Item = (Projection, Assignment);
219    type IntoIter = std::collections::btree_map::IntoIter<Projection, Assignment>;
220
221    fn into_iter(self) -> Self::IntoIter {
222        self.assignments.into_iter()
223    }
224}
225
226impl<'a> IntoIterator for &'a Assignments {
227    type Item = (&'a Projection, &'a Assignment);
228    type IntoIter = std::collections::btree_map::Iter<'a, Projection, Assignment>;
229
230    fn into_iter(self) -> Self::IntoIter {
231        self.assignments.iter()
232    }
233}
234
235/// Indexes into the assignments by projection. Panics if no assignment exists
236/// for the given key.
237///
238/// The index accepts any type that implements `AsRef<[usize]>`:
239/// [`Projection`], `&[usize]`, or `[usize; N]` arrays.
240impl<Q> ops::Index<Q> for Assignments
241where
242    Q: AsRef<[usize]>,
243{
244    type Output = Assignment;
245
246    fn index(&self, index: Q) -> &Self::Output {
247        match self.assignments.get(index.as_ref()) {
248            Some(ret) => ret,
249            None => panic!("no assignment for projection"),
250        }
251    }
252}
253
254/// Mutably indexes into the assignments by projection. Panics if no assignment
255/// exists for the given key.
256///
257/// The index accepts any type that implements `AsRef<[usize]>`:
258/// [`Projection`], `&[usize]`, or `[usize; N]` arrays.
259impl<Q> ops::IndexMut<Q> for Assignments
260where
261    Q: AsRef<[usize]>,
262{
263    fn index_mut(&mut self, index: Q) -> &mut Self::Output {
264        match self.assignments.get_mut(index.as_ref()) {
265            Some(ret) => ret,
266            None => panic!("no assignment for projection"),
267        }
268    }
269}
270
271impl Assignment {
272    /// Returns `true` if this is the `Set` variant.
273    pub fn is_set(&self) -> bool {
274        matches!(self, Self::Set(_))
275    }
276
277    /// Returns `true` if this is the `Remove` variant.
278    pub fn is_remove(&self) -> bool {
279        matches!(self, Self::Remove(_))
280    }
281
282    /// Appends another assignment, converting to `Batch` if needed.
283    pub fn push(&mut self, other: Assignment) {
284        match self {
285            Self::Batch(entries) => entries.push(other),
286            _ => {
287                let prev = std::mem::replace(self, Assignment::Batch(Vec::new()));
288                if let Assignment::Batch(entries) = self {
289                    entries.push(prev);
290                    entries.push(other);
291                }
292            }
293        }
294    }
295}
296
297impl Node for Assignment {
298    fn visit<V: Visit>(&self, mut visit: V) {
299        visit.visit_assignment(self);
300    }
301
302    fn visit_mut<V: VisitMut>(&mut self, mut visit: V) {
303        visit.visit_assignment_mut(self);
304    }
305}