• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16481624455

23 Jul 2025 08:55PM UTC coverage: 81.413% (+0.3%) from 81.07%
16481624455

Pull #3973

github

web-flow
Merge 8cba7d598 into 2ddcfbf30
Pull Request #3973: fix: Pruning expressions check NanCount where appropriate

223 of 234 new or added lines in 14 files covered. (95.3%)

33 existing lines in 3 files now uncovered.

42531 of 52241 relevant lines covered (81.41%)

172575.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.19
/vortex-expr/src/exprs/binary.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::fmt::Display;
5
use std::hash::Hash;
6

7
use vortex_array::compute::{Operator as ArrayOperator, add, and_kleene, compare, or_kleene};
8
use vortex_array::{ArrayRef, DeserializeMetadata, ProstMetadata};
9
use vortex_dtype::DType;
10
use vortex_error::{VortexResult, vortex_bail};
11
use vortex_proto::expr as pb;
12

13
use crate::{
14
    AnalysisExpr, ExprEncodingRef, ExprId, ExprRef, IntoExpr, Operator, Scope, StatsCatalog,
15
    VTable, vtable,
16
};
17

18
vtable!(Binary);
19

20
#[allow(clippy::derived_hash_with_manual_eq)]
21
#[derive(Debug, Clone, Hash)]
22
pub struct BinaryExpr {
23
    lhs: ExprRef,
24
    operator: Operator,
25
    rhs: ExprRef,
26
}
27

28
impl PartialEq for BinaryExpr {
29
    fn eq(&self, other: &Self) -> bool {
18,270✔
30
        self.lhs.eq(&other.lhs) && self.operator == other.operator && self.rhs.eq(&other.rhs)
18,270✔
31
    }
18,270✔
32
}
33

34
pub struct BinaryExprEncoding;
35

36
impl VTable for BinaryVTable {
37
    type Expr = BinaryExpr;
38
    type Encoding = BinaryExprEncoding;
39
    type Metadata = ProstMetadata<pb::BinaryOpts>;
40

41
    fn id(_encoding: &Self::Encoding) -> ExprId {
131✔
42
        ExprId::new_ref("binary")
131✔
43
    }
131✔
44

45
    fn encoding(_expr: &Self::Expr) -> ExprEncodingRef {
3✔
46
        ExprEncodingRef::new_ref(BinaryExprEncoding.as_ref())
3✔
47
    }
3✔
48

49
    fn metadata(expr: &Self::Expr) -> Option<Self::Metadata> {
3✔
50
        Some(ProstMetadata(pb::BinaryOpts {
3✔
51
            op: expr.operator.into(),
3✔
52
        }))
3✔
53
    }
3✔
54

55
    fn children(expr: &Self::Expr) -> Vec<&ExprRef> {
44,013✔
56
        vec![expr.lhs(), expr.rhs()]
44,013✔
57
    }
44,013✔
58

59
    fn with_children(expr: &Self::Expr, children: Vec<ExprRef>) -> VortexResult<Self::Expr> {
3,226✔
60
        Ok(BinaryExpr::new(
3,226✔
61
            children[0].clone(),
3,226✔
62
            expr.op(),
3,226✔
63
            children[1].clone(),
3,226✔
64
        ))
3,226✔
65
    }
3,226✔
66

67
    fn build(
3✔
68
        _encoding: &Self::Encoding,
3✔
69
        metadata: &<Self::Metadata as DeserializeMetadata>::Output,
3✔
70
        children: Vec<ExprRef>,
3✔
71
    ) -> VortexResult<Self::Expr> {
3✔
72
        Ok(BinaryExpr::new(
3✔
73
            children[0].clone(),
3✔
74
            metadata.op().into(),
3✔
75
            children[1].clone(),
3✔
76
        ))
3✔
77
    }
3✔
78

79
    fn evaluate(expr: &Self::Expr, scope: &Scope) -> VortexResult<ArrayRef> {
8,168✔
80
        let lhs = expr.lhs.unchecked_evaluate(scope)?;
8,168✔
81
        let rhs = expr.rhs.unchecked_evaluate(scope)?;
8,168✔
82

83
        match expr.operator {
8,168✔
84
            Operator::Eq => compare(&lhs, &rhs, ArrayOperator::Eq),
412✔
85
            Operator::NotEq => compare(&lhs, &rhs, ArrayOperator::NotEq),
4✔
86
            Operator::Lt => compare(&lhs, &rhs, ArrayOperator::Lt),
1,541✔
87
            Operator::Lte => compare(&lhs, &rhs, ArrayOperator::Lte),
806✔
88
            Operator::Gt => compare(&lhs, &rhs, ArrayOperator::Gt),
2,127✔
89
            Operator::Gte => compare(&lhs, &rhs, ArrayOperator::Gte),
828✔
90
            Operator::And => and_kleene(&lhs, &rhs),
870✔
91
            Operator::Or => or_kleene(&lhs, &rhs),
1,580✔
UNCOV
92
            Operator::Add => add(&lhs, &rhs),
×
93
        }
94
    }
8,168✔
95

96
    fn return_dtype(expr: &Self::Expr, scope: &DType) -> VortexResult<DType> {
10,392✔
97
        let lhs = expr.lhs.return_dtype(scope)?;
10,392✔
98
        let rhs = expr.rhs.return_dtype(scope)?;
10,392✔
99

100
        if expr.operator == Operator::Add {
10,392✔
UNCOV
101
            if lhs.is_primitive() && lhs.eq_ignore_nullability(&rhs) {
×
102
                return Ok(lhs.with_nullability(lhs.nullability() | rhs.nullability()));
×
UNCOV
103
            }
×
UNCOV
104
            vortex_bail!("incompatible types for checked add: {} {}", lhs, rhs);
×
105
        }
10,392✔
106

107
        Ok(DType::Bool((lhs.is_nullable() || rhs.is_nullable()).into()))
10,392✔
108
    }
10,392✔
109
}
110

