1use vortex_error::VortexResult;
12use vortex_error::vortex_ensure_eq;
13use vortex_mask::MaskValuesRef;
14use vortex_session::VortexSession;
15
16use super::batch::BorrowedRowFnArgs;
17use super::batch::RowFnExecutionArgs;
18use super::batch::finalize_kernel_output;
19use super::row_fn::RowFn;
20use super::visitor::BatchPlanner;
21use super::visitor::ExecuteDenseWithRetry;
22use super::visitor::ExecuteFilteredRows;
23use super::visitor::ExecuteRows;
24use super::visitor::ExecuteValidRows;
25use crate::ArrayRef;
26use crate::ExecutionCtx;
27use crate::dtype::DType;
28use crate::expr::Expression;
29use crate::expr::union_child_validities;
30use crate::scalar_fn::Arity;
31use crate::scalar_fn::ChildName;
32use crate::scalar_fn::ExecutionArgs;
33use crate::scalar_fn::ScalarFnId;
34use crate::scalar_fn::ScalarFnVTable;
35use crate::scalar_fn::unstable::row::execute::DenseAttempt;
36
37impl<F: RowFn> ScalarFnVTable for F {
38 type Options = F::Options;
39
40 fn id(&self) -> ScalarFnId {
41 RowFn::id(self)
42 }
43
44 fn serialize(&self, options: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
45 RowFn::serialize(self, options)
46 }
47
48 fn deserialize(&self, metadata: &[u8], session: &VortexSession) -> VortexResult<Self::Options> {
49 RowFn::deserialize(self, metadata, session)
50 }
51
52 fn arity(&self, _options: &Self::Options) -> Arity {
53 Arity::Exact(F::ARG_NAMES.len())
54 }
55
56 fn child_name(&self, _options: &Self::Options, child_index: usize) -> ChildName {
57 ChildName::from(F::ARG_NAMES[child_index])
58 }
59
60 fn return_dtype(&self, options: &Self::Options, args: &[DType]) -> VortexResult<DType> {
61 row_fn_return_dtype(self, options, args)
62 }
63
64 fn execute(
65 &self,
66 options: &Self::Options,
67 args: &dyn ExecutionArgs,
68 ctx: &mut ExecutionCtx,
69 ) -> VortexResult<ArrayRef> {
70 execute_rows(self, options, args, ctx)
71 }
72
73 fn validity(
74 &self,
75 _options: &Self::Options,
76 expression: &Expression,
77 ) -> VortexResult<Option<Expression>> {
78 union_child_validities(expression)
79 }
80
81 fn is_strict(&self, _options: &Self::Options) -> bool {
84 true
85 }
86
87 fn is_infallible(&self, _options: &Self::Options) -> bool {
88 F::INFALLIBLE
89 }
90}
91
92pub fn row_fn_return_dtype<F: RowFn>(
94 function: &F,
95 options: &F::Options,
96 args: &[DType],
97) -> VortexResult<DType> {
98 ensure_arity(function, args.len())?;
99
100 let plan = function.dispatch(options, args, BatchPlanner::<F>::new(args))?;
101
102 Ok(plan.result_dtype(args))
103}
104
105pub fn execute_rows<F: RowFn>(
114 function: &F,
115 options: &F::Options,
116 args: &dyn ExecutionArgs,
117 ctx: &mut ExecutionCtx,
118) -> VortexResult<ArrayRef> {
119 ensure_arity(function, args.num_inputs())?;
120
121 if args.num_inputs() == 0 {
122 return execute_nullary_rows(function, options, args.row_count(), ctx);
123 }
124
125 let batch = prepare_batch(function, options, args)?;
126 batch.execute(
127 |args, ctx| execute_row_kernel(function, options, args, ctx),
128 |args, ctx| execute_dense_attempt(function, options, args, ctx),
129 |args, valid, ctx| try_execute_valid_rows(function, options, args, valid, ctx),
130 |args, valid, ctx| execute_filtered_rows(function, options, args, valid, ctx),
131 ctx,
132 )
133}
134
135fn execute_nullary_rows<F: RowFn>(
137 function: &F,
138 options: &F::Options,
139 row_count: usize,
140 ctx: &mut ExecutionCtx,
141) -> VortexResult<ArrayRef> {
142 let plan = function.dispatch(options, &[], BatchPlanner::<F>::new(&[]))?;
143 let result_dtype = plan.result_dtype(&[]);
144 let args = BorrowedRowFnArgs::new(&[], row_count, &[], &plan);
145
146 let values = plan.relabel_output(execute_row_kernel(function, options, args, ctx)?)?;
149
150 finalize_kernel_output(RowFn::id(function), &result_dtype, row_count, values, ctx)
151}
152
153fn ensure_arity<F: RowFn>(function: &F, actual: usize) -> VortexResult<()> {
154 let expected = F::ARG_NAMES.len();
155 vortex_ensure_eq!(
156 actual,
157 expected,
158 "row function {} requires arity {expected}, got {actual}",
159 RowFn::id(function),
160 );
161
162 Ok(())
163}
164
165fn execute_row_kernel<F: RowFn>(
166 function: &F,
167 options: &F::Options,
168 args: BorrowedRowFnArgs<'_>,
169 ctx: &mut ExecutionCtx,
170) -> VortexResult<ArrayRef> {
171 function.dispatch(
172 options,
173 args.dtypes(),
174 ExecuteRows::<F>::new(&args, args.dtypes(), args.plan(), ctx),
175 )
176}
177
178fn execute_dense_attempt<F: RowFn>(
179 function: &F,
180 options: &F::Options,
181 args: BorrowedRowFnArgs<'_>,
182 ctx: &mut ExecutionCtx,
183) -> VortexResult<DenseAttempt> {
184 function.dispatch(
185 options,
186 args.dtypes(),
187 ExecuteDenseWithRetry::<F>::new(&args, ctx),
188 )
189}
190
191fn try_execute_valid_rows<F: RowFn>(
192 function: &F,
193 options: &F::Options,
194 args: BorrowedRowFnArgs<'_>,
195 valid: MaskValuesRef,
196 ctx: &mut ExecutionCtx,
197) -> VortexResult<Option<ArrayRef>> {
198 function.dispatch(
199 options,
200 args.dtypes(),
201 ExecuteValidRows::<F>::new(&args, args.dtypes(), args.plan(), valid, ctx),
202 )
203}
204
205fn execute_filtered_rows<F: RowFn>(
207 function: &F,
208 options: &F::Options,
209 args: BorrowedRowFnArgs<'_>,
210 valid: MaskValuesRef,
211 ctx: &mut ExecutionCtx,
212) -> VortexResult<ArrayRef> {
213 function.dispatch(
214 options,
215 args.dtypes(),
216 ExecuteFilteredRows::<F>::new(&args, args.dtypes(), args.plan(), valid, ctx),
217 )
218}
219
220fn prepare_batch<F: RowFn>(
221 function: &F,
222 options: &F::Options,
223 args: &dyn ExecutionArgs,
224) -> VortexResult<RowFnExecutionArgs> {
225 RowFnExecutionArgs::new(RowFn::id(function), args, |arg_dtypes| {
226 function.dispatch(options, arg_dtypes, BatchPlanner::<F>::new(arg_dtypes))
227 })
228}
229
230#[cfg(test)]
231mod tests {
232 use std::sync::Arc;
233 use std::sync::atomic::AtomicUsize;
234 use std::sync::atomic::Ordering;
235
236 use rstest::rstest;
237 use vortex_error::VortexError;
238 use vortex_error::VortexResult;
239 use vortex_session::registry::CachedId;
240
241 use super::execute_rows;
242 use super::row_fn_return_dtype;
243 use crate::IntoArray;
244 use crate::VortexSessionExecute;
245 use crate::array_session;
246 use crate::arrays::PrimitiveArray;
247 use crate::assert_arrays_eq;
248 use crate::dtype::DType;
249 use crate::scalar_fn::EmptyOptions;
250 use crate::scalar_fn::ScalarFnId;
251 use crate::scalar_fn::VecExecutionArgs;
252 use crate::scalar_fn::unstable::row::RowFn;
253 use crate::scalar_fn::unstable::row::RowVisitor;
254 use crate::validity::Validity;
255
256 #[derive(Clone)]
257 struct IndexingRowFn;
258
259 #[derive(Clone)]
260 struct NullarySeven;
261
262 #[derive(Clone)]
263 struct ChangingDispatchRowFn {
264 dispatches: Arc<AtomicUsize>,
265 change: DispatchChange,
266 }
267
268 #[derive(Clone, Copy)]
269 enum DispatchChange {
270 Policy,
271 Element,
272 }
273
274 impl RowFn for NullarySeven {
275 type Options = EmptyOptions;
276
277 const ARG_NAMES: &'static [&'static str] = &[];
278 const INFALLIBLE: bool = true;
279
280 fn id(&self) -> ScalarFnId {
281 static ID: CachedId = CachedId::new("test.nullary_seven");
282 *ID
283 }
284
285 fn dispatch<V: RowVisitor>(
286 &self,
287 _options: &Self::Options,
288 _args: &[DType],
289 visitor: V,
290 ) -> VortexResult<V::VisitResult> {
291 visitor.visit::<(), i64>(|()| 7)
292 }
293 }
294
295 impl RowFn for IndexingRowFn {
296 type Options = EmptyOptions;
297
298 const ARG_NAMES: &'static [&'static str] = &["value"];
299
300 const INFALLIBLE: bool = true;
301
302 fn id(&self) -> ScalarFnId {
303 static ID: CachedId = CachedId::new("test.indexing_row_fn");
304 *ID
305 }
306
307 fn dispatch<V: RowVisitor>(
308 &self,
309 _options: &Self::Options,
310 args: &[DType],
311 visitor: V,
312 ) -> VortexResult<V::VisitResult> {
313 _ = &args[0];
314
315 visitor.visit::<(i64,), i64>(|(value,)| value)
316 }
317 }
318
319 impl RowFn for ChangingDispatchRowFn {
320 type Options = EmptyOptions;
321
322 const ARG_NAMES: &'static [&'static str] = &["value"];
323 const INFALLIBLE: bool = false;
324
325 fn id(&self) -> ScalarFnId {
326 static ID: CachedId = CachedId::new("test.changing_dispatch_row_fn");
327 *ID
328 }
329
330 fn dispatch<V: RowVisitor>(
331 &self,
332 _options: &Self::Options,
333 _args: &[DType],
334 visitor: V,
335 ) -> VortexResult<V::VisitResult> {
336 if self.dispatches.fetch_add(1, Ordering::Relaxed) == 0 {
337 visitor.visit::<(i64,), i64>(|(value,)| value)
338 } else {
339 match self.change {
340 DispatchChange::Policy => visitor
341 .visit_deferred::<(i64,), i64, bool>(|(value,)| (value, false), |_| Ok(())),
342 DispatchChange::Element => visitor.visit::<(u64,), u64>(|(value,)| value),
343 }
344 }
345 }
346 }
347
348 #[test]
349 fn test_return_dtype_rejects_wrong_arity_before_dispatch() {
350 let error = row_fn_return_dtype(&IndexingRowFn, &EmptyOptions, &[])
351 .expect_err("wrong arity must fail before dispatch");
352
353 assert_arity_error(error);
354 }
355
356 #[test]
357 fn test_execute_rejects_wrong_arity_before_dispatch() {
358 let args = VecExecutionArgs::new(vec![], 0);
359 let mut ctx = array_session().create_execution_ctx();
360 let error = execute_rows(&IndexingRowFn, &EmptyOptions, &args, &mut ctx)
361 .expect_err("wrong arity must fail before dispatch");
362
363 assert_arity_error(error);
364 }
365
366 #[rstest]
367 #[case::empty(0)]
368 #[case::nonempty(3)]
369 fn test_execute_nullary_rows(#[case] row_count: usize) -> VortexResult<()> {
370 let args = VecExecutionArgs::new(vec![], row_count);
371 let mut ctx = array_session().create_execution_ctx();
372
373 let actual = execute_rows(&NullarySeven, &EmptyOptions, &args, &mut ctx)?;
374 let expected = PrimitiveArray::from_iter(vec![7_i64; row_count]).into_array();
375
376 assert_arrays_eq!(&actual, &expected, &mut ctx);
377 Ok(())
378 }
379
380 #[test]
381 fn test_execute_rejects_dispatch_that_changes_after_planning() -> VortexResult<()> {
382 let function = ChangingDispatchRowFn {
383 dispatches: Arc::new(AtomicUsize::new(0)),
384 change: DispatchChange::Policy,
385 };
386 let input = PrimitiveArray::new(vec![1_i64, 2], Validity::NonNullable).into_array();
387 let args = VecExecutionArgs::new(vec![input], 2);
388 let mut ctx = array_session().create_execution_ctx();
389
390 let error = match execute_rows(&function, &EmptyOptions, &args, &mut ctx) {
391 Err(error) => error,
392 Ok(_) => vortex_error::vortex_bail!("dispatch must not change after planning"),
393 };
394 let message = error.to_string();
395
396 assert!(
397 message.contains("row dispatch must select the planned nullable execution policy"),
398 "unexpected error: {error}",
399 );
400 assert!(
401 message.contains("planned Dense, got DenseWithRetry"),
402 "unexpected error: {error}",
403 );
404 Ok(())
405 }
406
407 #[test]
408 fn test_execute_revalidates_element_types_after_planning() -> VortexResult<()> {
409 let function = ChangingDispatchRowFn {
410 dispatches: Arc::new(AtomicUsize::new(0)),
411 change: DispatchChange::Element,
412 };
413 let input = PrimitiveArray::new(vec![1_i64, 2], Validity::NonNullable).into_array();
414 let args = VecExecutionArgs::new(vec![input], 2);
415 let mut ctx = array_session().create_execution_ctx();
416
417 let error = match execute_rows(&function, &EmptyOptions, &args, &mut ctx) {
418 Err(error) => error,
419 Ok(_) => vortex_error::vortex_bail!("dispatch must preserve its planned element types"),
420 };
421
422 assert!(
423 error.to_string().contains("expected a u64 column"),
424 "unexpected error: {error}",
425 );
426 Ok(())
427 }
428
429 #[track_caller]
430 fn assert_arity_error(error: VortexError) {
431 assert!(
432 error.to_string().contains("requires arity 1, got 0"),
433 "unexpected error: {error}",
434 );
435 }
436}