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}