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

vortex-data / vortex / 16835981860

08 Aug 2025 04:56PM UTC coverage: 84.921% (-0.01%) from 84.933%
16835981860

Pull #4170

github

web-flow
Merge 0830e3ed7 into 089b4a001
Pull Request #4170: feat[duckdb]: support more operators

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

55 existing lines in 1 file now uncovered.

50657 of 59652 relevant lines covered (84.92%)

568178.36 hits per line

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

77.14
/vortex-duckdb/src/convert/expr.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::sync::Arc;
5

6
use itertools::Itertools;
7
use vortex::compute::{BetweenOptions, StrictComparison};
8
use vortex::dtype::Nullability;
9
use vortex::error::{VortexError, VortexExpect, VortexResult, vortex_bail, vortex_err};
10
use vortex::expr::{
11
    BetweenExpr, BinaryExpr, ExprRef, LikeExpr, LiteralExpr, Operator, and_collect, col, is_null,
12
    list_contains, lit, not, or_collect,
13
};
14
use vortex::scalar::Scalar;
15

16
use crate::cpp::DUCKDB_VX_EXPR_TYPE;
17
use crate::duckdb::{Expression, ExpressionClass};
18

19
const DUCKDB_FUNCTION_NAME_CONTAINS: &str = "contains";
20

21
fn like_pattern_str(value: &Expression) -> VortexResult<Option<String>> {
4✔
22
    match value.as_class().vortex_expect("unknown class") {
4✔
23
        ExpressionClass::BoundConstant(constant) => {
4✔
24
            Ok(Some(format!("%{}%", constant.value.as_string().as_str())))
4✔
25
        }
26
        _ => Ok(None),
×
27
    }
28
}
4✔
29

