Skip to main content

uqa_sql/ast/
assignment_target.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Ordered mutation targets shared by parsed and executable expressions.
8
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11/// The targets of one SET item. A multiple target consumes one positional subquery result.
12/// Single targets retain the predecessor's string or indirection-object encoding.
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(untagged)]
15pub enum AssignmentTargets<E = super::Expr> {
16    Single(AssignmentTarget<E>),
17    Multiple(MultipleAssignmentTargets<E>),
18}
19
20/// Positional outputs remain stable when a stored MERGE loses one of its target columns.
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct MultipleAssignmentTargets<E> {
23    pub targets: Vec<AssignmentTarget<E>>,
24    pub source_positions: Vec<usize>,
25    pub source_width: usize,
26}
27
28impl<E> From<Vec<AssignmentTarget<E>>> for MultipleAssignmentTargets<E> {
29    fn from(targets: Vec<AssignmentTarget<E>>) -> Self {
30        Self {
31            source_positions: (0..targets.len()).collect(),
32            source_width: targets.len(),
33            targets,
34        }
35    }
36}
37
38impl<E> From<AssignmentTarget<E>> for AssignmentTargets<E> {
39    fn from(target: AssignmentTarget<E>) -> Self {
40        Self::Single(target)
41    }
42}
43
44impl<E> From<String> for AssignmentTargets<E> {
45    fn from(column: String) -> Self {
46        Self::Single(column.into())
47    }
48}
49
50impl<E> From<&str> for AssignmentTargets<E> {
51    fn from(column: &str) -> Self {
52        column.to_owned().into()
53    }
54}
55
56impl<E> AssignmentTargets<E> {
57    pub fn targets(&self) -> &[AssignmentTarget<E>] {
58        match self {
59            Self::Single(target) => std::slice::from_ref(target),
60            Self::Multiple(group) => &group.targets,
61        }
62    }
63
64    pub fn targets_mut(&mut self) -> &mut [AssignmentTarget<E>] {
65        match self {
66            Self::Single(target) => std::slice::from_mut(target),
67            Self::Multiple(group) => &mut group.targets,
68        }
69    }
70
71    pub fn source_position(&self, target: usize) -> usize {
72        match self {
73            Self::Single(_) => target,
74            Self::Multiple(group) => group.source_positions[target],
75        }
76    }
77
78    pub fn column_names(&self) -> impl Iterator<Item = &str> {
79        self.targets().iter().map(|target| target.column.as_str())
80    }
81
82    pub fn expressions(&self) -> impl Iterator<Item = &E> {
83        self.targets()
84            .iter()
85            .flat_map(AssignmentTarget::expressions)
86    }
87
88    pub fn expressions_mut(&mut self) -> impl Iterator<Item = &mut E> {
89        self.targets_mut()
90            .iter_mut()
91            .flat_map(AssignmentTarget::expressions_mut)
92    }
93
94    pub fn map<T>(self, mut map: impl FnMut(E) -> T) -> AssignmentTargets<T> {
95        match self {
96            Self::Single(target) => AssignmentTargets::Single(target.map(map)),
97            Self::Multiple(group) => AssignmentTargets::Multiple(MultipleAssignmentTargets {
98                targets: group
99                    .targets
100                    .into_iter()
101                    .map(|target| target.map(&mut map))
102                    .collect(),
103                source_positions: group.source_positions,
104                source_width: group.source_width,
105            }),
106        }
107    }
108}
109
110/// Target syntax is separate from the value expression. Every bound belongs to the original input row, even when repeated targets compose writes to one column.
111#[derive(Debug, Clone, PartialEq)]
112pub struct AssignmentTarget<E = super::Expr> {
113    pub column: String,
114    pub indirection: Vec<AssignmentStep<E>>,
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub enum AssignmentStep<E> {
119    Field(String),
120    Index(Box<E>),
121    Slice {
122        lower: Option<Box<E>>,
123        upper: Option<Box<E>>,
124    },
125}
126
127impl<E> From<String> for AssignmentTarget<E> {
128    fn from(column: String) -> Self {
129        Self {
130            column,
131            indirection: Vec::new(),
132        }
133    }
134}
135
136impl<E> From<&str> for AssignmentTarget<E> {
137    fn from(column: &str) -> Self {
138        column.to_owned().into()
139    }
140}
141
142impl<E> AssignmentTarget<E> {
143    pub fn is_whole_column(&self) -> bool {
144        self.indirection.is_empty()
145    }
146
147    pub fn expressions(&self) -> impl Iterator<Item = &E> {
148        self.indirection.iter().flat_map(|step| {
149            match step {
150                AssignmentStep::Field(_) => [None, None],
151                AssignmentStep::Index(index) => [Some(index.as_ref()), None],
152                AssignmentStep::Slice { lower, upper } => [lower.as_deref(), upper.as_deref()],
153            }
154            .into_iter()
155            .flatten()
156        })
157    }
158
159    pub fn expressions_mut(&mut self) -> impl Iterator<Item = &mut E> {
160        self.indirection.iter_mut().flat_map(|step| {
161            match step {
162                AssignmentStep::Field(_) => [None, None],
163                AssignmentStep::Index(index) => [Some(index.as_mut()), None],
164                AssignmentStep::Slice { lower, upper } => {
165                    [lower.as_deref_mut(), upper.as_deref_mut()]
166                }
167            }
168            .into_iter()
169            .flatten()
170        })
171    }
172
173    pub fn map<T>(self, mut map: impl FnMut(E) -> T) -> AssignmentTarget<T> {
174        AssignmentTarget {
175            column: self.column,
176            indirection: self
177                .indirection
178                .into_iter()
179                .map(|step| match step {
180                    AssignmentStep::Field(field) => AssignmentStep::Field(field),
181                    AssignmentStep::Index(index) => AssignmentStep::Index(Box::new(map(*index))),
182                    AssignmentStep::Slice { lower, upper } => AssignmentStep::Slice {
183                        lower: lower.map(|value| Box::new(map(*value))),
184                        upper: upper.map(|value| Box::new(map(*value))),
185                    },
186                })
187                .collect(),
188        }
189    }
190}
191
192impl<E: Serialize> Serialize for AssignmentTarget<E> {
193    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
194        #[derive(Serialize)]
195        #[serde(untagged)]
196        enum Target<'a, E> {
197            Column(&'a str),
198            Partial {
199                column: &'a str,
200                indirection: &'a [AssignmentStep<E>],
201            },
202        }
203        if self.is_whole_column() {
204            Target::<E>::Column(&self.column).serialize(serializer)
205        } else {
206            Target::Partial {
207                column: &self.column,
208                indirection: &self.indirection,
209            }
210            .serialize(serializer)
211        }
212    }
213}
214
215impl<'de, E: Deserialize<'de>> Deserialize<'de> for AssignmentTarget<E> {
216    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
217        #[derive(Deserialize)]
218        #[serde(untagged)]
219        enum Target<E> {
220            Column(String),
221            Partial {
222                column: String,
223                indirection: Vec<AssignmentStep<E>>,
224            },
225        }
226        Ok(match Target::deserialize(deserializer)? {
227            Target::Column(column) => column.into(),
228            Target::Partial {
229                column,
230                indirection,
231            } => Self {
232                column,
233                indirection,
234            },
235        })
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn ordinary_target_encoding_preserves_predecessor_definitions() {
245        let target: AssignmentTarget<i32> = serde_json::from_str("\"value\"").unwrap();
246        assert!(target.is_whole_column());
247        assert_eq!(target.column, "value");
248        assert_eq!(serde_json::to_string(&target).unwrap(), "\"value\"");
249        let group: AssignmentTargets<i32> = serde_json::from_str("\"value\"").unwrap();
250        assert!(matches!(group, AssignmentTargets::Single(_)));
251        assert_eq!(serde_json::to_string(&group).unwrap(), "\"value\"");
252        let grouped: AssignmentTargets<i32> =
253            AssignmentTargets::Multiple(vec!["a".into(), "b".into()].into());
254        let encoded = serde_json::to_string(&grouped).unwrap();
255        assert_eq!(
256            serde_json::from_str::<AssignmentTargets<i32>>(&encoded).unwrap(),
257            grouped
258        );
259    }
260
261    #[test]
262    fn partial_targets_keep_bound_omission_order_and_expression_rewrites() {
263        let mut target = AssignmentTarget {
264            column: "value".into(),
265            indirection: vec![
266                AssignmentStep::Index(Box::new(1)),
267                AssignmentStep::Field("items".into()),
268                AssignmentStep::Slice {
269                    lower: None,
270                    upper: Some(Box::new(2)),
271                },
272                AssignmentStep::Slice {
273                    lower: Some(Box::new(3)),
274                    upper: None,
275                },
276            ],
277        };
278        for value in target.expressions_mut() {
279            *value += 10;
280        }
281        assert_eq!(
282            target.expressions().copied().collect::<Vec<_>>(),
283            [11, 12, 13]
284        );
285        let rendered = serde_json::to_string(&target).unwrap();
286        assert_eq!(
287            serde_json::from_str::<AssignmentTarget<i32>>(&rendered).unwrap(),
288            target
289        );
290        let lowered = target.map(|value| value.to_string());
291        assert_eq!(
292            lowered
293                .expressions()
294                .map(String::as_str)
295                .collect::<Vec<_>>(),
296            ["11", "12", "13"]
297        );
298        assert!(matches!(
299            lowered.indirection[2],
300            AssignmentStep::Slice { lower: None, .. }
301        ));
302        assert!(matches!(
303            lowered.indirection[3],
304            AssignmentStep::Slice { upper: None, .. }
305        ));
306    }
307}