111
impl BinaryExpr {
112
    pub fn new(lhs: ExprRef, operator: Operator, rhs: ExprRef) -> Self {
12,759✔
113
        Self { lhs, operator, rhs }
12,759✔
114
    }
12,759✔
115

116
    pub fn new_expr(lhs: ExprRef, operator: Operator, rhs: ExprRef) -> ExprRef {
494✔
117
        Self::new(lhs, operator, rhs).into_expr()
494✔
118
    }
494✔
119

120
    pub fn lhs(&self) -> &ExprRef {
49,449✔
121
        &self.lhs
49,449✔
122
    }
49,449✔
123

124
    pub fn rhs(&self) -> &ExprRef {
48,658✔
125
        &self.rhs
48,658✔
126
    }
48,658✔
127

128
    pub fn op(&self) -> Operator {
11,008✔
129
        self.operator
11,008✔
130
    }
11,008✔
131
}
132

133
impl Display for BinaryExpr {
134
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1,907✔
135
        write!(f, "({} {} {})", self.lhs, self.operator, self.rhs)
1,907✔
136
    }
1,907✔
137
}
138

139
impl AnalysisExpr for BinaryExpr {
140
    fn stat_falsification(&self, catalog: &mut dyn StatsCatalog) -> Option<ExprRef> {
1,237✔
141
        match self.operator {
371✔
142
            Operator::Eq => {
143
                let min_lhs = self.lhs.min(catalog);
128✔
144
                let max_lhs = self.lhs.max(catalog);
128✔
145

146
                let min_rhs = self.rhs.min(catalog);
128✔
147
                let max_rhs = self.rhs.max(catalog);
128✔
148

149
                let left = min_lhs.zip(max_rhs).map(|(a, b)| gt(a, b));
128✔
150
                let right = min_rhs.zip(max_lhs).map(|(a, b)| gt(a, b));
128✔
151
                left.into_iter().chain(right).reduce(or)
128✔
152
            }
153
            Operator::NotEq => {
154
                let min_lhs = self.lhs.min(catalog)?;
1✔
155
                let max_lhs = self.lhs.max(catalog)?;
663✔
156

662✔
157
                let min_rhs = self.rhs.min(catalog)?;
663✔
158
                let max_rhs = self.rhs.max(catalog)?;
663✔
159

662✔
160
                Some(and(eq(min_lhs, max_rhs), eq(max_lhs, min_rhs)))
663✔
161
            }
662✔
162
            Operator::Gt => Some(lt_eq(self.lhs.max(catalog)?, self.rhs.min(catalog)?)),
813✔
163
            Operator::Gte => Some(lt(self.lhs.max(catalog)?, self.rhs.min(catalog)?)),
705✔
164
            Operator::Lt => Some(gt_eq(self.lhs.min(catalog)?, self.rhs.max(catalog)?)),
666✔
165
            Operator::Lte => Some(gt(self.lhs.min(catalog)?, self.rhs.max(catalog)?)),
683✔
166
            Operator::And => self
684✔
167
                .lhs
22✔
168
                .stat_falsification(catalog)
684✔
169
                .into_iter()
22✔
170
                .chain(self.rhs.stat_falsification(catalog))
22✔
171
                .reduce(or),
684✔
172
            Operator::Or => Some(and(
1✔
173
                self.lhs.stat_falsification(catalog)?,
663✔
174
                self.rhs.stat_falsification(catalog)?,
1✔
175
            )),
866✔
176
            Operator::Add => None,
177
        }
162✔
178
    }
533✔
179
}
180

