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

vortex-data / vortex / 16275012398

14 Jul 2025 06:38PM UTC coverage: 81.568% (+0.4%) from 81.147%
16275012398

Pull #3852

github

web-flow
Merge e4d6d19f4 into 65447ae8a
Pull Request #3852: feat: call optimize in compressor

5 of 6 new or added lines in 1 file covered. (83.33%)

370 existing lines in 35 files now uncovered.

46288 of 56748 relevant lines covered (81.57%)

157514.89 hits per line

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

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

4
use std::fmt::{Debug, Display, Formatter};
5
use std::hash::Hash;
6

7
use vortex_array::compute::list_contains as compute_list_contains;
8
use vortex_array::{ArrayRef, DeserializeMetadata, EmptyMetadata};
9
use vortex_dtype::DType;
10
use vortex_error::{VortexResult, vortex_bail};
11

12
use crate::{
13
    AnalysisExpr, ExprEncodingRef, ExprId, ExprRef, IntoExpr, LiteralVTable, Scope, StatsCatalog,
14
    VTable, and, gt, lit, lt, or, vtable,
15
};
16

17
vtable!(ListContains);
18

19
#[allow(clippy::derived_hash_with_manual_eq)]
20
#[derive(Debug, Clone, Hash)]
21
pub struct ListContainsExpr {
22
    list: ExprRef,
23
    value: ExprRef,
24
}
25

26
impl PartialEq for ListContainsExpr {
27
    fn eq(&self, other: &Self) -> bool {
226✔
28
        self.list.eq(&other.list) && self.value.eq(&other.value)
226✔
29
    }
226✔
30
}
31

32
pub struct ListContainsExprEncoding;
33

34
impl VTable for ListContainsVTable {
35
    type Expr = ListContainsExpr;
36
    type Encoding = ListContainsExprEncoding;
37
    type Metadata = EmptyMetadata;
38

39
    fn id(_encoding: &Self::Encoding) -> ExprId {
123✔
40
        ExprId::new_ref("list_contains")
123✔
41
    }
123✔
42

43
    fn encoding(_expr: &Self::Expr) -> ExprEncodingRef {
×
44
        ExprEncodingRef::new_ref(ListContainsExprEncoding.as_ref())
×
45
    }
×
46

47
    fn metadata(_expr: &Self::Expr) -> Option<Self::Metadata> {
×
48
        Some(EmptyMetadata)
×
49
    }
×
50

51
    fn children(expr: &Self::Expr) -> Vec<&ExprRef> {
1,638✔
52
        vec![&expr.list, &expr.value]
1,638✔
53
    }
1,638✔
54

55
    fn with_children(_expr: &Self::Expr, children: Vec<ExprRef>) -> VortexResult<Self::Expr> {
78✔
56
        Ok(ListContainsExpr::new(
78✔
57
            children[0].clone(),
78✔
58
            children[1].clone(),
78✔
59
        ))
78✔
60
    }
78✔
61

62
    fn build(
×
63
        _encoding: &Self::Encoding,
×
64
        _metadata: &<Self::Metadata as DeserializeMetadata>::Output,
×
65
        children: Vec<ExprRef>,
×
66
    ) -> VortexResult<Self::Expr> {
×
67
        if children.len() != 2 {
×
68
            vortex_bail!(
×
69
                "ListContains expression must have exactly 2 children, got {}",
×
70
                children.len()
×
71
            );
×
72
        }
×
73
        Ok(ListContainsExpr::new(
×
74
            children[0].clone(),
×
75
            children[1].clone(),
×
76
        ))
×
77
    }
×
78

79
    fn evaluate(expr: &Self::Expr, scope: &Scope) -> VortexResult<ArrayRef> {
31✔
80
        compute_list_contains(
31✔
81
            expr.list.evaluate(scope)?.as_ref(),
31✔
82
            expr.value.evaluate(scope)?.as_ref(),
31✔
83
        )
84
    }
31✔
85

86
    fn return_dtype(expr: &Self::Expr, scope: &DType) -> VortexResult<DType> {
84✔
87
        Ok(DType::Bool(
84✔
88
            expr.list.return_dtype(scope)?.nullability()
84✔
89
                | expr.value.return_dtype(scope)?.nullability(),
84✔
90
        ))
91
    }
84✔
92
}
93

94
impl ListContainsExpr {
95
    pub fn new(list: ExprRef, value: ExprRef) -> Self {
111✔
96
        Self { list, value }
111✔
97
    }
111✔
98

99
    pub fn new_expr(list: ExprRef, value: ExprRef) -> ExprRef {
×
100
        Self::new(list, value).into_expr()
×
101
    }
×
102

UNCOV
103
    pub fn value(&self) -> &ExprRef {
×
UNCOV
104
        &self.value
×
UNCOV
105
    }
×
106
}
107

108
pub fn list_contains(list: ExprRef, value: ExprRef) -> ExprRef {
33✔
109
    ListContainsExpr::new(list, value).into_expr()
33✔
110
}
33✔
111

