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