30
#[allow(clippy::cognitive_complexity)]
31
pub fn try_from_bound_expression(value: &Expression) -> VortexResult<Option<ExprRef>> {
614✔
32
    let Some(value) = value.as_class() else {
614✔
33
        vortex_bail!("no expression class id {:?}", value.as_class_id())
×
34
    };
35
    Ok(Some(match value {
614✔
36
        ExpressionClass::BoundColumnRef(col_ref) => col(col_ref.name.to_str()?),
166✔
37
        ExpressionClass::BoundConstant(const_) => lit(Scalar::try_from(const_.value)?),
195✔
38
        ExpressionClass::BoundComparison(compare) => {
103✔
39
            let operator: Operator = compare.op.try_into()?;
103✔
40

41
            let Some(left) = try_from_bound_expression(&compare.left)? else {
103✔
42
                return Ok(None);
×
43
            };
44
            let Some(right) = try_from_bound_expression(&compare.right)? else {
103✔
45
                return Ok(None);
×
46
            };
47

48
            BinaryExpr::new_expr(left, operator, right)
103✔
49
        }
50
        ExpressionClass::BoundBetween(between) => {
34✔
51
            let Some(array) = try_from_bound_expression(&between.input)? else {
34✔
52
                return Ok(None);
×
53
            };
54
            let Some(lower) = try_from_bound_expression(&between.lower)? else {
34✔
55
                return Ok(None);
×
56
            };
57
            let Some(upper) = try_from_bound_expression(&between.upper)? else {
34✔
58
                return Ok(None);
×
59
            };
60
            BetweenExpr::new_expr(
34✔
61
                array,
34✔
62
                lower,
34✔
63
                upper,
34✔
64
                BetweenOptions {
65
                    lower_strict: if between.lower_inclusive {
34✔
66
                        StrictComparison::NonStrict
33✔
67
                    } else {
68
                        StrictComparison::Strict
1✔
69
                    },
70
                    upper_strict: if between.upper_inclusive {
34✔
71
                        StrictComparison::NonStrict
9✔
72
                    } else {
73
                        StrictComparison::Strict
25✔
74
                    },
75
                },
76
            )
77
        }
78
        ExpressionClass::BoundOperator(operator) => match operator.op {
46✔
79
            DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_OPERATOR_NOT
80
            | DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_OPERATOR_IS_NULL
81
            | DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_OPERATOR_IS_NOT_NULL => {
82
                let children = operator.children().collect_vec();
6✔
83
                assert_eq!(children.len(), 1);
6✔
84
                let Some(child) = try_from_bound_expression(&children[0])? else {
6✔
85
                    return Ok(None);
6✔
86
                };
NEW
87
                match operator.op {
×
NEW
88
                    DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_OPERATOR_NOT => not(child),
×
NEW
89
                    DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_OPERATOR_IS_NULL => is_null(child),
×
90
                    DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_OPERATOR_IS_NOT_NULL => {
NEW
91
                        not(is_null(child))
×
92
                    }
NEW
93
                    _ => unreachable!(),
×
94
                }
95
            }
96
            DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_COMPARE_IN => {
97
                // First child is element, rest form the list.
98
                let children = operator.children().collect_vec();
40✔
99
                assert!(children.len() >= 2);
40✔
100
                let Some(element) = try_from_bound_expression(&children[0])? else {
40✔
101
                    return Ok(None);
30✔
102
                };
103

104
                let Some(list_elements) = children
10✔
105
                    .iter()
10✔
106
                    .skip(1)
10✔
107
                    .map(|c| {
39✔
108
                        let Some(value) = try_from_bound_expression(c)? else {
39✔
109
                            return Ok(None);
×
110
                        };
111
                        Ok(Some(
112
                            LiteralExpr::maybe_from(&value)
39✔
113
                                .ok_or_else(|| {
39✔
114
                                    vortex_err!("cannot have a non literal in a in_list")
×
115
                                })?
×
116
                                .value()
39✔
117
                                .clone(),
39✔
118
                        ))
119
                    })
39✔
120
                    .collect::<VortexResult<Option<Vec<_>>>>()?
10✔
121
                else {
122
                    return Ok(None);
×
123
                };
124
                let list = Scalar::list(
10✔
125
                    Arc::new(list_elements[0].dtype().clone()),
10✔
126
                    list_elements,
10✔
127
                    Nullability::Nullable,
10✔
128
                );
129
                list_contains(lit(list), element)
10✔
130
            }
131
            _ => todo!("operator {:?}", operator.op),
×
132
        },
133
        ExpressionClass::BoundFunction(func) => match func.scalar_function.name() {
64✔
134
            DUCKDB_FUNCTION_NAME_CONTAINS => {
64✔
135
                let children = func.children().collect_vec();
4✔
136
                assert_eq!(children.len(), 2);
4✔
137
                let Some(value) = try_from_bound_expression(&children[0])? else {
4✔
138
                    return Ok(None);
×
139
                };
140
                let Some(pattern_lit) = like_pattern_str(&children[1])? else {
4✔
141
                    vortex_bail!("expected pattern to be bound string")
×
142
                };
143
                let pattern = LiteralExpr::new_expr(pattern_lit);
4✔
144
                LikeExpr::new_expr(value, pattern, false, false)
4✔
145
            }
146
            _ => {
147
                log::debug!("bound function {}", func.scalar_function.name());
60✔
148
                return Ok(None);
60✔
149
            }
150
        },
151
        ExpressionClass::BoundConjunction(conj) => {
6✔
152
            let Some(children) = conj
6✔
153
                .children()
6✔
154
                .map(|c| try_from_bound_expression(&c))
12✔
155
                .collect::<VortexResult<Option<Vec<_>>>>()?
6✔
156
            else {
157
                return Ok(None);
×
158
            };
159
            match conj.op {
6✔
160
                DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_CONJUNCTION_AND => {
161
                    and_collect(children).vortex_expect("cannot be empty")
×
162
                }
163
                DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_CONJUNCTION_OR => {
164
                    or_collect(children).vortex_expect("cannot be empty")
6✔
165
                }
166
                _ => vortex_bail!("unexpected operator {:?} in bound conjunction", conj.op),
×
167
            }
168
        }
169
    }))
170
}
614✔
171

172
impl TryFrom<DUCKDB_VX_EXPR_TYPE> for Operator {
173
    type Error = VortexError;
174

175
    fn try_from(value: DUCKDB_VX_EXPR_TYPE) -> VortexResult<Self> {
427✔
176
        Ok(match value {
427✔
177
            DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_INVALID => vortex_bail!("invalid expr"),
×
178
            DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_COMPARE_EQUAL => Operator::Eq,
86✔
179
            DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_COMPARE_NOTEQUAL => Operator::NotEq,
3✔
180
            DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_COMPARE_LESSTHAN => Operator::Lt,
24✔
181
            DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_COMPARE_GREATERTHAN => Operator::Gt,
13✔
182
            DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_COMPARE_LESSTHANOREQUALTO => Operator::Lte,
150✔
183
            DUCKDB_VX_EXPR_TYPE::DUCKDB_VX_EXPR_TYPE_COMPARE_GREATERTHANOREQUALTO => Operator::Gte,
151✔
184
            _ => todo!("cannot convert {:?}", value),
×
185
        })
186
    }
427✔
187
}
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