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}