1use 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 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 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 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 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 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 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 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 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
326fn 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 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 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 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))); 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 Expr::BinaryExpr(BinaryExpr {
728 left: Box::new(column("column1")),
729 op: Operator::Gt,
730 right: Box::new("value1".lit()),
731 }),
732 Expr::BinaryExpr(BinaryExpr {
734 left: Box::new(column("non_existent")),
735 op: Operator::Eq,
736 right: Box::new("value".lit()),
737 }),
738 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}