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