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

vortex-data / vortex / 16935267080

13 Aug 2025 11:00AM UTC coverage: 24.312% (-63.3%) from 87.658%
16935267080

Pull #4226

github

web-flow
Merge 81b48c7fb into baa6ea202
Pull Request #4226: Support converting TimestampTZ to and from duckdb

0 of 2 new or added lines in 1 file covered. (0.0%)

20666 existing lines in 469 files now uncovered.

8726 of 35892 relevant lines covered (24.31%)

147.74 hits per line

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

11.83
/vortex-expr/src/transform/match_between.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use vortex_array::compute::{BetweenOptions, StrictComparison};
5

6
use crate::forms::conjuncts;
7
use crate::{
8
    BetweenExpr, BinaryVTable, ExprRef, GetItemExpr, IntoExpr, LiteralExpr, Operator, and, lit,
9
};
10

11
/// This pass looks for expression of the form
12
///      `x >= a && x < b` and converts them into x between a and b`
13
pub fn find_between(expr: ExprRef) -> ExprRef {
28✔
14
    // We search all pairs of cnfs to find any pair of expressions can be converted into a between
15
    // expression.
16
    let mut conjuncts = conjuncts(&expr);
28✔
17
    let mut rest = vec![];
28✔
18

19
    for idx in 0..conjuncts.len() {
28✔
20
        let Some(c) = conjuncts.get(idx).cloned() else {
28✔
UNCOV
21
            continue;
×
22
        };
23
        let mut matched = false;
28✔
24
        for idx2 in (idx + 1)..conjuncts.len() {
28✔
25
            // Since values are removed in iterations there might not be a value at idx2,
26
            // but all values will have been considered.
UNCOV
27
            let Some(c2) = conjuncts.get(idx2) else {
×
28
                continue;
×
29
            };
UNCOV
30
            if let Some(expr) = maybe_match(&c, c2) {
×
UNCOV
31
                rest.push(expr);
×
UNCOV
32
                conjuncts.remove(idx2);
×
UNCOV
33
                matched = true;
×
UNCOV
34
                break;
×
UNCOV
35
            }
×
36
        }
37
        if !matched {
28✔
38
            rest.push(c.clone())
28✔
UNCOV
39
        }
×
40
    }
41

42
    rest.into_iter().reduce(and).unwrap_or_else(|| lit(true))
28✔
43
}
28✔
44

UNCOV
45
fn maybe_match(lhs: &ExprRef, rhs: &ExprRef) -> Option<ExprRef> {
×
UNCOV
46
    let (Some(lhs), Some(rhs)) = (lhs.as_opt::<BinaryVTable>(), rhs.as_opt::<BinaryVTable>())
×
47
    else {
UNCOV
48
        return None;
×
49
    };
50

51
    // Cannot compare to self
UNCOV
52
    if lhs.lhs().eq(lhs.rhs()) || rhs.lhs().eq(rhs.rhs()) {
×
53
        return None;
×
UNCOV
54
    }
×
55

56
    // Extract pairs of comparison of the form (left left_op eq) and (eq right_op right)
UNCOV
57
    let (eq, left, left_op, right, right_op) =
×
UNCOV
58
        if GetItemExpr::is(lhs.lhs()) && lhs.lhs().eq(rhs.lhs()) {
×
UNCOV
59
            (
×
UNCOV
60
                lhs.lhs().clone(),
×
UNCOV
61
                lhs.rhs().clone(),
×
UNCOV
62
                lhs.op().swap(),
×
UNCOV
63
                rhs.rhs().clone(),
×
UNCOV
64
                rhs.op(),
×
UNCOV
65
            )
×
UNCOV
66
        } else if GetItemExpr::is(lhs.lhs()) && lhs.lhs().eq(rhs.rhs()) {
×
UNCOV
67
            (
×
UNCOV
68
                lhs.lhs().clone(),
×
UNCOV
69
                lhs.rhs().clone(),
×
UNCOV
70
                lhs.op().swap(),
×
UNCOV
71
                rhs.lhs().clone(),
×
UNCOV
72
                rhs.op().swap(),
×
UNCOV
73
            )
×
UNCOV
74
        } else if GetItemExpr::is(lhs.rhs()) && lhs.rhs().eq(rhs.lhs()) {
×
UNCOV
75
            (
×
UNCOV
76
                lhs.rhs().clone(),
×
UNCOV
77
                lhs.lhs().clone(),
×
UNCOV
78
                lhs.op(),
×
UNCOV
79
                rhs.rhs().clone(),
×
UNCOV
80
                rhs.op(),
×
UNCOV
81
            )
×
UNCOV
82
        } else if GetItemExpr::is(lhs.rhs()) && lhs.rhs().eq(rhs.rhs()) {
×
UNCOV
83
            (
×
UNCOV
84
                lhs.rhs().clone(),
×
UNCOV
85
                lhs.lhs().clone(),
×
UNCOV
86
                lhs.op(),
×
UNCOV
87
                rhs.lhs().clone(),
×
UNCOV
88
                rhs.op().swap(),
×
UNCOV
89
            )
×
90
        } else {
UNCOV
91
            return None;
×
92
        };
93

94
    // Find the greater op.
UNCOV
95
    let (Some(left_lit), Some(right_lit)) = (
×
UNCOV
96
        LiteralExpr::maybe_from(&left),
×
UNCOV
97
        LiteralExpr::maybe_from(&right),
×
98
    ) else {
UNCOV
99
        return None;
×
100
    };
101

UNCOV
102
    let (left, left_op, right, right_op) = if left_lit.value() > right_lit.value() {
×
UNCOV
103
        (right, right_op, left, left_op)
×
104
    } else {
UNCOV
105
        (left, left_op, right, right_op)
×
106
    };
107

108
    // Check if the operators form an inequality.
UNCOV
109
    let (left_op, right_op) = if let (Some(left_op), Some(right_op)) = (
×
UNCOV
110
        maybe_strict_comparison(left_op),
×
UNCOV
111
        maybe_strict_comparison(right_op),
×
112
    ) {
UNCOV
113
        (left_op, right_op)
×
UNCOV
114
    } else if let (Some(left_op), Some(right_op)) = (
×
UNCOV
115
        maybe_strict_comparison(left_op.swap()),
×
UNCOV
116
        maybe_strict_comparison(right_op.swap()),
×
117
    ) {
UNCOV
118
        (left_op, right_op)
×
119
    } else {
120
        return None;
×
121
    };
122

UNCOV
123
    let expr = BetweenExpr::new(
×
UNCOV
124
        eq.clone(),
×
UNCOV
125
        left,
×
UNCOV
126
        right,
×
UNCOV
127
        BetweenOptions {
×
UNCOV
128
            lower_strict: left_op,
×
UNCOV
129
            upper_strict: right_op,
×
UNCOV
130
        },
×
131
    );
UNCOV
132
    Some(expr.into_expr())
×
UNCOV
133
}
×
134