162✔
181
/// Create a new `BinaryExpr` using the `Eq` operator.
162✔
182
///
183
/// ## Example usage
162✔
184
///
162✔
185
/// ```
186
/// use vortex_array::arrays::{BoolArray, PrimitiveArray };
162✔
187
/// use vortex_array::{Array, IntoArray, ToCanonical};
188
/// use vortex_array::validity::Validity;
189
/// use vortex_buffer::buffer;
162✔
190
/// use vortex_expr::{eq, root, lit, Scope};
162✔
191
///
162✔
192
/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
162✔
193
/// let result = eq(root(), lit(3)).evaluate(&Scope::new(xs.to_array())).unwrap();
162✔
194
///
162✔
195
/// assert_eq!(
196
///     result.to_bool().unwrap().boolean_buffer(),
197
///     BoolArray::from_iter(vec![false, false, true]).boolean_buffer(),
6✔
198
/// );
6✔
199
/// ```
200
pub fn eq(lhs: ExprRef, rhs: ExprRef) -> ExprRef {
310✔
201
    BinaryExpr::new(lhs, Operator::Eq, rhs).into_expr()
310✔
202
}
304✔
203

6✔
204
/// Create a new `BinaryExpr` using the `NotEq` operator.
205
///
6✔
206
/// ## Example usage
6✔
207
///
6✔
208
/// ```
6✔
209
/// use vortex_array::arrays::{BoolArray, PrimitiveArray };
6✔
210
/// use vortex_array::{IntoArray, ToCanonical};
6✔
211
/// use vortex_array::validity::Validity;
212
/// use vortex_buffer::buffer;
213
/// use vortex_expr::{root, lit, not_eq, Scope};
14✔
214
///
215
/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
14✔
216
/// let result = not_eq(root(), lit(3)).evaluate(&Scope::new(xs.to_array())).unwrap();
14✔
217
///
14✔
218
/// assert_eq!(
14✔
219
///     result.to_bool().unwrap().boolean_buffer(),
14✔
220
///     BoolArray::from_iter(vec![true, true, false]).boolean_buffer(),
14✔
221
/// );
222
/// ```
223
pub fn not_eq(lhs: ExprRef, rhs: ExprRef) -> ExprRef {
110✔
224
    BinaryExpr::new(lhs, Operator::NotEq, rhs).into_expr()
336✔
225
}
110✔
226

226✔
227
/// Create a new `BinaryExpr` using the `Gte` operator.
226✔
228
///
226✔
229
/// ## Example usage
226✔
230
///
226✔
231
/// ```
226✔
232
/// use vortex_array::arrays::{BoolArray, PrimitiveArray };
233
/// use vortex_array::{IntoArray, ToCanonical};
234
/// use vortex_array::validity::Validity;
235
/// use vortex_buffer::buffer;
48✔
236
/// use vortex_expr::{gt_eq, root, lit, Scope};
237
///
48✔
238
/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
48✔
239
/// let result = gt_eq(root(), lit(3)).evaluate(&Scope::new(xs.to_array())).unwrap();
48✔
240
///
48✔
241
/// assert_eq!(
48✔
242
///     result.to_bool().unwrap().boolean_buffer(),
48✔
243
///     BoolArray::from_iter(vec![false, false, true]).boolean_buffer(),
244
/// );
245
/// ```
246
pub fn gt_eq(lhs: ExprRef, rhs: ExprRef) -> ExprRef {
359✔
247
    BinaryExpr::new(lhs, Operator::Gte, rhs).into_expr()
153✔
248
}
359✔
249

