Skip to main content

vortex_array/expr/
exprs.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Factory functions for creating [`Expression`]s from scalar function vtables.
5
6use std::sync::Arc;
7
8use vortex_error::VortexExpect;
9use vortex_error::VortexResult;
10use vortex_error::vortex_ensure;
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::BoundExpression;
20use crate::expr::Expression;
21use crate::scalar::Scalar;
22use crate::scalar::ScalarValue;
23use crate::scalar_fn::EmptyOptions;
24use crate::scalar_fn::ScalarFnVTableExt;
25use crate::scalar_fn::fns::between::Between;
26use crate::scalar_fn::fns::between::BetweenOptions;
27use crate::scalar_fn::fns::between::StrictComparison;
28use crate::scalar_fn::fns::binary::Binary;
29use crate::scalar_fn::fns::byte_length::ByteLength;
30use crate::scalar_fn::fns::case_when::CaseWhen;
31use crate::scalar_fn::fns::case_when::CaseWhenOptions;
32use crate::scalar_fn::fns::cast::Cast;
33use crate::scalar_fn::fns::dynamic::DynamicComparison;
34use crate::scalar_fn::fns::dynamic::DynamicComparisonExpr;
35use crate::scalar_fn::fns::dynamic::Rhs;
36use crate::scalar_fn::fns::ext_storage::ExtStorage;
37use crate::scalar_fn::fns::fill_null::FillNull;
38use crate::scalar_fn::fns::get_item::GetItem;
39use crate::scalar_fn::fns::is_nan::IsNan;
40use crate::scalar_fn::fns::is_not_null::IsNotNull;
41use crate::scalar_fn::fns::is_null::IsNull;
42use crate::scalar_fn::fns::like::Like;
43use crate::scalar_fn::fns::like::LikeOptions;
44use crate::scalar_fn::fns::list_contains::ListContains;
45use crate::scalar_fn::fns::list_contains::ListContainsOptions;
46use crate::scalar_fn::fns::list_length::ListLength;
47use crate::scalar_fn::fns::list_sum::ListSum;
48use crate::scalar_fn::fns::literal::Literal;
49use crate::scalar_fn::fns::mask::Mask;
50use crate::scalar_fn::fns::merge::DuplicateHandling;
51use crate::scalar_fn::fns::merge::Merge;
52use crate::scalar_fn::fns::not::Not;
53use crate::scalar_fn::fns::operators::CompareOperator;
54use crate::scalar_fn::fns::operators::Operator;
55use crate::scalar_fn::fns::pack::Pack;
56use crate::scalar_fn::fns::pack::PackOptions;
57use crate::scalar_fn::fns::replace_time_zone::ReplaceTimeZone;
58use crate::scalar_fn::fns::replace_time_zone::ReplaceTimeZoneOptions;
59use crate::scalar_fn::fns::select::FieldSelection;
60use crate::scalar_fn::fns::select::Select;
61use crate::scalar_fn::fns::variant_get::VariantGet;
62use crate::scalar_fn::fns::variant_get::VariantGetOptions;
63use crate::scalar_fn::fns::variant_get::VariantPath;
64use crate::scalar_fn::fns::zip::Zip;
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    Expression::Root
72}
73
74/// Creates a bound expression that references a root scope with the given dtype.
75pub fn bound_root(dtype: DType) -> BoundExpression {
76    BoundExpression::new_root(dtype)
77}
78
79/// Return whether the expression is a root expression.
80pub fn is_root(expr: &Expression) -> bool {
81    expr.is_root()
82}
83
84// ---- Literal ----
85
86/// Create a new `Literal` expression from a type that coerces to `Scalar`.
87///
88///
89/// ## Example usage
90///
91/// ```
92/// use vortex_array::arrays::PrimitiveArray;
93/// use vortex_array::dtype::Nullability;
94/// use vortex_array::expr::lit;
95/// use vortex_array::scalar_fn::fns::literal::Literal;
96/// use vortex_array::scalar::Scalar;
97///
98/// let number = lit(34i32);
99///
100/// let scalar = number.as_::<Literal>();
101/// assert_eq!(scalar, &Scalar::primitive(34i32, Nullability::NonNullable));
102/// ```
103pub fn lit(value: impl Into<Scalar>) -> Expression {
104    Literal.new_expr(value.into(), [])
105}
106
107/// Creates a bound literal expression.
108pub fn bound_lit(value: impl Into<Scalar>) -> BoundExpression {
109    Literal
110        .try_new_bound_expr(value.into(), [])
111        .vortex_expect("literal expressions are always well-typed")
112}
113
114// ---- GetItem / Col ----
115
116/// Creates an expression that accesses a field from the root array.
117///
118/// Equivalent to `get_item(field, root())` - extracts a named field from the input array.
119///
120/// ```rust
121/// # use vortex_array::expr::col;
122/// let expr = col("name");
123/// ```
124pub fn col(field: impl Into<FieldName>) -> Expression {
125    GetItem.new_expr(field.into(), vec![root()])
126}
127
128/// Creates a bound expression that accesses a field from a root scope with the given dtype.
129pub fn bound_col(field: impl Into<FieldName>, scope: DType) -> BoundExpression {
130    bound_get_item(field, bound_root(scope))
131}
132
133/// Creates an expression that extracts a named field from a struct expression.
134///
135/// Accesses the specified field from the result of the child expression.
136///
137/// ```rust
138/// # use vortex_array::expr::{get_item, root};
139/// let expr = get_item("user_id", root());
140/// ```
141pub fn get_item(field: impl Into<FieldName>, child: Expression) -> Expression {
142    GetItem.new_expr(field.into(), vec![child])
143}
144
145/// Creates a bound expression that extracts a named field from a struct expression.
146pub fn bound_get_item(field: impl Into<FieldName>, child: BoundExpression) -> BoundExpression {
147    GetItem
148        .try_new_bound_expr(field.into(), [child])
149        .vortex_expect("get-item expressions must reference a field in the child dtype")
150}
151
152// ---- VariantGet ----
153
154/// Creates an expression that extracts a path from a Variant expression.
155///
156/// Missing paths, traversal mismatches, and failed casts return null. When `dtype` is `None`,
157/// results are nullable Variant values; otherwise results are nullable values of `dtype`.
158pub fn variant_get(
159    child: Expression,
160    path: impl Into<VariantPath>,
161    dtype: Option<DType>,
162) -> Expression {
163    VariantGet.new_expr(VariantGetOptions::new(path.into(), dtype), vec![child])
164}
165
166/// Creates a bound expression that extracts a path from a Variant expression.
167pub fn bound_variant_get(
168    child: BoundExpression,
169    path: impl Into<VariantPath>,
170    dtype: Option<DType>,
171) -> BoundExpression {
172    VariantGet
173        .try_new_bound_expr(VariantGetOptions::new(path.into(), dtype), [child])
174        .vortex_expect("variant-get expressions require a Variant child")
175}
176
177// ---- CaseWhen ----
178
179/// Creates a CASE WHEN expression with one WHEN/THEN pair and an ELSE value.
180pub fn case_when(
181    condition: Expression,
182    then_value: Expression,
183    else_value: Expression,
184) -> Expression {
185    let options = CaseWhenOptions {
186        num_when_then_pairs: 1,
187        has_else: true,
188    };
189    CaseWhen.new_expr(options, [condition, then_value, else_value])
190}
191
192/// Creates a bound CASE WHEN expression with one WHEN/THEN pair and an ELSE value.
193pub fn bound_case_when(
194    condition: BoundExpression,
195    then_value: BoundExpression,
196    else_value: BoundExpression,
197) -> BoundExpression {
198    let options = CaseWhenOptions {
199        num_when_then_pairs: 1,
200        has_else: true,
201    };
202    CaseWhen
203        .try_new_bound_expr(options, [condition, then_value, else_value])
204        .vortex_expect("case expressions must have boolean conditions and matching branch dtypes")
205}
206
207/// Creates a CASE WHEN expression with one WHEN/THEN pair and no ELSE value.
208pub fn case_when_no_else(condition: Expression, then_value: Expression) -> Expression {
209    let options = CaseWhenOptions {
210        num_when_then_pairs: 1,
211        has_else: false,
212    };
213    CaseWhen.new_expr(options, [condition, then_value])
214}
215
216/// Creates a bound CASE WHEN expression with one WHEN/THEN pair and no ELSE value.
217pub fn bound_case_when_no_else(
218    condition: BoundExpression,
219    then_value: BoundExpression,
220) -> BoundExpression {
221    let options = CaseWhenOptions {
222        num_when_then_pairs: 1,
223        has_else: false,
224    };
225    CaseWhen
226        .try_new_bound_expr(options, [condition, then_value])
227        .vortex_expect("case expressions must have boolean conditions")
228}
229
230/// Creates an n-ary CASE WHEN expression from WHEN/THEN pairs and an optional ELSE value.
231pub fn nested_case_when(
232    when_then_pairs: Vec<(Expression, Expression)>,
233    else_value: Option<Expression>,
234) -> Expression {
235    assert!(
236        !when_then_pairs.is_empty(),
237        "nested_case_when requires at least one when/then pair"
238    );
239
240    let has_else = else_value.is_some();
241    let mut children = Vec::with_capacity(when_then_pairs.len() * 2 + usize::from(has_else));
242    for (condition, then_value) in &when_then_pairs {
243        children.push(condition.clone());
244        children.push(then_value.clone());
245    }
246    if let Some(else_expr) = else_value {
247        children.push(else_expr);
248    }
249
250    let Ok(num_when_then_pairs) = u32::try_from(when_then_pairs.len()) else {
251        vortex_panic!("nested_case_when has too many when/then pairs");
252    };
253    let options = CaseWhenOptions {
254        num_when_then_pairs,
255        has_else,
256    };
257    CaseWhen.new_expr(options, children)
258}
259
260/// Creates a bound n-ary CASE WHEN expression from WHEN/THEN pairs and an optional ELSE value.
261pub fn bound_nested_case_when(
262    when_then_pairs: Vec<(BoundExpression, BoundExpression)>,
263    else_value: Option<BoundExpression>,
264) -> BoundExpression {
265    assert!(
266        !when_then_pairs.is_empty(),
267        "nested_case_when requires at least one when/then pair"
268    );
269
270    let Ok(num_when_then_pairs) = u32::try_from(when_then_pairs.len()) else {
271        vortex_panic!("nested_case_when has too many when/then pairs");
272    };
273    let has_else = else_value.is_some();
274    let mut children = Vec::with_capacity(when_then_pairs.len() * 2 + usize::from(has_else));
275    for (condition, then_value) in when_then_pairs {
276        children.push(condition);
277        children.push(then_value);
278    }
279    if let Some(else_expr) = else_value {
280        children.push(else_expr);
281    }
282
283    let options = CaseWhenOptions {
284        num_when_then_pairs,
285        has_else,
286    };
287    CaseWhen
288        .try_new_bound_expr(options, children)
289        .vortex_expect("case expressions must have boolean conditions and matching branch dtypes")
290}
291
292// ---- Binary operators ----
293
294/// Creates a binary expression with the given operator.
295pub fn binary(operator: Operator, lhs: Expression, rhs: Expression) -> Expression {
296    Binary
297        .try_new_expr(operator, [lhs, rhs])
298        .vortex_expect("Failed to create binary expression")
299}
300
301/// Creates a bound binary expression with the given operator.
302pub fn bound_binary(
303    operator: Operator,
304    lhs: BoundExpression,
305    rhs: BoundExpression,
306) -> BoundExpression {
307    Binary
308        .try_new_bound_expr(operator, [lhs, rhs])
309        .vortex_expect("binary expressions must have compatible operand dtypes")
310}
311
312/// Create a new [`Binary`] using the [`Eq`](Operator::Eq) operator.
313///
314/// ## Example usage
315///
316/// ```
317/// # use vortex_array::arrays::{BoolArray, PrimitiveArray};
318/// # use vortex_array::arrays::bool::BoolArrayExt;
319/// # use vortex_array::IntoArray;
320/// # use vortex_array::{VortexSessionExecute, array_session};
321/// # use vortex_array::validity::Validity;
322/// # use vortex_buffer::buffer;
323/// # use vortex_array::expr::{eq, root, lit};
324/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
325/// let result = xs.into_array().apply(&eq(root(), lit(3))).unwrap();
326/// let mut ctx = array_session().create_execution_ctx();
327///
328/// assert_eq!(
329///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
330///     BoolArray::from_iter(vec![false, false, true]).to_bit_buffer(),
331/// );
332/// ```
333pub fn eq(lhs: Expression, rhs: Expression) -> Expression {
334    Binary
335        .try_new_expr(Operator::Eq, [lhs, rhs])
336        .vortex_expect("Failed to create Eq binary expression")
337}
338
339/// Creates a bound equality expression.
340pub fn bound_eq(lhs: BoundExpression, rhs: BoundExpression) -> BoundExpression {
341    bound_binary(Operator::Eq, lhs, rhs)
342}
343
344/// Create a new [`Binary`] using the [`NotEq`](Operator::NotEq) operator.
345///
346/// ## Example usage
347///
348/// ```
349/// # use vortex_array::arrays::{BoolArray, PrimitiveArray};
350/// # use vortex_array::arrays::bool::BoolArrayExt;
351/// # use vortex_array::IntoArray;
352/// # use vortex_array::{VortexSessionExecute, array_session};
353/// # use vortex_array::validity::Validity;
354/// # use vortex_buffer::buffer;
355/// # use vortex_array::expr::{root, lit, not_eq};
356/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
357/// let result = xs.into_array().apply(&not_eq(root(), lit(3))).unwrap();
358/// let mut ctx = array_session().create_execution_ctx();
359///
360/// assert_eq!(
361///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
362///     BoolArray::from_iter(vec![true, true, false]).to_bit_buffer(),
363/// );
364/// ```
365pub fn not_eq(lhs: Expression, rhs: Expression) -> Expression {
366    Binary
367        .try_new_expr(Operator::NotEq, [lhs, rhs])
368        .vortex_expect("Failed to create NotEq binary expression")
369}
370
371/// Creates a bound inequality expression.
372pub fn bound_not_eq(lhs: BoundExpression, rhs: BoundExpression) -> BoundExpression {
373    bound_binary(Operator::NotEq, lhs, rhs)
374}
375
376/// Create a new [`Binary`] using the [`Gte`](Operator::Gte) operator.
377///
378/// ## Example usage
379///
380/// ```
381/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
382/// # use vortex_array::arrays::bool::BoolArrayExt;
383/// # use vortex_array::IntoArray;
384/// # use vortex_array::{VortexSessionExecute, array_session};
385/// # use vortex_array::validity::Validity;
386/// # use vortex_buffer::buffer;
387/// # use vortex_array::expr::{gt_eq, root, lit};
388/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
389/// let result = xs.into_array().apply(&gt_eq(root(), lit(3))).unwrap();
390/// let mut ctx = array_session().create_execution_ctx();
391///
392/// assert_eq!(
393///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
394///     BoolArray::from_iter(vec![false, false, true]).to_bit_buffer(),
395/// );
396/// ```
397pub fn gt_eq(lhs: Expression, rhs: Expression) -> Expression {
398    Binary
399        .try_new_expr(Operator::Gte, [lhs, rhs])
400        .vortex_expect("Failed to create Gte binary expression")
401}
402
403/// Creates a bound greater-than-or-equal expression.
404pub fn bound_gt_eq(lhs: BoundExpression, rhs: BoundExpression) -> BoundExpression {
405    bound_binary(Operator::Gte, lhs, rhs)
406}
407
408/// Create a new [`Binary`] using the [`Gt`](Operator::Gt) operator.
409///
410/// ## Example usage
411///
412/// ```
413/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
414/// # use vortex_array::arrays::bool::BoolArrayExt;
415/// # use vortex_array::IntoArray;
416/// # use vortex_array::{VortexSessionExecute, array_session};
417/// # use vortex_array::validity::Validity;
418/// # use vortex_buffer::buffer;
419/// # use vortex_array::expr::{gt, root, lit};
420/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
421/// let result = xs.into_array().apply(&gt(root(), lit(2))).unwrap();
422/// let mut ctx = array_session().create_execution_ctx();
423///
424/// assert_eq!(
425///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
426///     BoolArray::from_iter(vec![false, false, true]).to_bit_buffer(),
427/// );
428/// ```
429pub fn gt(lhs: Expression, rhs: Expression) -> Expression {
430    Binary
431        .try_new_expr(Operator::Gt, [lhs, rhs])
432        .vortex_expect("Failed to create Gt binary expression")
433}
434
435/// Creates a bound greater-than expression.
436pub fn bound_gt(lhs: BoundExpression, rhs: BoundExpression) -> BoundExpression {
437    bound_binary(Operator::Gt, lhs, rhs)
438}
439
440/// Create a new [`Binary`] using the [`Lte`](Operator::Lte) operator.
441///
442/// ## Example usage
443///
444/// ```
445/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
446/// # use vortex_array::arrays::bool::BoolArrayExt;
447/// # use vortex_array::IntoArray;
448/// # use vortex_array::{VortexSessionExecute, array_session};
449/// # use vortex_array::validity::Validity;
450/// # use vortex_buffer::buffer;
451/// # use vortex_array::expr::{root, lit, lt_eq};
452/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
453/// let result = xs.into_array().apply(&lt_eq(root(), lit(2))).unwrap();
454/// let mut ctx = array_session().create_execution_ctx();
455///
456/// assert_eq!(
457///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
458///     BoolArray::from_iter(vec![true, true, false]).to_bit_buffer(),
459/// );
460/// ```
461pub fn lt_eq(lhs: Expression, rhs: Expression) -> Expression {
462    Binary
463        .try_new_expr(Operator::Lte, [lhs, rhs])
464        .vortex_expect("Failed to create Lte binary expression")
465}
466
467/// Creates a bound less-than-or-equal expression.
468pub fn bound_lt_eq(lhs: BoundExpression, rhs: BoundExpression) -> BoundExpression {
469    bound_binary(Operator::Lte, lhs, rhs)
470}
471
472/// Create a new [`Binary`] using the [`Lt`](Operator::Lt) operator.
473///
474/// ## Example usage
475///
476/// ```
477/// # use vortex_array::arrays::{BoolArray, PrimitiveArray };
478/// # use vortex_array::arrays::bool::BoolArrayExt;
479/// # use vortex_array::IntoArray;
480/// # use vortex_array::{VortexSessionExecute, array_session};
481/// # use vortex_array::validity::Validity;
482/// # use vortex_buffer::buffer;
483/// # use vortex_array::expr::{root, lit, lt};
484/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
485/// let result = xs.into_array().apply(&lt(root(), lit(3))).unwrap();
486/// let mut ctx = array_session().create_execution_ctx();
487///
488/// assert_eq!(
489///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
490///     BoolArray::from_iter(vec![true, true, false]).to_bit_buffer(),
491/// );
492/// ```
493pub fn lt(lhs: Expression, rhs: Expression) -> Expression {
494    Binary
495        .try_new_expr(Operator::Lt, [lhs, rhs])
496        .vortex_expect("Failed to create Lt binary expression")
497}
498
499/// Creates a bound less-than expression.
500pub fn bound_lt(lhs: BoundExpression, rhs: BoundExpression) -> BoundExpression {
501    bound_binary(Operator::Lt, lhs, rhs)
502}
503
504/// Create a new [`Binary`] using the [`Or`](Operator::Or) operator.
505///
506/// ## Example usage
507///
508/// ```
509/// # use vortex_array::arrays::BoolArray;
510/// # use vortex_array::arrays::bool::BoolArrayExt;
511/// # use vortex_array::IntoArray;
512/// # use vortex_array::{VortexSessionExecute, array_session};
513/// # use vortex_array::expr::{root, lit, or};
514/// let xs = BoolArray::from_iter(vec![true, false, true]);
515/// let result = xs.into_array().apply(&or(root(), lit(false))).unwrap();
516/// let mut ctx = array_session().create_execution_ctx();
517///
518/// assert_eq!(
519///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
520///     BoolArray::from_iter(vec![true, false, true]).to_bit_buffer(),
521/// );
522/// ```
523pub fn or(lhs: Expression, rhs: Expression) -> Expression {
524    Binary
525        .try_new_expr(Operator::Or, [lhs, rhs])
526        .vortex_expect("Failed to create Or binary expression")
527}
528
529/// Creates a bound boolean OR expression.
530pub fn bound_or(lhs: BoundExpression, rhs: BoundExpression) -> BoundExpression {
531    bound_binary(Operator::Or, lhs, rhs)
532}
533
534/// Collects a list of `or`ed values into a single expression using a balanced tree.
535///
536/// This creates a balanced binary tree to avoid deep nesting that could cause
537/// stack overflow during drop or evaluation.
538///
539/// [a, b, c, d] => or(or(a, b), or(c, d))
540pub fn or_collect<I>(iter: I) -> Option<Expression>
541where
542    I: IntoIterator<Item = Expression>,
543{
544    iter.into_iter().reduce_balanced(or)
545}
546
547/// Collects bound expressions into a balanced tree of boolean OR expressions.
548pub fn bound_or_collect<I>(iter: I) -> Option<BoundExpression>
549where
550    I: IntoIterator<Item = BoundExpression>,
551{
552    iter.into_iter().reduce_balanced(bound_or)
553}
554
555/// Create a new [`Binary`] using the [`And`](Operator::And) operator.
556///
557/// ## Example usage
558///
559/// ```
560/// # use vortex_array::arrays::BoolArray;
561/// # use vortex_array::arrays::bool::BoolArrayExt;
562/// # use vortex_array::IntoArray;
563/// # use vortex_array::{VortexSessionExecute, array_session};
564/// # use vortex_array::expr::{and, root, lit};
565/// let xs = BoolArray::from_iter(vec![true, false, true]).into_array();
566/// let result = xs.apply(&and(root(), lit(true))).unwrap();
567/// let mut ctx = array_session().create_execution_ctx();
568///
569/// assert_eq!(
570///     result.execute::<BoolArray>(&mut ctx).unwrap().to_bit_buffer(),
571///     BoolArray::from_iter(vec![true, false, true]).to_bit_buffer(),
572/// );
573/// ```
574pub fn and(lhs: Expression, rhs: Expression) -> Expression {
575    Binary
576        .try_new_expr(Operator::And, [lhs, rhs])
577        .vortex_expect("Failed to create And binary expression")
578}
579
580/// Creates a bound boolean AND expression.
581pub fn bound_and(lhs: BoundExpression, rhs: BoundExpression) -> BoundExpression {
582    bound_binary(Operator::And, lhs, rhs)
583}
584
585/// Collects a list of `and`ed values into a single expression using a balanced tree.
586///
587/// This creates a balanced binary tree to avoid deep nesting that could cause
588/// stack overflow during drop or evaluation.
589///
590/// [a, b, c, d] => and(and(a, b), and(c, d))
591pub fn and_collect<I>(iter: I) -> Option<Expression>
592where
593    I: IntoIterator<Item = Expression>,
594{
595    iter.into_iter().reduce_balanced(and)
596}
597
598/// Collects bound expressions into a balanced tree of boolean AND expressions.
599pub fn bound_and_collect<I>(iter: I) -> Option<BoundExpression>
600where
601    I: IntoIterator<Item = BoundExpression>,
602{
603    iter.into_iter().reduce_balanced(bound_and)
604}
605
606/// Create a new [`Binary`] using the [`Add`](Operator::Add) operator.
607///
608/// ## Example usage
609///
610/// ```
611/// # use vortex_array::IntoArray;
612/// # use vortex_array::arrays::PrimitiveArray;
613/// # use vortex_array::builtins::ArrayBuiltins;
614/// # use vortex_array::{VortexSessionExecute, array_session};
615/// # use vortex_buffer::buffer;
616/// # use vortex_array::expr::{checked_add, lit, root};
617/// let xs = buffer![1, 2, 3].into_array();
618/// let result = xs.apply(&checked_add(root(), lit(5))).unwrap();
619///
620/// let mut ctx = array_session().create_execution_ctx();
621/// let result = result.execute::<PrimitiveArray>(&mut ctx).unwrap();
622/// assert_eq!(result.as_slice::<i32>(), [6, 7, 8]);
623/// ```
624pub fn checked_add(lhs: Expression, rhs: Expression) -> Expression {
625    Binary
626        .try_new_expr(Operator::Add, [lhs, rhs])
627        .vortex_expect("Failed to create Add binary expression")
628}
629
630/// Creates a bound checked-add expression.
631pub fn bound_checked_add(lhs: BoundExpression, rhs: BoundExpression) -> BoundExpression {
632    bound_binary(Operator::Add, lhs, rhs)
633}
634
635// ---- Not ----
636
637/// Creates an expression that logically inverts boolean values.
638///
639/// Returns the logical negation of the input boolean expression.
640///
641/// ```rust
642/// # use vortex_array::expr::{not, root};
643/// let expr = not(root());
644/// ```
645pub fn not(operand: Expression) -> Expression {
646    Not.new_expr(EmptyOptions, vec![operand])
647}
648
649/// Creates a bound expression that logically inverts boolean values.
650pub fn bound_not(operand: BoundExpression) -> BoundExpression {
651    Not.try_new_bound_expr(EmptyOptions, [operand])
652        .vortex_expect("not expressions require a boolean operand")
653}
654
655// ---- Between ----
656
657/// Creates an expression that checks if values are between two bounds.
658///
659/// Returns a boolean array indicating which values fall within the specified range.
660/// The comparison strictness is controlled by the options parameter.
661///
662/// ```rust
663/// # use vortex_array::scalar_fn::fns::between::BetweenOptions;
664/// # use vortex_array::scalar_fn::fns::between::StrictComparison;
665/// # use vortex_array::expr::{between, lit, root};
666/// let opts = BetweenOptions {
667///     lower_strict: StrictComparison::NonStrict,
668///     upper_strict: StrictComparison::NonStrict,
669/// };
670/// let expr = between(root(), lit(10), lit(20), opts);
671/// ```
672pub fn between(
673    arr: Expression,
674    lower: Expression,
675    upper: Expression,
676    options: BetweenOptions,
677) -> Expression {
678    Between
679        .try_new_expr(options, [arr, lower, upper])
680        .vortex_expect("Failed to create Between expression")
681}
682
683/// Creates a bound expression that checks if values are between two bounds.
684pub fn bound_between(
685    arr: BoundExpression,
686    lower: BoundExpression,
687    upper: BoundExpression,
688    options: BetweenOptions,
689) -> BoundExpression {
690    Between
691        .try_new_bound_expr(options, [arr, lower, upper])
692        .vortex_expect("between expressions require compatible operand dtypes")
693}
694
695// ---- Select ----
696
697/// Creates an expression that selects (includes) specific fields from an array.
698///
699/// Projects only the specified fields from the child expression, which must be of DType struct.
700/// ```rust
701/// # use vortex_array::expr::{select, root};
702/// let expr = select(["name", "age"], root());
703/// ```
704pub fn select(field_names: impl Into<FieldNames>, child: Expression) -> Expression {
705    Select
706        .try_new_expr(FieldSelection::Include(field_names.into()), [child])
707        .vortex_expect("Failed to create Select expression")
708}
709
710/// Creates a bound expression that selects specific fields from a struct expression.
711pub fn bound_select(field_names: impl Into<FieldNames>, child: BoundExpression) -> BoundExpression {
712    Select
713        .try_new_bound_expr(FieldSelection::Include(field_names.into()), [child])
714        .vortex_expect("select expressions require fields from a struct child")
715}
716
717/// Creates an expression that excludes specific fields from an array.
718///
719/// Projects all fields except the specified ones from the input struct expression.
720///
721/// ```rust
722/// # use vortex_array::expr::{select_exclude, root};
723/// let expr = select_exclude(["internal_id", "metadata"], root());
724/// ```
725pub fn select_exclude(fields: impl Into<FieldNames>, child: Expression) -> Expression {
726    Select
727        .try_new_expr(FieldSelection::Exclude(fields.into()), [child])
728        .vortex_expect("Failed to create Select expression")
729}
730
731/// Creates a bound expression that excludes specific fields from a struct expression.
732pub fn bound_select_exclude(
733    fields: impl Into<FieldNames>,
734    child: BoundExpression,
735) -> BoundExpression {
736    Select
737        .try_new_bound_expr(FieldSelection::Exclude(fields.into()), [child])
738        .vortex_expect("select expressions require fields from a struct child")
739}
740
741// ---- Pack ----
742
743/// Creates an expression that packs values into a struct with named fields.
744///
745/// ```rust
746/// # use vortex_array::dtype::Nullability;
747/// # use vortex_array::expr::{pack, col, lit};
748/// let expr = pack([("id", col("user_id")), ("constant", lit(42))], Nullability::NonNullable);
749/// ```
750pub fn pack(
751    elements: impl IntoIterator<Item = (impl Into<FieldName>, Expression)>,
752    nullability: Nullability,
753) -> Expression {
754    let (names, values): (Vec<_>, Vec<_>) = elements
755        .into_iter()
756        .map(|(name, value)| (name.into(), value))
757        .unzip();
758    Pack.new_expr(
759        PackOptions {
760            names: names.into(),
761            nullability,
762        },
763        values,
764    )
765}
766
767/// Creates a bound expression that packs values into a struct with named fields.
768pub fn bound_pack(
769    elements: impl IntoIterator<Item = (impl Into<FieldName>, BoundExpression)>,
770    nullability: Nullability,
771) -> BoundExpression {
772    let (names, values): (Vec<_>, Vec<_>) = elements
773        .into_iter()
774        .map(|(name, value)| (name.into(), value))
775        .unzip();
776    Pack.try_new_bound_expr(
777        PackOptions {
778            names: names.into(),
779            nullability,
780        },
781        values,
782    )
783    .vortex_expect("pack expressions must have one name per child")
784}
785
786// ---- Cast ----
787
788/// Creates an expression that casts values to a target data type.
789///
790/// Converts the input expression's values to the specified target type.
791///
792/// ```rust
793/// # use vortex_array::dtype::{DType, Nullability, PType};
794/// # use vortex_array::expr::{cast, root};
795/// let expr = cast(root(), DType::Primitive(PType::I64, Nullability::NonNullable));
796/// ```
797pub fn cast(child: Expression, target: DType) -> Expression {
798    Cast.try_new_expr(target, [child])
799        .vortex_expect("Failed to create Cast expression")
800}
801
802/// Creates a bound expression that casts values to a target dtype.
803pub fn bound_cast(child: BoundExpression, target: DType) -> BoundExpression {
804    Cast.try_new_bound_expr(target, [child])
805        .vortex_expect("cast expressions require a supported source and target dtype")
806}
807
808// ---- FillNull ----
809
810/// Creates an expression that replaces null values with a fill value.
811///
812/// ```rust
813/// # use vortex_array::expr::{fill_null, root, lit};
814/// let expr = fill_null(root(), lit(0i32));
815/// ```
816pub fn fill_null(child: Expression, fill_value: Expression) -> Expression {
817    FillNull.new_expr(EmptyOptions, [child, fill_value])
818}
819
820/// Creates a bound expression that replaces null values with a fill value.
821pub fn bound_fill_null(child: BoundExpression, fill_value: BoundExpression) -> BoundExpression {
822    FillNull
823        .try_new_bound_expr(EmptyOptions, [child, fill_value])
824        .vortex_expect("fill-null expressions require compatible child and fill dtypes")
825}
826
827// ---- IsNull ----
828
829/// Creates an expression that checks for null values.
830///
831/// Returns a boolean array indicating which positions contain null values.
832///
833/// ```rust
834/// # use vortex_array::expr::{is_null, root};
835/// let expr = is_null(root());
836/// ```
837pub fn is_null(child: Expression) -> Expression {
838    IsNull.new_expr(EmptyOptions, vec![child])
839}
840
841/// Creates a bound expression that checks for null values.
842pub fn bound_is_null(child: BoundExpression) -> BoundExpression {
843    IsNull
844        .try_new_bound_expr(EmptyOptions, [child])
845        .vortex_expect("is-null expressions are always well-typed")
846}
847
848// ---- IsNan ----
849
850/// Creates an expression that checks for NaN values.
851///
852/// The expression is strict: null inputs produce null outputs, so the output nullability
853/// follows the input. Only primitive float inputs are supported.
854///
855/// ```rust
856/// # use vortex_array::expr::{is_nan, root};
857/// let expr = is_nan(root());
858/// ```
859pub fn is_nan(child: Expression) -> Expression {
860    IsNan.new_expr(EmptyOptions, vec![child])
861}
862
863/// Creates a bound expression that checks for NaN values.
864pub fn bound_is_nan(child: BoundExpression) -> BoundExpression {
865    IsNan
866        .try_new_bound_expr(EmptyOptions, [child])
867        .vortex_expect("is-nan expressions are always well-typed")
868}
869
870// ---- IsNotNull ----
871
872/// Creates an expression that checks for non-null values.
873///
874/// Returns a boolean array indicating which positions contain non-null values.
875///
876/// ```rust
877/// # use vortex_array::expr::{is_not_null, root};
878/// let expr = is_not_null(root());
879/// ```
880pub fn is_not_null(child: Expression) -> Expression {
881    IsNotNull.new_expr(EmptyOptions, vec![child])
882}
883
884/// Creates a bound expression that checks for non-null values.
885pub fn bound_is_not_null(child: BoundExpression) -> BoundExpression {
886    IsNotNull
887        .try_new_bound_expr(EmptyOptions, [child])
888        .vortex_expect("is-not-null expressions are always well-typed")
889}
890
891// ---- Like ----
892
893/// Creates a SQL LIKE expression.
894pub fn like(child: Expression, pattern: Expression) -> Expression {
895    Like.new_expr(
896        LikeOptions {
897            negated: false,
898            case_insensitive: false,
899        },
900        [child, pattern],
901    )
902}
903
904/// Creates a bound SQL LIKE expression.
905pub fn bound_like(child: BoundExpression, pattern: BoundExpression) -> BoundExpression {
906    bound_like_with_options(child, pattern, false, false)
907}
908
909/// Creates a case-insensitive SQL ILIKE expression.
910pub fn ilike(child: Expression, pattern: Expression) -> Expression {
911    Like.new_expr(
912        LikeOptions {
913            negated: false,
914            case_insensitive: true,
915        },
916        [child, pattern],
917    )
918}
919
920/// Creates a bound case-insensitive SQL ILIKE expression.
921pub fn bound_ilike(child: BoundExpression, pattern: BoundExpression) -> BoundExpression {
922    bound_like_with_options(child, pattern, false, true)
923}
924
925/// Creates a negated SQL NOT LIKE expression.
926pub fn not_like(child: Expression, pattern: Expression) -> Expression {
927    Like.new_expr(
928        LikeOptions {
929            negated: true,
930            case_insensitive: false,
931        },
932        [child, pattern],
933    )
934}
935
936/// Creates a bound negated SQL NOT LIKE expression.
937pub fn bound_not_like(child: BoundExpression, pattern: BoundExpression) -> BoundExpression {
938    bound_like_with_options(child, pattern, true, false)
939}
940
941/// Creates a negated case-insensitive SQL NOT ILIKE expression.
942pub fn not_ilike(child: Expression, pattern: Expression) -> Expression {
943    Like.new_expr(
944        LikeOptions {
945            negated: true,
946            case_insensitive: true,
947        },
948        [child, pattern],
949    )
950}
951
952/// Creates a bound negated case-insensitive SQL NOT ILIKE expression.
953pub fn bound_not_ilike(child: BoundExpression, pattern: BoundExpression) -> BoundExpression {
954    bound_like_with_options(child, pattern, true, true)
955}
956
957fn bound_like_with_options(
958    child: BoundExpression,
959    pattern: BoundExpression,
960    negated: bool,
961    case_insensitive: bool,
962) -> BoundExpression {
963    Like.try_new_bound_expr(
964        LikeOptions {
965            negated,
966            case_insensitive,
967        },
968        [child, pattern],
969    )
970    .vortex_expect("like expressions require UTF-8 or binary operands")
971}
972
973// ---- Mask ----
974
975/// Creates a mask expression that applies the given boolean mask to the input array.
976pub fn mask(array: Expression, mask: Expression) -> Expression {
977    Mask.new_expr(EmptyOptions, [array, mask])
978}
979
980/// Creates a bound mask expression.
981pub fn bound_mask(array: BoundExpression, mask: BoundExpression) -> BoundExpression {
982    Mask.try_new_bound_expr(EmptyOptions, [array, mask])
983        .vortex_expect("mask expressions require a boolean mask")
984}
985
986// ---- Merge ----
987
988/// Creates an expression that merges struct expressions into a single struct.
989///
990/// Combines fields from all input expressions. If field names are duplicated,
991/// later expressions win. Fields are not recursively merged.
992///
993/// ```rust
994/// # use vortex_array::dtype::Nullability;
995/// # use vortex_array::expr::{merge, get_item, root};
996/// let expr = merge([get_item("a", root()), get_item("b", root())]);
997/// ```
998pub fn merge(elements: impl IntoIterator<Item = impl Into<Expression>>) -> Expression {
999    use itertools::Itertools as _;
1000    let values = elements.into_iter().map(|value| value.into()).collect_vec();
1001    Merge.new_expr(DuplicateHandling::default(), values)
1002}
1003
1004/// Creates a bound expression that merges struct expressions.
1005pub fn bound_merge(elements: impl IntoIterator<Item = BoundExpression>) -> BoundExpression {
1006    bound_merge_opts(elements, DuplicateHandling::default())
1007}
1008
1009/// Creates a merge expression with explicit duplicate handling.
1010pub fn merge_opts(
1011    elements: impl IntoIterator<Item = impl Into<Expression>>,
1012    duplicate_handling: DuplicateHandling,
1013) -> Expression {
1014    use itertools::Itertools as _;
1015    let values = elements.into_iter().map(|value| value.into()).collect_vec();
1016    Merge.new_expr(duplicate_handling, values)
1017}
1018
1019/// Creates a bound merge expression with explicit duplicate handling.
1020pub fn bound_merge_opts(
1021    elements: impl IntoIterator<Item = BoundExpression>,
1022    duplicate_handling: DuplicateHandling,
1023) -> BoundExpression {
1024    Merge
1025        .try_new_bound_expr(duplicate_handling, elements)
1026        .vortex_expect("merge expressions require non-nullable struct children")
1027}
1028
1029// ---- Zip ----
1030
1031/// Creates a zip expression that conditionally selects between two arrays.
1032///
1033/// ```rust
1034/// # use vortex_array::expr::{zip_expr, root, lit};
1035/// let expr = zip_expr(lit(true), root(), lit(0i32));
1036/// ```
1037pub fn zip_expr(mask: Expression, if_true: Expression, if_false: Expression) -> Expression {
1038    Zip.new_expr(EmptyOptions, [if_true, if_false, mask])
1039}
1040
1041/// Creates a bound zip expression that conditionally selects between two arrays.
1042pub fn bound_zip_expr(
1043    mask: BoundExpression,
1044    if_true: BoundExpression,
1045    if_false: BoundExpression,
1046) -> BoundExpression {
1047    Zip.try_new_bound_expr(EmptyOptions, [if_true, if_false, mask])
1048        .vortex_expect("zip expressions require a boolean mask and compatible value dtypes")
1049}
1050
1051// ---- Dynamic ----
1052
1053/// Creates a dynamic comparison expression from its complete options.
1054pub fn dynamic_with_options(options: DynamicComparisonExpr, lhs: Expression) -> Expression {
1055    DynamicComparison.new_expr(options, [lhs])
1056}
1057
1058/// Creates a bound dynamic comparison expression from its complete options.
1059pub fn bound_dynamic_with_options(
1060    options: DynamicComparisonExpr,
1061    lhs: BoundExpression,
1062) -> BoundExpression {
1063    DynamicComparison
1064        .try_new_bound_expr(options, [lhs])
1065        .vortex_expect("dynamic comparisons require a compatible left-hand dtype")
1066}
1067
1068/// Creates a dynamic comparison expression.
1069pub fn dynamic(
1070    operator: CompareOperator,
1071    rhs_value: impl Fn() -> Option<ScalarValue> + Send + Sync + 'static,
1072    rhs_dtype: DType,
1073    default: bool,
1074    lhs: Expression,
1075) -> Expression {
1076    dynamic_with_options(
1077        DynamicComparisonExpr {
1078            operator,
1079            rhs: Arc::new(Rhs {
1080                value: Arc::new(rhs_value),
1081                dtype: rhs_dtype,
1082            }),
1083            default,
1084        },
1085        lhs,
1086    )
1087}
1088
1089/// Creates a bound dynamic comparison expression.
1090pub fn bound_dynamic(
1091    operator: CompareOperator,
1092    rhs_value: impl Fn() -> Option<ScalarValue> + Send + Sync + 'static,
1093    rhs_dtype: DType,
1094    default: bool,
1095    lhs: BoundExpression,
1096) -> BoundExpression {
1097    bound_dynamic_with_options(
1098        DynamicComparisonExpr {
1099            operator,
1100            rhs: Arc::new(Rhs {
1101                value: Arc::new(rhs_value),
1102                dtype: rhs_dtype,
1103            }),
1104            default,
1105        },
1106        lhs,
1107    )
1108}
1109
1110// ---- ListContains ----
1111
1112/// Creates an expression that checks whether a list contains a value.
1113///
1114/// A null element never matches anything: a needle that matches no element yields `false`. For
1115/// SQL's three-valued `IN`, where a null element makes that `null`, use [`in_list`].
1116///
1117/// ```rust
1118/// # use vortex_array::expr::{list_contains, lit, root};
1119/// let expr = list_contains(root(), lit(42));
1120/// ```
1121pub fn list_contains(list: Expression, value: Expression) -> Expression {
1122    list_contains_opts(list, value, ListContainsOptions::default())
1123}
1124
1125/// Creates an expression that checks whether a list contains a value, with explicit
1126/// [`ListContainsOptions`].
1127pub fn list_contains_opts(
1128    list: Expression,
1129    value: Expression,
1130    options: ListContainsOptions,
1131) -> Expression {
1132    ListContains.new_expr(options, [list, value])
1133}
1134
1135/// Creates `needle IN (list)` with SQL null semantics: a null element is an unknown value, so a
1136/// needle that matches no element yields `null` rather than `false` when the list holds one, and
1137/// `not(in_list(..))` never admits such a row.
1138///
1139/// ```rust
1140/// # use vortex_array::expr::{in_list, lit, root};
1141/// let expr = in_list(root(), lit(vec![1, 2, 3]));
1142/// ```
1143pub fn in_list(needle: Expression, list: Expression) -> Expression {
1144    list_contains_opts(
1145        list,
1146        needle,
1147        ListContainsOptions {
1148            sql_null_semantics: true,
1149        },
1150    )
1151}
1152
1153/// Creates `needle IN (elements)` with SQL null semantics for a list of constant elements of
1154/// `element_dtype`, ignoring nullability. An element can also be an untyped null.
1155///
1156/// The nullability of `element_dtype` is not used: the list elements are nullable only when a
1157/// null element is present.
1158///
1159/// Non-null integers that hold every value between their smallest and largest become a range
1160/// check, which is cheaper to evaluate and prunes with tighter bounds than a set lookup, as for
1161/// the keys of a small dimension table that a join pushes down.
1162///
1163/// # Errors
1164///
1165/// Returns an error if an element is not a null and not of `element_dtype`.
1166///
1167/// ```rust
1168/// # use vortex_array::dtype::{DType, Nullability, PType};
1169/// # use vortex_array::expr::{in_list_of, root};
1170/// let dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
1171/// let expr = in_list_of(root(), dtype, vec![3i32.into(), 1i32.into(), 2i32.into()])?;
1172/// assert_eq!(expr.to_string(), "(1i32 <= $ <= 3i32)");
1173/// # Ok::<(), vortex_error::VortexError>(())
1174/// ```
1175pub fn in_list_of(
1176    needle: Expression,
1177    element_dtype: DType,
1178    elements: Vec<Scalar>,
1179) -> VortexResult<Expression> {
1180    let element_dtype = if elements.iter().any(Scalar::is_null) {
1181        element_dtype.as_nullable()
1182    } else {
1183        element_dtype.as_nonnullable()
1184    };
1185    let mut elements = elements
1186        .iter()
1187        .map(|element| {
1188            vortex_ensure!(
1189                element.dtype() == &DType::Null
1190                    || element.dtype().eq_ignore_nullability(&element_dtype),
1191                "IN list element {element} is not of type {element_dtype}"
1192            );
1193            element.cast(&element_dtype)
1194        })
1195        .collect::<VortexResult<Vec<_>>>()?;
1196
1197    if let Some((low, high)) = contiguous_integer_bounds(&elements) {
1198        let options = BetweenOptions {
1199            lower_strict: StrictComparison::NonStrict,
1200            upper_strict: StrictComparison::NonStrict,
1201        };
1202        return Ok(between(
1203            needle,
1204            lit(low.clone()),
1205            lit(high.clone()),
1206            options,
1207        ));
1208    }
1209
1210    // Ordered elements make equal lists from the same set, so that equal filters deduplicate.
1211    if elements
1212        .windows(2)
1213        .all(|pair| pair[0].partial_cmp(&pair[1]).is_some())
1214    {
1215        elements.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1216    }
1217    let list = Scalar::list(Arc::new(element_dtype), elements, Nullability::NonNullable);
1218    Ok(in_list(needle, lit(list)))
1219}
1220
1221/// The smallest and largest of non-null integer `scalars` when they hold every integer between
1222/// those two.
1223fn contiguous_integer_bounds(scalars: &[Scalar]) -> Option<(&Scalar, &Scalar)> {
1224    let mut values = scalars
1225        .iter()
1226        .map(|scalar| {
1227            let primitive = scalar.as_primitive_opt()?;
1228            if !primitive.ptype().is_int() {
1229                return None;
1230            }
1231            let value = if primitive.ptype().is_unsigned_int() {
1232                primitive.as_opt::<u64>()??.into()
1233            } else {
1234                i128::from(primitive.as_opt::<i64>()??)
1235            };
1236            Some((value, scalar))
1237        })
1238        .collect::<Option<Vec<(i128, &Scalar)>>>()?;
1239    values.sort_unstable_by_key(|(value, _)| *value);
1240    values.dedup_by_key(|(value, _)| *value);
1241    let (low, low_scalar) = values.first()?;
1242    let (high, high_scalar) = values.last()?;
1243    (high - low + 1 == i128::try_from(values.len()).ok()?).then_some((*low_scalar, *high_scalar))
1244}
1245
1246/// Creates a bound expression that checks whether a list contains a value.
1247pub fn bound_list_contains(list: BoundExpression, value: BoundExpression) -> BoundExpression {
1248    ListContains
1249        .try_new_bound_expr(ListContainsOptions::default(), [list, value])
1250        .vortex_expect("list-contains expressions require a list child")
1251}
1252
1253/// Creates a bound `needle IN (list)` with SQL null semantics. See [`in_list`].
1254pub fn bound_in_list(needle: BoundExpression, list: BoundExpression) -> BoundExpression {
1255    ListContains
1256        .try_new_bound_expr(
1257            ListContainsOptions {
1258                sql_null_semantics: true,
1259            },
1260            [list, needle],
1261        )
1262        .vortex_expect("list-contains expressions require a list child")
1263}
1264
1265// ---- ByteLength ----
1266
1267/// Creates an expression that computes the byte length of each element.
1268/// This is akin to ANSI SQL OCTET_LENGTH(), or DuckDB's strlen().
1269///
1270/// ```rust
1271/// # use vortex_array::expr::{byte_length, root};
1272/// let expr = byte_length(root());
1273/// ```
1274pub fn byte_length(input: Expression) -> Expression {
1275    ByteLength.new_expr(EmptyOptions, [input])
1276}
1277
1278/// Creates a bound expression that computes each element's byte length.
1279pub fn bound_byte_length(input: BoundExpression) -> BoundExpression {
1280    ByteLength
1281        .try_new_bound_expr(EmptyOptions, [input])
1282        .vortex_expect("byte-length expressions require a variable-length binary child")
1283}
1284
1285// ---- ExtStorage ----
1286
1287/// Creates an expression that extracts the storage values from an extension array.
1288///
1289/// ```rust
1290/// # use vortex_array::expr::{ext_storage, root};
1291/// let expr = ext_storage(root());
1292/// ```
1293pub fn ext_storage(input: Expression) -> Expression {
1294    ExtStorage.new_expr(EmptyOptions, [input])
1295}
1296
1297/// Creates a bound expression that extracts an extension array's storage values.
1298pub fn bound_ext_storage(input: BoundExpression) -> BoundExpression {
1299    ExtStorage
1300        .try_new_bound_expr(EmptyOptions, [input])
1301        .vortex_expect("extension-storage expressions require an extension child")
1302}
1303
1304// ---- ListLength ----
1305
1306/// Creates an expression that computes the number of elements in each list
1307/// for `List` and `FixedSizeList` inputs. This is akin to ANSI SQL `CARDINALITY()`,
1308/// or DuckDB's `len()`/`array_length()`.
1309///
1310/// ```rust
1311/// # use vortex_array::expr::{list_length, root};
1312/// let expr = list_length(root());
1313/// ```
1314pub fn list_length(input: Expression) -> Expression {
1315    ListLength.new_expr(EmptyOptions, [input])
1316}
1317
1318/// Creates a bound expression that computes the number of elements in each list.
1319pub fn bound_list_length(input: BoundExpression) -> BoundExpression {
1320    ListLength
1321        .try_new_bound_expr(EmptyOptions, [input])
1322        .vortex_expect("list-length expressions require a list child")
1323}
1324
1325// ---- ListSum ----
1326
1327/// Creates an expression that sums the elements of each list for `List` and
1328/// `FixedSizeList` inputs, akin to DuckDB's `list_sum()`.
1329///
1330/// Follows SQL `SUM` semantics per list: null lists, empty lists, and lists whose elements are
1331/// all null yield null; null elements are skipped; integer and decimal overflow yields a null
1332/// value. The result dtype follows `sum`'s widening rules and is always nullable. NaN float
1333/// elements are skipped by default; see [`list_sum_opts`] for the NaN-including variant.
1334///
1335/// ```rust
1336/// # use vortex_array::expr::{list_sum, root};
1337/// let expr = list_sum(root());
1338/// ```
1339pub fn list_sum(input: Expression) -> Expression {
1340    ListSum.new_expr(NumericalAggregateOpts::default(), [input])
1341}
1342
1343/// Creates a bound expression that sums the elements of each list.
1344pub fn bound_list_sum(input: BoundExpression) -> BoundExpression {
1345    ListSum
1346        .try_new_bound_expr(NumericalAggregateOpts::default(), [input])
1347        .vortex_expect("list-sum expressions require a numeric list child")
1348}
1349
1350/// Creates a [`list_sum`] expression with explicit [`NumericalAggregateOpts`], controlling
1351/// whether NaN float elements are skipped (the default) or poison the list's sum to NaN.
1352pub fn list_sum_opts(input: Expression, options: NumericalAggregateOpts) -> Expression {
1353    ListSum.new_expr(options, [input])
1354}
1355
1356/// Creates a bound list-sum expression with explicit aggregate options.
1357pub fn bound_list_sum_opts(
1358    input: BoundExpression,
1359    options: NumericalAggregateOpts,
1360) -> BoundExpression {
1361    ListSum
1362        .try_new_bound_expr(options, [input])
1363        .vortex_expect("list-sum expressions require a numeric list child")
1364}
1365
1366/// Reinterpret timestamp wall times in `options.time_zone`, preserving the time unit.
1367///
1368/// `ambiguous` is a UTF-8 expression containing `raise`, `earliest`, `latest`, or `null`.
1369/// Removing the timezone preserves wall time in a timezone-naive timestamp.
1370pub fn replace_time_zone(
1371    input: Expression,
1372    ambiguous: Expression,
1373    options: ReplaceTimeZoneOptions,
1374) -> Expression {
1375    ReplaceTimeZone.new_expr(options, [input, ambiguous])
1376}
1377
1378/// Create a bound timezone replacement expression.
1379pub fn bound_replace_time_zone(
1380    input: BoundExpression,
1381    ambiguous: BoundExpression,
1382    options: ReplaceTimeZoneOptions,
1383) -> BoundExpression {
1384    ReplaceTimeZone
1385        .try_new_bound_expr(options, [input, ambiguous])
1386        .vortex_expect("timezone replacement requires a timestamp and a UTF-8 ambiguity policy")
1387}
1388
1389/// Constructors for expressions whose children have already been bound and type-checked.
1390///
1391/// These mirror the constructors in [`crate::expr`] and panic when the supplied children do not
1392/// form a well-typed expression. Use [`BoundExpression::try_new`] when construction must be
1393/// fallible.
1394pub mod bound {
1395    pub use super::bound_and as and;
1396    pub use super::bound_and_collect as and_collect;
1397    pub use super::bound_between as between;
1398    pub use super::bound_binary as binary;
1399    pub use super::bound_byte_length as byte_length;
1400    pub use super::bound_case_when as case_when;
1401    pub use super::bound_case_when_no_else as case_when_no_else;
1402    pub use super::bound_cast as cast;
1403    pub use super::bound_checked_add as checked_add;
1404    pub use super::bound_col as col;
1405    pub use super::bound_dynamic as dynamic;
1406    pub use super::bound_dynamic_with_options as dynamic_with_options;
1407    pub use super::bound_eq as eq;
1408    pub use super::bound_ext_storage as ext_storage;
1409    pub use super::bound_fill_null as fill_null;
1410    pub use super::bound_get_item as get_item;
1411    pub use super::bound_gt as gt;
1412    pub use super::bound_gt_eq as gt_eq;
1413    pub use super::bound_ilike as ilike;
1414    pub use super::bound_in_list as in_list;
1415    pub use super::bound_is_nan as is_nan;
1416    pub use super::bound_is_not_null as is_not_null;
1417    pub use super::bound_is_null as is_null;
1418    pub use super::bound_like as like;
1419    pub use super::bound_list_contains as list_contains;
1420    pub use super::bound_list_length as list_length;
1421    pub use super::bound_list_sum as list_sum;
1422    pub use super::bound_list_sum_opts as list_sum_opts;
1423    pub use super::bound_lit as lit;
1424    pub use super::bound_lt as lt;
1425    pub use super::bound_lt_eq as lt_eq;
1426    pub use super::bound_mask as mask;
1427    pub use super::bound_merge as merge;
1428    pub use super::bound_merge_opts as merge_opts;
1429    pub use super::bound_nested_case_when as nested_case_when;
1430    pub use super::bound_not as not;
1431    pub use super::bound_not_eq as not_eq;
1432    pub use super::bound_not_ilike as not_ilike;
1433    pub use super::bound_not_like as not_like;
1434    pub use super::bound_or as or;
1435    pub use super::bound_or_collect as or_collect;
1436    pub use super::bound_pack as pack;
1437    pub use super::bound_replace_time_zone as replace_time_zone;
1438    pub use super::bound_root as root;
1439    pub use super::bound_select as select;
1440    pub use super::bound_select_exclude as select_exclude;
1441    pub use super::bound_variant_get as variant_get;
1442    pub use super::bound_zip_expr as zip_expr;
1443}