1use std::ptr;
7
8use vortex_error::VortexExpect;
9use vortex_error::VortexResult;
10
11use crate::expr::BoundExpression;
12use crate::scalar_fn::ScalarFnVTableExt;
13use crate::scalar_fn::fns::between::Between;
14use crate::scalar_fn::fns::between::BetweenOptions;
15use crate::scalar_fn::fns::between::StrictComparison;
16use crate::scalar_fn::fns::binary::Binary;
17use crate::scalar_fn::fns::get_item::GetItem;
18use crate::scalar_fn::fns::literal::Literal;
19use crate::scalar_fn::fns::operators::Operator;
20
21pub fn find_between(expr: BoundExpression) -> VortexResult<BoundExpression> {
28 Ok(try_find_between(&expr)?.unwrap_or(expr))
29}
30
31fn try_find_between(expr: &BoundExpression) -> VortexResult<Option<BoundExpression>> {
32 let children = expr.children();
33 let mut new_children: Option<Vec<BoundExpression>> = None;
34 for (idx, child) in children.iter().enumerate() {
35 if let Some(rewritten) = try_find_between(child)? {
36 new_children
37 .get_or_insert_with(|| children[..idx].to_vec())
38 .push(rewritten);
39 } else if let Some(new_children) = new_children.as_mut() {
40 new_children.push(child.clone());
41 }
42 }
43 let rewritten = new_children
44 .map(|children| expr.clone().with_children(children))
45 .transpose()?;
46
47 let current = rewritten.as_ref().unwrap_or(expr);
48 if is_and(current)
49 && let Some(merged) = merge_straddling_pairs(current)?
50 {
51 return Ok(Some(merged));
52 }
53 Ok(rewritten)
54}
55
56fn merge_straddling_pairs(expr: &BoundExpression) -> VortexResult<Option<BoundExpression>> {
63 let mut current: Option<BoundExpression> = None;
64
65 loop {
66 let expr = current.as_ref().unwrap_or(expr);
67 if !is_and(expr) {
68 break;
69 }
70 let lhs = expr.child(0);
71 let rhs = expr.child(1);
72
73 let Some((lower, upper, between)) = find_conjunct(rhs, &mut |rhs_conjunct| {
74 let rhs_cmp = RangeComparison::try_new(rhs_conjunct)?;
75 find_conjunct(lhs, &mut |lhs_conjunct| {
76 let lhs_cmp = RangeComparison::try_new(lhs_conjunct)?;
77 let between = lhs_cmp.between(&rhs_cmp)?;
78 Some((lhs_conjunct, rhs_conjunct, between))
79 })
80 }) else {
81 break;
82 };
83
84 tracing::debug!(%lower, %upper, "merged range comparisons into {between}");
85
86 let new_lhs = rewrite_conjunct(lhs, lower, Some(&between))?
87 .vortex_expect("matched conjunct is part of the lhs")
88 .vortex_expect("replaced conjunct is never removed");
89 let new_rhs = rewrite_conjunct(rhs, upper, None)?
90 .vortex_expect("matched conjunct is part of the rhs");
91
92 current = Some(match new_rhs {
93 Some(new_rhs) => expr.clone().with_children([new_lhs, new_rhs])?,
94 None => new_lhs,
95 });
96 }
97
98 Ok(current)
99}
100
101fn is_and(expr: &BoundExpression) -> bool {
102 expr.as_opt::<Binary>() == Some(&Operator::And)
103}
104
105fn find_conjunct<'a, T>(
107 expr: &'a BoundExpression,
108 f: &mut impl FnMut(&'a BoundExpression) -> Option<T>,
109) -> Option<T> {
110 if is_and(expr) {
111 find_conjunct(expr.child(0), f).or_else(|| find_conjunct(expr.child(1), f))
112 } else {
113 f(expr)
114 }
115}
116
117fn rewrite_conjunct(
123 expr: &BoundExpression,
124 target: &BoundExpression,
125 replacement: Option<&BoundExpression>,
126) -> VortexResult<Option<Option<BoundExpression>>> {
127 if ptr::eq(expr, target) {
128 return Ok(Some(replacement.cloned()));
129 }
130 if !is_and(expr) {
131 return Ok(None);
132 }
133
134 for idx in 0..2 {
135 let Some(new_child) = rewrite_conjunct(expr.child(idx), target, replacement)? else {
136 continue;
137 };
138 let sibling = expr.child(1 - idx).clone();
139 let rewritten = match new_child {
140 Some(new_child) if idx == 0 => expr.clone().with_children([new_child, sibling])?,
141 Some(new_child) => expr.clone().with_children([sibling, new_child])?,
142 None => sibling,
143 };
144 return Ok(Some(Some(rewritten)));
145 }
146
147 Ok(None)
148}
149
150struct RangeComparison<'a> {
152 column: &'a BoundExpression,
153 literal: &'a BoundExpression,
154 is_lower: bool,
155 strict: StrictComparison,
156}
157
158impl<'a> RangeComparison<'a> {
159 fn try_new(expr: &'a BoundExpression) -> Option<Self> {
160 let operator = *expr.as_opt::<Binary>()?;
161 let lhs = expr.child(0);
162 let rhs = expr.child(1);
163
164 let (column, literal, operator) = if lhs.is::<GetItem>() && rhs.is::<Literal>() {
166 (lhs, rhs, operator)
167 } else if rhs.is::<GetItem>() && lhs.is::<Literal>() {
168 (rhs, lhs, operator.swap()?)
169 } else {
170 return None;
171 };
172
173 let (is_lower, strict) = match operator {
174 Operator::Gt => (true, StrictComparison::Strict),
175 Operator::Gte => (true, StrictComparison::NonStrict),
176 Operator::Lt => (false, StrictComparison::Strict),
177 Operator::Lte => (false, StrictComparison::NonStrict),
178 _ => return None,
179 };
180
181 Some(Self {
182 column,
183 literal,
184 is_lower,
185 strict,
186 })
187 }
188
189 fn between(&self, other: &Self) -> Option<BoundExpression> {
191 if self.is_lower == other.is_lower || self.column != other.column {
192 return None;
193 }
194 let (lower, upper) = if self.is_lower {
195 (self, other)
196 } else {
197 (other, self)
198 };
199
200 Between
202 .try_new_bound_expr(
203 BetweenOptions {
204 lower_strict: lower.strict,
205 upper_strict: upper.strict,
206 },
207 [
208 self.column.clone(),
209 lower.literal.clone(),
210 upper.literal.clone(),
211 ],
212 )
213 .ok()
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use rstest::rstest;
220 use vortex_buffer::buffer;
221 use vortex_error::VortexResult;
222
223 use super::find_between;
224 use crate::IntoArray;
225 use crate::VortexSessionExecute;
226 use crate::array_session;
227 use crate::arrays::BoolArray;
228 use crate::arrays::StructArray;
229 use crate::dtype::DType;
230 use crate::dtype::Nullability;
231 use crate::dtype::PType;
232 use crate::dtype::StructFields;
233 use crate::expr::Expression;
234 use crate::expr::and;
235 use crate::expr::between;
236 use crate::expr::col;
237 use crate::expr::gt;
238 use crate::expr::gt_eq;
239 use crate::expr::lit;
240 use crate::expr::lt;
241 use crate::expr::lt_eq;
242 use crate::expr::or;
243 use crate::scalar::Scalar;
244 use crate::scalar_fn::fns::between::BetweenOptions;
245 use crate::scalar_fn::fns::between::StrictComparison;
246
247 fn scope() -> DType {
248 let i32_dtype = DType::Primitive(PType::I32, Nullability::NonNullable);
249 DType::Struct(
250 StructFields::new(
251 ["x", "y", "z"].into(),
252 vec![i32_dtype.clone(), i32_dtype.clone(), i32_dtype],
253 ),
254 Nullability::NonNullable,
255 )
256 }
257
258 fn between_x(lower: i32, upper: i32, lower_strict: bool, upper_strict: bool) -> Expression {
259 let strictness = |strict| {
260 if strict {
261 StrictComparison::Strict
262 } else {
263 StrictComparison::NonStrict
264 }
265 };
266 between(
267 col("x"),
268 lit(lower),
269 lit(upper),
270 BetweenOptions {
271 lower_strict: strictness(lower_strict),
272 upper_strict: strictness(upper_strict),
273 },
274 )
275 }
276
277 #[rstest]
278 #[case::bad_match(
280 and(lt_eq(lit(100), col("x")), gt(lit(-100), col("x"))),
281 between_x(100, -100, false, true)
282 )]
283 #[case::literals_on_left(
284 and(lt(lit(2), col("x")), gt_eq(lit(5), col("x"))),
285 between_x(2, 5, true, false)
286 )]
287 #[case::columns_on_left(
288 and(gt_eq(col("x"), lit(2)), lt(col("x"), lit(5))),
289 between_x(2, 5, false, true)
290 )]
291 #[case::mixed_sides(
292 and(gt_eq(col("x"), lit(2)), gt_eq(lit(5), col("x"))),
293 between_x(2, 5, false, false)
294 )]
295 #[case::upper_first(
296 and(gt_eq(lit(5), col("x")), lt(lit(2), col("x"))),
297 between_x(2, 5, true, false)
298 )]
299 #[case::other_conjunct_first(
300 and(
301 and(gt_eq(col("y"), lit(10)), gt_eq(lit(5), col("x"))),
302 lt(lit(2), col("x")),
303 ),
304 and(gt_eq(col("y"), lit(10)), between_x(2, 5, true, false))
305 )]
306 #[case::other_conjunct_last(
307 and(
308 and(gt_eq(lit(5), col("x")), gt_eq(col("y"), lit(10))),
309 lt(lit(2), col("x")),
310 ),
311 and(between_x(2, 5, true, false), gt_eq(col("y"), lit(10)))
312 )]
313 #[case::skips_non_leaf_conjuncts(
314 and(
315 and(gt_eq(col("x"), lit(2)), gt(col("y"), col("z"))),
316 lt(col("x"), lit(5))
317 ),
318 and(between_x(2, 5, false, true), gt(col("y"), col("z")))
319 )]
320 #[case::pair_within_one_side(
321 and(
322 and(gt_eq(col("x"), lit(2)), lt(col("x"), lit(5))),
323 gt(col("y"), col("z"))
324 ),
325 and(between_x(2, 5, false, true), gt(col("y"), col("z")))
326 )]
327 #[case::under_or(
328 or(
329 and(gt_eq(col("x"), lit(2)), lt(col("x"), lit(5))),
330 gt(col("y"), col("z"))
331 ),
332 or(between_x(2, 5, false, true), gt(col("y"), col("z")))
333 )]
334 #[case::no_pair(
335 and(gt_eq(col("x"), lit(2)), lt(col("y"), lit(5))),
336 and(gt_eq(col("x"), lit(2)), lt(col("y"), lit(5)))
337 )]
338 #[case::same_direction(
339 and(gt_eq(col("x"), lit(2)), gt(col("x"), lit(5))),
340 and(gt_eq(col("x"), lit(2)), gt(col("x"), lit(5)))
341 )]
342 fn finds_between(#[case] expr: Expression, #[case] expected: Expression) -> VortexResult<()> {
343 let scope = scope();
344 assert_eq!(find_between(expr.bind(&scope)?)?, expected.bind(&scope)?);
345 Ok(())
346 }
347
348 #[test]
350 fn merges_many_pairs() -> VortexResult<()> {
351 let scope = scope();
352 let lowers = ["x", "y", "z"].map(|name| gt_eq(col(name), lit(0)));
353 let uppers = ["x", "y", "z"].map(|name| lt(col(name), lit(10)));
354 let [lx, ly, lz] = lowers;
355 let [ux, uy, uz] = uppers;
356 let expr = and(and(and(lx, ly), lz), and(and(uz, uy), ux));
357
358 let range = |name| {
359 between(
360 col(name),
361 lit(0),
362 lit(10),
363 BetweenOptions {
364 lower_strict: StrictComparison::NonStrict,
365 upper_strict: StrictComparison::Strict,
366 },
367 )
368 };
369 let expected = and(and(range("x"), range("y")), range("z"));
370
371 assert_eq!(find_between(expr.bind(&scope)?)?, expected.bind(&scope)?);
372 Ok(())
373 }
374
375 #[test]
378 fn null_literal_bound_is_value_preserving() -> VortexResult<()> {
379 let session = array_session();
380 let ctx = &mut session.create_execution_ctx();
381 let data = StructArray::from_fields(&[("x", buffer![10, 1].into_array())])?.into_array();
382
383 let null_lit = lit(Scalar::null(DType::Primitive(
384 PType::I32,
385 Nullability::Nullable,
386 )));
387 let expr = and(gt_eq(col("x"), null_lit), lt_eq(col("x"), lit(5i32)));
388
389 let before = data
390 .clone()
391 .apply(&expr)?
392 .execute::<BoolArray>(ctx)?
393 .opt_bool_vec(ctx);
394
395 let rewritten = find_between(expr.bind(data.dtype())?)?;
396 let after = data
397 .apply_bound(&rewritten)?
398 .execute::<BoolArray>(ctx)?
399 .opt_bool_vec(ctx);
400
401 assert_eq!(before, [Some(false), None]);
403 assert_eq!(before, after);
404
405 Ok(())
406 }
407}