toasty_core/stmt/
projection.rs

1use crate::{
2    schema::{
3        app::{Field, FieldId},
4        db::ColumnId,
5    },
6    stmt::{Expr, Value},
7};
8
9use indexmap::Equivalent;
10use std::{
11    fmt,
12    hash::{Hash, Hasher},
13    ops,
14};
15
16#[derive(Clone, PartialEq, Eq)]
17pub struct Projection {
18    steps: Steps,
19}
20
21pub trait Project {
22    fn project(self, projection: &Projection) -> Option<Expr>;
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26enum Steps {
27    /// References the projection base
28    Identity,
29
30    /// One field step
31    Single([usize; 1]),
32
33    /// Multi field steps
34    Multi(Vec<usize>),
35}
36
37// Custom Hash implementation to ensure compatibility with Equivalent trait:
38// - Single-step projections hash like their contained usize: hash(Projection([1])) == hash(1)
39// - Multi-step projections hash like their slice: hash(Projection([1,2])) == hash([1,2])
40// This allows IndexMap lookups with both usize and [usize] to work correctly.
41impl Hash for Projection {
42    fn hash<H: Hasher>(&self, state: &mut H) {
43        self.steps.hash(state);
44    }
45}
46
47impl Hash for Steps {
48    fn hash<H: Hasher>(&self, state: &mut H) {
49        match self {
50            Steps::Identity => {
51                // Hash a discriminant for identity
52                0u8.hash(state);
53            }
54            Steps::Single([index]) => {
55                // Hash as a single usize (no length prefix, no array wrapping)
56                // This makes hash(Projection([i])) == hash(i)
57                index.hash(state);
58            }
59            Steps::Multi(indices) => {
60                // Hash as a slice: this includes length and elements
61                // This makes hash(Projection([i,j])) == hash([i,j])
62                indices.as_slice().hash(state);
63            }
64        }
65    }
66}
67
68pub struct Iter<'a>(std::slice::Iter<'a, usize>);
69
70impl Projection {
71    pub const fn identity() -> Self {
72        Self {
73            steps: Steps::Identity,
74        }
75    }
76
77    /// The path references the root (i.e. the projection base)
78    pub const fn is_identity(&self) -> bool {
79        matches!(self.steps, Steps::Identity)
80    }
81
82    pub fn single(step: usize) -> Self {
83        Self {
84            steps: Steps::Single([step]),
85        }
86    }
87
88    /// Mostly here for `const`
89    pub const fn from_index(index: usize) -> Self {
90        Self {
91            steps: Steps::Single([index]),
92        }
93    }
94
95    pub fn as_slice(&self) -> &[usize] {
96        self.steps.as_slice()
97    }
98
99    pub fn push(&mut self, step: usize) {
100        match &mut self.steps {
101            Steps::Identity => {
102                self.steps = Steps::Single([step]);
103            }
104            Steps::Single([first]) => {
105                self.steps = Steps::Multi(vec![*first, step]);
106            }
107            Steps::Multi(steps) => {
108                steps.push(step);
109            }
110        }
111    }
112
113    pub fn resolves_to(&self, other: impl Into<Self>) -> bool {
114        let other = other.into();
115        *self == other
116    }
117}
118
119impl ops::Deref for Projection {
120    type Target = [usize];
121
122    fn deref(&self) -> &Self::Target {
123        self.steps.as_slice()
124    }
125}
126
127impl ops::DerefMut for Projection {
128    fn deref_mut(&mut self) -> &mut Self::Target {
129        match &mut self.steps {
130            Steps::Identity => &mut [],
131            Steps::Single(step) => &mut step[..],
132            Steps::Multi(steps) => &mut steps[..],
133        }
134    }
135}
136
137impl<'a> IntoIterator for &'a Projection {
138    type Item = usize;
139    type IntoIter = Iter<'a>;
140
141    fn into_iter(self) -> Self::IntoIter {
142        Iter(self[..].iter())
143    }
144}
145
146impl From<&Field> for Projection {
147    fn from(value: &Field) -> Self {
148        Self::single(value.id.index)
149    }
150}
151
152impl From<FieldId> for Projection {
153    fn from(value: FieldId) -> Self {
154        Self::single(value.index)
155    }
156}
157
158impl From<ColumnId> for Projection {
159    fn from(value: ColumnId) -> Self {
160        Self::single(value.index)
161    }
162}
163
164impl From<usize> for Projection {
165    fn from(value: usize) -> Self {
166        Self::single(value)
167    }
168}
169
170impl From<&[usize]> for Projection {
171    fn from(value: &[usize]) -> Self {
172        match value {
173            [] => Self::identity(),
174            [value] => Self::single(*value),
175            value => Self {
176                steps: Steps::Multi(value.into()),
177            },
178        }
179    }
180}
181
182impl<const N: usize> From<[usize; N]> for Projection {
183    fn from(value: [usize; N]) -> Self {
184        Self::from(&value[..])
185    }
186}
187
188impl fmt::Debug for Projection {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        let mut f = f.debug_tuple("Projection");
191
192        if self.is_identity() {
193            f.field(&"identity");
194        } else {
195            for field in &self[..] {
196                f.field(&field);
197            }
198        }
199
200        f.finish()
201    }
202}
203
204impl Steps {
205    fn as_slice(&self) -> &[usize] {
206        match self {
207            Self::Identity => &[],
208            Self::Single(step) => &step[..],
209            Self::Multi(steps) => &steps[..],
210        }
211    }
212}
213
214impl Iterator for Iter<'_> {
215    type Item = usize;
216
217    fn next(&mut self) -> Option<usize> {
218        self.0.next().copied()
219    }
220}
221
222impl Project for Expr {
223    fn project(self, projection: &Projection) -> Option<Expr> {
224        Some(self.entry(projection)?.to_expr())
225    }
226}
227
228impl Project for &Expr {
229    fn project(self, projection: &Projection) -> Option<Expr> {
230        Some(self.entry(projection)?.to_expr())
231    }
232}
233
234impl Project for &&Expr {
235    fn project(self, projection: &Projection) -> Option<Expr> {
236        Some(self.entry(projection)?.to_expr())
237    }
238}
239
240impl Project for Value {
241    fn project(self, projection: &Projection) -> Option<Expr> {
242        Some(self.entry(projection).to_expr())
243    }
244}
245
246impl Project for &Value {
247    fn project(self, projection: &Projection) -> Option<Expr> {
248        Some(self.entry(projection).to_expr())
249    }
250}
251
252impl Project for &&Value {
253    fn project(self, projection: &Projection) -> Option<Expr> {
254        Some(self.entry(projection).to_expr())
255    }
256}
257
258// Allow using usize directly where Projection is expected (for single-step projections)
259impl Equivalent<Projection> for usize {
260    fn equivalent(&self, key: &Projection) -> bool {
261        matches!(key.as_slice(), [index] if *index == *self)
262    }
263}
264
265// Allow using &Projection where Projection is expected
266impl Equivalent<Projection> for &Projection {
267    fn equivalent(&self, key: &Projection) -> bool {
268        *self == key
269    }
270}
271
272// Allow using [usize] slices where Projection is expected
273impl Equivalent<Projection> for [usize] {
274    fn equivalent(&self, key: &Projection) -> bool {
275        self == key.as_slice()
276    }
277}
278
279// PartialEq implementations for ergonomic comparisons
280impl PartialEq<usize> for Projection {
281    fn eq(&self, other: &usize) -> bool {
282        matches!(self.as_slice(), [index] if *index == *other)
283    }
284}
285
286impl PartialEq<Projection> for usize {
287    fn eq(&self, other: &Projection) -> bool {
288        other == self
289    }
290}
291
292impl PartialEq<[usize]> for Projection {
293    fn eq(&self, other: &[usize]) -> bool {
294        self.as_slice() == other
295    }
296}
297
298impl PartialEq<Projection> for [usize] {
299    fn eq(&self, other: &Projection) -> bool {
300        other == self
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_projection_eq_usize() {
310        let proj_single = Projection::from(5);
311        let proj_multi = Projection::from([1, 2]);
312
313        // Single-step projection == usize (both directions)
314        assert_eq!(proj_single, 5);
315        assert_eq!(5, proj_single);
316        assert_ne!(proj_single, 3);
317        assert_ne!(3, proj_single);
318
319        // Multi-step projection != usize
320        assert_ne!(proj_multi, 1);
321        assert_ne!(1, proj_multi);
322    }
323
324    #[test]
325    fn test_projection_eq_slice() {
326        let proj_single = Projection::from(5);
327        let proj_multi = Projection::from([1, 2, 3]);
328
329        // Projection == [usize] slice (both directions)
330        assert_eq!(proj_single, [5][..]);
331        assert_eq!([5][..], proj_single);
332        assert_eq!(proj_multi, [1, 2, 3][..]);
333        assert_eq!([1, 2, 3][..], proj_multi);
334
335        // Mismatches
336        assert_ne!(proj_single, [1, 2][..]);
337        assert_ne!([1, 2][..], proj_single);
338    }
339
340    #[test]
341    fn test_projection_hash_compatibility() {
342        use std::collections::hash_map::DefaultHasher;
343        use std::hash::{Hash, Hasher};
344
345        fn hash<T: Hash + ?Sized>(value: &T) -> u64 {
346            let mut hasher = DefaultHasher::new();
347            value.hash(&mut hasher);
348            hasher.finish()
349        }
350
351        // Single-step projection should hash like the contained usize
352        let proj_single = Projection::from(42);
353        assert_eq!(hash(&proj_single), hash(&42_usize));
354
355        // Multi-step projection should hash like the slice
356        let proj_multi = Projection::from([1, 2, 3]);
357        let slice: &[usize] = &[1, 2, 3];
358        assert_eq!(hash(&proj_multi), hash(slice));
359
360        // Verify this works with IndexMap
361        use indexmap::IndexMap;
362        let mut map = IndexMap::new();
363        map.insert(Projection::from(5), "value");
364
365        // Can look up with usize
366        assert_eq!(map.get(&5_usize), Some(&"value"));
367
368        // Can look up with single-element slice
369        let slice_single: &[usize] = &[5];
370        assert_eq!(map.get(slice_single), Some(&"value"));
371
372        // Multi-step example
373        map.insert(Projection::from([1, 2]), "multi");
374
375        // Can look up with multi-element slice
376        let slice_multi: &[usize] = &[1, 2];
377        assert_eq!(map.get(slice_multi), Some(&"multi"));
378    }
379}