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
215 .context
216 .contains_delete(self.row_group_idx)
217 .inspect_err(|e| {
218 error!(e; "Failed to decode min value of op_type, fallback to FlatRowGroupReader");
219 })
220 .unwrap_or(true);
221 put_only && self.select_all() && self.context.remaining_filters_preserve_last_row()
222 } else {
223 false
224 };
225
226 let flat_prune_reader = if use_last_row_reader {
227 let flat_row_group_reader =
228 FlatRowGroupReader::new(self.context.clone(), parquet_reader);
229 let cache_strategy = if self.context.reader_builder.has_predicate_prefilter() {
232 CacheStrategy::Disabled
233 } else {
234 self.context.reader_builder.cache_strategy().clone()
235 };
236 let reader = FlatRowGroupLastRowCachedReader::new(
237 self.file_handle().file_id().file_id(),
238 self.row_group_idx,
239 cache_strategy,
240 self.context.read_format().parquet_read_columns(),
241 self.context.read_format().json_target_types().clone(),
242 flat_row_group_reader,
243 );
244 FlatPruneReader::new_with_last_row_reader(self.context.clone(), reader, skip_fields)
245 } else {
246 let flat_row_group_reader =
247 FlatRowGroupReader::new(self.context.clone(), parquet_reader);
248 FlatPruneReader::new_with_row_group_reader(
249 self.context.clone(),
250 flat_row_group_reader,
251 skip_fields,
252 )
253 };
254
255 Ok(Some(flat_prune_reader))
256 }
257
258 pub(crate) async fn primary_key_reader(
262 &self,
263 fetch_metrics: Option<&ParquetFetchMetrics>,
264 ) -> Result<Option<ProjectedRecordBatchStream>> {
265 self.primary_key_reader_inner(fetch_metrics, true).await
266 }
267
268 async fn primary_key_reader_inner(
269 &self,
270 fetch_metrics: Option<&ParquetFetchMetrics>,
271 check_dynamic_filter: bool,
272 ) -> Result<Option<ProjectedRecordBatchStream>> {
273 if check_dynamic_filter && !self.in_dynamic_filter_range() {
274 return Ok(None);
275 }
276 let stream = self
277 .context
278 .reader_builder
279 .build_primary_key(self.context.build_context(
280 self.row_group_idx,
281 self.row_selection.clone(),
282 fetch_metrics,
283 ))
284 .await?;
285 if self.context.compat_batch().is_none() {
286 return Ok(Some(stream));
287 }
288
289 let context = self.context.clone();
290 let stream = stream
291 .map(move |batch| {
292 let batch = batch?;
293 let compat = context.compat_batch().context(UnexpectedSnafu {
294 reason: "Primary-key compatibility helper is missing",
295 })?;
296 let primary_key = compat.compat_primary_key(batch.column(0))?;
297 RecordBatch::try_new(batch.schema(), vec![primary_key]).context(NewRecordBatchSnafu)
298 })
299 .boxed();
300 Ok(Some(stream))
301 }
302
303 pub(crate) async fn reader_by_primary_key(
310 &self,
311 primary_key_filter: &mut dyn mito_codec::row_converter::PrimaryKeyFilter,
312 fetch_metrics: Option<&ParquetFetchMetrics>,
313 ) -> Result<Option<FlatRowGroupReader>> {
314 let Some(mut primary_keys) = self.primary_key_reader_inner(fetch_metrics, false).await?
315 else {
316 return Ok(None);
317 };
318
319 let mut masks = Vec::new();
320 while let Some(batch) = primary_keys.next().await {
321 let batch = batch?;
322 masks.push(BooleanArray::from(primary_key_filter_mask(
323 &batch,
324 primary_key_filter,
325 )?));
326 }
327 let Some(selected) = refine_primary_key_selection(&masks, &self.row_selection) else {
328 return Ok(None);
329 };
330
331 let stream = self
332 .context
333 .reader_builder
334 .build_without_prefilter(self.context.build_context(
335 self.row_group_idx,
336 Some(selected),
337 fetch_metrics,
338 ))
339 .await?;
340 Ok(Some(FlatRowGroupReader::new(self.context.clone(), stream)))
341 }
342
343 pub(crate) fn compat_batch(&self) -> Option<&FlatCompatBatch> {
345 self.context.compat_batch()
346 }
347
348 pub(crate) fn compaction_projection_mapper(&self) -> Option<&CompactionProjectionMapper> {
350 self.context.compaction_projection_mapper()
351 }
352
353 pub(crate) fn precise_filter_flat(
355 &self,
356 input: RecordBatch,
357 skip_fields: bool,
358 skip_tags: bool,
359 ) -> Result<Option<RecordBatch>> {
360 self.context
361 .precise_filter_flat(input, skip_fields, skip_tags)
362 }
363
364 pub(crate) fn pre_filter_mode(&self) -> PreFilterMode {
366 self.context.pre_filter_mode()
367 }
368
369 pub(crate) fn file_handle(&self) -> &FileHandle {
371 self.context.reader_builder.file_handle()
372 }
373}
374
375fn refine_primary_key_selection(
376 masks: &[BooleanArray],
377 original: &Option<RowSelection>,
378) -> Option<RowSelection> {
379 if masks.is_empty() {
380 return None;
381 }
382 let selected = RowSelection::from_filters(masks);
383 let selected = match original {
384 Some(original) => original.and_then(&selected),
385 None => selected,
386 };
387 (selected.row_count() > 0).then_some(selected)
388}
389
390pub struct FileRangeContext {
392 reader_builder: RowGroupReaderBuilder,
394 base: RangeBase,
396}
397
398pub type FileRangeContextRef = Arc<FileRangeContext>;
399
400impl FileRangeContext {
401 pub(crate) fn new(reader_builder: RowGroupReaderBuilder, base: RangeBase) -> Self {
403 Self {
404 reader_builder,
405 base,
406 }
407 }
408
409 pub(crate) fn filters(&self) -> &[SimpleFilterContext] {
411 &self.base.filters
412 }
413
414 pub(crate) fn has_partition_filter(&self) -> bool {
416 self.base.partition_filter.is_some()
417 }
418
419 fn remaining_filters_preserve_last_row(&self) -> bool {
422 !self.has_partition_filter()
423 && self
424 .filters()
425 .iter()
426 .all(|filter| filter.semantic_type() == SemanticType::Tag)
427 }
428
429 pub(crate) fn read_format(&self) -> &FlatReadFormat {
431 &self.base.read_format
432 }
433
434 pub(crate) fn reader_builder(&self) -> &RowGroupReaderBuilder {
436 &self.reader_builder
437 }
438
439 pub(crate) fn compat_batch(&self) -> Option<&FlatCompatBatch> {
441 self.base.compat_batch.as_ref()
442 }
443
444 pub(crate) fn compaction_projection_mapper(&self) -> Option<&CompactionProjectionMapper> {
446 self.base.compaction_projection_mapper.as_ref()
447 }
448
449 pub(crate) fn set_compat_batch(&mut self, compat: Option<FlatCompatBatch>) {
451 self.base.compat_batch = compat;
452 }
453
454 pub(crate) fn precise_filter_flat(
458 &self,
459 input: RecordBatch,
460 skip_fields: bool,
461 skip_tags: bool,
462 ) -> Result<Option<RecordBatch>> {
463 self.base.precise_filter_flat(input, skip_fields, skip_tags)
464 }
465
466 pub(crate) fn pre_filter_mode(&self) -> PreFilterMode {
467 self.base.pre_filter_mode
468 }
469
470 pub(crate) fn contains_delete(&self, row_group_index: usize) -> Result<bool> {
472 let metadata = self.reader_builder.parquet_metadata();
473 row_group_contains_delete(metadata, row_group_index, self.reader_builder.file_path())
474 }
475
476 pub(crate) fn build_context<'a>(
478 &'a self,
479 row_group_idx: usize,
480 row_selection: Option<RowSelection>,
481 fetch_metrics: Option<&'a ParquetFetchMetrics>,
482 ) -> RowGroupBuildContext<'a> {
483 RowGroupBuildContext {
484 row_group_idx,
485 row_selection,
486 fetch_metrics,
487 }
488 }
489
490 pub(crate) fn memory_size(&self) -> usize {
493 self.reader_builder.parquet_metadata_size()
494 }
495}
496
497#[derive(Debug, Clone, Copy, PartialEq, Eq)]
499pub enum PreFilterMode {
500 All,
502 SkipFields,
504}
505
506impl PreFilterMode {
507 pub(crate) fn skip_fields(self) -> bool {
508 matches!(self, Self::SkipFields)
509 }
510}
511
512pub(crate) struct PartitionFilterContext {
514 pub(crate) region_partition_physical_expr: Arc<dyn PhysicalExpr>,
515 pub(crate) partition_schema: Arc<Schema>,
518}
519
520pub(crate) struct RangeBase {
522 pub(crate) filters: Vec<SimpleFilterContext>,
524 pub(crate) dyn_filters: Vec<Arc<DynamicFilterPhysicalExpr>>,
526 pub(crate) read_format: FlatReadFormat,
528 pub(crate) expected_metadata: Option<RegionMetadataRef>,
529 pub(crate) prune_schema: Arc<Schema>,
531 pub(crate) codec: Arc<dyn PrimaryKeyCodec>,
533 pub(crate) compat_batch: Option<FlatCompatBatch>,
535 pub(crate) compaction_projection_mapper: Option<CompactionProjectionMapper>,
537 pub(crate) pre_filter_mode: PreFilterMode,
539 pub(crate) partition_filter: Option<PartitionFilterContext>,
541}
542
543pub(crate) struct TagDecodeState {
544 decoded_pks: Option<DecodedPrimaryKeys>,
545 decoded_tag_cache: HashMap<ColumnId, ArrayRef>,
546}
547
548impl TagDecodeState {
549 pub(crate) fn new() -> Self {
550 Self {
551 decoded_pks: None,
552 decoded_tag_cache: HashMap::new(),
553 }
554 }
555}
556
557impl RangeBase {
558 pub(crate) fn precise_filter_flat(
567 &self,
568 input: RecordBatch,
569 skip_fields: bool,
570 skip_tags: bool,
571 ) -> Result<Option<RecordBatch>> {
572 let mut tag_decode_state = TagDecodeState::new();
573 let mask =
574 self.compute_filter_mask_flat(&input, skip_fields, skip_tags, &mut tag_decode_state)?;
575
576 let Some(mut mask) = mask else {
578 return Ok(None);
579 };
580
581 if let Some(partition_filter) = &self.partition_filter {
583 let record_batch = self.project_record_batch_for_pruning_flat(
584 &input,
585 &partition_filter.partition_schema,
586 &mut tag_decode_state,
587 )?;
588 let partition_mask = self.evaluate_partition_filter(&record_batch, partition_filter)?;
589 mask = mask.bitand(&partition_mask);
590 }
591
592 let num_selected = mask.count_set_bits();
593 if num_selected == 0 {
594 return Ok(None);
595 }
596 if num_selected == input.num_rows() {
597 return Ok(Some(input));
600 }
601
602 let filtered_batch =
603 datatypes::arrow::compute::filter_record_batch(&input, &BooleanArray::from(mask))
604 .context(ComputeArrowSnafu)?;
605
606 if filtered_batch.num_rows() > 0 {
607 Ok(Some(filtered_batch))
608 } else {
609 Ok(None)
610 }
611 }
612
613 pub(crate) fn compute_filter_mask_flat(
624 &self,
625 input: &RecordBatch,
626 skip_fields: bool,
627 skip_tags: bool,
628 tag_decode_state: &mut TagDecodeState,
629 ) -> Result<Option<BooleanBuffer>> {
630 let mut mask = BooleanBuffer::new_set(input.num_rows());
631
632 let metadata = self.read_format.metadata();
633
634 for filter_ctx in &self.filters {
636 let filter = match filter_ctx.filter() {
637 MaybeFilter::Filter(f) => f,
638 MaybeFilter::Matched => continue,
640 MaybeFilter::Pruned => return Ok(None),
642 };
643
644 if skip_fields && filter_ctx.semantic_type() == SemanticType::Field {
646 continue;
647 }
648 if skip_tags && filter_ctx.semantic_type() == SemanticType::Tag {
649 continue;
650 }
651
652 let column_idx = self
656 .read_format
657 .projected_index_by_id(filter_ctx.column_id());
658 if let Some(idx) = column_idx {
659 let column = &input.columns().get(idx).unwrap();
660 let result = filter.evaluate_array(column).context(RecordBatchSnafu)?;
661 mask = mask.bitand(&result);
662 } else if filter_ctx.semantic_type() == SemanticType::Tag {
663 let column_id = filter_ctx.column_id();
665
666 if let Some(tag_column) =
667 self.maybe_decode_tag_column(metadata, column_id, input, tag_decode_state)?
668 {
669 let result = filter
670 .evaluate_array(&tag_column)
671 .context(RecordBatchSnafu)?;
672 mask = mask.bitand(&result);
673 }
674 } else if filter_ctx.semantic_type() == SemanticType::Timestamp {
675 let time_index_pos = time_index_column_index(input.num_columns());
676 let column = &input.columns()[time_index_pos];
677 let result = filter.evaluate_array(column).context(RecordBatchSnafu)?;
678 mask = mask.bitand(&result);
679 }
680 }
682
683 Ok(Some(mask))
684 }
685
686 fn maybe_decode_tag_column(
688 &self,
689 metadata: &RegionMetadataRef,
690 column_id: ColumnId,
691 input: &RecordBatch,
692 tag_decode_state: &mut TagDecodeState,
693 ) -> Result<Option<ArrayRef>> {
694 let Some(pk_index) = metadata.primary_key_index(column_id) else {
695 return Ok(None);
696 };
697
698 if let Some(cached_column) = tag_decode_state.decoded_tag_cache.get(&column_id) {
699 return Ok(Some(cached_column.clone()));
700 }
701
702 if tag_decode_state.decoded_pks.is_none() {
703 tag_decode_state.decoded_pks = Some(decode_primary_keys(self.codec.as_ref(), input)?);
704 }
705
706 let pk_index = if self.codec.encoding() == PrimaryKeyEncoding::Sparse {
707 None
708 } else {
709 Some(pk_index)
710 };
711 let Some(column_index) = metadata.column_index_by_id(column_id) else {
712 return Ok(None);
713 };
714 let Some(decoded) = tag_decode_state.decoded_pks.as_ref() else {
715 return Ok(None);
716 };
717
718 let column_metadata = &metadata.column_metadatas[column_index];
719 let tag_column = decoded.get_tag_column(
720 column_id,
721 pk_index,
722 &column_metadata.column_schema.data_type,
723 )?;
724 tag_decode_state
725 .decoded_tag_cache
726 .insert(column_id, tag_column.clone());
727
728 Ok(Some(tag_column))
729 }
730
731 fn evaluate_partition_filter(
733 &self,
734 record_batch: &RecordBatch,
735 partition_filter: &PartitionFilterContext,
736 ) -> Result<BooleanBuffer> {
737 let columnar_value = partition_filter
738 .region_partition_physical_expr
739 .evaluate(record_batch)
740 .context(EvalPartitionFilterSnafu)?;
741 let array = columnar_value
742 .into_array(record_batch.num_rows())
743 .context(EvalPartitionFilterSnafu)?;
744 let boolean_array =
745 array
746 .as_any()
747 .downcast_ref::<BooleanArray>()
748 .context(UnexpectedSnafu {
749 reason: "Failed to downcast to BooleanArray".to_string(),
750 })?;
751
752 let mut mask = boolean_array.values().clone();
754 if let Some(nulls) = boolean_array.nulls() {
755 mask = mask.bitand(nulls.inner());
756 }
757
758 Ok(mask)
759 }
760
761 fn project_record_batch_for_pruning_flat(
766 &self,
767 input: &RecordBatch,
768 schema: &Arc<Schema>,
769 tag_decode_state: &mut TagDecodeState,
770 ) -> Result<RecordBatch> {
771 let arrow_schema = schema.arrow_schema();
772 let mut columns = Vec::with_capacity(arrow_schema.fields().len());
773
774 let metadata = self.read_format.metadata();
775
776 for field in arrow_schema.fields() {
777 let column_id = metadata.column_by_name(field.name()).map(|c| c.column_id);
778
779 let Some(column_id) = column_id else {
780 return UnexpectedSnafu {
781 reason: format!(
782 "Partition pruning schema expects column '{}' but it is missing in \
783 region metadata",
784 field.name()
785 ),
786 }
787 .fail();
788 };
789
790 if let Some(idx) = self.read_format.projected_index_by_id(column_id) {
791 columns.push(input.column(idx).clone());
792 continue;
793 }
794
795 if metadata.time_index_column().column_id == column_id {
796 let time_index_pos = time_index_column_index(input.num_columns());
797 columns.push(input.column(time_index_pos).clone());
798 continue;
799 }
800
801 if let Some(tag_column) =
802 self.maybe_decode_tag_column(metadata, column_id, input, tag_decode_state)?
803 {
804 columns.push(tag_column);
805 continue;
806 }
807
808 return UnexpectedSnafu {
809 reason: format!(
810 "Partition pruning schema expects column '{}' (id {}) but it is not \
811 present in projected record batch",
812 field.name(),
813 column_id
814 ),
815 }
816 .fail();
817 }
818
819 RecordBatch::try_new(arrow_schema.clone(), columns).context(NewRecordBatchSnafu)
820 }
821}
822
823#[cfg(test)]
824mod tests {
825 use std::sync::Arc;
826
827 use datafusion_expr::{col, lit};
828 use datatypes::prelude::ConcreteDataType;
829 use datatypes::schema::ColumnSchema;
830 use datatypes::value::Value;
831 use parquet::arrow::arrow_reader::RowSelector;
832 use partition::expr::col as partition_col;
833
834 use super::*;
835 use crate::read::read_columns::ReadColumns;
836 use crate::sst::parquet::flat_format::FlatReadFormat;
837 use crate::test_util::sst_util::{new_record_batch_with_custom_sequence, sst_region_metadata};
838
839 fn new_test_range_base(filters: Vec<SimpleFilterContext>) -> RangeBase {
840 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
841
842 let read_format = FlatReadFormat::new(
843 metadata.clone(),
844 ReadColumns::new(metadata.column_metadatas.iter().map(|c| c.column_id)),
845 None,
846 "test",
847 true,
848 )
849 .unwrap();
850
851 RangeBase {
852 filters,
853 dyn_filters: vec![],
854 read_format,
855 expected_metadata: None,
856 prune_schema: metadata.schema.clone(),
857 codec: mito_codec::row_converter::build_primary_key_codec(metadata.as_ref()),
858 compat_batch: None,
859 compaction_projection_mapper: None,
860 pre_filter_mode: PreFilterMode::All,
861 partition_filter: None,
862 }
863 }
864
865 #[test]
866 fn test_compute_filter_mask_flat_applies_remaining_simple_filters() {
867 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
868 let filters = vec![
869 SimpleFilterContext::new_opt(&metadata, None, &col("tag_0").eq(lit("a"))).unwrap(),
870 SimpleFilterContext::new_opt(&metadata, None, &col("field_0").gt(lit(1_u64))).unwrap(),
871 ];
872 let base = new_test_range_base(filters);
873 let batch = new_record_batch_with_custom_sequence(&["b", "x"], 0, 4, 1);
874
875 let mask = base
876 .compute_filter_mask_flat(&batch, false, false, &mut TagDecodeState::new())
877 .unwrap()
878 .unwrap();
879 assert_eq!(mask.count_set_bits(), 0);
880
881 let mask = base
882 .compute_filter_mask_flat(&batch, false, true, &mut TagDecodeState::new())
883 .unwrap()
884 .unwrap();
885 assert_eq!(mask.count_set_bits(), 2);
886 }
887
888 #[test]
889 fn test_precise_filter_flat_returns_input_when_nothing_is_filtered() {
890 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
891 let tag_filter =
892 SimpleFilterContext::new_opt(&metadata, None, &col("tag_0").eq(lit("z"))).unwrap();
893 let base = new_test_range_base(vec![tag_filter]);
894 let batch = new_record_batch_with_custom_sequence(&["b", "x"], 0, 4, 1);
895
896 let filtered = base
899 .precise_filter_flat(batch.clone(), false, true)
900 .unwrap()
901 .unwrap();
902 assert_eq!(batch, filtered);
903 }
904
905 #[test]
906 fn test_compute_filter_mask_flat_does_not_postfilter_physical_filters() {
907 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
908 let read_format = FlatReadFormat::new(
909 metadata.clone(),
910 ReadColumns::new(metadata.column_metadatas.iter().map(|c| c.column_id)),
911 None,
912 "test",
913 true,
914 )
915 .unwrap();
916 let physical_filter = crate::sst::parquet::reader::PhysicalFilterContext::new_opt(
917 &metadata,
918 None,
919 &read_format,
920 &col("field_0").in_list(vec![lit(1_u64), lit(2_u64)], false),
921 );
922 assert!(physical_filter.is_some());
923 let base = new_test_range_base(vec![]);
924 let batch = new_record_batch_with_custom_sequence(&["b", "x"], 0, 4, 1);
925
926 let mask = base
927 .compute_filter_mask_flat(&batch, false, false, &mut TagDecodeState::new())
928 .unwrap()
929 .unwrap();
930 assert_eq!(mask.count_set_bits(), 4);
931 }
932
933 #[test]
934 fn test_precise_filter_flat_applies_partition_filter_when_skipping_tags() {
935 let metadata: RegionMetadataRef = Arc::new(sst_region_metadata());
936 let tag_filter =
937 SimpleFilterContext::new_opt(&metadata, None, &col("tag_0").eq(lit("z"))).unwrap();
938 let mut base = new_test_range_base(vec![tag_filter]);
939 let batch = new_record_batch_with_custom_sequence(&["b", "x"], 0, 4, 1);
940
941 let batch_schema = batch.schema();
942 let tag_field = batch_schema.field(0);
943 let partition_schema = Arc::new(Schema::new(vec![ColumnSchema::new(
944 "tag_0".to_string(),
945 ConcreteDataType::from_arrow_type(tag_field.data_type()),
946 tag_field.is_nullable(),
947 )]));
948 let partition_expr = partition_col("tag_0")
949 .gt_eq(Value::String("a".into()))
950 .and(partition_col("tag_0").lt(Value::String("c".into())));
951 base.partition_filter = Some(PartitionFilterContext {
952 region_partition_physical_expr: partition_expr
953 .try_as_physical_expr(partition_schema.arrow_schema())
954 .unwrap(),
955 partition_schema,
956 });
957
958 let filtered = base
959 .precise_filter_flat(batch, false, true)
960 .unwrap()
961 .unwrap();
962 assert_eq!(filtered.num_rows(), 4);
963
964 let out_of_partition = new_record_batch_with_custom_sequence(&["z", "x"], 0, 4, 1);
965 assert!(
966 base.precise_filter_flat(out_of_partition, false, true)
967 .unwrap()
968 .is_none()
969 );
970 }
971
972 #[test]
973 fn test_refine_primary_key_selection_intersects_original_selection() {
974 let original = Some(RowSelection::from(vec![
975 RowSelector::skip(2),
976 RowSelector::select(3),
977 RowSelector::skip(1),
978 RowSelector::select(2),
979 ]));
980 let mask = BooleanArray::from(vec![true, false, true, false, true]);
981 let actual = refine_primary_key_selection(&[mask], &original).unwrap();
982 let expected = RowSelection::from(vec![
983 RowSelector::skip(2),
984 RowSelector::select(1),
985 RowSelector::skip(1),
986 RowSelector::select(1),
987 RowSelector::skip(2),
988 RowSelector::select(1),
989 ]);
990 assert_eq!(actual, expected);
991 }
992}