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;
205/// # use vortex_array::{VortexSessionExecute, array_session};
206/// # use vortex_array::validity::Validity;
207/// # use vortex_buffer::buffer;
208/// # use vortex_array::expr::{eq, root, lit};
209/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
210/// let result = xs.into_array().apply(&eq(root(), lit(3))).unwrap();
211/// let mut ctx = array_session().create_execution_ctx();
212///
213/// assert_eq!(
214///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
215///     BoolArray::from_iter(vec![false, false, true]).to_bit_buffer(),
216/// );
217/// ```
218pub fn eq(lhs: Expression, rhs: Expression) -> Expression {
219    Binary
220        .try_new_expr(Operator::Eq, [lhs, rhs])
221        .vortex_expect("Failed to create Eq binary expression")
222}
223
224/// Create a new [`Binary`] using the [`NotEq`](Operator::NotEq) operator.
225///
226/// ## Example usage
227///
228/// ```
229/// # use vortex_array::arrays::{BoolArray, PrimitiveArray};
230/// # use vortex_array::arrays::bool::BoolArrayExt;
231/// # use vortex_array::IntoArray;
232/// # use vortex_array::{VortexSessionExecute, array_session};
233/// # use vortex_array::validity::Validity;
234/// # use vortex_buffer::buffer;
235/// # use vortex_array::expr::{root, lit, not_eq};
236/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
237/// let result = xs.into_array().apply(&not_eq(root(), lit(3))).unwrap();
238/// let mut ctx = array_session().create_execution_ctx();
239///
240/// assert_eq!(
241///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
242///     BoolArray::from_iter(vec![true, true, false]).to_bit_buffer(),
243/// );
244/// ```
245pub fn not_eq(lhs: Expression, rhs: Expression) -> Expression {
246    Binary
247        .try_new_expr(Operator::NotEq, [lhs, rhs])
248        .vortex_expect("Failed to create NotEq binary expression")
249}
250
251/// Create a new [`Binary`] using the [`Gte`](Operator::Gte) operator.
252///
253/// ## Example usage
254///
255/// ```
256/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
257/// # use vortex_array::arrays::bool::BoolArrayExt;
258/// # use vortex_array::IntoArray;
259/// # use vortex_array::{VortexSessionExecute, array_session};
260/// # use vortex_array::validity::Validity;
261/// # use vortex_buffer::buffer;
262/// # use vortex_array::expr::{gt_eq, root, lit};
263/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
264/// let result = xs.into_array().apply(&gt_eq(root(), lit(3))).unwrap();
265/// let mut ctx = array_session().create_execution_ctx();
266///
267/// assert_eq!(
268///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
269///     BoolArray::from_iter(vec![false, false, true]).to_bit_buffer(),
270/// );
271/// ```
272pub fn gt_eq(lhs: Expression, rhs: Expression) -> Expression {
273    Binary
274        .try_new_expr(Operator::Gte, [lhs, rhs])
275        .vortex_expect("Failed to create Gte binary expression")
276}
277
278/// Create a new [`Binary`] using the [`Gt`](Operator::Gt) operator.
279///
280/// ## Example usage
281///
282/// ```
283/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
284/// # use vortex_array::arrays::bool::BoolArrayExt;
285/// # use vortex_array::IntoArray;
286/// # use vortex_array::{VortexSessionExecute, array_session};
287/// # use vortex_array::validity::Validity;
288/// # use vortex_buffer::buffer;
289/// # use vortex_array::expr::{gt, root, lit};
290/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
291/// let result = xs.into_array().apply(&gt(root(), lit(2))).unwrap();
292/// let mut ctx = array_session().create_execution_ctx();
293///
294/// assert_eq!(
295///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
296///     BoolArray::from_iter(vec![false, false, true]).to_bit_buffer(),
297/// );
298/// ```
299pub fn gt(lhs: Expression, rhs: Expression) -> Expression {
300    Binary
301        .try_new_expr(Operator::Gt, [lhs, rhs])
302        .vortex_expect("Failed to create Gt binary expression")
303}
304
305/// Create a new [`Binary`] using the [`Lte`](Operator::Lte) operator.
306///
307/// ## Example usage
308///
309/// ```
310/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
311/// # use vortex_array::arrays::bool::BoolArrayExt;
312/// # use vortex_array::IntoArray;
313/// # use vortex_array::{VortexSessionExecute, array_session};
314/// # use vortex_array::validity::Validity;
315/// # use vortex_buffer::buffer;
316/// # use vortex_array::expr::{root, lit, lt_eq};
317/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
318/// let result = xs.into_array().apply(&lt_eq(root(), lit(2))).unwrap();
319/// let mut ctx = array_session().create_execution_ctx();
320///
321/// assert_eq!(
322///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
323///     BoolArray::from_iter(vec![true, true, false]).to_bit_buffer(),
324/// );
325/// ```
326pub fn lt_eq(lhs: Expression, rhs: Expression) -> Expression {
327    Binary
328        .try_new_expr(Operator::Lte, [lhs, rhs])
329        .vortex_expect("Failed to create Lte binary expression")
330}
331
332/// Create a new [`Binary`] using the [`Lt`](Operator::Lt) operator.
333///
334/// ## Example usage
335///
336/// ```
337/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
338/// # use vortex_array::arrays::bool::BoolArrayExt;
339/// # use vortex_array::IntoArray;
340/// # use vortex_array::{VortexSessionExecute, array_session};
341/// # use vortex_array::validity::Validity;
342/// # use vortex_buffer::buffer;
343/// # use vortex_array::expr::{root, lit, lt};
344/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
345/// let result = xs.into_array().apply(&lt(root(), lit(3))).unwrap();
346/// let mut ctx = array_session().create_execution_ctx();
347///
348/// assert_eq!(
349///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
350///     BoolArray::from_iter(vec![true, true, false]).to_bit_buffer(),
351/// );
352/// ```
353pub fn lt(lhs: Expression, rhs: Expression) -> Expression {
354    Binary
355        .try_new_expr(Operator::Lt, [lhs, rhs])
356        .vortex_expect("Failed to create Lt binary expression")
357}
358
359/// Create a new [`Binary`] using the [`Or`](Operator::Or) operator.
360///
361/// ## Example usage
362///
363/// ```
364/// # use vortex_array::arrays::BoolArray;
365/// # use vortex_array::arrays::bool::BoolArrayExt;
366/// # use vortex_array::IntoArray;
367/// # use vortex_array::{VortexSessionExecute, array_session};
368/// # use vortex_array::expr::{root, lit, or};
369/// let xs = BoolArray::from_iter(vec![true, false, true]);
370/// let result = xs.into_array().apply(&or(root(), lit(false))).unwrap();
371/// let mut ctx = array_session().create_execution_ctx();
372///
373/// assert_eq!(
374///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
375///     BoolArray::from_iter(vec![true, false, true]).to_bit_buffer(),
376/// );
377/// ```
378pub fn or(lhs: Expression, rhs: Expression) -> Expression {
379    Binary
380        .try_new_expr(Operator::Or, [lhs, rhs])
381        .vortex_expect("Failed to create Or binary expression")
382}
383
384/// Collects a list of `or`ed values into a single expression using a balanced tree.
385///
386/// This creates a balanced binary tree to avoid deep nesting that could cause
387/// stack overflow during drop or evaluation.
388///
389/// [a, b, c, d] => or(or(a, b), or(c, d))
390pub fn or_collect<I>(iter: I) -> Option<Expression>
391where
392    I: IntoIterator<Item = Expression>,
393{
394    iter.into_iter().reduce_balanced(or)
395}
396
397/// Create a new [`Binary`] using the [`And`](Operator::And) operator.
398///
399/// ## Example usage
400///
401/// ```
402/// # use vortex_array::arrays::BoolArray;
403/// # use vortex_array::arrays::bool::BoolArrayExt;
404/// # use vortex_array::IntoArray;
405/// # use vortex_array::{VortexSessionExecute, array_session};
406/// # use vortex_array::expr::{and, root, lit};
407/// let xs = BoolArray::from_iter(vec![true, false, true]).into_array();
408/// let result = xs.apply(&and(root(), lit(true))).unwrap();
409/// let mut ctx = array_session().create_execution_ctx();
410///
411/// assert_eq!(
412///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
413///     BoolArray::from_iter(vec![true, false, true]).to_bit_buffer(),
414/// );
415/// ```
416pub fn and(lhs: Expression, rhs: Expression) -> Expression {
417    Binary
418        .try_new_expr(Operator::And, [lhs, rhs])
419        .vortex_expect("Failed to create And binary expression")
420}
421
422/// Collects a list of `and`ed values into a single expression using a balanced tree.
423///
424/// This creates a balanced binary tree to avoid deep nesting that could cause
425/// stack overflow during drop or evaluation.
426///
427/// [a, b, c, d] => and(and(a, b), and(c, d))
428pub fn and_collect<I>(iter: I) -> Option<Expression>
429where
430    I: IntoIterator<Item = Expression>,
431{
432    iter.into_iter().reduce_balanced(and)
433}
434
435/// Create a new [`Binary`] using the [`Add`](Operator::Add) operator.
436///
437/// ## Example usage
438///
439/// ```
440/// # use vortex_array::IntoArray;
441/// # use vortex_array::arrow::ArrowArrayExecutor;
442/// # use vortex_array::{VortexSessionExecute, array_session};
443/// # use vortex_buffer::buffer;
444/// # use vortex_array::expr::{checked_add, lit, root};
445/// let xs = buffer![1, 2, 3].into_array();
446/// let result = xs.apply(&checked_add(root(), lit(5))).unwrap();
447///
448/// let mut ctx = array_session().create_execution_ctx();
449/// assert_eq!(
450///     &result.execute_arrow(None, &mut ctx).unwrap(),
451///     &buffer![6, 7, 8]
452///         .into_array()
453///         .execute_arrow(None, &mut ctx)
454///         .unwrap()
455/// );
456/// ```
457pub fn checked_add(lhs: Expression, rhs: Expression) -> Expression {
458    Binary
459        .try_new_expr(Operator::Add, [lhs, rhs])
460        .vortex_expect("Failed to create Add binary expression")
461}
462
463// ---- Not ----
464
465/// Creates an expression that logically inverts boolean values.
466///
467/// Returns the logical negation of the input boolean expression.
468///
469/// ```rust
470/// # use vortex_array::expr::{not, root};
471/// let expr = not(root());
472/// ```
473pub fn not(operand: Expression) -> Expression {
474    Not.new_expr(EmptyOptions, vec![operand])
475}
476
477// ---- Between ----
478
479/// Creates an expression that checks if values are between two bounds.
480///
481/// Returns a boolean array indicating which values fall within the specified range.
482/// The comparison strictness is controlled by the options parameter.
483///
484/// ```rust
485/// # use vortex_array::scalar_fn::fns::between::BetweenOptions;
486/// # use vortex_array::scalar_fn::fns::between::StrictComparison;
487/// # use vortex_array::expr::{between, lit, root};
488/// let opts = BetweenOptions {
489///     lower_strict: StrictComparison::NonStrict,
490///     upper_strict: StrictComparison::NonStrict,
491/// };
492/// let expr = between(root(), lit(10), lit(20), opts);
493/// ```
494pub fn between(
495    arr: Expression,
496    lower: Expression,
497    upper: Expression,
498    options: BetweenOptions,
499) -> Expression {
500    Between
501        .try_new_expr(options, [arr, lower, upper])
502        .vortex_expect("Failed to create Between expression")
503}
504
505// ---- Select ----
506
507/// Creates an expression that selects (includes) specific fields from an array.
508///
509/// Projects only the specified fields from the child expression, which must be of DType struct.
510/// ```rust
511/// # use vortex_array::expr::{select, root};
512/// let expr = select(["name", "age"], root());
513/// ```
514pub fn select(field_names: impl Into<FieldNames>, child: Expression) -> Expression {
515    Select
516        .try_new_expr(FieldSelection::Include(field_names.into()), [child])
517        .vortex_expect("Failed to create Select expression")
518}
519
520/// Creates an expression that excludes specific fields from an array.
521///
522/// Projects all fields except the specified ones from the input struct expression.
523///
524/// ```rust
525/// # use vortex_array::expr::{select_exclude, root};
526/// let expr = select_exclude(["internal_id", "metadata"], root());
527/// ```
528pub fn select_exclude(fields: impl Into<FieldNames>, child: Expression) -> Expression {
529    Select
530        .try_new_expr(FieldSelection::Exclude(fields.into()), [child])
531        .vortex_expect("Failed to create Select expression")
532}
533
534// ---- Pack ----
535
536/// Creates an expression that packs values into a struct with named fields.
537///
538/// ```rust
539/// # use vortex_array::dtype::Nullability;
540/// # use vortex_array::expr::{pack, col, lit};
541/// let expr = pack([("id", col("user_id")), ("constant", lit(42))], Nullability::NonNullable);
542/// ```
543pub fn pack(
544    elements: impl IntoIterator<Item = (impl Into<FieldName>, Expression)>,
545    nullability: Nullability,
546) -> Expression {
547    let (names, values): (Vec<_>, Vec<_>) = elements
548        .into_iter()
549        .map(|(name, value)| (name.into(), value))
550        .unzip();
551    Pack.new_expr(
552        PackOptions {
553            names: names.into(),
554            nullability,
555        },
556        values,
557    )
558}
559
560// ---- Cast ----
561
562/// Creates an expression that casts values to a target data type.
563///
564/// Converts the input expression's values to the specified target type.
565///
566/// ```rust
567/// # use vortex_array::dtype::{DType, Nullability, PType};
568/// # use vortex_array::expr::{cast, root};
569/// let expr = cast(root(), DType::Primitive(PType::I64, Nullability::NonNullable));
570/// ```
571pub fn cast(child: Expression, target: DType) -> Expression {
572    Cast.try_new_expr(target, [child])
573        .vortex_expect("Failed to create Cast expression")
574}
575
576// ---- FillNull ----
577
578/// Creates an expression that replaces null values with a fill value.
579///
580/// ```rust
581/// # use vortex_array::expr::{fill_null, root, lit};
582/// let expr = fill_null(root(), lit(0i32));
583/// ```
584pub fn fill_null(child: Expression, fill_value: Expression) -> Expression {
585    FillNull.new_expr(EmptyOptions, [child, fill_value])
586}
587
588// ---- IsNull ----
589
590/// Creates an expression that checks for null values.
591///
592/// Returns a boolean array indicating which positions contain null values.
593///
594/// ```rust
595/// # use vortex_array::expr::{is_null, root};
596/// let expr = is_null(root());
597/// ```
598pub fn is_null(child: Expression) -> Expression {
599    IsNull.new_expr(EmptyOptions, vec![child])
600}
601
602// ---- IsNotNull ----
603
604/// Creates an expression that checks for non-null values.
605///
606/// Returns a boolean array indicating which positions contain non-null values.
607///
608/// ```rust
609/// # use vortex_array::expr::{is_not_null, root};
610/// let expr = is_not_null(root());
611/// ```
612pub fn is_not_null(child: Expression) -> Expression {
613    IsNotNull.new_expr(EmptyOptions, vec![child])
614}
615
616// ---- Like ----
617
618/// Creates a SQL LIKE expression.
619pub fn like(child: Expression, pattern: Expression) -> Expression {
620    Like.new_expr(
621        LikeOptions {
622            negated: false,
623            case_insensitive: false,
624        },
625        [child, pattern],
626    )
627}
628
629/// Creates a case-insensitive SQL ILIKE expression.
630pub fn ilike(child: Expression, pattern: Expression) -> Expression {
631    Like.new_expr(
632        LikeOptions {
633            negated: false,
634            case_insensitive: true,
635        },
636        [child, pattern],
637    )
638}
639
640/// Creates a negated SQL NOT LIKE expression.
641pub fn not_like(child: Expression, pattern: Expression) -> Expression {
642    Like.new_expr(
643        LikeOptions {
644            negated: true,
645            case_insensitive: false,
646        },
647        [child, pattern],
648    )
649}
650
651/// Creates a negated case-insensitive SQL NOT ILIKE expression.
652pub fn not_ilike(child: Expression, pattern: Expression) -> Expression {
653    Like.new_expr(
654        LikeOptions {
655            negated: true,
656            case_insensitive: true,
657        },
658        [child, pattern],
659    )
660}
661
662// ---- Mask ----
663
664/// Creates a mask expression that applies the given boolean mask to the input array.
665pub fn mask(array: Expression, mask: Expression) -> Expression {
666    Mask.new_expr(EmptyOptions, [array, mask])
667}
668
669// ---- Merge ----
670
671/// Creates an expression that merges struct expressions into a single struct.
672///
673/// Combines fields from all input expressions. If field names are duplicated,
674/// later expressions win. Fields are not recursively merged.
675///
676/// ```rust
677/// # use vortex_array::dtype::Nullability;
678/// # use vortex_array::expr::{merge, get_item, root};
679/// let expr = merge([get_item("a", root()), get_item("b", root())]);
680/// ```
681pub fn merge(elements: impl IntoIterator<Item = impl Into<Expression>>) -> Expression {
682    use itertools::Itertools as _;
683    let values = elements.into_iter().map(|value| value.into()).collect_vec();
684    Merge.new_expr(DuplicateHandling::default(), values)
685}
686
687/// Creates a merge expression with explicit duplicate handling.
688pub fn merge_opts(
689    elements: impl IntoIterator<Item = impl Into<Expression>>,
690    duplicate_handling: DuplicateHandling,
691) -> Expression {
692    use itertools::Itertools as _;
693    let values = elements.into_iter().map(|value| value.into()).collect_vec();
694    Merge.new_expr(duplicate_handling, values)
695}
696
697// ---- Zip ----
698
699/// Creates a zip expression that conditionally selects between two arrays.
700///
701/// ```rust
702/// # use vortex_array::expr::{zip_expr, root, lit};
703/// let expr = zip_expr(lit(true), root(), lit(0i32));
704/// ```
705pub fn zip_expr(mask: Expression, if_true: Expression, if_false: Expression) -> Expression {
706    Zip.new_expr(EmptyOptions, [if_true, if_false, mask])
707}
708
709// ---- Dynamic ----
710
711/// Creates a dynamic comparison expression.
712pub fn dynamic(
713    operator: CompareOperator,
714    rhs_value: impl Fn() -> Option<ScalarValue> + Send + Sync + 'static,
715    rhs_dtype: DType,
716    default: bool,
717    lhs: Expression,
718) -> Expression {
719    DynamicComparison.new_expr(
720        DynamicComparisonExpr {
721            operator,
722            rhs: Arc::new(Rhs {
723                value: Arc::new(rhs_value),
724                dtype: rhs_dtype,
725            }),
726            default,
727        },
728        [lhs],
729    )
730}
731
732// ---- ListContains ----
733
734/// Creates an expression that checks if a value is contained in a list.
735///
736/// Returns a boolean array indicating whether the value appears in each list.
737///
738/// ```rust
739/// # use vortex_array::expr::{list_contains, lit, root};
740/// let expr = list_contains(root(), lit(42));
741/// ```
742pub fn list_contains(list: Expression, value: Expression) -> Expression {
743    ListContains.new_expr(EmptyOptions, [list, value])
744}
745
746// ---- ByteLength ----
747
748/// Creates an expression that computes the byte length of each element.
749/// This is akin to ANSI SQL OCTET_LENGTH(), or DuckDB's strlen().
750///
751/// ```rust
752/// # use vortex_array::expr::{byte_length, root};
753/// let expr = byte_length(root());
754/// ```
755pub fn byte_length(input: Expression) -> Expression {
756    ByteLength.new_expr(EmptyOptions, [input])
757}
758
759// ---- ExtStorage ----
760
761/// Creates an expression that extracts the storage values from an extension array.
762///
763/// ```rust
764/// # use vortex_array::expr::{ext_storage, root};
765/// let expr = ext_storage(root());
766/// ```
767pub fn ext_storage(input: Expression) -> Expression {
768    ExtStorage.new_expr(EmptyOptions, [input])
769}
770
771// ---- ListLength ----
772
773/// Creates an expression that computes the number of elements in each list
774/// for `List` and `FixedSizeList` inputs. This is akin to ANSI SQL `CARDINALITY()`,
775/// or DuckDB's `len()`/`array_length()`.
776///
777/// ```rust
778/// # use vortex_array::expr::{list_length, root};
779/// let expr = list_length(root());
780/// ```
781pub fn list_length(input: Expression) -> Expression {
782    ListLength.new_expr(EmptyOptions, [input])
783}