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}