vortex_array/expr/exprs.rs
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Factory functions for creating [`Expression`]s from scalar function vtables.
5
6use std::sync::Arc;
7use std::sync::LazyLock;
8
9use vortex_error::VortexExpect;
10use vortex_error::vortex_panic;
11use vortex_utils::iter::ReduceBalancedIterExt;
12
13use crate::dtype::DType;
14use crate::dtype::FieldName;
15use crate::dtype::FieldNames;
16use crate::dtype::Nullability;
17use crate::expr::Expression;
18use crate::scalar::Scalar;
19use crate::scalar::ScalarValue;
20use crate::scalar_fn::EmptyOptions;
21use crate::scalar_fn::ScalarFnVTableExt;
22use crate::scalar_fn::fns::between::Between;
23use crate::scalar_fn::fns::between::BetweenOptions;
24use crate::scalar_fn::fns::binary::Binary;
25use crate::scalar_fn::fns::byte_length::ByteLength;
26use crate::scalar_fn::fns::case_when::CaseWhen;
27use crate::scalar_fn::fns::case_when::CaseWhenOptions;
28use crate::scalar_fn::fns::cast::Cast;
29use crate::scalar_fn::fns::dynamic::DynamicComparison;
30use crate::scalar_fn::fns::dynamic::DynamicComparisonExpr;
31use crate::scalar_fn::fns::dynamic::Rhs;
32use crate::scalar_fn::fns::ext_storage::ExtStorage;
33use crate::scalar_fn::fns::fill_null::FillNull;
34use crate::scalar_fn::fns::get_item::GetItem;
35use crate::scalar_fn::fns::is_not_null::IsNotNull;
36use crate::scalar_fn::fns::is_null::IsNull;
37use crate::scalar_fn::fns::like::Like;
38use crate::scalar_fn::fns::like::LikeOptions;
39use crate::scalar_fn::fns::list_contains::ListContains;
40use crate::scalar_fn::fns::list_length::ListLength;
41use crate::scalar_fn::fns::literal::Literal;
42use crate::scalar_fn::fns::mask::Mask;
43use crate::scalar_fn::fns::merge::DuplicateHandling;
44use crate::scalar_fn::fns::merge::Merge;
45use crate::scalar_fn::fns::not::Not;
46use crate::scalar_fn::fns::operators::CompareOperator;
47use crate::scalar_fn::fns::operators::Operator;
48use crate::scalar_fn::fns::pack::Pack;
49use crate::scalar_fn::fns::pack::PackOptions;
50use crate::scalar_fn::fns::root::Root;
51use crate::scalar_fn::fns::select::FieldSelection;
52use crate::scalar_fn::fns::select::Select;
53use crate::scalar_fn::fns::variant_get::VariantGet;
54use crate::scalar_fn::fns::variant_get::VariantGetOptions;
55use crate::scalar_fn::fns::variant_get::VariantPath;
56use crate::scalar_fn::fns::zip::Zip;
57
58static ROOT: LazyLock<Expression> = LazyLock::new(|| {
59 Root.try_new_expr(EmptyOptions, vec![])
60 .vortex_expect("Creating root() shouldn't fail")
61});
62
63/// Creates an expression that references the root scope.
64///
65/// Returns the entire input array as passed to the expression evaluator.
66/// This is commonly used as the starting point for field access and other operations.
67pub fn root() -> Expression {
68 ROOT.clone()
69}
70
71/// Return whether the expression is a root expression.
72pub fn is_root(expr: &Expression) -> bool {
73 // root doesn't have any children, and scalar_fns have distinct ids
74 // so we should almost always hit this eq check
75 (expr.scalar_fn().id() == ROOT.scalar_fn().id()) || expr.is::<Root>()
76}
77
78// ---- Literal ----
79
80/// Create a new `Literal` expression from a type that coerces to `Scalar`.
81///
82///
83/// ## Example usage
84///
85/// ```
86/// use vortex_array::arrays::PrimitiveArray;
87/// use vortex_array::dtype::Nullability;
88/// use vortex_array::expr::lit;
89/// use vortex_array::scalar_fn::fns::literal::Literal;
90/// use vortex_array::scalar::Scalar;
91///
92/// let number = lit(34i32);
93///
94/// let scalar = number.as_::<Literal>();
95/// assert_eq!(scalar, &Scalar::primitive(34i32, Nullability::NonNullable));
96/// ```
97pub fn lit(value: impl Into<Scalar>) -> Expression {
98 Literal.new_expr(value.into(), [])
99}
100
101// ---- GetItem / Col ----
102
103/// Creates an expression that accesses a field from the root array.
104///
105/// Equivalent to `get_item(field, root())` - extracts a named field from the input array.
106///
107/// ```rust
108/// # use vortex_array::expr::col;
109/// let expr = col("name");
110/// ```
111pub fn col(field: impl Into<FieldName>) -> Expression {
112 GetItem.new_expr(field.into(), vec![root()])
113}
114
115/// Creates an expression that extracts a named field from a struct expression.
116///
117/// Accesses the specified field from the result of the child expression.
118///
119/// ```rust
120/// # use vortex_array::expr::{get_item, root};
121/// let expr = get_item("user_id", root());
122/// ```
123pub fn get_item(field: impl Into<FieldName>, child: Expression) -> Expression {
124 GetItem.new_expr(field.into(), vec![child])
125}
126
127// ---- VariantGet ----
128
129/// Creates an expression that extracts a path from a Variant expression.
130///
131/// Missing paths, traversal mismatches, and failed casts return null. When `dtype` is `None`,
132/// results are nullable Variant values; otherwise results are nullable values of `dtype`.
133pub fn variant_get(
134 child: Expression,
135 path: impl Into<VariantPath>,
136 dtype: Option<DType>,
137) -> Expression {
138 VariantGet.new_expr(VariantGetOptions::new(path.into(), dtype), vec![child])
139}
140
141// ---- CaseWhen ----
142
143/// Creates a CASE WHEN expression with one WHEN/THEN pair and an ELSE value.
144pub fn case_when(
145 condition: Expression,
146 then_value: Expression,
147 else_value: Expression,
148) -> Expression {
149 let options = CaseWhenOptions {
150 num_when_then_pairs: 1,
151 has_else: true,
152 };
153 CaseWhen.new_expr(options, [condition, then_value, else_value])
154}
155
156/// Creates a CASE WHEN expression with one WHEN/THEN pair and no ELSE value.
157pub fn case_when_no_else(condition: Expression, then_value: Expression) -> Expression {
158 let options = CaseWhenOptions {
159 num_when_then_pairs: 1,
160 has_else: false,
161 };
162 CaseWhen.new_expr(options, [condition, then_value])
163}
164
165/// Creates an n-ary CASE WHEN expression from WHEN/THEN pairs and an optional ELSE value.
166pub fn nested_case_when(
167 when_then_pairs: Vec<(Expression, Expression)>,
168 else_value: Option<Expression>,
169) -> Expression {
170 assert!(
171 !when_then_pairs.is_empty(),
172 "nested_case_when requires at least one when/then pair"
173 );
174
175 let has_else = else_value.is_some();
176 let mut children = Vec::with_capacity(when_then_pairs.len() * 2 + usize::from(has_else));
177 for (condition, then_value) in &when_then_pairs {
178 children.push(condition.clone());
179 children.push(then_value.clone());
180 }
181 if let Some(else_expr) = else_value {
182 children.push(else_expr);
183 }
184
185 let Ok(num_when_then_pairs) = u32::try_from(when_then_pairs.len()) else {
186 vortex_panic!("nested_case_when has too many when/then pairs");
187 };
188 let options = CaseWhenOptions {
189 num_when_then_pairs,
190 has_else,
191 };
192 CaseWhen.new_expr(options, children)
193}
194
195// ---- Binary operators ----
196
197/// Create a new [`Binary`] using the [`Eq`](Operator::Eq) operator.
198///
199/// ## Example usage
200///
201/// ```
202/// # use vortex_array::arrays::{BoolArray, PrimitiveArray};
203/// # use vortex_array::arrays::bool::BoolArrayExt;
204/// # use vortex_array::{IntoArray, ToCanonical};
205/// # use vortex_array::validity::Validity;
206/// # use vortex_buffer::buffer;
207/// # use vortex_array::expr::{eq, root, lit};
208/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
209/// let result = xs.into_array().apply(&eq(root(), lit(3))).unwrap();
210///
211/// assert_eq!(
212/// result.to_bool().to_bit_buffer(),
213/// BoolArray::from_iter(vec![false, false, true]).to_bit_buffer(),
214/// );
215/// ```
216pub fn eq(lhs: Expression, rhs: Expression) -> Expression {
217 Binary
218 .try_new_expr(Operator::Eq, [lhs, rhs])
219 .vortex_expect("Failed to create Eq binary expression")
220}
221
222/// Create a new [`Binary`] using the [`NotEq`](Operator::NotEq) operator.
223///
224/// ## Example usage
225///
226/// ```
227/// # use vortex_array::arrays::{BoolArray, PrimitiveArray};
228/// # use vortex_array::arrays::bool::BoolArrayExt;
229/// # use vortex_array::{ IntoArray, ToCanonical};
230/// # use vortex_array::validity::Validity;
231/// # use vortex_buffer::buffer;
232/// # use vortex_array::expr::{root, lit, not_eq};
233/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
234/// let result = xs.into_array().apply(¬_eq(root(), lit(3))).unwrap();
235///
236/// assert_eq!(
237/// result.to_bool().to_bit_buffer(),
238/// BoolArray::from_iter(vec![true, true, false]).to_bit_buffer(),
239/// );
240/// ```
241pub fn not_eq(lhs: Expression, rhs: Expression) -> Expression {
242 Binary
243 .try_new_expr(Operator::NotEq, [lhs, rhs])
244 .vortex_expect("Failed to create NotEq binary expression")
245}
246
247/// Create a new [`Binary`] using the [`Gte`](Operator::Gte) operator.
248///
249/// ## Example usage
250///
251/// ```
252/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
253/// # use vortex_array::arrays::bool::BoolArrayExt;
254/// # use vortex_array::{IntoArray, ToCanonical};
255/// # use vortex_array::validity::Validity;
256/// # use vortex_buffer::buffer;
257/// # use vortex_array::expr::{gt_eq, root, lit};
258/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
259/// let result = xs.into_array().apply(>_eq(root(), lit(3))).unwrap();
260///
261/// assert_eq!(
262/// result.to_bool().to_bit_buffer(),
263/// BoolArray::from_iter(vec![false, false, true]).to_bit_buffer(),
264/// );
265/// ```
266pub fn gt_eq(lhs: Expression, rhs: Expression) -> Expression {
267 Binary
268 .try_new_expr(Operator::Gte, [lhs, rhs])
269 .vortex_expect("Failed to create Gte binary expression")
270}
271
272/// Create a new [`Binary`] using the [`Gt`](Operator::Gt) operator.
273///
274/// ## Example usage
275///
276/// ```
277/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
278/// # use vortex_array::arrays::bool::BoolArrayExt;
279/// # use vortex_array::{IntoArray, ToCanonical};
280/// # use vortex_array::validity::Validity;
281/// # use vortex_buffer::buffer;
282/// # use vortex_array::expr::{gt, root, lit};
283/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
284/// let result = xs.into_array().apply(>(root(), lit(2))).unwrap();
285///
286/// assert_eq!(
287/// result.to_bool().to_bit_buffer(),
288/// BoolArray::from_iter(vec![false, false, true]).to_bit_buffer(),
289/// );
290/// ```
291pub fn gt(lhs: Expression, rhs: Expression) -> Expression {
292 Binary
293 .try_new_expr(Operator::Gt, [lhs, rhs])
294 .vortex_expect("Failed to create Gt binary expression")
295}
296
297/// Create a new [`Binary`] using the [`Lte`](Operator::Lte) operator.
298///
299/// ## Example usage
300///
301/// ```
302/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
303/// # use vortex_array::arrays::bool::BoolArrayExt;
304/// # use vortex_array::{IntoArray, ToCanonical};
305/// # use vortex_array::validity::Validity;
306/// # use vortex_buffer::buffer;
307/// # use vortex_array::expr::{root, lit, lt_eq};
308/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
309/// let result = xs.into_array().apply(<_eq(root(), lit(2))).unwrap();
310///
311/// assert_eq!(
312/// result.to_bool().to_bit_buffer(),
313/// BoolArray::from_iter(vec![true, true, false]).to_bit_buffer(),
314/// );
315/// ```
316pub fn lt_eq(lhs: Expression, rhs: Expression) -> Expression {
317 Binary
318 .try_new_expr(Operator::Lte, [lhs, rhs])
319 .vortex_expect("Failed to create Lte binary expression")
320}
321
322/// Create a new [`Binary`] using the [`Lt`](Operator::Lt) operator.
323///
324/// ## Example usage
325///
326/// ```
327/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
328/// # use vortex_array::arrays::bool::BoolArrayExt;
329/// # use vortex_array::{IntoArray, ToCanonical};
330/// # use vortex_array::validity::Validity;
331/// # use vortex_buffer::buffer;
332/// # use vortex_array::expr::{root, lit, lt};
333/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
334/// let result = xs.into_array().apply(<(root(), lit(3))).unwrap();
335///
336/// assert_eq!(
337/// result.to_bool().to_bit_buffer(),
338/// BoolArray::from_iter(vec![true, true, false]).to_bit_buffer(),
339/// );
340/// ```
341pub fn lt(lhs: Expression, rhs: Expression) -> Expression {
342 Binary
343 .try_new_expr(Operator::Lt, [lhs, rhs])
344 .vortex_expect("Failed to create Lt binary expression")
345}
346
347/// Create a new [`Binary`] using the [`Or`](Operator::Or) operator.
348///
349/// ## Example usage
350///
351/// ```
352/// # use vortex_array::arrays::BoolArray;
353/// # use vortex_array::arrays::bool::BoolArrayExt;
354/// # use vortex_array::{IntoArray, ToCanonical};
355/// # use vortex_array::expr::{root, lit, or};
356/// let xs = BoolArray::from_iter(vec![true, false, true]);
357/// let result = xs.into_array().apply(&or(root(), lit(false))).unwrap();
358///
359/// assert_eq!(
360/// result.to_bool().to_bit_buffer(),
361/// BoolArray::from_iter(vec![true, false, true]).to_bit_buffer(),
362/// );
363/// ```
364pub fn or(lhs: Expression, rhs: Expression) -> Expression {
365 Binary
366 .try_new_expr(Operator::Or, [lhs, rhs])
367 .vortex_expect("Failed to create Or binary expression")
368}
369
370/// Collects a list of `or`ed values into a single expression using a balanced tree.
371///
372/// This creates a balanced binary tree to avoid deep nesting that could cause
373/// stack overflow during drop or evaluation.
374///
375/// [a, b, c, d] => or(or(a, b), or(c, d))
376pub fn or_collect<I>(iter: I) -> Option<Expression>
377where
378 I: IntoIterator<Item = Expression>,
379{
380 iter.into_iter().reduce_balanced(or)
381}
382
383/// Create a new [`Binary`] using the [`And`](Operator::And) operator.
384///
385/// ## Example usage
386///
387/// ```
388/// # use vortex_array::arrays::BoolArray;
389/// # use vortex_array::arrays::bool::BoolArrayExt;
390/// # use vortex_array::{IntoArray, ToCanonical};
391/// # use vortex_array::expr::{and, root, lit};
392/// let xs = BoolArray::from_iter(vec![true, false, true]).into_array();
393/// let result = xs.apply(&and(root(), lit(true))).unwrap();
394///
395/// assert_eq!(
396/// result.to_bool().to_bit_buffer(),
397/// BoolArray::from_iter(vec![true, false, true]).to_bit_buffer(),
398/// );
399/// ```
400pub fn and(lhs: Expression, rhs: Expression) -> Expression {
401 Binary
402 .try_new_expr(Operator::And, [lhs, rhs])
403 .vortex_expect("Failed to create And binary expression")
404}
405
406/// Collects a list of `and`ed values into a single expression using a balanced tree.
407///
408/// This creates a balanced binary tree to avoid deep nesting that could cause
409/// stack overflow during drop or evaluation.
410///
411/// [a, b, c, d] => and(and(a, b), and(c, d))
412pub fn and_collect<I>(iter: I) -> Option<Expression>
413where
414 I: IntoIterator<Item = Expression>,
415{
416 iter.into_iter().reduce_balanced(and)
417}
418
419/// Create a new [`Binary`] using the [`Add`](Operator::Add) operator.
420///
421/// ## Example usage
422///
423/// ```
424/// # use vortex_array::IntoArray;
425/// # use vortex_array::arrow::ArrowArrayExecutor;
426/// # use vortex_array::{VortexSessionExecute, array_session};
427/// # use vortex_buffer::buffer;
428/// # use vortex_array::expr::{checked_add, lit, root};
429/// let xs = buffer![1, 2, 3].into_array();
430/// let result = xs.apply(&checked_add(root(), lit(5))).unwrap();
431///
432/// let mut ctx = array_session().create_execution_ctx();
433/// assert_eq!(
434/// &result.execute_arrow(None, &mut ctx).unwrap(),
435/// &buffer![6, 7, 8]
436/// .into_array()
437/// .execute_arrow(None, &mut ctx)
438/// .unwrap()
439/// );
440/// ```
441pub fn checked_add(lhs: Expression, rhs: Expression) -> Expression {
442 Binary
443 .try_new_expr(Operator::Add, [lhs, rhs])
444 .vortex_expect("Failed to create Add binary expression")
445}
446
447// ---- Not ----
448
449/// Creates an expression that logically inverts boolean values.
450///
451/// Returns the logical negation of the input boolean expression.
452///
453/// ```rust
454/// # use vortex_array::expr::{not, root};
455/// let expr = not(root());
456/// ```
457pub fn not(operand: Expression) -> Expression {
458 Not.new_expr(EmptyOptions, vec![operand])
459}
460
461// ---- Between ----
462
463/// Creates an expression that checks if values are between two bounds.
464///
465/// Returns a boolean array indicating which values fall within the specified range.
466/// The comparison strictness is controlled by the options parameter.
467///
468/// ```rust
469/// # use vortex_array::scalar_fn::fns::between::BetweenOptions;
470/// # use vortex_array::scalar_fn::fns::between::StrictComparison;
471/// # use vortex_array::expr::{between, lit, root};
472/// let opts = BetweenOptions {
473/// lower_strict: StrictComparison::NonStrict,
474/// upper_strict: StrictComparison::NonStrict,
475/// };
476/// let expr = between(root(), lit(10), lit(20), opts);
477/// ```
478pub fn between(
479 arr: Expression,
480 lower: Expression,
481 upper: Expression,
482 options: BetweenOptions,
483) -> Expression {
484 Between
485 .try_new_expr(options, [arr, lower, upper])
486 .vortex_expect("Failed to create Between expression")
487}
488
489// ---- Select ----
490
491/// Creates an expression that selects (includes) specific fields from an array.
492///
493/// Projects only the specified fields from the child expression, which must be of DType struct.
494/// ```rust
495/// # use vortex_array::expr::{select, root};
496/// let expr = select(["name", "age"], root());
497/// ```
498pub fn select(field_names: impl Into<FieldNames>, child: Expression) -> Expression {
499 Select
500 .try_new_expr(FieldSelection::Include(field_names.into()), [child])
501 .vortex_expect("Failed to create Select expression")
502}
503
504/// Creates an expression that excludes specific fields from an array.
505///
506/// Projects all fields except the specified ones from the input struct expression.
507///
508/// ```rust
509/// # use vortex_array::expr::{select_exclude, root};
510/// let expr = select_exclude(["internal_id", "metadata"], root());
511/// ```
512pub fn select_exclude(fields: impl Into<FieldNames>, child: Expression) -> Expression {
513 Select
514 .try_new_expr(FieldSelection::Exclude(fields.into()), [child])
515 .vortex_expect("Failed to create Select expression")
516}
517
518// ---- Pack ----
519
520/// Creates an expression that packs values into a struct with named fields.
521///
522/// ```rust
523/// # use vortex_array::dtype::Nullability;
524/// # use vortex_array::expr::{pack, col, lit};
525/// let expr = pack([("id", col("user_id")), ("constant", lit(42))], Nullability::NonNullable);
526/// ```
527pub fn pack(
528 elements: impl IntoIterator<Item = (impl Into<FieldName>, Expression)>,
529 nullability: Nullability,
530) -> Expression {
531 let (names, values): (Vec<_>, Vec<_>) = elements
532 .into_iter()
533 .map(|(name, value)| (name.into(), value))
534 .unzip();
535 Pack.new_expr(
536 PackOptions {
537 names: names.into(),
538 nullability,
539 },
540 values,
541 )
542}
543
544// ---- Cast ----
545
546/// Creates an expression that casts values to a target data type.
547///
548/// Converts the input expression's values to the specified target type.
549///
550/// ```rust
551/// # use vortex_array::dtype::{DType, Nullability, PType};
552/// # use vortex_array::expr::{cast, root};
553/// let expr = cast(root(), DType::Primitive(PType::I64, Nullability::NonNullable));
554/// ```
555pub fn cast(child: Expression, target: DType) -> Expression {
556 Cast.try_new_expr(target, [child])
557 .vortex_expect("Failed to create Cast expression")
558}
559
560// ---- FillNull ----
561
562/// Creates an expression that replaces null values with a fill value.
563///
564/// ```rust
565/// # use vortex_array::expr::{fill_null, root, lit};
566/// let expr = fill_null(root(), lit(0i32));
567/// ```
568pub fn fill_null(child: Expression, fill_value: Expression) -> Expression {
569 FillNull.new_expr(EmptyOptions, [child, fill_value])
570}
571
572// ---- IsNull ----
573
574/// Creates an expression that checks for null values.
575///
576/// Returns a boolean array indicating which positions contain null values.
577///
578/// ```rust
579/// # use vortex_array::expr::{is_null, root};
580/// let expr = is_null(root());
581/// ```
582pub fn is_null(child: Expression) -> Expression {
583 IsNull.new_expr(EmptyOptions, vec![child])
584}
585
586// ---- IsNotNull ----
587
588/// Creates an expression that checks for non-null values.
589///
590/// Returns a boolean array indicating which positions contain non-null values.
591///
592/// ```rust
593/// # use vortex_array::expr::{is_not_null, root};
594/// let expr = is_not_null(root());
595/// ```
596pub fn is_not_null(child: Expression) -> Expression {
597 IsNotNull.new_expr(EmptyOptions, vec![child])
598}
599
600// ---- Like ----
601
602/// Creates a SQL LIKE expression.
603pub fn like(child: Expression, pattern: Expression) -> Expression {
604 Like.new_expr(
605 LikeOptions {
606 negated: false,
607 case_insensitive: false,
608 },
609 [child, pattern],
610 )
611}
612
613/// Creates a case-insensitive SQL ILIKE expression.
614pub fn ilike(child: Expression, pattern: Expression) -> Expression {
615 Like.new_expr(
616 LikeOptions {
617 negated: false,
618 case_insensitive: true,
619 },
620 [child, pattern],
621 )
622}
623
624/// Creates a negated SQL NOT LIKE expression.
625pub fn not_like(child: Expression, pattern: Expression) -> Expression {
626 Like.new_expr(
627 LikeOptions {
628 negated: true,
629 case_insensitive: false,
630 },
631 [child, pattern],
632 )
633}
634
635/// Creates a negated case-insensitive SQL NOT ILIKE expression.
636pub fn not_ilike(child: Expression, pattern: Expression) -> Expression {
637 Like.new_expr(
638 LikeOptions {
639 negated: true,
640 case_insensitive: true,
641 },
642 [child, pattern],
643 )
644}
645
646// ---- Mask ----
647
648/// Creates a mask expression that applies the given boolean mask to the input array.
649pub fn mask(array: Expression, mask: Expression) -> Expression {
650 Mask.new_expr(EmptyOptions, [array, mask])
651}
652
653// ---- Merge ----
654
655/// Creates an expression that merges struct expressions into a single struct.
656///
657/// Combines fields from all input expressions. If field names are duplicated,
658/// later expressions win. Fields are not recursively merged.
659///
660/// ```rust
661/// # use vortex_array::dtype::Nullability;
662/// # use vortex_array::expr::{merge, get_item, root};
663/// let expr = merge([get_item("a", root()), get_item("b", root())]);
664/// ```
665pub fn merge(elements: impl IntoIterator<Item = impl Into<Expression>>) -> Expression {
666 use itertools::Itertools as _;
667 let values = elements.into_iter().map(|value| value.into()).collect_vec();
668 Merge.new_expr(DuplicateHandling::default(), values)
669}
670
671/// Creates a merge expression with explicit duplicate handling.
672pub fn merge_opts(
673 elements: impl IntoIterator<Item = impl Into<Expression>>,
674 duplicate_handling: DuplicateHandling,
675) -> Expression {
676 use itertools::Itertools as _;
677 let values = elements.into_iter().map(|value| value.into()).collect_vec();
678 Merge.new_expr(duplicate_handling, values)
679}
680
681// ---- Zip ----
682
683/// Creates a zip expression that conditionally selects between two arrays.
684///
685/// ```rust
686/// # use vortex_array::expr::{zip_expr, root, lit};
687/// let expr = zip_expr(lit(true), root(), lit(0i32));
688/// ```
689pub fn zip_expr(mask: Expression, if_true: Expression, if_false: Expression) -> Expression {
690 Zip.new_expr(EmptyOptions, [if_true, if_false, mask])
691}
692
693// ---- Dynamic ----
694
695/// Creates a dynamic comparison expression.
696pub fn dynamic(
697 operator: CompareOperator,
698 rhs_value: impl Fn() -> Option<ScalarValue> + Send + Sync + 'static,
699 rhs_dtype: DType,
700 default: bool,
701 lhs: Expression,
702) -> Expression {
703 DynamicComparison.new_expr(
704 DynamicComparisonExpr {
705 operator,
706 rhs: Arc::new(Rhs {
707 value: Arc::new(rhs_value),
708 dtype: rhs_dtype,
709 }),
710 default,
711 },
712 [lhs],
713 )
714}
715
716// ---- ListContains ----
717
718/// Creates an expression that checks if a value is contained in a list.
719///
720/// Returns a boolean array indicating whether the value appears in each list.
721///
722/// ```rust
723/// # use vortex_array::expr::{list_contains, lit, root};
724/// let expr = list_contains(root(), lit(42));
725/// ```
726pub fn list_contains(list: Expression, value: Expression) -> Expression {
727 ListContains.new_expr(EmptyOptions, [list, value])
728}
729
730// ---- ByteLength ----
731
732/// Creates an expression that computes the byte length of each element.
733/// This is akin to ANSI SQL OCTET_LENGTH(), or DuckDB's strlen().
734///
735/// ```rust
736/// # use vortex_array::expr::{byte_length, root};
737/// let expr = byte_length(root());
738/// ```
739pub fn byte_length(input: Expression) -> Expression {
740 ByteLength.new_expr(EmptyOptions, [input])
741}
742
743// ---- ExtStorage ----
744
745/// Creates an expression that extracts the storage values from an extension array.
746///
747/// ```rust
748/// # use vortex_array::expr::{ext_storage, root};
749/// let expr = ext_storage(root());
750/// ```
751pub fn ext_storage(input: Expression) -> Expression {
752 ExtStorage.new_expr(EmptyOptions, [input])
753}
754
755// ---- ListLength ----
756
757/// Creates an expression that computes the number of elements in each list
758/// for `List` and `FixedSizeList` inputs. This is akin to ANSI SQL `CARDINALITY()`,
759/// or DuckDB's `len()`/`array_length()`.
760///
761/// ```rust
762/// # use vortex_array::expr::{list_length, root};
763/// let expr = list_length(root());
764/// ```
765pub fn list_length(input: Expression) -> Expression {
766 ListLength.new_expr(EmptyOptions, [input])
767}