206✔
250
/// Create a new `BinaryExpr` using the `Gt` operator.
206✔
251
///
206✔
252
/// ## Example usage
206✔
253
///
206✔
254
/// ```
255
/// use vortex_array::arrays::{BoolArray, PrimitiveArray };
188✔
256
/// use vortex_array::{IntoArray, ToCanonical};
188✔
257
/// use vortex_array::validity::Validity;
188✔
258
/// use vortex_buffer::buffer;
188✔
259
/// use vortex_expr::{gt, root, lit, Scope};
188✔
260
///
188✔
261
/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
16✔
262
/// let result = gt(root(), lit(2)).evaluate(&Scope::new(xs.to_array())).unwrap();
16✔
263
///
16✔
264
/// assert_eq!(
265
///     result.to_bool().unwrap().boolean_buffer(),
266
///     BoolArray::from_iter(vec![false, false, true]).boolean_buffer(),
267
/// );
866✔
268
/// ```
269
pub fn gt(lhs: ExprRef, rhs: ExprRef) -> ExprRef {
651✔
270
    BinaryExpr::new(lhs, Operator::Gt, rhs).into_expr()
651✔
271
}
651✔
272

273
/// Create a new `BinaryExpr` using the `Lte` operator.
274
///
275
/// ## Example usage
276
///
277
/// ```
278
/// use vortex_array::arrays::{BoolArray, PrimitiveArray };
279
/// use vortex_array::{IntoArray, ToCanonical};
280
/// use vortex_array::validity::Validity;
281
/// use vortex_buffer::buffer;
282
/// use vortex_expr::{root, lit, lt_eq, Scope};
283
///
284
/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
285
/// let result = lt_eq(root(), lit(2)).evaluate(&Scope::new(xs.to_array())).unwrap();
286
///
287
/// assert_eq!(
288
///     result.to_bool().unwrap().boolean_buffer(),
289
///     BoolArray::from_iter(vec![true, true, false]).boolean_buffer(),
12✔
290
/// );
12✔
291
/// ```
12✔
292
pub fn lt_eq(lhs: ExprRef, rhs: ExprRef) -> ExprRef {
220✔
293
    BinaryExpr::new(lhs, Operator::Lte, rhs).into_expr()
220✔
294
}
220✔
295

296
/// Create a new `BinaryExpr` using the `Lt` operator.
297
///
298
/// ## Example usage
299
///
300
/// ```
301
/// use vortex_array::arrays::{BoolArray, PrimitiveArray };
302
/// use vortex_array::{IntoArray, ToCanonical};
303
/// use vortex_array::validity::Validity;
304
/// use vortex_buffer::buffer;
305
/// use vortex_expr::{root, lit, lt, Scope};
306
///
307
/// let xs = PrimitiveArray::new(buffer![1i32, 2i32, 3i32], Validity::NonNullable);
308
/// let result = lt(root(), lit(3)).evaluate(&Scope::new(xs.to_array())).unwrap();
309
///
310
/// assert_eq!(
311
///     result.to_bool().unwrap().boolean_buffer(),
312
///     BoolArray::from_iter(vec![true, true, false]).boolean_buffer(),
313
/// );
314
/// ```
315
pub fn lt(lhs: ExprRef, rhs: ExprRef) -> ExprRef {
272✔
316
    BinaryExpr::new(lhs, Operator::Lt, rhs).into_expr()
272✔
317
}
272✔
318

