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