vortex_array/scalar_fn/fns/
select.rs1use std::fmt::Display;
5use std::fmt::Formatter;
6
7use itertools::Itertools;
8use prost::Message;
9use vortex_error::VortexExpect;
10use vortex_error::VortexResult;
11use vortex_error::vortex_bail;
12use vortex_error::vortex_err;
13use vortex_session::VortexSession;
14use vortex_session::registry::CachedId;
15
16use crate::ArrayRef;
17use crate::ExecutionCtx;
18use crate::IntoArray;
19use crate::arrays::Constant;
20use crate::arrays::ConstantArray;
21use crate::arrays::StructArray;
22use crate::arrays::struct_::StructArrayExt;
23use crate::dtype::DType;
24use crate::dtype::FieldNames;
25use crate::expr::BoundExpression;
26use crate::expr::display::ExprDisplay;
27use crate::expr::field::DisplayFieldNames;
28use crate::proto::expr::FieldNames as ProtoFieldNames;
29use crate::proto::expr::SelectOpts;
30use crate::proto::expr::select_opts::Opts;
31use crate::scalar_fn::Arity;
32use crate::scalar_fn::ChildName;
33use crate::scalar_fn::ExecutionArgs;
34use crate::scalar_fn::ScalarFnId;
35use crate::scalar_fn::ScalarFnVTable;
36use crate::scalar_fn::ScalarFnVTableExt;
37use crate::scalar_fn::fns::get_item::GetItem;
38use crate::scalar_fn::fns::pack::Pack;
39use crate::scalar_fn::fns::pack::PackOptions;
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
42pub enum FieldSelection {
43 Include(FieldNames),
44 Exclude(FieldNames),
45}
46
47#[derive(Clone)]
48pub struct Select;
49
50impl ScalarFnVTable for Select {
51 type Options = FieldSelection;
52
53 fn id(&self) -> ScalarFnId {
54 static ID: CachedId = CachedId::new("vortex.select");
55 *ID
56 }
57
58 fn serialize(&self, instance: &FieldSelection) -> VortexResult<Option<Vec<u8>>> {
59 let opts = match instance {
60 FieldSelection::Include(fields) => Opts::Include(ProtoFieldNames {
61 names: fields.iter().map(|f| f.to_string()).collect(),
62 }),
63 FieldSelection::Exclude(fields) => Opts::Exclude(ProtoFieldNames {
64 names: fields.iter().map(|f| f.to_string()).collect(),
65 }),
66 };
67
68 let select_opts = SelectOpts { opts: Some(opts) };
69 Ok(Some(select_opts.encode_to_vec()))
70 }
71
72 fn deserialize(
73 &self,
74 _metadata: &[u8],
75 _session: &VortexSession,
76 ) -> VortexResult<FieldSelection> {
77 let prost_metadata = SelectOpts::decode(_metadata)?;
78
79 let select_opts = prost_metadata
80 .opts
81 .ok_or_else(|| vortex_err!("SelectOpts missing opts field"))?;
82
83 let field_selection = match select_opts {
84 Opts::Include(field_names) => FieldSelection::Include(FieldNames::from_iter(
85 field_names.names.iter().map(|s| s.as_str()),
86 )),
87 Opts::Exclude(field_names) => FieldSelection::Exclude(FieldNames::from_iter(
88 field_names.names.iter().map(|s| s.as_str()),
89 )),
90 };
91
92 Ok(field_selection)
93 }
94
95 fn arity(&self, _options: &FieldSelection) -> Arity {
96 Arity::Exact(1)
97 }
98
99 fn child_name(&self, _instance: &FieldSelection, child_idx: usize) -> ChildName {
100 match child_idx {
101 0 => ChildName::from("child"),
102 _ => unreachable!(),
103 }
104 }
105
106 fn fmt_sql(
107 &self,
108 selection: &FieldSelection,
109 expr: &dyn ExprDisplay,
110 f: &mut Formatter<'_>,
111 ) -> std::fmt::Result {
112 Display::fmt(expr.display_child(0), f)?;
113 match selection {
114 FieldSelection::Include(fields) => {
115 write!(f, "{{{}}}", DisplayFieldNames(fields))
116 }
117 FieldSelection::Exclude(fields) => {
118 write!(f, "{{~ {}}}", DisplayFieldNames(fields))
119 }
120 }
121 }
122
123 fn return_dtype(
124 &self,
125 selection: &FieldSelection,
126 arg_dtypes: &[DType],
127 ) -> VortexResult<DType> {
128 let child_dtype = &arg_dtypes[0];
129 let child_struct_dtype = child_dtype
130 .as_struct_fields_opt()
131 .ok_or_else(|| vortex_err!("Select child not a struct dtype"))?;
132
133 let projected = match selection {
134 FieldSelection::Include(fields) => child_struct_dtype.project(fields.as_ref())?,
135 FieldSelection::Exclude(fields) => child_struct_dtype
136 .names()
137 .iter()
138 .cloned()
139 .zip_eq(child_struct_dtype.fields())
140 .filter(|(name, _)| !fields.as_ref().contains(name))
141 .collect(),
142 };
143
144 Ok(DType::Struct(projected, child_dtype.nullability()))
145 }
146
147 fn execute(
148 &self,
149 selection: &FieldSelection,
150 args: &dyn ExecutionArgs,
151 ctx: &mut ExecutionCtx,
152 ) -> VortexResult<ArrayRef> {
153 let child = args.get(0)?;
154 if let Some(constant) = child.as_opt::<Constant>() {
155 let child_struct_dtype = child
156 .dtype()
157 .as_struct_fields_opt()
158 .ok_or_else(|| vortex_err!("Select child not a struct dtype"))?;
159 let included = selection.normalize_to_included_fields(child_struct_dtype.names())?;
160 let scalar = constant.scalar().as_struct().project(included.as_ref())?;
161
162 return Ok(ConstantArray::new(scalar, child.len()).into_array());
163 }
164
165 let child = child.execute::<StructArray>(ctx)?;
166
167 let result = match selection {
168 FieldSelection::Include(f) => child.project(f.as_ref()),
169 FieldSelection::Exclude(names) => {
170 let included_names = child
171 .names()
172 .iter()
173 .filter(|&f| !names.as_ref().contains(f))
174 .cloned()
175 .collect::<Vec<_>>();
176 child.project(included_names.as_slice())
177 }
178 }?;
179
180 result.into_array().execute(ctx)
181 }
182
183 fn simplify(
184 &self,
185 selection: &FieldSelection,
186 expr: &BoundExpression,
187 ) -> VortexResult<Option<BoundExpression>> {
188 let child_struct = expr.child(0);
189 let struct_dtype = child_struct.dtype();
190 let struct_nullability = struct_dtype.nullability();
191
192 let struct_fields = struct_dtype.as_struct_fields_opt().ok_or_else(|| {
193 vortex_err!(
194 "Select child must return a struct dtype, however it was a {}",
195 struct_dtype
196 )
197 })?;
198
199 let included_fields = selection.normalize_to_included_fields(struct_fields.names())?;
201 let all_included_fields_are_nullable = included_fields.iter().all(|name| {
202 struct_fields
203 .field(name)
204 .vortex_expect(
205 "`normalize_to_included_fields` checks that the included fields already exist \
206 in `struct_fields`",
207 )
208 .is_nullable()
209 });
210
211 if included_fields.is_empty() {
216 return Ok(Some(Pack.try_new_bound_expr(
217 PackOptions {
218 names: FieldNames::empty(),
219 nullability: struct_nullability,
220 },
221 [],
222 )?));
223 }
224
225 let child_is_pack = child_struct.is::<Pack>();
232
233 let would_intersect_validity =
237 struct_nullability.is_nullable() && !all_included_fields_are_nullable;
238
239 if child_is_pack && !would_intersect_validity {
240 let fields = included_fields
241 .iter()
242 .map(|name| GetItem.try_new_bound_expr(name.clone(), [child_struct.clone()]))
243 .collect::<VortexResult<Vec<_>>>()?;
244 let pack_expr = Pack.try_new_bound_expr(
245 PackOptions {
246 names: included_fields,
247 nullability: struct_nullability,
248 },
249 fields,
250 )?;
251
252 return Ok(Some(pack_expr));
253 }
254
255 Ok(None)
256 }
257
258 fn is_strict(&self, _options: &FieldSelection) -> bool {
259 true
260 }
261
262 fn is_infallible(&self, _instance: &FieldSelection) -> bool {
263 true
265 }
266}
267
268impl FieldSelection {
269 pub fn include(columns: FieldNames) -> Self {
270 assert_eq!(columns.iter().unique().collect_vec().len(), columns.len());
271 Self::Include(columns)
272 }
273
274 pub fn exclude(columns: FieldNames) -> Self {
275 assert_eq!(columns.iter().unique().collect_vec().len(), columns.len());
276 Self::Exclude(columns)
277 }
278
279 pub fn is_include(&self) -> bool {
280 matches!(self, Self::Include(_))
281 }
282
283 pub fn is_exclude(&self) -> bool {
284 matches!(self, Self::Exclude(_))
285 }
286
287 pub fn field_names(&self) -> &FieldNames {
288 let (FieldSelection::Include(fields) | FieldSelection::Exclude(fields)) = self;
289
290 fields
291 }
292
293 pub fn normalize_to_included_fields(
294 &self,
295 available_fields: &FieldNames,
296 ) -> VortexResult<FieldNames> {
297 if self
299 .field_names()
300 .iter()
301 .any(|f| !available_fields.iter().contains(f))
302 {
303 vortex_bail!(
304 "Select fields {:?} must be a subset of child fields {:?}",
305 self,
306 available_fields
307 );
308 }
309
310 match self {
311 FieldSelection::Include(fields) => Ok(fields.clone()),
312 FieldSelection::Exclude(exc_fields) => Ok(available_fields
313 .iter()
314 .filter(|f| !exc_fields.iter().contains(f))
315 .cloned()
316 .collect()),
317 }
318 }
319}
320
321impl Display for FieldSelection {
322 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
323 match self {
324 FieldSelection::Include(fields) => write!(f, "{{{}}}", DisplayFieldNames(fields)),
325 FieldSelection::Exclude(fields) => write!(f, "~{{{}}}", DisplayFieldNames(fields)),
326 }
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use vortex_buffer::buffer;
333 use vortex_error::VortexResult;
334
335 use crate::ArrayRef;
336 use crate::IntoArray;
337 use crate::VortexSessionExecute;
338 use crate::array_session;
339 use crate::arrays::Constant;
340 use crate::arrays::ConstantArray;
341 use crate::arrays::ScalarFnArray;
342 use crate::arrays::struct_::StructArrayExt;
343 use crate::dtype::DType;
344 use crate::dtype::FieldName;
345 use crate::dtype::FieldNames;
346 use crate::dtype::Nullability;
347 use crate::dtype::Nullability::Nullable;
348 use crate::dtype::PType::I32;
349 use crate::dtype::StructFields;
350 use crate::expr::root;
351 use crate::expr::select;
352 use crate::expr::select_exclude;
353 use crate::expr::test_harness;
354 use crate::scalar::Scalar;
355 use crate::scalar_fn::ScalarFnVTableExt;
356 use crate::scalar_fn::fns::select::FieldSelection;
357 use crate::scalar_fn::fns::select::Select;
358 use crate::scalar_fn::fns::select::StructArray;
359
360 fn test_array() -> StructArray {
361 StructArray::from_fields(&[
362 ("a", buffer![0, 1, 2].into_array()),
363 ("b", buffer![4, 5, 6].into_array()),
364 ])
365 .unwrap()
366 }
367
368 #[test]
369 pub fn include_columns() {
370 let mut ctx = array_session().create_execution_ctx();
371 let st = test_array();
372 let select = select(vec![FieldName::from("a")], root());
373 let selected = st
374 .into_array()
375 .apply(&select)
376 .unwrap()
377 .execute::<StructArray>(&mut ctx)
378 .unwrap();
379 let selected_names = selected.names().clone();
380 assert_eq!(selected_names.as_ref(), &["a"]);
381 }
382
383 #[test]
384 pub fn exclude_columns() {
385 let mut ctx = array_session().create_execution_ctx();
386 let st = test_array();
387 let select = select_exclude(vec![FieldName::from("a")], root());
388 let selected = st
389 .into_array()
390 .apply(&select)
391 .unwrap()
392 .execute::<StructArray>(&mut ctx)
393 .unwrap();
394 let selected_names = selected.names().clone();
395 assert_eq!(selected_names.as_ref(), &["b"]);
396 }
397
398 #[test]
399 fn dtype() {
400 let dtype = test_harness::struct_dtype();
401
402 let select_expr = select(vec![FieldName::from("a")], root());
403 let expected_dtype = DType::Struct(
404 dtype
405 .as_struct_fields_opt()
406 .unwrap()
407 .project(&["a".into()])
408 .unwrap(),
409 Nullability::NonNullable,
410 );
411 assert_eq!(select_expr.return_dtype(&dtype).unwrap(), expected_dtype);
412
413 let select_expr_exclude = select_exclude(
414 vec![
415 FieldName::from("col1"),
416 FieldName::from("col2"),
417 FieldName::from("bool1"),
418 FieldName::from("bool2"),
419 ],
420 root(),
421 );
422 assert_eq!(
423 select_expr_exclude.return_dtype(&dtype).unwrap(),
424 expected_dtype
425 );
426
427 let select_expr_exclude = select_exclude(
428 vec![FieldName::from("col1"), FieldName::from("col2")],
429 root(),
430 );
431 assert_eq!(
432 select_expr_exclude.return_dtype(&dtype).unwrap(),
433 DType::Struct(
434 dtype
435 .as_struct_fields_opt()
436 .unwrap()
437 .project(&["a".into(), "bool1".into(), "bool2".into()])
438 .unwrap(),
439 Nullability::NonNullable
440 )
441 );
442 }
443
444 #[test]
445 fn test_as_include_names() {
446 let field_names = FieldNames::from(["a", "b", "c"]);
447 let include = select(["a"], root());
448 let exclude = select_exclude(["b", "c"], root());
449 assert_eq!(
450 &include
451 .as_::<Select>()
452 .normalize_to_included_fields(&field_names)
453 .unwrap(),
454 &exclude
455 .as_::<Select>()
456 .normalize_to_included_fields(&field_names)
457 .unwrap()
458 );
459 }
460
461 #[test]
462 fn execute_constant_struct_stays_constant() -> VortexResult<()> {
463 let field_count = 128usize;
464 let names = (0..field_count)
465 .map(|idx| FieldName::from(format!("f{idx}")))
466 .collect::<Vec<_>>();
467 let dtypes = vec![I32.into(); field_count];
468 let fields = StructFields::new(FieldNames::from(names), dtypes);
469 let scalar = Scalar::struct_(
470 DType::Struct(fields, Nullability::NonNullable),
471 (0..128).map(Scalar::from),
472 );
473 let array = ConstantArray::new(scalar, 1_000_000).into_array();
474 let mut ctx = array_session().create_execution_ctx();
475
476 let item: ArrayRef = ScalarFnArray::try_new(
477 Select.bind(FieldSelection::Include(FieldNames::from(["f97", "f3"]))),
478 vec![array],
479 )?
480 .into_array()
481 .execute(&mut ctx)?;
482
483 let constant = item.as_::<Constant>();
484 assert_eq!(constant.len(), 1_000_000);
485 assert_eq!(
486 constant.scalar(),
487 &Scalar::struct_(
488 DType::Struct(
489 StructFields::new(
490 FieldNames::from(["f97", "f3"]),
491 vec![I32.into(), I32.into()],
492 ),
493 Nullability::NonNullable,
494 ),
495 [Scalar::from(97), Scalar::from(3)],
496 )
497 );
498 Ok(())
499 }
500
501 #[test]
502 fn test_remove_select_rule() {
503 let dtype = DType::Struct(
504 StructFields::new(["a", "b"].into(), vec![I32.into(), I32.into()]),
505 Nullable,
506 );
507 let e = select(["a", "b"], root());
508
509 let result = e.bind(&dtype).unwrap().optimize_recursive().unwrap();
510
511 assert!(result.dtype().is_nullable());
512 }
513
514 #[test]
515 fn test_remove_select_rule_exclude_fields() {
516 use crate::expr::select_exclude;
517
518 let dtype = DType::Struct(
519 StructFields::new(
520 ["a", "b", "c"].into(),
521 vec![I32.into(), I32.into(), I32.into()],
522 ),
523 Nullable,
524 );
525 let e = select_exclude(["c"], root());
526
527 let result = e.bind(&dtype).unwrap().optimize_recursive().unwrap();
528
529 let result_dtype = result.dtype();
531 assert!(result_dtype.is_nullable());
532 let fields = result_dtype.as_struct_fields_opt().unwrap();
533 assert_eq!(fields.names().as_ref(), &["a", "b"]);
534 }
535}