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