112
impl Display for ListContainsExpr {
UNCOV
113
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
×
UNCOV
114
        write!(f, "contains({}, {})", &self.list, &self.value)
×
UNCOV
115
    }
×
116
}
117

118
impl AnalysisExpr for ListContainsExpr {
119
    // falsification(contains([1,2,5], x)) =>
120
    //   falsification(x != 1) and falsification(x != 2) and falsification(x != 5)
121

122
    fn stat_falsification(&self, catalog: &mut dyn StatsCatalog) -> Option<ExprRef> {
27✔
123
        let min = self.list.min(catalog)?;
27✔
124
        let max = self.list.max(catalog)?;
27✔
125
        // If the list is constant when we can compare each element to the value
126
        if min == max {
27✔
127
            let list_ = min
27✔
128
                .as_opt::<LiteralVTable>()
27✔
129
                .and_then(|l| l.value().as_list_opt())
27✔
130
                .and_then(|l| l.elements())?;
27✔
131
            if list_.is_empty() {
27✔
132
                // contains([], x) is always false.
UNCOV
133
                return Some(lit(true));
×
134
            }
27✔
135
            let value_max = self.value.max(catalog)?;
27✔
136
            let value_min = self.value.min(catalog)?;
27✔
137

138
            return list_
27✔
139
                .iter()
27✔
140
                .map(move |v| {
87✔
141
                    or(
87✔
142
                        lt(value_max.clone(), lit(v.clone())),
87✔
143
                        gt(value_min.clone(), lit(v.clone())),
87✔
144
                    )
87✔
145
                })
87✔
146
                .reduce(and);
27✔
UNCOV
147
        }
×
UNCOV
148

×
UNCOV
149
        None
×
150
    }
27✔
151
}
152

