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