Skip to main content

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