1use std::collections::HashMap;
19use std::ops::BitAnd;
20use std::sync::Arc;
21
22use api::v1::{OpType, SemanticType};
23use common_telemetry::error;
24use datafusion::physical_plan::PhysicalExpr;
25use datafusion::physical_plan::expressions::DynamicFilterPhysicalExpr;
26use datatypes::arrow::array::{Array as _, ArrayRef, BooleanArray};
27use datatypes::arrow::buffer::BooleanBuffer;
28use datatypes::arrow::record_batch::RecordBatch;
29use datatypes::schema::Schema;
30use futures::StreamExt;
31use mito_codec::row_converter::PrimaryKeyCodec;
32use parquet::arrow::arrow_reader::RowSelection;
33use parquet::file::metadata::ParquetMetaData;
34use parquet::file::statistics::Statistics;
35use snafu::{OptionExt, ResultExt};
36use store_api::codec::PrimaryKeyEncoding;
37use store_api::metadata::RegionMetadataRef;
38use store_api::storage::{ColumnId, TimeSeriesRowSelector};
39use table::predicate::Predicate;
40
41use crate::cache::CacheStrategy;
42use crate::error::{
43 ComputeArrowSnafu, DecodeStatsSnafu, EvalPartitionFilterSnafu, NewRecordBatchSnafu,
44 RecordBatchSnafu, Result, StatsNotPresentSnafu, UnexpectedSnafu,
45};
46use crate::read::compat::FlatCompatBatch;
47use crate::read::flat_projection::CompactionProjectionMapper;
48use crate::read::last_row::FlatRowGroupLastRowCachedReader;
49use crate::read::prune::FlatPruneReader;
50use crate::sst::file::FileHandle;
51use crate::sst::parquet::flat_format::{
52 DecodedPrimaryKeys, FlatReadFormat, decode_primary_keys, primary_key_column_index,
53 time_index_column_index,
54};
55use crate::sst::parquet::json_align::ProjectedRecordBatchStream;
56use crate::sst::parquet::prefilter::primary_key_filter_mask;
57use crate::sst::parquet::reader::{
58 FlatRowGroupReader, MaybeFilter, RowGroupBuildContext, RowGroupReaderBuilder,
59 SimpleFilterContext,
60};
61use crate::sst::parquet::row_group::ParquetFetchMetrics;
62use crate::sst::parquet::stats::RowGroupPruningStats;
63
64pub(crate) fn row_group_contains_delete(
69 parquet_meta: &ParquetMetaData,
70 row_group_index: usize,
71 file_path: &str,
72) -> Result<bool> {
73 let row_group_metadata = &parquet_meta.row_groups()[row_group_index];
74
75 let column_metadata = &row_group_metadata.columns().last().unwrap();
77 let stats = column_metadata
78 .statistics()
79 .context(StatsNotPresentSnafu { file_path })?;
80 stats
81 .min_bytes_opt()
82 .context(StatsNotPresentSnafu { file_path })?
83 .try_into()
84 .map(i32::from_le_bytes)
85 .map(|min_op_type| min_op_type == OpType::Delete as i32)
86 .ok()
87 .context(DecodeStatsSnafu { file_path })
88}
89
90#[derive(Clone)]
93pub struct FileRange {
94 context: FileRangeContextRef,
96 row_group_idx: usize,
98 row_selection: Option<RowSelection>,
100}
101
102impl FileRange {
103 pub(crate) fn region_metadata(&self) -> &RegionMetadataRef {
105 self.context.read_format().metadata()
106 }
107
108 pub(crate) fn primary_key_range(&self) -> Option<(&[u8], &[u8])> {
110 let metadata = self.context.reader_builder.parquet_metadata();
111 let num_columns = metadata.file_metadata().schema_descr().num_columns();
112 let primary_key_index = primary_key_column_index(num_columns);
113 match metadata
114 .row_group(self.row_group_idx)
115 .column(primary_key_index)
116 .statistics()?
117 {
118 Statistics::ByteArray(statistics) => {
119 Some((statistics.min_bytes_opt()?, statistics.max_bytes_opt()?))
120 }
121 _ => None,
122 }
123 }
124
125 pub(crate) fn new(
127 context: FileRangeContextRef,
128 row_group_idx: usize,
129 row_selection: Option<RowSelection>,
130 ) -> Self {
131 Self {
132 context,
133 row_group_idx,
134 row_selection,
135 }
136 }
137
138 fn select_all(&self) -> bool {
140 let rows_in_group = self
141 .context
142 .reader_builder
143 .parquet_metadata()
144 .row_group(self.row_group_idx)
145 .num_rows();
146
147 let Some(row_selection) = &self.row_selection else {
148 return true;
149 };
150 row_selection.row_count() == rows_in_group as usize
151 }
152
153 fn in_dynamic_filter_range(&self) -> bool {
158 if self.context.base.dyn_filters.is_empty() {
159 return true;
160 }
161 let curr_row_group = self
162 .context
163 .reader_builder
164 .parquet_metadata()
165 .row_group(self.row_group_idx);
166 let read_format = self.context.read_format();
167 let prune_schema = &self.context.base.prune_schema;
168 let stats = RowGroupPruningStats::new(
169 std::slice::from_ref(curr_row_group),
170 read_format,
171 self.context.base.expected_metadata.clone(),
172 self.context.base.pre_filter_mode.skip_fields(),
173 );
174
175 let pred = Predicate::with_dyn_filters(vec![], self.context.base.dyn_filters.clone());
177
178 pred.prune_with_stats(&stats, prune_schema.arrow_schema())
179 .first()
180 .cloned()
181 .unwrap_or(true) }
183
184 pub async fn flat_reader(
186 &self,
187 selector: Option<TimeSeriesRowSelector>,
188 fetch_metrics: Option<&ParquetFetchMetrics>,
189 ) -> Result<Option<FlatPruneReader>> {
190 if !self.in_dynamic_filter_range() {
191 return Ok(None);
192 }
193 let skip_fields = self.context.base.pre_filter_mode.skip_fields();
195 let parquet_reader = self
196 .context
197 .reader_builder
198 .build(self.context.build_context(
199 self.row_group_idx,
200 self.row_selection.clone(),
201 fetch_metrics,
202 ))
203 .await?;
204
205 let use_last_row_reader = if selector
206 .map(|s| s == TimeSeriesRowSelector::LastRow)
207 .unwrap_or(false)
208 {
209 let put_only = !self
212 .context
213 .contains_delete(self.row_group_idx)
214 .inspect_err(|e| {
215 error!(e; "Failed to decode min value of op_type, fallback to FlatRowGroupReader");
216 })
217 .unwrap_or(true);
218 put_only && self.select_all()
219 } else {
220 false
221 };
222
223 let flat_prune_reader = if use_last_row_reader {
224 let flat_row_group_reader =
225 FlatRowGroupReader::new(self.context.clone(), parquet_reader);
226 let cache_strategy = if self.context.reader_builder.has_predicate_prefilter() {
229 CacheStrategy::Disabled
230 } else {
231 self.context.reader_builder.cache_strategy().clone()
232 };
233 let reader = FlatRowGroupLastRowCachedReader::new(
234 self.file_handle().file_id().file_id(),
235 self.row_group_idx,
236 cache_strategy,
237 self.context.read_format().parquet_read_columns(),
238 flat_row_group_reader,
239 );
240 FlatPruneReader::new_with_last_row_reader(self.context.clone(), reader, skip_fields)
241 } else {
242 let flat_row_group_reader =
243 FlatRowGroupReader::new(self.context.clone(), parquet_reader);
244 FlatPruneReader::new_with_row_group_reader(
245 self.context.clone(),
246 flat_row_group_reader,
247 skip_fields,
248 )
249 };
250
251 Ok(Some(flat_prune_reader))
252 }
253
254 pub(crate) async fn primary_key_reader(
258 &self,
259 fetch_metrics: Option<&ParquetFetchMetrics>,
260 ) -> Result<Option<ProjectedRecordBatchStream>> {
261 self.primary_key_reader_inner(fetch_metrics, true).await
262 }
263
264 async fn primary_key_reader_inner(
265 &self,
266 fetch_metrics: Option<&ParquetFetchMetrics>,
267 check_dynamic_filter: bool,
268 ) -> Result<Option<ProjectedRecordBatchStream>> {
269 if check_dynamic_filter && !self.in_dynamic_filter_range() {
270 return Ok(None);
271 }
272 let stream = self
273 .context
274 .reader_builder
275 .build_primary_key(self.context.build_context(
276 self.row_group_idx,
277 self.row_selection.clone(),
278 fetch_metrics,
279 ))
280 .await?;
281 if self.context.compat_batch().is_none() {
282 return Ok(Some(stream));
283 }
284
285 let context = self.context.clone();
286 let stream = stream
287 .map(move |batch| {
288 let batch = batch?;
289 let compat = context.compat_batch().context(UnexpectedSnafu {
290 reason: "Primary-key compatibility helper is missing",
291 })?;
292 let primary_key = compat.compat_primary_key(batch.column(0))?;
293 RecordBatch::try_new(batch.schema(), vec![primary_key]).context(NewRecordBatchSnafu)
294 })
295 .boxed();
296 Ok(Some(stream))
297 }
298
299 pub(crate) async fn reader_by_primary_key(
306 &self,
307 primary_key_filter: &mut dyn mito_codec::row_converter::PrimaryKeyFilter,
308 fetch_metrics: Option<&ParquetFetchMetrics>,
309 ) -> Result<Option<FlatRowGroupReader>> {
310 let Some(mut primary_keys) = self.primary_key_reader_inner(fetch_metrics, false).await?
311 else {
312 return Ok(None);
313 };
314
315 let mut masks = Vec::new();
316 while let Some(batch) = primary_keys.next().await {
317 let batch = batch?;
318 masks.push(BooleanArray::from(primary_key_filter_mask(
319 &batch,
320 primary_key_filter,
321 )?));
322 }
323 let Some(selected) = refine_primary_key_selection(&masks, &self.row_selection) else {
324 return Ok(None);
325 };
326
327 let stream = self
328 .context
329 .reader_builder
330 .build_without_prefilter(self.context.build_context(
331 self.row_group_idx,
332 Some(selected),
333 fetch_metrics,
334 ))
335 .await?;
336 Ok(Some(FlatRowGroupReader::new(self.context.clone(), stream)))
337 }
338
339 pub(crate) fn compat_batch(&self) -> Option<&FlatCompatBatch> {
341 self.context.compat_batch()
342 }
343
344 pub(crate) fn compaction_projection_mapper(&self) -> Option<&CompactionProjectionMapper> {
346 self.context.compaction_projection_mapper()
347 }
348
349 pub(crate) fn precise_filter_flat(
351 &self,
352 input: RecordBatch,
353 skip_fields: bool,
354 skip_tags: bool,
355 ) -> Result<Option<RecordBatch>> {
356 self.context
357 .precise_filter_flat(input, skip_fields, skip_tags)
358 }
359
360 pub(crate) fn pre_filter_mode(&self) -> PreFilterMode {
362 self.context.pre_filter_mode()
363 }
364
365 pub(crate) fn file_handle(&self) -> &FileHandle {
367 self.context.reader_builder.file_handle()
368 }
369}
370
371fn refine_primary_key_selection(
372 masks: &[BooleanArray],
373 original: &Option<RowSelection>,
374) -> Option<RowSelection> {
375 if masks.is_empty() {
376 return None;
377 }
378 let selected = RowSelection::from_filters(masks);
379 let selected = match original {
380 Some(original) => original.and_then(&selected),
381 None => selected,
382 };
383 (selected.row_count() > 0).then_some(selected)
384}
385
386pub struct FileRangeContext {
388 reader_builder: RowGroupReaderBuilder,
390 base: RangeBase,
392}
393
394pub type FileRangeContextRef = Arc<FileRangeContext>;
395
396impl FileRangeContext {
397 pub(crate) fn new(reader_builder: RowGroupReaderBuilder, base: RangeBase) -> Self {
399 Self {
400 reader_builder,
401 base,
402 }
403 }
404
405 pub(crate) fn filters(&self) -> &[SimpleFilterContext] {
407 &self.base.filters
408 }
409
410 pub(crate) fn has_partition_filter(&self) -> bool {
412 self.base.partition_filter.is_some()
413 }
414
415 pub(crate) fn read_format(&self) -> &FlatReadFormat {
417 &self.base.read_format
418 }
419
420 pub(crate) fn reader_builder(&self) -> &RowGroupReaderBuilder {
422 &self.reader_builder
423 }
424
425 pub(crate) fn compat_batch(&self) -> Option<&FlatCompatBatch> {
427 self.base.compat_batch.as_ref()
428 }
429
430 pub(crate) fn compaction_projection_mapper(&self) -> Option<&CompactionProjectionMapper> {
432 self.base.compaction_projection_mapper.as_ref()
433 }
434
435 pub(crate) fn set_compat_batch(&mut self, compat: Option<FlatCompatBatch>) {
437 self.base.compat_batch = compat;
438 }
439
440 pub(crate) fn precise_filter_flat(
444 &self,
445 input: RecordBatch,
446 skip_fields: bool,
447 skip_tags: bool,
448 ) -> Result<Option<RecordBatch>> {
449 self.base.precise_filter_flat(input, skip_fields, skip_tags)
450 }
451
452 pub(crate) fn pre_filter_mode(&self) -> PreFilterMode {
453 self.base.pre_filter_mode
454 }
455
456 pub(crate) fn contains_delete(&self, row_group_index: usize) -> Result<bool> {
458 let metadata = self.reader_builder.parquet_metadata();
459 row_group_contains_delete(metadata, row_group_index, self.reader_builder.file_path())
460 }
461
462 pub(crate) fn build_context<'a>(
464 &'a self,
465 row_group_idx: usize,
466 row_selection: Option<RowSelection>,
467 fetch_metrics: Option<&'a ParquetFetchMetrics>,
468 ) -> RowGroupBuildContext<'a> {
469 RowGroupBuildContext {
470 row_group_idx,
471 row_selection,
472 fetch_metrics,
473 }
474 }
475
476 pub(crate) fn memory_size(&self) -> usize {
479 self.reader_builder.parquet_metadata_size()
480 }
481}
482
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
485pub enum PreFilterMode {
486 All,
488 SkipFields,
490}
491
492impl PreFilterMode {
493 pub(crate) fn skip_fields(self) -> bool {
494 matches!(self, Self::SkipFields)
495 }
496}
497
498pub(crate) struct PartitionFilterContext {
500 pub(crate) region_partition_physical_expr: Arc<dyn PhysicalExpr>,
501 pub(crate) partition_schema: Arc<Schema>,
504}
505
506pub(crate) struct RangeBase {
508 pub(crate) filters: Vec<SimpleFilterContext>,
510 pub(crate) dyn_filters: Vec<Arc<DynamicFilterPhysicalExpr>>,
512 pub(crate) read_format: FlatReadFormat,
514 pub(crate) expected_metadata: Option<RegionMetadataRef>,
515 pub(crate) prune_schema: Arc<Schema>,
517 pub(crate) codec: Arc<dyn PrimaryKeyCodec>,
519 pub(crate) compat_batch: Option<FlatCompatBatch>,
521 pub(crate) compaction_projection_mapper: Option<CompactionProjectionMapper>,
523 pub(crate) pre_filter_mode: PreFilterMode,
525 pub(crate) partition_filter: Option<PartitionFilterContext>,
527}
528
529pub(crate) struct TagDecodeState {
530 decoded_pks: Option<DecodedPrimaryKeys>,
531 decoded_tag_cache: HashMap<ColumnId, ArrayRef>,
532}
533
534impl TagDecodeState {
535 pub(crate) fn new() -> Self {
536 Self {
537 decoded_pks: None,
538 decoded_tag_cache: HashMap::new(),
539 }
540 }
541}
542
543impl RangeBase {
544 pub(crate) fn precise_filter_flat(
553 &self,
554 input: RecordBatch,
555 skip_fields: bool,
556 skip_tags: bool,
557 ) -> Result<Option<RecordBatch>> {
558 let mut tag_decode_state = TagDecodeState::new();
559 let mask =
560 self.compute_filter_mask_flat(&input, skip_fields, skip_tags, &mut tag_decode_state)?;
561
562 let Some(mut mask) = mask else {
564 return Ok(None);
565 };
566
567 if let Some(partition_filter) = &self.partition_filter {
569 let record_batch = self.project_record_batch_for_pruning_flat(
570 &input,
571 &partition_filter.partition_schema,
572 &mut tag_decode_state,
573 )?;
574 let partition_mask = self.evaluate_partition_filter(&record_batch, partition_filter)?;
575 mask = mask.bitand(&partition_mask);
576 }
577
578 let num_selected = mask.count_set_bits();
579 if num_selected == 0 {
580 return Ok(None);
581 }
582 if num_selected == input.num_rows() {
583 return Ok(Some(input));
586 }
587
588 let filtered_batch =
589 datatypes::arrow::compute::filter_record_batch(&input, &BooleanArray::from(mask))
590 .context(ComputeArrowSnafu)?;
591
592 if filtered_batch.num_rows() > 0 {
593 Ok(Some(filtered_batch))
594 } else {
595 Ok(None)
596 }
597 }
598
599 pub(crate) fn compute_filter_mask_flat(
610 &self,
611 input: &RecordBatch,
612 skip_fields: bool,
613 skip_tags: bool,
614 tag_decode_state: &mut TagDecodeState,
615 ) -> Result<Option<BooleanBuffer>> {
616 let mut mask = BooleanBuffer::new_set(input.num_rows());
617
618 let metadata = self.read_format.metadata();
619
620 for filter_ctx in &self.filters {
622 let filter = match filter_ctx.filter() {
623 MaybeFilter::Filter(f) => f,
624 MaybeFilter::Matched => continue,
626 MaybeFilter::Pruned => return Ok(None),
628 };
629
630 if skip_fields && filter_ctx.semantic_type() == SemanticType::Field {
632 continue;
633 }
634 if skip_tags && filter_ctx.semantic_type() == SemanticType::Tag {
635 continue;
636 }
637
638 let column_idx = self
642 .read_format
643 .projected_index_by_id(filter_ctx.column_id());
644 if let Some(idx) = column_idx {
645 let column = &input.columns().get(idx).unwrap();
646 let result = filter.evaluate_array(column).context(RecordBatchSnafu)?;
647 mask = mask.bitand(&result);
648 } else if filter_ctx.semantic_type() == SemanticType::Tag {
649 let column_id = filter_ctx.column_id();
651
652 if let Some(tag_column) =
653 self.maybe_decode_tag_column(metadata, column_id, input, tag_decode_state)?
654 {
655 let result = filter
656 .evaluate_array(&tag_column)
657 .context(RecordBatchSnafu)?;
658 mask = mask.bitand(&result);
659 }
660 } else if filter_ctx.semantic_type() == SemanticType::Timestamp {
661 let time_index_pos = time_index_column_index(input.num_columns());
662 let column = &input.columns()[time_index_pos];
663 let result = filter.evaluate_array(column).context(RecordBatchSnafu)?;
664 mask = mask.bitand(&result);
665 }
666 }
668
669 Ok(Some(mask))
670 }
671
672 fn maybe_decode_tag_column(
674 &self,
675 metadata: &RegionMetadataRef,
676 column_id: ColumnId,
677 input: &RecordBatch,
678 tag_decode_state: &mut TagDecodeState,
679 ) -> Result<Option<ArrayRef>> {
680 let Some(pk_index) = metadata.primary_key_index(column_id) else {
681 return Ok(None);
682 };
683
684 if let Some(cached_column) = tag_decode_state.decoded_tag_cache.get(&column_id) {
685 return Ok(Some(cached_column.clone()));
686 }
687
688 if tag_decode_state.decoded_pks.is_none() {
689 tag_decode_state.decoded_pks = Some(decode_primary_keys(self.codec.as_ref(), input)?);
690 }
691
692 let pk_index = if self.codec.encoding() == PrimaryKeyEncoding::Sparse {
693 None
694 } else {
695 Some(pk_index)
696 };
697 let Some(column_index) = metadata.column_index_by_id(column_id) else {
698 return Ok(None);
699 };
700 let Some(decoded) = tag_decode_state.decoded_pks.as_ref() else {
701 return Ok(None);
702 };
703
704 let column_metadata = &metadata.column_metadatas[column_index];
705 let tag_column = decoded.get_tag_column(
706 column_id,
707 pk_index,
708 &column_metadata.column_schema.data_type,
709 )?;
710 tag_decode_state
711 .decoded_tag_cache
712 .insert(column_id, tag_column.clone());
713
714 Ok(Some(tag_column))
715 }
716
717 fn evaluate_partition_filter(
719 &self,
720 record_batch: &RecordBatch,
721 partition_filter: &PartitionFilterContext,
722 ) -> Result<BooleanBuffer> {
723 let columnar_value = partition_filter
724 .region_partition_physical_expr
725 .evaluate(record_batch)
726 .context(EvalPartitionFilterSnafu)?;
727 let array = columnar_value
728 .into_array(record_batch.num_rows())
729 .context(EvalPartitionFilterSnafu)?;
730 let boolean_array =
731 array
732 .as_any()
733 .downcast_ref::<BooleanArray>()
734 .context(UnexpectedSnafu {
735 reason: "Failed to downcast to BooleanArray".to_string(),
736 })?;
737
738 let mut mask = boolean_array.values().clone();
740 if let Some(nulls) = boolean_array.nulls() {
741 mask = mask.bitand(nulls.inner());
742 }
743
744 Ok(mask)
745 }
746
747 fn project_record_batch_for_pruning_flat(
752 &self,
753 input: &RecordBatch,
754 schema: &Arc<Schema>,
755 tag_decode_state: &mut TagDecodeState,
756 ) -> Result<RecordBatch> {
757 let arrow_schema = schema.arrow_schema();
758 let mut columns = Vec::with_capacity(arrow_schema.fields().len());
759
760 let metadata = self.read_format.metadata();
761
762 for field in arrow_schema.fields() {
763 let column_id = metadata.column_by_name(field.name()).map(|c| c.column_id);
764
765 let Some(column_id) = column_id else {
766 return UnexpectedSnafu {
767 reason: format!(
768 "Partition pruning schema expects column '{}' but it is missing in \
769 region metadata",
770 field.name()
771 ),
772 }
773 .fail();
774 };
775
776 if let Some(idx) = self.read_format.projected_index_by_id(column_id) {
777 columns.push(input.column(idx).clone());
778 continue;
779 }
780
781 if metadata.time_index_column().column_id == column_id {
782 let time_index_pos = time_index_column_index(input.num_columns());
783 columns.push(input.column(time_index_pos).clone());
784 continue;
785 }
786
787 if let Some(tag_column) =
788 self.maybe_decode_tag_column(metadata, column_id, input, tag_decode_state)?
789 {
790 columns.push(tag_column);
791 continue;
792 }
793
794 return UnexpectedSnafu {
795 reason: format!(
796 "Partition pruning schema expects column '{}' (id {}) but it is not \
797 present in projected record batch",
798 field.name(),
799 column_id
800 ),
801 }
802 .fail();
803 }
804
805 RecordBatch::try_new(arrow_schema.clone(), columns).context(NewRecordBatchSnafu)
806 }
807}
808
809#[cfg(test)]
810mod tests {
811 use std::sync::Arc;
812
813 use datafusion_expr::{col, lit};
814 use datatypes::prelude::ConcreteDataType;
815 use datatypes::schema::ColumnSchema;
816 use datatypes::value::Value;
817 use parquet::arrow::arrow_reader::RowSelector;
818 use partition::expr::col as partition_col;
819
820 use super::*;
821 use crate::read::read_columns::ReadColumns;
822 use crate::sst::parquet::flat_format::FlatReadFormat;
823 use crate::test_util::sst_util::{new_record_batch_with_custom_sequence, sst_region_metadata};
824
825 fn new_test_range_base(filters: Vec<SimpleFilterContext>) -> RangeBase {
826 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
827
828 let read_format = FlatReadFormat::new(
829 metadata.clone(),
830 ReadColumns::from_deduped_column_ids(
831 metadata.column_metadatas.iter().map(|c| c.column_id),
832 ),
833 None,
834 "test",
835 true,
836 )
837 .unwrap();
838
839 RangeBase {
840 filters,
841 dyn_filters: vec![],
842 read_format,
843 expected_metadata: None,
844 prune_schema: metadata.schema.clone(),
845 codec: mito_codec::row_converter::build_primary_key_codec(metadata.as_ref()),
846 compat_batch: None,
847 compaction_projection_mapper: None,
848 pre_filter_mode: PreFilterMode::All,
849 partition_filter: None,
850 }
851 }
852
853 #[test]
854 fn test_compute_filter_mask_flat_applies_remaining_simple_filters() {
855 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
856 let filters = vec![
857 SimpleFilterContext::new_opt(&metadata, None, &col("tag_0").eq(lit("a"))).unwrap(),
858 SimpleFilterContext::new_opt(&metadata, None, &col("field_0").gt(lit(1_u64))).unwrap(),
859 ];
860 let base = new_test_range_base(filters);
861 let batch = new_record_batch_with_custom_sequence(&["b", "x"], 0, 4, 1);
862
863 let mask = base
864 .compute_filter_mask_flat(&batch, false, false, &mut TagDecodeState::new())
865 .unwrap()
866 .unwrap();
867 assert_eq!(mask.count_set_bits(), 0);
868
869 let mask = base
870 .compute_filter_mask_flat(&batch, false, true, &mut TagDecodeState::new())
871 .unwrap()
872 .unwrap();
873 assert_eq!(mask.count_set_bits(), 2);
874 }
875
876 #[test]
877 fn test_precise_filter_flat_returns_input_when_nothing_is_filtered() {
878 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
879 let tag_filter =
880 SimpleFilterContext::new_opt(&metadata, None, &col("tag_0").eq(lit("z"))).unwrap();
881 let base = new_test_range_base(vec![tag_filter]);
882 let batch = new_record_batch_with_custom_sequence(&["b", "x"], 0, 4, 1);
883
884 let filtered = base
887 .precise_filter_flat(batch.clone(), false, true)
888 .unwrap()
889 .unwrap();
890 assert_eq!(batch, filtered);
891 }
892
893 #[test]
894 fn test_compute_filter_mask_flat_does_not_postfilter_physical_filters() {
895 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
896 let read_format = FlatReadFormat::new(
897 metadata.clone(),
898 ReadColumns::from_deduped_column_ids(
899 metadata.column_metadatas.iter().map(|c| c.column_id),
900 ),
901 None,
902 "test",
903 true,
904 )
905 .unwrap();
906 let physical_filter = crate::sst::parquet::reader::PhysicalFilterContext::new_opt(
907 &metadata,
908 None,
909 &read_format,
910 &col("field_0").in_list(vec![lit(1_u64), lit(2_u64)], false),
911 );
912 assert!(physical_filter.is_some());
913 let base = new_test_range_base(vec![]);
914 let batch = new_record_batch_with_custom_sequence(&["b", "x"], 0, 4, 1);
915
916 let mask = base
917 .compute_filter_mask_flat(&batch, false, false, &mut TagDecodeState::new())
918 .unwrap()
919 .unwrap();
920 assert_eq!(mask.count_set_bits(), 4);
921 }
922
923 #[test]
924 fn test_precise_filter_flat_applies_partition_filter_when_skipping_tags() {
925 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
926 let tag_filter =
927 SimpleFilterContext::new_opt(&metadata, None, &col("tag_0").eq(lit("z"))).unwrap();
928 let mut base = new_test_range_base(vec![tag_filter]);
929 let batch = new_record_batch_with_custom_sequence(&["b", "x"], 0, 4, 1);
930
931 let batch_schema = batch.schema();
932 let tag_field = batch_schema.field(0);
933 let partition_schema = Arc::new(Schema::new(vec![ColumnSchema::new(
934 "tag_0".to_string(),
935 ConcreteDataType::from_arrow_type(tag_field.data_type()),
936 tag_field.is_nullable(),
937 )]));
938 let partition_expr = partition_col("tag_0")
939 .gt_eq(Value::String("a".into()))
940 .and(partition_col("tag_0").lt(Value::String("c".into())));
941 base.partition_filter = Some(PartitionFilterContext {
942 region_partition_physical_expr: partition_expr
943 .try_as_physical_expr(partition_schema.arrow_schema())
944 .unwrap(),
945 partition_schema,
946 });
947
948 let filtered = base
949 .precise_filter_flat(batch, false, true)
950 .unwrap()
951 .unwrap();
952 assert_eq!(filtered.num_rows(), 4);
953
954 let out_of_partition = new_record_batch_with_custom_sequence(&["z", "x"], 0, 4, 1);
955 assert!(
956 base.precise_filter_flat(out_of_partition, false, true)
957 .unwrap()
958 .is_none()
959 );
960 }
961
962 #[test]
963 fn test_refine_primary_key_selection_intersects_original_selection() {
964 let original = Some(RowSelection::from(vec![
965 RowSelector::skip(2),
966 RowSelector::select(3),
967 RowSelector::skip(1),
968 RowSelector::select(2),
969 ]));
970 let mask = BooleanArray::from(vec![true, false, true, false, true]);
971 let actual = refine_primary_key_selection(&[mask], &original).unwrap();
972 let expected = RowSelection::from(vec![
973 RowSelector::skip(2),
974 RowSelector::select(1),
975 RowSelector::skip(1),
976 RowSelector::select(1),
977 RowSelector::skip(2),
978 RowSelector::select(1),
979 ]);
980 assert_eq!(actual, expected);
981 }
982}