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