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