UNCOV
135
fn maybe_strict_comparison(op: Operator) -> Option<StrictComparison> {
×
UNCOV
136
    match op {
×
UNCOV
137
        Operator::Lt => Some(StrictComparison::Strict),
×
UNCOV
138
        Operator::Lte => Some(StrictComparison::NonStrict),
×
UNCOV
139
        _ => None,
×
140
    }
UNCOV
141
}
×
142

143
#[cfg(test)]
144
mod tests {
145
    use vortex_array::compute::{BetweenOptions, StrictComparison};
146

147
    use crate::transform::match_between::find_between;
148
    use crate::{and, between, col, gt_eq, lit, lt};
149

150
    #[test]
151
    fn test_match_between() {
152
        let expr = and(lt(lit(2), col("x")), gt_eq(lit(5), col("x")));
153
        let find = find_between(expr);
154

155
        // 2 < x <= 5
156
        assert_eq!(
157
            &between(
158
                col("x"),
159
                lit(2),
160
                lit(5),
161
                BetweenOptions {
162
                    lower_strict: StrictComparison::Strict,
163
                    upper_strict: StrictComparison::NonStrict,
164
                }
165
            ),
166
            &find
167
        );
168
    }
169

170
    #[test]
171
    fn test_match_2_between() {
172
        let expr = and(gt_eq(col("x"), lit(2)), lt(col("x"), lit(5)));
173
        let find = find_between(expr);
174

175
        // 2 <= x < 5
176
        assert_eq!(
177
            &between(
178
                col("x"),
179
                lit(2),
180
                lit(5),
181
                BetweenOptions {
182
                    lower_strict: StrictComparison::NonStrict,
183
                    upper_strict: StrictComparison::Strict,
184
                }
185
            ),
186
            &find
187
        );
188
    }
189

190
    #[test]
191
    fn test_match_3_between() {
192
        let expr = and(gt_eq(col("x"), lit(2)), gt_eq(lit(5), col("x")));
193
        let find = find_between(expr);
194

195
        // 2 <= x < 5
196
        assert_eq!(
197
            &between(
198
                col("x"),
199
                lit(2),
200
                lit(5),
201
                BetweenOptions {
202
                    lower_strict: StrictComparison::NonStrict,
203
                    upper_strict: StrictComparison::NonStrict,
204
                }
205
            ),
206
            &find
207
        );
208
    }
209

210
    #[test]
211
    fn test_match_4_between() {
212
        let expr = and(gt_eq(lit(5), col("x")), lt(lit(2), col("x")));
213
        let find = find_between(expr);
214

215
        // 2 < x <= 5
216
        assert_eq!(
217
            &between(
218
                col("x"),
219
                lit(2),
220
                lit(5),
221
                BetweenOptions {
222
                    lower_strict: StrictComparison::Strict,
223
                    upper_strict: StrictComparison::NonStrict,
224
                }
225
            ),
226
            &find
227
        );
228
    }
229

230
    #[test]
231
    fn test_match_5_between() {
232
        let expr = and(
233
            and(gt_eq(col("y"), lit(10)), gt_eq(lit(5), col("x"))),
234
            lt(lit(2), col("x")),
235
        );
236
        let find = find_between(expr);
237

238
        // $.y >= 10 /\ 2 < $.x <= 5
239
        assert_eq!(
240
            &and(
241
                gt_eq(col("y"), lit(10)),
242
                between(
243
                    col("x"),
244
                    lit(2),
245
                    lit(5),
246
                    BetweenOptions {
247
                        lower_strict: StrictComparison::Strict,
248
                        upper_strict: StrictComparison::NonStrict,
249
                    }
250
                )
251
            ),
252
            &find
253
        );
254
    }
255

256
    #[test]
257
    fn test_match_6_between() {
258
        let expr = and(
259
            and(gt_eq(lit(5), col("x")), gt_eq(col("y"), lit(10))),
260
            lt(lit(2), col("x")),
261
        );
262
        let find = find_between(expr);
263

264
        // $.y >= 10 /\ 2 < $.x <= 5
265
        assert_eq!(
266
            &and(
267
                between(
268
                    col("x"),
269
                    lit(2),
270
                    lit(5),
271
                    BetweenOptions {
272
                        lower_strict: StrictComparison::Strict,
273
                        upper_strict: StrictComparison::NonStrict,
274
                    }
275
                ),
276
                gt_eq(col("y"), lit(10)),
277
            ),
278
            &find
279
        );
280
    }
281
}
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