153
#[cfg(test)]
154
mod tests {
155
    use vortex_array::arrays::{BoolArray, BooleanBuffer, ListArray, PrimitiveArray};
156
    use vortex_array::stats::Stat;
157
    use vortex_array::validity::Validity;
158
    use vortex_array::{Array, ArrayRef, IntoArray};
159
    use vortex_dtype::PType::I32;
160
    use vortex_dtype::{DType, Field, FieldPath, FieldPathSet, Nullability, StructFields};
161
    use vortex_scalar::Scalar;
162
    use vortex_utils::aliases::hash_map::HashMap;
163

164
    use crate::list_contains::list_contains;
165
    use crate::pruning::checked_pruning_expr;
166
    use crate::{Arc, HashSet, Scope, and, get_item, get_item_scope, gt, lit, lt, or, root};
167

168
    fn test_array() -> ArrayRef {
3✔
169
        ListArray::try_new(
3✔
170
            PrimitiveArray::from_iter(vec![1, 1, 2, 2, 2, 2, 2, 3, 3, 3]).into_array(),
3✔
171
            PrimitiveArray::from_iter(vec![0, 5, 10]).into_array(),
3✔
172
            Validity::AllValid,
3✔
173
        )
3✔
174
        .unwrap()
3✔
175
        .into_array()
3✔
176
    }
3✔
177

178
    #[test]
179
    pub fn test_one() {
1✔
180
        let arr = test_array();
1✔
181

1✔
182
        let expr = list_contains(root(), lit(1));
1✔
183
        let item = expr.evaluate(&Scope::new(arr)).unwrap();
1✔
184

1✔
185
        assert_eq!(
1✔
186
            item.scalar_at(0).unwrap(),
1✔
187
            Scalar::bool(true, Nullability::Nullable)
1✔
188
        );
1✔
189
        assert_eq!(
1✔
190
            item.scalar_at(1).unwrap(),
1✔
191
            Scalar::bool(false, Nullability::Nullable)
1✔
192
        );
1✔
193
    }
1✔
194

195
    #[test]
196
    pub fn test_all() {
1✔
197
        let arr = test_array();
1✔
198

1✔
199
        let expr = list_contains(root(), lit(2));
1✔
200
        let item = expr.evaluate(&Scope::new(arr)).unwrap();
1✔
201

1✔
202
        assert_eq!(
1✔
203
            item.scalar_at(0).unwrap(),
1✔
204
            Scalar::bool(true, Nullability::Nullable)
1✔
205
        );
1✔
206
        assert_eq!(
1✔
207
            item.scalar_at(1).unwrap(),
1✔
208
            Scalar::bool(true, Nullability::Nullable)
1✔
209
        );
1✔
210
    }
1✔
211

212
    #[test]
213
    pub fn test_none() {
1✔
214
        let arr = test_array();
1✔
215

1✔
216
        let expr = list_contains(root(), lit(4));
1✔
217
        let item = expr.evaluate(&Scope::new(arr)).unwrap();
1✔
218

1✔
219
        assert_eq!(
1✔
220
            item.scalar_at(0).unwrap(),
1✔
221
            Scalar::bool(false, Nullability::Nullable)
1✔
222
        );
1✔
223
        assert_eq!(
1✔
224
            item.scalar_at(1).unwrap(),
1✔
225
            Scalar::bool(false, Nullability::Nullable)
1✔
226
        );
1✔
227
    }
1✔
228

229
    #[test]
230
    pub fn test_empty() {
1✔
231
        let arr = ListArray::try_new(
1✔
232
            PrimitiveArray::from_iter(vec![1, 1, 2, 2, 2]).into_array(),
1✔
233
            PrimitiveArray::from_iter(vec![0, 5, 5]).into_array(),
1✔
234
            Validity::AllValid,
1✔
235
        )
1✔
236
        .unwrap()
1✔
237
        .into_array();
1✔
238

1✔
239
        let expr = list_contains(root(), lit(2));
1✔
240
        let item = expr.evaluate(&Scope::new(arr)).unwrap();
1✔
241

1✔
242
        assert_eq!(
1✔
243
            item.scalar_at(0).unwrap(),
1✔
244
            Scalar::bool(true, Nullability::Nullable)
1✔
245
        );
1✔
246
        assert_eq!(
1✔
247
            item.scalar_at(1).unwrap(),
1✔
248
            Scalar::bool(false, Nullability::Nullable)
1✔
249
        );
1✔
250
    }
1✔
251

252
    #[test]
253
    pub fn test_nullable() {
1✔
254
        let arr = ListArray::try_new(
1✔
255
            PrimitiveArray::from_iter(vec![1, 1, 2, 2, 2]).into_array(),
1✔
256
            PrimitiveArray::from_iter(vec![0, 5, 5]).into_array(),
1✔
257
            Validity::Array(BoolArray::from(BooleanBuffer::from(vec![true, false])).into_array()),
1✔
258
        )
1✔
259
        .unwrap()
1✔
260
        .into_array();
1✔
261

1✔
262
        let expr = list_contains(root(), lit(2));
1✔
263
        let item = expr.evaluate(&Scope::new(arr)).unwrap();
1✔
264

1✔
265
        assert_eq!(
1✔
266
            item.scalar_at(0).unwrap(),
1✔
267
            Scalar::bool(true, Nullability::Nullable)
1✔
268
        );
1✔
269
        assert!(!item.is_valid(1).unwrap());
1✔
270
    }
1✔
271

272
    #[test]
273
    pub fn test_return_type() {
1✔
274
        let scope = DType::Struct(
1✔
275
            StructFields::new(
1✔
276
                ["array"].into(),
1✔
277
                vec![DType::List(
1✔
278
                    Arc::new(DType::Primitive(I32, Nullability::NonNullable)),
1✔
279
                    Nullability::Nullable,
1✔
280
                )],
1✔
281
            ),
1✔
282
            Nullability::NonNullable,
1✔
283
        );
1✔
284

1✔
285
        let expr = list_contains(get_item("array", root()), lit(2));
1✔
286

1✔
287
        // Expect nullable, although scope is non-nullable
1✔
288
        assert_eq!(
1✔
289
            expr.return_dtype(&scope).unwrap(),
1✔
290
            DType::Bool(Nullability::Nullable)
1✔
291
        );
1✔
292
    }
1✔
293

294
    #[test]
295
    pub fn list_falsification() {
1✔
296
        let expr = list_contains(
1✔
297
            lit(Scalar::list(
1✔
298
                Arc::new(DType::Primitive(I32, Nullability::NonNullable)),
1✔
299
                vec![1.into(), 2.into(), 3.into()],
1✔
300
                Nullability::NonNullable,
1✔
301
            )),
1✔
302
            get_item_scope("a"),
1✔
303
        );
1✔
304

1✔
305
        let (expr, st) = checked_pruning_expr(
1✔
306
            &expr,
1✔
307
            &FieldPathSet::from_iter([
1✔
308
                FieldPath::from_iter([Field::Name("a".into()), Field::Name("max".into())]),
1✔
309
                FieldPath::from_iter([Field::Name("a".into()), Field::Name("min".into())]),
1✔
310
            ]),
1✔
311
        )
1✔
312
        .unwrap();
1✔
313

1✔
314
        assert_eq!(
1✔
315
            &expr,
1✔
316
            &and(
1✔
317
                and(
1✔
318
                    or(
1✔
319
                        lt(get_item_scope("a_max"), lit(1i32)),
1✔
320
                        gt(get_item_scope("a_min"), lit(1i32)),
1✔
321
                    ),
1✔
322
                    or(
1✔
323
                        lt(get_item_scope("a_max"), lit(2i32)),
1✔
324
                        gt(get_item_scope("a_min"), lit(2i32)),
1✔
325
                    )
1✔
326
                ),
1✔
327
                or(
1✔
328
                    lt(get_item_scope("a_max"), lit(3i32)),
1✔
329
                    gt(get_item_scope("a_min"), lit(3i32)),
1✔
330
                )
1✔
331
            )
1✔
332
        );
1✔
333

334
        assert_eq!(
1✔
335
            st.map(),
1✔
336
            &HashMap::from_iter([(
1✔
337
                FieldPath::from_name("a"),
1✔
338
                HashSet::from([Stat::Min, Stat::Max])
1✔
339
            )])
1✔
340
        );
1✔
341
    }
1✔
342
}
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