319
/// Create a new `BinaryExpr` using the `Or` operator.
320
///
321
/// ## Example usage
322
///
323
/// ```
324
/// use vortex_array::arrays::BoolArray;
325
/// use vortex_array::{IntoArray, ToCanonical};
326
/// use vortex_expr::{root, lit, or, Scope};
327
///
328
/// let xs = BoolArray::from_iter(vec![true, false, true]);
329
/// let result = or(root(), lit(false)).evaluate(&Scope::new(xs.to_array())).unwrap();
330
///
331
/// assert_eq!(
332
///     result.to_bool().unwrap().boolean_buffer(),
333
///     BoolArray::from_iter(vec![true, false, true]).boolean_buffer(),
334
/// );
335
/// ```
48✔
336
pub fn or(lhs: ExprRef, rhs: ExprRef) -> ExprRef {
1,857✔
337
    BinaryExpr::new(lhs, Operator::Or, rhs).into_expr()
1,857✔
338
}
1,809✔
339

340
/// Collects a list of `or`ed values into a single vortex, expr
341
/// [x, y, z] => x or (y or z)
342
pub fn or_collect<I>(iter: I) -> Option<ExprRef>
343
where
344
    I: IntoIterator<Item = ExprRef>,
345
    I::IntoIter: DoubleEndedIterator<Item = ExprRef>,
346
{
347
    let mut iter = iter.into_iter();
348
    let first = iter.next_back()?;
349
    Some(iter.rfold(first, |acc, elem| or(elem, acc)))
350
}
351

352
/// Create a new `BinaryExpr` using the `And` operator.
353
///
354
/// ## Example usage
355
///
356
/// ```
357
/// use vortex_array::arrays::BoolArray;
358
/// use vortex_array::{IntoArray, ToCanonical};
1,346✔
359
/// use vortex_expr::{and, root, lit, Scope};
1,346✔
360
///
1,346✔
361
/// let xs = BoolArray::from_iter(vec![true, false, true]);
362
/// let result = and(root(), lit(true)).evaluate(&Scope::new(xs.to_array())).unwrap();
363
///
364
/// assert_eq!(
365
///     result.to_bool().unwrap().boolean_buffer(),
366
///     BoolArray::from_iter(vec![true, false, true]).boolean_buffer(),
367
/// );
368
/// ```
369
pub fn and(lhs: ExprRef, rhs: ExprRef) -> ExprRef {
368✔
370
    BinaryExpr::new(lhs, Operator::And, rhs).into_expr()
368✔
371
}
368✔
372

373
/// Collects a list of `and`ed values into a single vortex, expr
374
/// [x, y, z] => x and (y and z)
375
pub fn and_collect<I>(iter: I) -> Option<ExprRef>
232✔
376
where
232✔
377
    I: IntoIterator<Item = ExprRef>,
232✔
378
    I::IntoIter: DoubleEndedIterator<Item = ExprRef>,
232✔
379
{
380
    let mut iter = iter.into_iter();
232✔
381
    let first = iter.next_back()?;
246✔
382
    Some(iter.rfold(first, |acc, elem| and(elem, acc)))
141✔
383
}
246✔
384

385
/// Collects a list of `and`ed values into a single vortex, expr
386
/// [x, y, z] => x and (y and z)
387
pub fn and_collect_right<I>(iter: I) -> Option<ExprRef>
1✔
388
where
1✔
389
    I: IntoIterator<Item = ExprRef>,
1✔
390
{
391
    let iter = iter.into_iter();
1✔
392
    iter.reduce(and)
1✔
393
}
1✔
394

395
/// Create a new `BinaryExpr` using the `CheckedAdd` operator.
396
///
397
/// ## Example usage
398
///
399
/// ```
400
/// use vortex_array::IntoArray;
401
/// use vortex_array::arrow::IntoArrowArray as _;
402
/// use vortex_buffer::buffer;
403
/// use vortex_expr::{Scope, checked_add, lit, root};
404
///
1,042✔
405
/// let xs = buffer![1, 2, 3].into_array();
1,042✔
406
/// let result = checked_add(root(), lit(5))
1,042✔
407
///     .evaluate(&Scope::new(xs.to_array()))
408
///     .unwrap();
409
///
410
/// assert_eq!(
411
///     &result.into_arrow_preferred().unwrap(),
412
///     &buffer![6, 7, 8]
413
///         .into_array()
414
///         .into_arrow_preferred()
415
///         .unwrap()
416
/// );
417
/// ```
418
pub fn checked_add(lhs: ExprRef, rhs: ExprRef) -> ExprRef {
419
    BinaryExpr::new(lhs, Operator::Add, rhs).into_expr()
420
}
421

