vortex_array/scalar_fn/fns/
dynamic.rs1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::hash::Hash;
8use std::hash::Hasher;
9use std::sync::Arc;
10
11use parking_lot::Mutex;
12use vortex_error::VortexExpect;
13use vortex_error::VortexResult;
14use vortex_error::vortex_bail;
15use vortex_session::registry::CachedId;
16
17use crate::ArrayRef;
18use crate::ExecutionCtx;
19use crate::IntoArray;
20use crate::arrays::ConstantArray;
21use crate::dtype::DType;
22use crate::expr::BoundExpression;
23use crate::expr::display::ExprDisplay;
24use crate::expr::traversal::NodeExt;
25use crate::expr::traversal::NodeVisitor;
26use crate::expr::traversal::TraversalOrder;
27use crate::scalar::Scalar;
28use crate::scalar::ScalarValue;
29use crate::scalar_fn::Arity;
30use crate::scalar_fn::ChildName;
31use crate::scalar_fn::ExecutionArgs;
32use crate::scalar_fn::ScalarFnId;
33use crate::scalar_fn::ScalarFnVTable;
34use crate::scalar_fn::ScalarFnVTableExt;
35use crate::scalar_fn::VecExecutionArgs;
36use crate::scalar_fn::fns::binary::Binary;
37use crate::scalar_fn::fns::operators::CompareOperator;
38use crate::scalar_fn::fns::operators::Operator;
39
40#[derive(Clone)]
44pub struct DynamicComparison;
45
46impl ScalarFnVTable for DynamicComparison {
47 type Options = DynamicComparisonExpr;
48
49 fn id(&self) -> ScalarFnId {
50 static ID: CachedId = CachedId::new("vortex.dynamic");
51 *ID
52 }
53
54 fn arity(&self, _options: &Self::Options) -> Arity {
55 Arity::Exact(1)
56 }
57
58 fn child_name(&self, _instance: &Self::Options, child_idx: usize) -> ChildName {
59 match child_idx {
60 0 => ChildName::from("lhs"),
61 _ => unreachable!(),
62 }
63 }
64
65 fn fmt_sql(
66 &self,
67 dynamic: &DynamicComparisonExpr,
68 expr: &dyn ExprDisplay,
69 f: &mut Formatter<'_>,
70 ) -> std::fmt::Result {
71 Display::fmt(expr.display_child(0), f)?;
72 write!(f, " {} dynamic(", dynamic.operator)?;
73 match dynamic.scalar() {
74 None => write!(f, "scalar=<none>")?,
75 Some(scalar) => write!(f, "scalar={scalar}")?,
76 }
77 write!(f, ")")
78 }
79
80 fn return_dtype(
81 &self,
82 dynamic: &DynamicComparisonExpr,
83 arg_dtypes: &[DType],
84 ) -> VortexResult<DType> {
85 let lhs = &arg_dtypes[0];
86 if !dynamic.rhs.dtype.eq_ignore_nullability(lhs) {
87 vortex_bail!(
88 "Incompatible dtypes for dynamic comparison: expected {} (ignore nullability) but got {}",
89 &dynamic.rhs.dtype,
90 lhs
91 );
92 }
93 Ok(DType::Bool(
94 lhs.nullability() | dynamic.rhs.dtype.nullability(),
95 ))
96 }
97
98 fn execute(
99 &self,
100 data: &Self::Options,
101 args: &dyn ExecutionArgs,
102 ctx: &mut ExecutionCtx,
103 ) -> VortexResult<ArrayRef> {
104 if let Some(scalar) = data.rhs.scalar() {
105 let lhs = args.get(0)?;
106 let rhs = ConstantArray::new(scalar, args.row_count()).into_array();
107
108 let delegate_args = VecExecutionArgs::new(vec![lhs, rhs], args.row_count());
109 return Binary
110 .bind(Operator::from(data.operator))
111 .execute(&delegate_args, ctx);
112 }
113 let ret_dtype =
114 DType::Bool(args.get(0)?.dtype().nullability() | data.rhs.dtype.nullability());
115
116 Ok(ConstantArray::new(
117 Scalar::try_new(ret_dtype, Some(data.default.into()))?,
118 args.row_count(),
119 )
120 .into_array())
121 }
122
123 fn is_strict(&self, _options: &Self::Options) -> bool {
124 false
125 }
126
127 fn is_infallible(&self, _options: &Self::Options) -> bool {
130 true
131 }
132}
133
134#[derive(Clone, Debug)]
135pub struct DynamicComparisonExpr {
136 pub(crate) operator: CompareOperator,
137 pub(crate) rhs: Arc<Rhs>,
138 pub(crate) default: bool,
140}
141
142impl DynamicComparisonExpr {
143 pub fn scalar(&self) -> Option<Scalar> {
144 (self.rhs.value)().map(|v| {
145 Scalar::try_new(self.rhs.dtype.clone(), Some(v))
146 .vortex_expect("`DynamicComparisonExpr` was invalid")
147 })
148 }
149}
150
151impl Display for DynamicComparisonExpr {
152 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
153 write!(
154 f,
155 "{} {}",
156 self.operator,
157 self.scalar()
158 .map_or_else(|| "<none>".to_string(), |v| v.to_string())
159 )
160 }
161}
162
163impl PartialEq for DynamicComparisonExpr {
164 fn eq(&self, other: &Self) -> bool {
165 self.operator == other.operator
166 && Arc::ptr_eq(&self.rhs, &other.rhs)
167 && self.default == other.default
168 }
169}
170impl Eq for DynamicComparisonExpr {}
171
172impl Hash for DynamicComparisonExpr {
173 fn hash<H: Hasher>(&self, state: &mut H) {
174 self.operator.hash(state);
175 Arc::as_ptr(&self.rhs).hash(state);
176 self.default.hash(state);
177 }
178}
179
180pub(crate) struct Rhs {
183 pub(crate) value: Arc<dyn Fn() -> Option<ScalarValue> + Send + Sync>,
185 pub(crate) dtype: DType,
187}
188
189impl Rhs {
190 pub fn scalar(&self) -> Option<Scalar> {
191 (self.value)().map(|v| {
192 Scalar::try_new(self.dtype.clone(), Some(v)).vortex_expect("`Rhs` was invalid")
193 })
194 }
195}
196
197impl Debug for Rhs {
198 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
199 f.debug_struct("Rhs")
200 .field("value", &"<dyn Fn() -> Option<ScalarValue> + Send + Sync>")
201 .field("dtype", &self.dtype)
202 .finish()
203 }
204}
205
206pub struct DynamicExprUpdates {
208 exprs: Box<[DynamicComparisonExpr]>,
209 prev_versions: Mutex<(u64, Vec<Option<Scalar>>)>,
211}
212
213impl DynamicExprUpdates {
214 pub fn new(expr: &BoundExpression) -> Option<Self> {
216 #[derive(Default)]
217 struct Visitor(Vec<DynamicComparisonExpr>);
218
219 impl NodeVisitor<'_> for Visitor {
220 type NodeTy = BoundExpression;
221
222 fn visit_down(&mut self, node: &'_ Self::NodeTy) -> VortexResult<TraversalOrder> {
223 if let Some(dynamic) = node
224 .as_scalar()
225 .and_then(|scalar_fn| scalar_fn.as_opt::<DynamicComparison>())
226 {
227 self.0.push(dynamic.clone());
228 }
229 Ok(TraversalOrder::Continue)
230 }
231 }
232
233 let mut visitor = Visitor::default();
234 expr.accept(&mut visitor).vortex_expect("Infallible");
235
236 if visitor.0.is_empty() {
237 return None;
238 }
239
240 let exprs = visitor.0.into_boxed_slice();
241 let prev_versions = exprs
242 .iter()
243 .map(|expr| {
244 (expr.rhs.value)().map(|v| {
245 Scalar::try_new(expr.rhs.dtype.clone(), Some(v))
246 .vortex_expect("`DynamicExprUpdates` was invalid")
247 })
248 })
249 .collect();
250
251 Some(Self {
252 exprs,
253 prev_versions: Mutex::new((0, prev_versions)),
254 })
255 }
256
257 pub fn version(&self) -> u64 {
258 let mut guard = self.prev_versions.lock();
259
260 let mut updated = false;
261 for (i, expr) in self.exprs.iter().enumerate() {
262 let current = expr.scalar();
263 if current != guard.1[i] {
264 updated = true;
268 guard.1[i] = current;
269 }
270 }
271
272 if updated {
273 guard.0 += 1;
274 }
275
276 guard.0
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use std::sync::atomic::AtomicI32;
283 use std::sync::atomic::Ordering;
284
285 use vortex_buffer::buffer;
286 use vortex_error::VortexResult;
287
288 use super::*;
289 use crate::IntoArray;
290 use crate::VortexSessionExecute;
291 use crate::array_session;
292 use crate::arrays::BoolArray;
293 use crate::assert_arrays_eq;
294 use crate::dtype::DType;
295 use crate::dtype::Nullability;
296 use crate::dtype::PType;
297 use crate::expr::dynamic;
298 use crate::expr::root;
299
300 #[test]
301 fn is_not_strict() {
302 let expr = dynamic(
303 CompareOperator::Lt,
304 || None,
305 DType::Primitive(PType::I32, Nullability::NonNullable),
306 true,
307 root(),
308 );
309
310 assert!(!expr.as_scalar().is_some_and(|f| f.signature().is_strict()));
311 }
312
313 #[test]
314 fn return_dtype_bool() -> VortexResult<()> {
315 let expr = dynamic(
316 CompareOperator::Lt,
317 || Some(5i32.into()),
318 DType::Primitive(PType::I32, Nullability::NonNullable),
319 true,
320 root(),
321 );
322 let input_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
323 assert_eq!(
324 expr.return_dtype(&input_dtype)?,
325 DType::Bool(Nullability::NonNullable)
326 );
327 Ok(())
328 }
329
330 #[test]
331 fn execute_with_value() -> VortexResult<()> {
332 let mut ctx = array_session().create_execution_ctx();
333 let input = buffer![1i32, 5, 10].into_array();
334 let expr = dynamic(
335 CompareOperator::Lt,
336 || Some(5i32.into()),
337 DType::Primitive(PType::I32, Nullability::NonNullable),
338 true,
339 root(),
340 );
341 let result = input.apply(&expr)?;
342 assert_arrays_eq!(result, BoolArray::from_iter([true, false, false]), &mut ctx);
343 Ok(())
344 }
345
346 #[test]
347 fn execute_without_value_default_true() -> VortexResult<()> {
348 let mut ctx = array_session().create_execution_ctx();
349 let input = buffer![1i32, 5, 10].into_array();
350 let expr = dynamic(
351 CompareOperator::Lt,
352 || None,
353 DType::Primitive(PType::I32, Nullability::NonNullable),
354 true,
355 root(),
356 );
357 let result = input.apply(&expr)?;
358 assert_arrays_eq!(result, BoolArray::from_iter([true, true, true]), &mut ctx);
359 Ok(())
360 }
361
362 #[test]
363 fn execute_without_value_default_false() -> VortexResult<()> {
364 let mut ctx = array_session().create_execution_ctx();
365 let input = buffer![1i32, 5, 10].into_array();
366 let expr = dynamic(
367 CompareOperator::Lt,
368 || None,
369 DType::Primitive(PType::I32, Nullability::NonNullable),
370 false,
371 root(),
372 );
373 let result = input.apply(&expr)?;
374 assert_arrays_eq!(
375 result,
376 BoolArray::from_iter([false, false, false]),
377 &mut ctx
378 );
379 Ok(())
380 }
381
382 #[test]
383 fn execute_value_flips() -> VortexResult<()> {
384 let mut ctx = array_session().create_execution_ctx();
385 let threshold = Arc::new(AtomicI32::new(5));
386 let threshold_clone = Arc::clone(&threshold);
387 let expr = dynamic(
388 CompareOperator::Lt,
389 move || Some(threshold_clone.load(Ordering::SeqCst).into()),
390 DType::Primitive(PType::I32, Nullability::NonNullable),
391 true,
392 root(),
393 );
394 let input = buffer![1i32, 5, 10].into_array();
395
396 let result = input.clone().apply(&expr)?;
397 assert_arrays_eq!(result, BoolArray::from_iter([true, false, false]), &mut ctx);
398
399 threshold.store(10, Ordering::SeqCst);
400 let result = input.apply(&expr)?;
401 assert_arrays_eq!(result, BoolArray::from_iter([true, true, false]), &mut ctx);
402
403 Ok(())
404 }
405}