Skip to content

Commit 81c72bc

Browse files
authored
Add scope variables to expression scope (#3672)
Part of #3671 Adds a new way of defining scope variables (before we deprecate the old method) whereby variables are keyed by type. This is typical for e.g. attaching context to an HTTP request using "middleware", which is essentially what we're doing here during expression evaluation. --------- Signed-off-by: Nicholas Gates <[email protected]>
1 parent 0456675 commit 81c72bc

File tree

3 files changed

+106
-2
lines changed

3 files changed

+106
-2
lines changed

vortex-expr/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod pruning;
1515
#[cfg(feature = "proto")]
1616
mod registry;
1717
mod scope;
18+
mod scope_vars;
1819
pub mod transform;
1920
pub mod traversal;
2021

vortex-expr/src/scope.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use std::any::Any;
2-
use std::fmt::Display;
1+
use std::any::{Any, TypeId};
2+
use std::fmt::{Debug, Display};
33
use std::str::FromStr;
44
use std::sync::Arc;
55

@@ -9,6 +9,8 @@ use vortex_dtype::{DType, FieldPathSet};
99
use vortex_error::{VortexError, VortexResult, vortex_bail, vortex_err};
1010
use vortex_utils::aliases::hash_map::HashMap;
1111

12+
use crate::scope_vars::{ScopeVar, ScopeVars};
13+
1214
type ExprScope<T> = HashMap<Identifier, T>;
1315

1416
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
@@ -88,6 +90,8 @@ pub struct Scope {
8890
/// A map identifiers to opaque values used by expressions, but
8991
/// cannot affect the result type/shape.
9092
vars: ExprScope<Arc<dyn Any + Send + Sync>>,
93+
/// Variables that can be set on the scope during expression evaluation.
94+
scope_vars: ScopeVars,
9195
}
9296

9397
pub type ScopeElement = (Identifier, ArrayRef);
@@ -156,6 +160,26 @@ impl Scope {
156160
self
157161
}
158162

163+
/// Returns a new evaluation scope with the given variable applied.
164+
pub fn with_scope_var<V: ScopeVar>(mut self, var: V) -> Self {
165+
self.scope_vars.insert(TypeId::of::<V>(), Box::new(var));
166+
self
167+
}
168+
169+
/// Returns the scope variable of type `V` if it exists.
170+
pub fn scope_var<V: ScopeVar>(&self) -> Option<&V> {
171+
self.scope_vars
172+
.get(&TypeId::of::<V>())
173+
.and_then(|boxed| (**boxed).as_any().downcast_ref::<V>())
174+
}
175+
176+
/// Returns the mutable scope variable of type `V` if it exists.
177+
pub fn scope_var_mut<V: ScopeVar>(&mut self) -> Option<&mut V> {
178+
self.scope_vars
179+
.get_mut(&TypeId::of::<V>())
180+
.and_then(|boxed| (**boxed).as_any_mut().downcast_mut::<V>())
181+
}
182+
159183
pub fn iter(&self) -> impl Iterator<Item = (&Identifier, &ArrayRef)> {
160184
let values = self.arrays.iter();
161185

@@ -285,3 +309,26 @@ impl ScopeFieldPathSet {
285309
self.with_set(ident, set)
286310
}
287311
}
312+
313+
#[cfg(test)]
314+
mod test {
315+
#[test]
316+
fn test_scope_var() {
317+
use super::*;
318+
319+
#[derive(Clone, PartialEq, Eq, Debug)]
320+
struct TestVar {
321+
value: i32,
322+
}
323+
324+
let scope = Scope::empty(100);
325+
assert!(scope.scope_var::<TestVar>().is_none());
326+
327+
let var = TestVar { value: 42 };
328+
let mut scope = scope.with_scope_var(var.clone());
329+
assert_eq!(scope.scope_var::<TestVar>(), Some(&var));
330+
331+
scope.scope_var_mut::<TestVar>().unwrap().value = 43;
332+
assert_eq!(scope.scope_var::<TestVar>(), Some(&TestVar { value: 43 }));
333+
}
334+
}

vortex-expr/src/scope_vars.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use std::any::{Any, TypeId};
2+
use std::hash::{BuildHasherDefault, Hasher};
3+
4+
use vortex_utils::aliases::hash_map::HashMap;
5+
6+
/// A TypeMap based on `https://docs.rs/http/1.2.0/src/http/extensions.rs.html#41-266`.
7+
pub(crate) type ScopeVars = HashMap<TypeId, Box<dyn ScopeVar>, BuildHasherDefault<IdHasher>>;
8+
9+
/// With TypeIds as keys, there's no need to hash them. They are already hashes
10+
/// themselves, coming from the compiler. The IdHasher just holds the u64 of
11+
/// the TypeId, and then returns it, instead of doing any bit fiddling.
12+
#[derive(Default)]
13+
pub(super) struct IdHasher(u64);
14+
15+
impl Hasher for IdHasher {
16+
#[inline]
17+
fn finish(&self) -> u64 {
18+
self.0
19+
}
20+
21+
fn write(&mut self, _: &[u8]) {
22+
unreachable!("TypeId calls write_u64");
23+
}
24+
25+
#[inline]
26+
fn write_u64(&mut self, id: u64) {
27+
self.0 = id;
28+
}
29+
}
30+
31+
/// A trait for scope variables that can be stored in a `ScopeVars` map.
32+
pub trait ScopeVar: Any + Send + Sync {
33+
fn as_any(&self) -> &dyn Any;
34+
fn as_any_mut(&mut self) -> &mut dyn Any;
35+
fn clone_box(&self) -> Box<dyn ScopeVar>;
36+
}
37+
38+
impl Clone for Box<dyn ScopeVar> {
39+
fn clone(&self) -> Self {
40+
(**self).clone_box()
41+
}
42+
}
43+
44+
impl<T: Clone + Send + Sync + 'static> ScopeVar for T {
45+
fn as_any(&self) -> &dyn Any {
46+
self
47+
}
48+
49+
fn as_any_mut(&mut self) -> &mut dyn Any {
50+
self
51+
}
52+
53+
fn clone_box(&self) -> Box<dyn ScopeVar> {
54+
Box::new(self.clone())
55+
}
56+
}

0 commit comments

Comments
 (0)