1use std::fmt::Display;
5use std::fmt::Formatter;
6use std::slice;
7
8use prost::Message;
9use vortex_error::VortexResult;
10use vortex_error::vortex_err;
11use vortex_session::VortexSession;
12use vortex_session::registry::CachedId;
13
14use crate::ArrayRef;
15use crate::ExecutionCtx;
16use crate::IntoArray;
17use crate::arrays::Constant;
18use crate::arrays::ConstantArray;
19use crate::arrays::ScalarFnArray;
20use crate::arrays::StructArray;
21use crate::arrays::struct_::StructArrayExt;
22use crate::builtins::ArrayBuiltins;
23use crate::dtype::DType;
24use crate::dtype::FieldName;
25use crate::dtype::Nullability;
26use crate::expr::display::ExprDisplay;
27use crate::proto::expr as pb;
28use crate::scalar::Scalar;
29use crate::scalar_fn::Arity;
30use crate::scalar_fn::ChildName;
31use crate::scalar_fn::EmptyOptions;
32use crate::scalar_fn::ExecutionArgs;
33use crate::scalar_fn::ReduceNode;
34use crate::scalar_fn::ScalarFnId;
35use crate::scalar_fn::ScalarFnVTable;
36use crate::scalar_fn::ScalarFnVTableExt;
37use crate::scalar_fn::fns::mask::Mask;
38use crate::scalar_fn::fns::pack::Pack;
39
40#[derive(Clone)]
41pub struct GetItem;
42
43impl GetItem {
44 pub fn try_new(
50 input: ArrayRef,
51 field_name: impl Into<FieldName>,
52 ) -> VortexResult<ScalarFnArray> {
53 ScalarFnArray::try_new(GetItem.bind(field_name.into()), [input])
54 }
55
56 pub(crate) fn apply_now<T: ReduceNode>(
63 struct_node: &T,
64 field_name: &FieldName,
65 ) -> VortexResult<T> {
66 if let Some(field) = Self::maybe_apply_now(struct_node, field_name)? {
67 return Ok(field);
68 }
69
70 struct_node.new_node(
71 GetItem.bind(field_name.clone()),
72 slice::from_ref(struct_node),
73 )
74 }
75
76 fn maybe_apply_now<T: ReduceNode>(
80 struct_node: &T,
81 field_name: &FieldName,
82 ) -> VortexResult<Option<T>> {
83 let Some(pack) = struct_node
84 .scalar_fn()
85 .and_then(|scalar_fn| scalar_fn.as_opt::<Pack>())
86 else {
87 return Ok(None);
88 };
89 let Some(idx) = pack.names.find(field_name) else {
90 return Ok(None);
91 };
92
93 let field = struct_node.child(idx);
94 if !pack.nullability.is_nullable() {
95 return Ok(Some(field));
96 }
97
98 Ok(Some(struct_node.new_node(
101 Mask.bind(EmptyOptions),
102 &[field, struct_node.new_constant(true.into())],
103 )?))
104 }
105}
106
107impl ScalarFnVTable for GetItem {
108 type Options = FieldName;
109
110 fn id(&self) -> ScalarFnId {
111 static ID: CachedId = CachedId::new("vortex.get_item");
112 *ID
113 }
114
115 fn serialize(&self, instance: &Self::Options) -> VortexResult<Option<Vec<u8>>> {
116 Ok(Some(
117 pb::GetItemOpts {
118 path: instance.to_string(),
119 }
120 .encode_to_vec(),
121 ))
122 }
123
124 fn deserialize(
125 &self,
126 _metadata: &[u8],
127 _session: &VortexSession,
128 ) -> VortexResult<Self::Options> {
129 let opts = pb::GetItemOpts::decode(_metadata)?;
130 Ok(FieldName::from(opts.path))
131 }
132
133 fn arity(&self, _field_name: &FieldName) -> Arity {
134 Arity::Exact(1)
135 }
136
137 fn child_name(&self, _instance: &Self::Options, child_idx: usize) -> ChildName {
138 match child_idx {
139 0 => ChildName::from("input"),
140 _ => unreachable!("Invalid child index {} for GetItem expression", child_idx),
141 }
142 }
143
144 fn fmt_sql(
145 &self,
146 field_name: &FieldName,
147 expr: &dyn ExprDisplay,
148 f: &mut Formatter<'_>,
149 ) -> std::fmt::Result {
150 Display::fmt(expr.display_child(0), f)?;
151 write!(f, ".{}", field_name)
152 }
153
154 fn return_dtype(&self, field_name: &FieldName, arg_dtypes: &[DType]) -> VortexResult<DType> {
155 let struct_dtype = &arg_dtypes[0];
156 let field_dtype = struct_dtype
157 .as_struct_fields_opt()
158 .and_then(|st| st.field(field_name))
159 .ok_or_else(|| {
160 vortex_err!("Couldn't find the {} field in the input scope", field_name)
161 })?;
162
163 if matches!(
165 (struct_dtype.nullability(), field_dtype.nullability()),
166 (Nullability::Nullable, Nullability::NonNullable)
167 ) {
168 return Ok(field_dtype.with_nullability(Nullability::Nullable));
169 }
170
171 Ok(field_dtype)
172 }
173
174 fn execute(
175 &self,
176 field_name: &FieldName,
177 args: &dyn ExecutionArgs,
178 ctx: &mut ExecutionCtx,
179 ) -> VortexResult<ArrayRef> {
180 let input = args.get(0)?;
181 if let Some(constant) = input.as_opt::<Constant>() {
182 let dtype = self.return_dtype(field_name, &[input.dtype().clone()])?;
183 let scalar = if constant.scalar().is_null() {
184 Scalar::null(dtype)
185 } else {
186 constant
187 .scalar()
188 .as_struct()
189 .field(field_name)
190 .ok_or_else(|| {
191 vortex_err!(
192 "Field '{}' missing from constant struct array {}",
193 field_name,
194 input.dtype()
195 )
196 })?
197 .cast(&dtype)?
198 };
199
200 return Ok(ConstantArray::new(scalar, input.len()).into_array());
201 }
202
203 let input = input.execute::<StructArray>(ctx)?;
204 let field = input.unmasked_field_by_name(field_name).cloned()?;
205
206 match input.dtype().nullability() {
207 Nullability::NonNullable => Ok(field),
208 Nullability::Nullable => field.mask(input.validity()?.to_array(input.len())),
209 }
210 }
211
212 fn reduce<T: ReduceNode>(&self, field_name: &FieldName, node: &T) -> VortexResult<Option<T>> {
213 Self::maybe_apply_now(&node.child(0), field_name)
214 }
215
216 fn is_strict(&self, _field_name: &FieldName) -> bool {
217 true
218 }
219
220 fn is_infallible(&self, _field_name: &FieldName) -> bool {
221 true
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use vortex_buffer::buffer;
229 use vortex_error::VortexResult;
230
231 use super::GetItem;
232 use crate::ArrayRef;
233 use crate::IntoArray;
234 use crate::VortexSessionExecute;
235 use crate::array_session;
236 use crate::arrays::Constant;
237 use crate::arrays::ConstantArray;
238 use crate::arrays::Filter;
239 use crate::arrays::Primitive;
240 use crate::arrays::PrimitiveArray;
241 use crate::arrays::ScalarFn;
242 use crate::arrays::Slice;
243 use crate::assert_arrays_eq;
244 use crate::builtins::ArrayBuiltins;
245 use crate::dtype::DType;
246 use crate::dtype::FieldName;
247 use crate::dtype::FieldNames;
248 use crate::dtype::Nullability;
249 use crate::dtype::Nullability::NonNullable;
250 use crate::dtype::PType;
251 use crate::dtype::StructFields;
252 use crate::expr::checked_add;
253 use crate::expr::get_item;
254 use crate::expr::lit;
255 use crate::expr::pack;
256 use crate::expr::root;
257 use crate::scalar::Scalar;
258 use crate::scalar_fn::fns::get_item::StructArray;
259 use crate::validity::Validity;
260
261 fn test_array() -> StructArray {
262 StructArray::from_fields(&[
263 ("a", buffer![0i32, 1, 2].into_array()),
264 ("b", buffer![4i64, 5, 6].into_array()),
265 ])
266 .unwrap()
267 }
268
269 #[test]
270 fn get_item_by_name() {
271 let st = test_array();
272 let get_item = get_item("a", root());
273 assert!(
274 get_item
275 .as_scalar()
276 .is_some_and(|f| f.signature().is_strict())
277 );
278 let item = st.into_array().apply(&get_item).unwrap();
279 assert_eq!(item.dtype(), &DType::from(PType::I32));
280 assert_arrays_eq!(
281 item,
282 buffer![0i32, 1, 2].into_array(),
283 &mut array_session().create_execution_ctx()
284 );
285 }
286
287 #[test]
288 fn get_item_by_name_none() {
289 let st = test_array();
290 let get_item = get_item("c", root());
291 assert!(st.into_array().apply(&get_item).is_err());
292 }
293
294 #[test]
295 fn get_nullable_field() {
296 let st = StructArray::try_new(
297 FieldNames::from(["a"]),
298 vec![buffer![1i32].into_array()],
299 1,
300 Validity::AllInvalid,
301 )
302 .unwrap()
303 .into_array();
304
305 let get_item_expr = get_item("a", root());
306 let item = st.apply(&get_item_expr).unwrap();
307 assert_eq!(
309 item.dtype(),
310 &DType::Primitive(PType::I32, Nullability::Nullable)
311 );
312 assert_arrays_eq!(
313 item,
314 PrimitiveArray::from_option_iter([None::<i32>]),
315 &mut array_session().create_execution_ctx()
316 );
317 }
318
319 #[test]
320 fn get_non_nullable_field_from_all_valid_nullable_struct() -> VortexResult<()> {
321 let st = StructArray::try_new(
322 FieldNames::from(["a"]),
323 vec![buffer![1i32].into_array()],
324 1,
325 Validity::AllValid,
326 )?
327 .into_array();
328
329 let item = st.apply(&get_item("a", root()))?;
330 assert_eq!(
331 item.dtype(),
332 &DType::Primitive(PType::I32, Nullability::Nullable)
333 );
334 assert_arrays_eq!(
335 item,
336 PrimitiveArray::from_option_iter([Some(1i32)]),
337 &mut array_session().create_execution_ctx()
338 );
339 Ok(())
340 }
341
342 #[test]
343 fn execute_constant_struct_stays_constant() -> VortexResult<()> {
344 let field_count = 128usize;
345 let selected_idx = 97i32;
346 let names = (0..field_count)
347 .map(|idx| FieldName::from(format!("f{idx}")))
348 .collect::<Vec<_>>();
349 let dtypes = vec![DType::Primitive(PType::I32, NonNullable); field_count];
350 let fields = StructFields::new(FieldNames::from(names), dtypes);
351 let scalar = Scalar::struct_(
352 DType::Struct(fields, NonNullable),
353 (0..128).map(|idx| Scalar::primitive(idx, NonNullable)),
354 );
355 let array = ConstantArray::new(scalar, 1_000_000).into_array();
356 let mut ctx = array_session().create_execution_ctx();
357
358 let item: ArrayRef = GetItem::try_new(array, "f97")?
359 .into_array()
360 .execute(&mut ctx)?;
361
362 let constant = item.as_::<Constant>();
363 assert_eq!(constant.len(), 1_000_000);
364 assert_eq!(
365 constant.scalar(),
366 &Scalar::primitive(selected_idx, NonNullable)
367 );
368 Ok(())
369 }
370
371 #[test]
372 fn get_item_pushes_through_filter() -> VortexResult<()> {
373 use vortex_mask::Mask;
374
375 let filtered = test_array()
376 .into_array()
377 .filter(Mask::from_iter([true, false, true]))?;
378
379 let item = filtered.get_item("a")?;
380
381 assert!(item.is::<Filter>());
382 assert!(!item.is::<ScalarFn>());
383 assert_arrays_eq!(
384 item,
385 buffer![0i32, 2].into_array(),
386 &mut array_session().create_execution_ctx()
387 );
388 Ok(())
389 }
390
391 #[test]
392 fn get_item_pushes_through_slice() -> VortexResult<()> {
393 let sliced = test_array().into_array().slice(1..3)?;
394
395 let item = sliced.get_item("a")?;
396
397 assert!(item.is::<Primitive>());
398 assert!(!item.is::<Slice>());
399 assert!(!item.is::<ScalarFn>());
400 assert_arrays_eq!(
401 item,
402 buffer![1i32, 2].into_array(),
403 &mut array_session().create_execution_ctx()
404 );
405 Ok(())
406 }
407
408 #[test]
409 fn get_item_pushes_through_masked() -> VortexResult<()> {
410 let masked = test_array()
411 .into_array()
412 .mask(Validity::from_iter([true, false, true]).to_array(3))?;
413
414 let item = masked.get_item("a")?;
415
416 assert!(item.is::<Primitive>());
417 assert_eq!(
418 item.dtype(),
419 &DType::Primitive(PType::I32, Nullability::Nullable)
420 );
421 assert!(!item.is::<ScalarFn>());
422 assert_arrays_eq!(
423 item,
424 PrimitiveArray::from_option_iter([Some(0i32), None, Some(2)]),
425 &mut array_session().create_execution_ctx()
426 );
427 Ok(())
428 }
429
430 #[test]
431 fn test_pack_get_item_rule() {
432 let pack_expr = pack([("a", lit(1)), ("b", lit(2))], NonNullable);
434 let get_item_expr = get_item("b", pack_expr);
435
436 let dtype = DType::Struct(StructFields::empty(), NonNullable);
437 let result = get_item_expr
438 .bind(&dtype)
439 .and_then(|expr| expr.optimize_recursive())
440 .unwrap();
441
442 assert_eq!(result, lit(2).bind(&dtype).unwrap());
443 }
444
445 #[test]
446 fn test_multi_level_pack_get_item_simplify() {
447 let inner_pack = pack([("a", lit(1)), ("b", lit(2))], NonNullable);
448 let get_a = get_item("a", inner_pack);
449
450 let outer_pack = pack([("x", get_a), ("y", lit(3)), ("z", lit(4))], NonNullable);
451 let get_z = get_item("z", outer_pack);
452
453 let dtype = DType::Primitive(PType::I32, NonNullable);
454
455 let result = get_z.bind(&dtype).unwrap().optimize_recursive().unwrap();
456 assert_eq!(result, lit(4).bind(&dtype).unwrap());
457 }
458
459 #[test]
460 fn test_deeply_nested_pack_get_item() {
461 let innermost = pack([("a", lit(42))], NonNullable);
462 let get_a = get_item("a", innermost);
463
464 let level2 = pack([("b", get_a)], NonNullable);
465 let get_b = get_item("b", level2);
466
467 let level3 = pack([("c", get_b)], NonNullable);
468 let get_c = get_item("c", level3);
469
470 let outermost = pack([("final", get_c)], NonNullable);
471 let get_final = get_item("final", outermost);
472
473 let dtype = DType::Primitive(PType::I32, NonNullable);
474
475 let result = get_final
476 .bind(&dtype)
477 .unwrap()
478 .optimize_recursive()
479 .unwrap();
480 assert_eq!(result, lit(42).bind(&dtype).unwrap());
481 }
482
483 #[test]
484 fn test_partial_pack_get_item_simplify() {
485 let inner_pack = pack([("x", lit(1)), ("y", lit(2))], NonNullable);
486 let get_x = get_item("x", inner_pack);
487 let add_expr = checked_add(get_x, lit(10));
488
489 let outer_pack = pack([("result", add_expr)], NonNullable);
490 let get_result = get_item("result", outer_pack);
491
492 let dtype = DType::Primitive(PType::I32, NonNullable);
493
494 let result = get_result
495 .bind(&dtype)
496 .unwrap()
497 .optimize_recursive()
498 .unwrap();
499 let expected = checked_add(lit(1), lit(10)).bind(&dtype).unwrap();
500 assert_eq!(&result, &expected);
501 }
502
503 #[test]
504 fn get_item_filter_list_field() {
505 let mut ctx = array_session().create_execution_ctx();
506 use vortex_mask::Mask;
507
508 use crate::arrays::BoolArray;
509 use crate::arrays::FilterArray;
510 use crate::arrays::ListArray;
511
512 let list = ListArray::try_new(
513 buffer![0f32, 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11.].into_array(),
514 buffer![2u64, 4, 6, 8, 10, 12].into_array(),
515 Validity::Array(BoolArray::from_iter([true, true, false, true, true]).into_array()),
516 &mut ctx,
517 )
518 .unwrap();
519
520 let filtered = FilterArray::try_new(
521 list.into_array(),
522 Mask::from_iter([true, true, false, false, false]),
523 )
524 .unwrap();
525
526 let st = StructArray::try_new(
527 FieldNames::from(["data"]),
528 vec![filtered.into_array()],
529 2,
530 Validity::AllValid,
531 )
532 .unwrap();
533
534 let result = st.into_array().apply(&get_item("data", root())).unwrap();
535 let expected = ListArray::try_new(
536 buffer![2f32, 3., 4., 5.].into_array(),
537 buffer![0u64, 2, 4].into_array(),
538 Validity::AllValid,
539 &mut ctx,
540 )
541 .unwrap();
542 assert_arrays_eq!(
543 result,
544 expected,
545 &mut array_session().create_execution_ctx()
546 );
547 }
548}