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