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