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