Skip to main content

mito2/sst/index/bloom_filter/applier/
builder.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{BTreeMap, BTreeSet};
16
17use common_telemetry::warn;
18use datafusion_common::ScalarValue;
19use datafusion_expr::expr::InList;
20use datafusion_expr::{BinaryExpr, Expr, Operator};
21use datatypes::data_type::ConcreteDataType;
22use datatypes::value::Value;
23use index::Bytes;
24use index::bloom_filter::applier::InListPredicate;
25use index::target::IndexTarget;
26use mito_codec::index::IndexValueCodec;
27use mito_codec::row_converter::SortField;
28use object_store::ObjectStore;
29use puffin::puffin_manager::cache::PuffinMetadataCacheRef;
30use snafu::{OptionExt, ResultExt};
31use store_api::metadata::RegionMetadata;
32use store_api::region_request::PathType;
33
34use crate::cache::file_cache::FileCacheRef;
35use crate::cache::index::bloom_filter_index::BloomFilterIndexCacheRef;
36use crate::error::{ColumnNotFoundSnafu, ConvertValueSnafu, EncodeSnafu, Result};
37use crate::sst::index::bloom_filter::applier::BloomFilterIndexApplier;
38use crate::sst::index::puffin_manager::PuffinManagerFactory;
39
40pub struct BloomFilterIndexApplierBuilder<'a> {
41    table_dir: String,
42    path_type: PathType,
43    object_store: ObjectStore,
44    metadata: &'a RegionMetadata,
45    puffin_manager_factory: PuffinManagerFactory,
46    file_cache: Option<FileCacheRef>,
47    puffin_metadata_cache: Option<PuffinMetadataCacheRef>,
48    bloom_filter_index_cache: Option<BloomFilterIndexCacheRef>,
49    predicates: BTreeMap<IndexTarget, Vec<InListPredicate>>,
50}
51
52impl<'a> BloomFilterIndexApplierBuilder<'a> {
53    pub fn new(
54        table_dir: String,
55        path_type: PathType,
56        object_store: ObjectStore,
57        metadata: &'a RegionMetadata,
58        puffin_manager_factory: PuffinManagerFactory,
59    ) -> Self {
60        Self {
61            table_dir,
62            path_type,
63            object_store,
64            metadata,
65            puffin_manager_factory,
66            file_cache: None,
67            puffin_metadata_cache: None,
68            bloom_filter_index_cache: None,
69            predicates: BTreeMap::default(),
70        }
71    }
72
73    pub fn with_file_cache(mut self, file_cache: Option<FileCacheRef>) -> Self {
74        self.file_cache = file_cache;
75        self
76    }
77
78    pub fn with_puffin_metadata_cache(
79        mut self,
80        puffin_metadata_cache: Option<PuffinMetadataCacheRef>,
81    ) -> Self {
82        self.puffin_metadata_cache = puffin_metadata_cache;
83        self
84    }
85
86    pub fn with_bloom_filter_index_cache(
87        mut self,
88        bloom_filter_index_cache: Option<BloomFilterIndexCacheRef>,
89    ) -> Self {
90        self.bloom_filter_index_cache = bloom_filter_index_cache;
91        self
92    }
93
94    /// Builds the applier with given filter expressions
95    pub fn build(mut self, exprs: &[Expr]) -> Result<Option<BloomFilterIndexApplier>> {
96        for expr in exprs {
97            self.traverse_and_collect(expr);
98        }
99
100        if self.predicates.is_empty() {
101            return Ok(None);
102        }
103
104        let expected_predicate_target_types = self.expected_predicate_target_types();
105        let applier = BloomFilterIndexApplier::new(
106            self.table_dir,
107            self.path_type,
108            self.object_store,
109            self.puffin_manager_factory,
110            self.predicates,
111            expected_predicate_target_types,
112        )
113        .with_file_cache(self.file_cache)
114        .with_puffin_metadata_cache(self.puffin_metadata_cache)
115        .with_bloom_filter_cache(self.bloom_filter_index_cache);
116
117        Ok(Some(applier))
118    }
119
120    /// Recursively traverses expressions to collect bloom filter predicates
121    fn traverse_and_collect(&mut self, expr: &Expr) {
122        let res = match expr {
123            Expr::BinaryExpr(BinaryExpr { left, op, right }) => match op {
124                Operator::And => {
125                    self.traverse_and_collect(left);
126                    self.traverse_and_collect(right);
127                    Ok(())
128                }
129                Operator::Eq => self.collect_eq(left, right),
130                Operator::Or => self.collect_or_eq_list(left, right),
131                _ => Ok(()),
132            },
133            Expr::InList(in_list) => self.collect_in_list(in_list),
134            _ => Ok(()),
135        };
136
137        if let Err(err) = res {
138            warn!(err; "Failed to collect bloom filter predicates, ignore it. expr: {expr}");
139        }
140    }
141
142    /// Returns the encoding type of each predicate target.
143    fn expected_predicate_target_types(&self) -> BTreeMap<IndexTarget, ConcreteDataType> {
144        self.predicates
145            .keys()
146            .filter_map(|col_id| {
147                let ty =
148                    crate::sst::index::column::skipping_target_data_type(self.metadata, col_id)?;
149                Some((col_id.clone(), ty))
150            })
151            .collect()
152    }
153
154    /// Helper function to get the column id and type
155    fn column_target_and_type(
156        &self,
157        column_name: &str,
158    ) -> Result<Option<(IndexTarget, ConcreteDataType)>> {
159        let column = self
160            .metadata
161            .column_by_name(column_name)
162            .context(ColumnNotFoundSnafu {
163                column: column_name,
164            })?;
165
166        Ok(Some((
167            IndexTarget::ColumnId(column.column_id),
168            column.column_schema.data_type.clone(),
169        )))
170    }
171
172    fn expr_to_index_target(&self, expr: &Expr) -> Result<Option<(IndexTarget, ConcreteDataType)>> {
173        if let Expr::Column(column) = expr {
174            return self.column_target_and_type(&column.name);
175        }
176        crate::sst::index::column::json_index_target(self.metadata, expr, |hint| {
177            hint.skipping_index.is_some()
178        })
179    }
180
181    /// Collects an equality expression (column = value)
182    fn collect_eq(&mut self, left: &Expr, right: &Expr) -> Result<()> {
183        let Some((col, lit)) = Self::eq_expr_col_lit(left, right)? else {
184            return Ok(());
185        };
186        if lit.is_null() {
187            return Ok(());
188        }
189        let Some((column_id, data_type)) = self.expr_to_index_target(col)? else {
190            return Ok(());
191        };
192        let value = encode_lit(lit, data_type)?;
193        self.predicates
194            .entry(column_id)
195            .or_default()
196            .push(InListPredicate {
197                list: BTreeSet::from([value]),
198            });
199
200        Ok(())
201    }
202
203    /// Collects an in list expression in the form of `column IN (lit, lit, ...)`.
204    fn collect_in_list(&mut self, in_list: &InList) -> Result<()> {
205        if in_list.list.is_empty() || in_list.negated {
206            return Ok(());
207        }
208
209        let Some((column_id, data_type)) = self.expr_to_index_target(&in_list.expr)? else {
210            return Ok(());
211        };
212
213        let mut valid_predicates = BTreeSet::new();
214        for expr in &in_list.list {
215            let Expr::Literal(lit, _) = expr else {
216                return Ok(());
217            };
218            if !lit.is_null() {
219                valid_predicates.insert(encode_lit(lit, data_type.clone())?);
220            }
221        }
222
223        if !valid_predicates.is_empty() {
224            self.predicates
225                .entry(column_id)
226                .or_default()
227                .push(InListPredicate {
228                    list: valid_predicates,
229                });
230        }
231
232        Ok(())
233    }
234
235    /// Collects an or expression in the form of `column = lit OR column = lit OR ...`.
236    fn collect_or_eq_list(&mut self, left: &Expr, right: &Expr) -> Result<()> {
237        let (eq_left, eq_right, or_list) = if let Expr::BinaryExpr(BinaryExpr {
238            left: l,
239            op: Operator::Eq,
240            right: r,
241        }) = left
242        {
243            (l, r, right)
244        } else if let Expr::BinaryExpr(BinaryExpr {
245            left: l,
246            op: Operator::Eq,
247            right: r,
248        }) = right
249        {
250            (l, r, left)
251        } else {
252            return Ok(());
253        };
254
255        let Some((col, lit)) = Self::eq_expr_col_lit(eq_left, eq_right)? else {
256            return Ok(());
257        };
258        if lit.is_null() {
259            return Ok(());
260        }
261        let Some((column_id, data_type)) = self.expr_to_index_target(col)? else {
262            return Ok(());
263        };
264
265        let mut inlist = BTreeSet::new();
266        inlist.insert(encode_lit(lit, data_type.clone())?);
267        if Self::collect_or_eq_list_rec(col, &data_type, or_list, &mut inlist)? {
268            self.predicates
269                .entry(column_id)
270                .or_default()
271                .push(InListPredicate { list: inlist });
272        }
273
274        Ok(())
275    }
276
277    fn collect_or_eq_list_rec(
278        column_expr: &Expr,
279        data_type: &ConcreteDataType,
280        expr: &Expr,
281        inlist: &mut BTreeSet<Bytes>,
282    ) -> Result<bool> {
283        if let Expr::BinaryExpr(BinaryExpr { left, op, right }) = expr {
284            match op {
285                Operator::Or => {
286                    let r = Self::collect_or_eq_list_rec(column_expr, data_type, left, inlist)?
287                        .then(|| {
288                            Self::collect_or_eq_list_rec(column_expr, data_type, right, inlist)
289                        })
290                        .transpose()?
291                        .unwrap_or(false);
292                    return Ok(r);
293                }
294                Operator::Eq => {
295                    let Some((col, lit)) = Self::eq_expr_col_lit(left, right)? else {
296                        return Ok(false);
297                    };
298                    if lit.is_null() || column_expr != col {
299                        return Ok(false);
300                    }
301                    let bytes = encode_lit(lit, data_type.clone())?;
302                    inlist.insert(bytes);
303                    return Ok(true);
304                }
305                _ => {}
306            }
307        }
308
309        Ok(false)
310    }
311
312    /// Helper function to get the column and literal value from an equality expr (column = lit)
313    fn eq_expr_col_lit<'b>(
314        left: &'b Expr,
315        right: &'b Expr,
316    ) -> Result<Option<(&'b Expr, &'b ScalarValue)>> {
317        let (col, lit) = match (left, right) {
318            (expr, Expr::Literal(lit, _)) => (expr, lit),
319            (Expr::Literal(lit, _), expr) => (expr, lit),
320            _ => return Ok(None),
321        };
322        Ok(Some((col, lit)))
323    }
324}
325
326// TODO(ruihang): extract this and the one under inverted_index into a common util mod.
327/// Helper function to encode a literal into bytes.
328fn encode_lit(lit: &ScalarValue, data_type: ConcreteDataType) -> Result<Bytes> {
329    let value = Value::try_from(lit.clone()).context(ConvertValueSnafu)?;
330    let mut bytes = vec![];
331    let field = SortField::new(data_type);
332    IndexValueCodec::encode_nonnull_value(value.as_value_ref(), &field, &mut bytes)
333        .context(EncodeSnafu)?;
334    Ok(bytes)
335}
336
337#[cfg(test)]
338mod tests {
339    use api::v1::SemanticType;
340    use datafusion_common::Column;
341    use datafusion_expr::{Literal, col, lit};
342    use datatypes::schema::ColumnSchema;
343    use object_store::services::Memory;
344    use store_api::metadata::{ColumnMetadata, RegionMetadata, RegionMetadataBuilder};
345    use store_api::storage::RegionId;
346
347    use super::*;
348
349    fn test_region_metadata() -> RegionMetadata {
350        let mut builder = RegionMetadataBuilder::new(RegionId::new(1234, 5678));
351        builder
352            .push_column_metadata(ColumnMetadata {
353                column_schema: ColumnSchema::new(
354                    "column1",
355                    ConcreteDataType::string_datatype(),
356                    false,
357                ),
358                semantic_type: SemanticType::Tag,
359                column_id: 1,
360            })
361            .push_column_metadata(ColumnMetadata {
362                column_schema: ColumnSchema::new(
363                    "column2",
364                    ConcreteDataType::int64_datatype(),
365                    false,
366                ),
367                semantic_type: SemanticType::Field,
368                column_id: 2,
369            })
370            .push_column_metadata(ColumnMetadata {
371                column_schema: ColumnSchema::new(
372                    "column3",
373                    ConcreteDataType::timestamp_millisecond_datatype(),
374                    false,
375                ),
376                semantic_type: SemanticType::Timestamp,
377                column_id: 3,
378            })
379            .primary_key(vec![1]);
380        builder.build().unwrap()
381    }
382
383    fn test_object_store() -> ObjectStore {
384        ObjectStore::new(Memory::default()).unwrap()
385    }
386
387    fn column(name: &str) -> Expr {
388        Expr::Column(Column::from_name(name))
389    }
390
391    #[test]
392    fn test_build_with_exprs() {
393        let (_d, factory) = PuffinManagerFactory::new_for_test_block("test_build_with_exprs_");
394        let metadata = test_region_metadata();
395        let builder = BloomFilterIndexApplierBuilder::new(
396            "test".to_string(),
397            PathType::Bare,
398            test_object_store(),
399            &metadata,
400            factory,
401        );
402        let exprs = vec![Expr::BinaryExpr(BinaryExpr {
403            left: Box::new(column("column1")),
404            op: Operator::Eq,
405            right: Box::new("value1".lit()),
406        })];
407        let result = builder.build(&exprs).unwrap();
408        assert!(result.is_some());
409
410        let predicates = result.unwrap().default_predicates;
411        assert_eq!(predicates.len(), 1);
412
413        let column_predicates = predicates.get(&IndexTarget::ColumnId(1)).unwrap();
414        assert_eq!(column_predicates.len(), 1);
415
416        let expected = encode_lit(
417            &ScalarValue::Utf8(Some("value1".to_string())),
418            ConcreteDataType::string_datatype(),
419        )
420        .unwrap();
421        assert_eq!(column_predicates[0].list, BTreeSet::from([expected]));
422    }
423
424    fn int64_lit(i: i64) -> Expr {
425        i.lit()
426    }
427
428    fn build_bloom_predicates(
429        exprs: &[Expr],
430    ) -> Option<BTreeMap<IndexTarget, Vec<InListPredicate>>> {
431        let (_d, factory) = PuffinManagerFactory::new_for_test_block("bloom_builder_");
432        let metadata = test_region_metadata();
433        BloomFilterIndexApplierBuilder::new(
434            "test".to_string(),
435            PathType::Bare,
436            test_object_store(),
437            &metadata,
438            factory,
439        )
440        .build(exprs)
441        .unwrap()
442        .map(|applier| (*applier.default_predicates).clone())
443    }
444
445    fn int64_inlist_predicate(values: impl IntoIterator<Item = i64>) -> InListPredicate {
446        InListPredicate {
447            list: values
448                .into_iter()
449                .map(|value| {
450                    encode_lit(
451                        &ScalarValue::Int64(Some(value)),
452                        ConcreteDataType::int64_datatype(),
453                    )
454                    .unwrap()
455                })
456                .collect(),
457        }
458    }
459
460    #[test]
461    fn bloom_pure_literal_in_extracts_exact_predicate() {
462        let expr = Expr::InList(InList {
463            expr: Box::new(column("column2")),
464            list: vec![int64_lit(1), int64_lit(2), int64_lit(3)],
465            negated: false,
466        });
467
468        assert_eq!(
469            build_bloom_predicates(&[expr]),
470            Some(BTreeMap::from([(
471                IndexTarget::ColumnId(2),
472                vec![int64_inlist_predicate([1, 2, 3])]
473            )]))
474        );
475    }
476
477    #[test]
478    fn bloom_pure_nonliteral_in_does_not_extract_predicate() {
479        let expr = Expr::InList(InList {
480            expr: Box::new(column("column1")),
481            list: vec![column("column1")],
482            negated: false,
483        });
484
485        assert_eq!(build_bloom_predicates(&[expr]), None);
486    }
487
488    #[test]
489    fn bloom_mixed_literal_null_in_extracts_exact_predicate() {
490        let expr = Expr::InList(InList {
491            expr: Box::new(column("column2")),
492            list: vec![
493                int64_lit(1),
494                Expr::Literal(ScalarValue::Int64(None), None),
495                int64_lit(3),
496            ],
497            negated: false,
498        });
499
500        assert_eq!(
501            build_bloom_predicates(&[expr]),
502            Some(BTreeMap::from([(
503                IndexTarget::ColumnId(2),
504                vec![int64_inlist_predicate([1, 3])]
505            )]))
506        );
507    }
508
509    #[test]
510    fn bloom_all_literal_null_in_does_not_extract_predicate() {
511        let expr = Expr::InList(InList {
512            expr: Box::new(column("column2")),
513            list: vec![
514                Expr::Literal(ScalarValue::Int64(None), None),
515                Expr::Literal(ScalarValue::Int64(None), None),
516            ],
517            negated: false,
518        });
519
520        assert_eq!(build_bloom_predicates(&[expr]), None);
521    }
522
523    #[test]
524    fn bloom_mixed_nonliteral_in_keeps_only_independent_predicate() {
525        let expr = Expr::BinaryExpr(BinaryExpr {
526            left: Box::new(Expr::InList(InList {
527                expr: Box::new(column("column1")),
528                list: vec!["definitely_absent".lit(), column("column1")],
529                negated: false,
530            })),
531            op: Operator::And,
532            right: Box::new(column("column2").eq(int64_lit(42))),
533        });
534
535        assert_eq!(
536            build_bloom_predicates(&[expr]),
537            Some(BTreeMap::from([(
538                IndexTarget::ColumnId(2),
539                vec![int64_inlist_predicate([42])]
540            )]))
541        );
542    }
543
544    #[test]
545    fn bloom_encoding_failure_in_does_not_extract_predicate() {
546        let expr = Expr::InList(InList {
547            expr: Box::new(column("column2")),
548            list: vec![int64_lit(1), "not_an_int64".lit()],
549            negated: false,
550        });
551
552        assert_eq!(build_bloom_predicates(&[expr]), None);
553    }
554
555    #[test]
556    fn test_build_with_in_list() {
557        let (_d, factory) = PuffinManagerFactory::new_for_test_block("test_build_with_in_list_");
558        let metadata = test_region_metadata();
559        let builder = BloomFilterIndexApplierBuilder::new(
560            "test".to_string(),
561            PathType::Bare,
562            test_object_store(),
563            &metadata,
564            factory,
565        );
566
567        let exprs = vec![Expr::InList(InList {
568            expr: Box::new(column("column2")),
569            list: vec![int64_lit(1), int64_lit(2), int64_lit(3)],
570            negated: false,
571        })];
572
573        let result = builder.build(&exprs).unwrap();
574        assert!(result.is_some());
575
576        let predicates = result.unwrap().default_predicates;
577        let column_predicates = predicates.get(&IndexTarget::ColumnId(2)).unwrap();
578        assert_eq!(column_predicates.len(), 1);
579        assert_eq!(column_predicates[0].list.len(), 3);
580    }
581
582    #[test]
583    fn test_build_with_or_chain() {
584        let (_d, factory) = PuffinManagerFactory::new_for_test_block("test_build_with_or_chain_");
585        let metadata = test_region_metadata();
586        let builder = || {
587            BloomFilterIndexApplierBuilder::new(
588                "test".to_string(),
589                PathType::Bare,
590                test_object_store(),
591                &metadata,
592                factory.clone(),
593            )
594        };
595
596        let expr = col("column1")
597            .eq(lit("value1"))
598            .or(col("column1")
599                .eq(lit("value2"))
600                .or(col("column1").eq(lit("value4"))))
601            .or(col("column1").eq(lit("value3")));
602
603        let result = builder().build(&[expr]).unwrap();
604        assert!(result.is_some());
605
606        let predicates = result.unwrap().default_predicates;
607        let column_predicates = predicates.get(&IndexTarget::ColumnId(1)).unwrap();
608        assert_eq!(column_predicates.len(), 1);
609        assert_eq!(column_predicates[0].list.len(), 4);
610        let or_chain_predicates = &column_predicates[0].list;
611        let encode_str = |s: &str| {
612            encode_lit(
613                &ScalarValue::Utf8(Some(s.to_string())),
614                ConcreteDataType::string_datatype(),
615            )
616            .unwrap()
617        };
618        assert!(or_chain_predicates.contains(&encode_str("value1")));
619        assert!(or_chain_predicates.contains(&encode_str("value2")));
620        assert!(or_chain_predicates.contains(&encode_str("value3")));
621        assert!(or_chain_predicates.contains(&encode_str("value4")));
622
623        // Test with null value
624        let expr = col("column1").eq(Expr::Literal(ScalarValue::Utf8(None), None));
625        let result = builder().build(&[expr]).unwrap();
626        assert!(result.is_none());
627
628        // Test with different column
629        let expr = col("column1")
630            .eq(lit("value1"))
631            .or(col("column2").eq(lit("value2")));
632        let result = builder().build(&[expr]).unwrap();
633        assert!(result.is_none());
634
635        // Test with non or chain
636        let expr = col("column1")
637            .eq(lit("value1"))
638            .or(col("column1").gt_eq(lit("value2")));
639        let result = builder().build(&[expr]).unwrap();
640        assert!(result.is_none());
641    }
642
643    #[test]
644    fn test_build_with_and_expressions() {
645        let (_d, factory) = PuffinManagerFactory::new_for_test_block("test_build_with_and_");
646        let metadata = test_region_metadata();
647        let builder = BloomFilterIndexApplierBuilder::new(
648            "test".to_string(),
649            PathType::Bare,
650            test_object_store(),
651            &metadata,
652            factory,
653        );
654        let exprs = vec![Expr::BinaryExpr(BinaryExpr {
655            left: Box::new(Expr::BinaryExpr(BinaryExpr {
656                left: Box::new(column("column1")),
657                op: Operator::Eq,
658                right: Box::new("value1".lit()),
659            })),
660            op: Operator::And,
661            right: Box::new(Expr::BinaryExpr(BinaryExpr {
662                left: Box::new(column("column2")),
663                op: Operator::Eq,
664                right: Box::new(int64_lit(42)),
665            })),
666        })];
667        let result = builder.build(&exprs).unwrap();
668        assert!(result.is_some());
669
670        let predicates = result.unwrap().default_predicates;
671        assert_eq!(predicates.len(), 2);
672        assert!(predicates.contains_key(&IndexTarget::ColumnId(1)));
673        assert!(predicates.contains_key(&IndexTarget::ColumnId(2)));
674    }
675
676    #[test]
677    fn test_build_with_null_values() {
678        let (_d, factory) = PuffinManagerFactory::new_for_test_block("test_build_with_null_");
679        let metadata = test_region_metadata();
680        let builder = BloomFilterIndexApplierBuilder::new(
681            "test".to_string(),
682            PathType::Bare,
683            test_object_store(),
684            &metadata,
685            factory,
686        );
687
688        let exprs = vec![
689            Expr::BinaryExpr(BinaryExpr {
690                left: Box::new(column("column1")),
691                op: Operator::Eq,
692                right: Box::new(Expr::Literal(ScalarValue::Utf8(None), None)),
693            }),
694            Expr::InList(InList {
695                expr: Box::new(column("column2")),
696                list: vec![
697                    int64_lit(1),
698                    Expr::Literal(ScalarValue::Int64(None), None),
699                    int64_lit(3),
700                ],
701                negated: false,
702            }),
703        ];
704
705        let result = builder.build(&exprs).unwrap();
706        assert!(result.is_some());
707
708        let predicates = result.unwrap().default_predicates;
709        assert!(!predicates.contains_key(&IndexTarget::ColumnId(1))); // Null equality should be ignored
710        let column2_predicates = predicates.get(&IndexTarget::ColumnId(2)).unwrap();
711        assert_eq!(column2_predicates[0].list.len(), 2);
712    }
713
714    #[test]
715    fn test_build_with_invalid_expressions() {
716        let (_d, factory) = PuffinManagerFactory::new_for_test_block("test_build_with_invalid_");
717        let metadata = test_region_metadata();
718        let builder = BloomFilterIndexApplierBuilder::new(
719            "test".to_string(),
720            PathType::Bare,
721            test_object_store(),
722            &metadata,
723            factory,
724        );
725        let exprs = vec![
726            // Non-equality operator
727            Expr::BinaryExpr(BinaryExpr {
728                left: Box::new(column("column1")),
729                op: Operator::Gt,
730                right: Box::new("value1".lit()),
731            }),
732            // Non-existent column
733            Expr::BinaryExpr(BinaryExpr {
734                left: Box::new(column("non_existent")),
735                op: Operator::Eq,
736                right: Box::new("value".lit()),
737            }),
738            // Negated IN list
739            Expr::InList(InList {
740                expr: Box::new(column("column2")),
741                list: vec![int64_lit(1), int64_lit(2)],
742                negated: true,
743            }),
744        ];
745
746        let result = builder.build(&exprs).unwrap();
747        assert!(result.is_none());
748    }
749
750    #[test]
751    fn test_build_with_multiple_predicates_same_column() {
752        let (_d, factory) = PuffinManagerFactory::new_for_test_block("test_build_with_multiple_");
753        let metadata = test_region_metadata();
754        let builder = BloomFilterIndexApplierBuilder::new(
755            "test".to_string(),
756            PathType::Bare,
757            test_object_store(),
758            &metadata,
759            factory,
760        );
761        let exprs = vec![
762            Expr::BinaryExpr(BinaryExpr {
763                left: Box::new(column("column1")),
764                op: Operator::Eq,
765                right: Box::new("value1".lit()),
766            }),
767            Expr::InList(InList {
768                expr: Box::new(column("column1")),
769                list: vec!["value2".lit(), "value3".lit()],
770                negated: false,
771            }),
772        ];
773
774        let result = builder.build(&exprs).unwrap();
775        assert!(result.is_some());
776
777        let predicates = result.unwrap().default_predicates;
778        let column_predicates = predicates.get(&IndexTarget::ColumnId(1)).unwrap();
779        assert_eq!(column_predicates.len(), 2);
780    }
781}