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::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(&not_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(&gt_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(&gt(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(&lt_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(&lt(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}