422
#[cfg(test)]
423
mod tests {
424
    use std::sync::Arc;
425

1,352✔
426
    use vortex_dtype::{DType, Nullability};
1,352✔
427

1,352✔
428
    use crate::{
429
        VortexExpr, and, and_collect, and_collect_right, col, eq, gt, gt_eq, lit, lt, lt_eq,
430
        not_eq, or, test_harness,
431
    };
4✔
432

4✔
433
    #[test]
4✔
434
    fn and_collect_left_assoc() {
5✔
435
        let values = vec![lit(1), lit(2), lit(3)];
1✔
436
        assert_eq!(
5✔
437
            Some(and(lit(1), and(lit(2), lit(3)))),
5✔
438
            and_collect(values.into_iter())
5✔
439
        );
4✔
440
    }
1✔
441

442
    #[test]
443
    fn and_collect_right_assoc() {
1✔
444
        let values = vec![lit(1), lit(2), lit(3)];
1✔
445
        assert_eq!(
1✔
446
            Some(and(and(lit(1), lit(2)), lit(3))),
1✔
447
            and_collect_right(values.into_iter())
1✔
448
        );
449
    }
1✔
450

451
    #[test]
452
    fn dtype() {
1✔
453
        let dtype = test_harness::struct_dtype();
1✔
454
        let bool1: Arc<dyn VortexExpr> = col("bool1");
1✔
455
        let bool2: Arc<dyn VortexExpr> = col("bool2");
1✔
456
        assert_eq!(
1✔
457
            and(bool1.clone(), bool2.clone())
1✔
458
                .return_dtype(&dtype)
1,327✔
459
                .unwrap(),
1,327✔
460
            DType::Bool(Nullability::NonNullable)
1,326✔
461
        );
462
        assert_eq!(
1✔
463
            or(bool1.clone(), bool2.clone())
1✔
464
                .return_dtype(&dtype)
273✔
465
                .unwrap(),
273✔
466
            DType::Bool(Nullability::NonNullable)
272✔
467
        );
272✔
468

469
        let col1: Arc<dyn VortexExpr> = col("col1");
273✔
470
        let col2: Arc<dyn VortexExpr> = col("col2");
273✔
471

172✔
472
        assert_eq!(
273✔
473
            eq(col1.clone(), col2.clone()).return_dtype(&dtype).unwrap(),
1✔
474
            DType::Bool(Nullability::Nullable)
475
        );
476
        assert_eq!(
1✔
477
            not_eq(col1.clone(), col2.clone())
1✔
478
                .return_dtype(&dtype)
1✔
479
                .unwrap(),
1✔
480
            DType::Bool(Nullability::Nullable)
481
        );
482
        assert_eq!(
1✔
483
            gt(col1.clone(), col2.clone()).return_dtype(&dtype).unwrap(),
1✔
484
            DType::Bool(Nullability::Nullable)
485
        );
486
        assert_eq!(
1✔
487
            gt_eq(col1.clone(), col2.clone())
1✔
488
                .return_dtype(&dtype)
1✔
489
                .unwrap(),
1✔
490
            DType::Bool(Nullability::Nullable)
491
        );
492
        assert_eq!(
1✔
493
            lt(col1.clone(), col2.clone()).return_dtype(&dtype).unwrap(),
1✔
494
            DType::Bool(Nullability::Nullable)
495
        );
496
        assert_eq!(
1✔
497
            lt_eq(col1.clone(), col2.clone())
1✔
498
                .return_dtype(&dtype)
1✔
499
                .unwrap(),
1✔
500
            DType::Bool(Nullability::Nullable)
501
        );
502

503
        assert_eq!(
1✔
504
            or(
1✔
505
                lt(col1.clone(), col2.clone()),
1✔
506
                not_eq(col1.clone(), col2.clone())
1✔
507
            )
1✔
508
            .return_dtype(&dtype)
1✔
509
            .unwrap(),
1✔
510
            DType::Bool(Nullability::Nullable)
511
        );
512
    }
1✔
513
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc