use std::collections::BTreeSet;
use arrow::{
array::{
Array, BooleanArray, Date32Array, Float32Array, Float64Array, Int32Array, Int64Array, LargeStringArray,
StringArray, StringViewArray, UInt32Array, UInt64Array,
},
datatypes::DataType,
record_batch::RecordBatch,
};
use super::{
access::{
local_parquet_source, mutable_local_parquet_source, predicate_columns, reader_projection_indices,
reader_projection_indices_for_exprs,
},
lineage::{dedup_lineages, simple_lineages_for_column, unique_simple_lineage},
rewrite,
};
use crate::{
backend::AnalysisContext,
error::{QueryError, QueryErrorCode, Result},
expr::{BinaryOp, ColumnRef, Expr, LiteralValue},
plan::{
AggregateNode, JoinNode, JoinSide, JoinType, NodeId, PlanNode, ProjectionNode, QueryNode, QueryPlan,
RewrittenNodes,
},
source::{LocalParquetSource, SourceColumnRef, SourceNode},
};
mod eval;
mod key_filter;
mod resolver;
mod seed;
pub use eval::I64ExprRangeAccumulator;
pub(crate) use eval::derive_i64_expr_range_from_filtered_subtree;
use eval::{array_i64_value, derive_dimension_key_filter, eval_bool};
pub(crate) use key_filter::select_overlapping_row_groups;
pub use key_filter::{
DIMENSION_KEY_FILTER_MAX_RUNS, DimensionKeyAccumulator, DimensionKeyFilter, KeyRange,
dimension_key_filter_from_batch,
};
pub use resolver::LocalParquetPruningResolver;
#[cfg(test)]
pub use resolver::NoDimensionPruningFacts;
pub use seed::seed_dimension_derived_reader_predicates;
use seed::{aggregate_group_output_name, remap_unqualified_columns, seed_reader_predicates_from_group_key_filters};
pub const DIMENSION_DERIVED_ROW_GROUP_PRUNING_RULE_NAME: &str = "dimension_derived_row_group_pruning";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DimensionDerivedRowGroupPruningOptions {
pub enabled: bool,
pub max_dimension_rows: u64,
}
impl DimensionDerivedRowGroupPruningOptions {
pub const DEFAULT_MAX_DIMENSION_ROWS: u64 = 1_000_000;
pub fn disabled() -> Self {
Self { enabled: false, max_dimension_rows: Self::DEFAULT_MAX_DIMENSION_ROWS }
}
pub fn validate(&self) -> Result<()> {
if self.max_dimension_rows == 0 {
return Err(QueryError::new(
QueryErrorCode::InvalidConfiguration,
"query_engine.dimension_derived_row_group_pruning_options.validate",
"dimension-derived row-group pruning option must be greater than zero",
)
.with_fact("field", "max_dimension_rows")
.with_fact("value", self.max_dimension_rows)
.with_fact("reason", "zero"));
}
Ok(())
}
}
impl Default for DimensionDerivedRowGroupPruningOptions {
fn default() -> Self {
Self { enabled: true, max_dimension_rows: Self::DEFAULT_MAX_DIMENSION_ROWS }
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DimensionDerivedRowGroupPruningDiagnostics {
pub considered_count: usize,
pub applied_count: usize,
pub candidates: Vec<DimensionDerivedRowGroupPruningCandidateDiagnostics>,
}
impl DimensionDerivedRowGroupPruningDiagnostics {
pub(crate) fn is_empty(&self) -> bool {
self.considered_count == 0 && self.applied_count == 0 && self.candidates.is_empty()
}
pub(crate) fn merge(&mut self, other: Self) {
self.considered_count += other.considered_count;
self.applied_count += other.applied_count;
self.candidates.extend(other.candidates);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DimensionDerivedRowGroupPruningCandidateDiagnostics {
pub(crate) rule_name: &'static str,
pub(crate) candidate_node_id: NodeId,
pub(crate) dimension_source_node_id: Option<NodeId>,
pub target_source_node_id: Option<NodeId>,
pub dimension_key_column: Option<String>,
pub target_key_column: Option<String>,
pub derived_min_key: Option<i64>,
pub derived_max_key: Option<i64>,
pub selected_row_groups_before: Option<usize>,
pub selected_row_groups_after: Option<usize>,
pub(crate) max_dimension_rows: u64,
pub applied: bool,
pub rejected_reason: Option<DimensionDerivedRowGroupPruningRejectedReason>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DimensionDerivedRowGroupPruningRejectedReason {
PassDisabled,
UnsupportedJoinShape,
DimensionKeyLineageUnavailable,
TargetKeyLineageUnavailable,
SameSource,
TargetLineageReusedInPlan,
DimensionPredicateAbsent,
DimensionScanNotAdmitted,
UnsupportedDimensionPredicate,
EmptyDimensionKeyRange,
TargetStatisticsUnavailable,
NoRowGroupReduction,
}
pub(crate) struct DimensionDerivedRowGroupPruningResult {
pub plan: QueryPlan,
pub change: super::NativeOptimizerChange,
pub diagnostics: DimensionDerivedRowGroupPruningDiagnostics,
}
fn record_dimension_pruning_candidate(
diagnostics: &mut DimensionDerivedRowGroupPruningDiagnostics,
candidate: DimensionDerivedRowGroupPruningCandidateDiagnostics,
) {
diagnostics.considered_count += 1;
diagnostics.candidates.push(candidate);
}
#[doc(hidden)]
pub(crate) fn optimize_dimension_derived_row_group_pruning(
plan: QueryPlan,
options: &DimensionDerivedRowGroupPruningOptions,
ctx: AnalysisContext<'_>,
resolver: &dyn LocalParquetPruningResolver,
) -> Result<DimensionDerivedRowGroupPruningResult> {
if !options.enabled {
return Ok(DimensionDerivedRowGroupPruningResult {
plan,
change: super::NativeOptimizerChange::Unchanged,
diagnostics: DimensionDerivedRowGroupPruningDiagnostics::default(),
});
}
let original_plan = plan.clone();
let (plan, seeded_reader_predicates) = seed_reader_predicates_from_group_key_filters(plan, ctx)?;
let mut nodes = plan.nodes().to_vec();
let parent_counts = rewrite::parent_counts(&plan);
let mut diagnostics = DimensionDerivedRowGroupPruningDiagnostics::default();
let mut changed = seeded_reader_predicates;
for node in plan.nodes() {
let PlanNode::Join(join) = &node.kind else {
continue;
};
if !join_shape_supported(join) {
continue;
}
for orientation in candidate_orientations(join) {
let mut candidate = DimensionDerivedRowGroupPruningCandidateDiagnostics {
rule_name: DIMENSION_DERIVED_ROW_GROUP_PRUNING_RULE_NAME,
candidate_node_id: node.id,
dimension_source_node_id: None,
target_source_node_id: None,
dimension_key_column: None,
target_key_column: None,
derived_min_key: None,
derived_max_key: None,
selected_row_groups_before: None,
selected_row_groups_after: None,
max_dimension_rows: options.max_dimension_rows,
applied: false,
rejected_reason: None,
};
let Some(dimension_lineage) = unique_simple_lineage(
&plan,
&parent_counts,
orientation.dimension_input,
orientation.dimension_key.name(),
) else {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::DimensionKeyLineageUnavailable);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
continue;
};
candidate.dimension_source_node_id = Some(dimension_lineage.source_node_id);
candidate.dimension_key_column = Some(dimension_lineage.source_column.clone());
let target_lineages = dedup_lineages(simple_lineages_for_column(
&plan,
&parent_counts,
orientation.target_input,
orientation.target_key.name(),
));
if target_lineages.is_empty() {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::TargetKeyLineageUnavailable);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
continue;
}
let mut target_candidates = Vec::new();
for target_lineage in target_lineages {
let mut target_candidate = candidate.clone();
target_candidate.target_source_node_id = Some(target_lineage.source_node_id);
target_candidate.target_key_column = Some(target_lineage.source_column.clone());
if dimension_lineage.source_node_id == target_lineage.source_node_id {
target_candidate.rejected_reason = Some(DimensionDerivedRowGroupPruningRejectedReason::SameSource);
record_dimension_pruning_candidate(&mut diagnostics, target_candidate);
continue;
}
if target_lineage.has_shared_path {
target_candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::TargetLineageReusedInPlan);
record_dimension_pruning_candidate(&mut diagnostics, target_candidate);
continue;
}
target_candidates.push((target_candidate, target_lineage));
}
if target_candidates.is_empty() {
continue;
}
let Some(dimension_source) = local_parquet_source(&plan, dimension_lineage.source_node_id) else {
for (mut candidate, _) in target_candidates {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::DimensionKeyLineageUnavailable);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
}
continue;
};
if dimension_source.reader_predicate.is_none() {
for (mut candidate, _) in target_candidates {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::DimensionPredicateAbsent);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
}
continue;
}
if !resolver.dimension_scan_admitted(dimension_source, options.max_dimension_rows) {
for (mut candidate, _) in target_candidates {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::DimensionScanNotAdmitted);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
}
continue;
}
let Some(key_filter) =
derive_dimension_key_filter(dimension_source, &dimension_lineage.source_column, resolver)
else {
for (mut candidate, _) in target_candidates {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::UnsupportedDimensionPredicate);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
}
continue;
};
let Some(key_filter) = key_filter else {
for (mut candidate, _) in target_candidates {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::EmptyDimensionKeyRange);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
}
continue;
};
let key_bounds = key_filter.bounds();
for (mut candidate, target_lineage) in target_candidates {
candidate.derived_min_key = Some(key_bounds.min);
candidate.derived_max_key = Some(key_bounds.max);
let Some(target_source) = mutable_local_parquet_source(&mut nodes, target_lineage.source_node_id)
else {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::TargetKeyLineageUnavailable);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
continue;
};
let Some(prune_result) =
prune_target_source(target_source, &target_lineage.source_column, &key_filter, resolver)
else {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::TargetStatisticsUnavailable);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
continue;
};
candidate.selected_row_groups_before = Some(prune_result.before);
candidate.selected_row_groups_after = Some(prune_result.after);
if !prune_result.applied {
candidate.rejected_reason =
Some(DimensionDerivedRowGroupPruningRejectedReason::NoRowGroupReduction);
record_dimension_pruning_candidate(&mut diagnostics, candidate);
continue;
}
candidate.applied = true;
diagnostics.applied_count += 1;
changed = true;
record_dimension_pruning_candidate(&mut diagnostics, candidate);
}
}
}
if seeded_reader_predicates && diagnostics.applied_count == 0 {
return Ok(DimensionDerivedRowGroupPruningResult {
plan: original_plan,
change: super::NativeOptimizerChange::Unchanged,
diagnostics,
});
}
let change = if changed { super::NativeOptimizerChange::Changed } else { super::NativeOptimizerChange::Unchanged };
let root = plan.root_id();
Ok(DimensionDerivedRowGroupPruningResult {
plan: plan.rewrite(RewrittenNodes::refined_sources(nodes, root))?,
change,
diagnostics,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DimensionDerivedPruningRequest {
pub(crate) dimension_source_node_id: NodeId,
pub dimension_key_column: String,
pub(crate) target_source_node_id: NodeId,
pub target_key_column: String,
}
pub fn discover_dimension_derived_pruning_requests(plan: &QueryPlan) -> Vec<DimensionDerivedPruningRequest> {
let parent_counts = rewrite::parent_counts(plan);
let mut requests = Vec::new();
for node in plan.nodes() {
let PlanNode::Join(join) = &node.kind else {
continue;
};
if !join_shape_supported(join) {
continue;
}
for orientation in candidate_orientations(join) {
let Some(dimension) = unique_simple_lineage(
plan,
&parent_counts,
orientation.dimension_input,
orientation.dimension_key.name(),
) else {
continue;
};
let targets = dedup_lineages(simple_lineages_for_column(
plan,
&parent_counts,
orientation.target_input,
orientation.target_key.name(),
));
if targets.is_empty() {
continue;
}
let Some(dimension_source) = local_parquet_source(plan, dimension.source_node_id) else {
continue;
};
if dimension_source.reader_predicate.is_none() {
continue;
}
for target in targets {
if dimension.source_node_id == target.source_node_id || target.has_shared_path {
continue;
}
requests.push(DimensionDerivedPruningRequest {
dimension_source_node_id: dimension.source_node_id,
dimension_key_column: dimension.source_column.clone(),
target_source_node_id: target.source_node_id,
target_key_column: target.source_column,
});
}
}
}
requests
}
fn join_shape_supported(join: &JoinNode) -> bool {
join.spec.join_type == JoinType::Inner
&& join.spec.keys.len() == 1
&& join.spec.residual_predicate.is_none()
&& !join.spec.output_projection.is_empty()
&& join.spec.build_artifact_id.is_none()
}
fn candidate_orientations(join: &JoinNode) -> [PruningOrientation<'_>; 2] {
let key = &join.spec.keys[0];
[
PruningOrientation {
dimension_input: join.inputs[0],
target_input: join.inputs[1],
dimension_key: &key.left,
target_key: &key.right,
},
PruningOrientation {
dimension_input: join.inputs[1],
target_input: join.inputs[0],
dimension_key: &key.right,
target_key: &key.left,
},
]
}
struct PruningOrientation<'a> {
dimension_input: NodeId,
target_input: NodeId,
dimension_key: &'a ColumnRef,
target_key: &'a ColumnRef,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct TargetPruneResult {
before: usize,
after: usize,
applied: bool,
}
fn prune_target_source(
source: &mut LocalParquetSource,
key_column: &str,
key_filter: &DimensionKeyFilter,
resolver: &dyn LocalParquetPruningResolver,
) -> Option<TargetPruneResult> {
let SourceColumnRef::File(key_file_index) = source.source_column_ref(key_column)? else {
return None;
};
let per_file_row_group_key_ranges = resolver.target_row_group_key_ranges(source, key_file_index)?;
if per_file_row_group_key_ranges.len() != source.files.len() {
return None;
}
let mut before = 0usize;
let mut after = 0usize;
let mut saw_statistics = false;
let mut new_row_groups_by_file = Vec::with_capacity(source.files.len());
for (file, row_group_key_ranges) in source.files.iter().zip(&per_file_row_group_key_ranges) {
let candidates = file.selected_or_all_row_groups(row_group_key_ranges.len());
if candidates.len() != row_group_key_ranges.len() {
return None;
}
let selection = select_overlapping_row_groups(row_group_key_ranges, key_filter);
before += selection.before;
after += selection.kept.len();
saw_statistics |= selection.saw_statistics;
new_row_groups_by_file.push(selection.kept.iter().map(|position| candidates[*position]).collect::<Vec<_>>());
}
if !saw_statistics {
return None;
}
if after == 0 || after >= before {
return Some(TargetPruneResult { before, after, applied: false });
}
for (file, row_groups) in source.files.iter_mut().zip(new_row_groups_by_file) {
file.row_groups = Some(row_groups);
}
source.refresh_diagnostics();
Some(TargetPruneResult { before, after, applied: true })
}
#[derive(Debug, Clone)]
pub struct DimensionDerivedApplyResult {
pub plan: QueryPlan,
pub applied: bool,
pub before: usize,
pub after: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalParquetRowGroupSelection {
pub total_row_groups: usize,
pub row_groups: Vec<usize>,
pub row_group_rows: Vec<u64>,
}
#[derive(Debug, Clone)]
pub(crate) struct LocalParquetRowGroupSelectionApplyResult {
pub(crate) plan: QueryPlan,
pub(crate) applied: bool,
pub(crate) before: usize,
pub(crate) after: usize,
}
pub(crate) fn apply_local_parquet_row_group_selections(
plan: QueryPlan,
source_node_id: NodeId,
selections: &[LocalParquetRowGroupSelection],
) -> Result<LocalParquetRowGroupSelectionApplyResult> {
let mut nodes = plan.nodes().to_vec();
let root = plan.root_id();
let unchanged = |nodes: Vec<QueryNode>| -> Result<LocalParquetRowGroupSelectionApplyResult> {
Ok(LocalParquetRowGroupSelectionApplyResult {
plan: plan.rewrite(RewrittenNodes::refined_sources(nodes, root))?,
applied: false,
before: 0,
after: 0,
})
};
let Some(source) = mutable_local_parquet_source(&mut nodes, source_node_id) else {
return unchanged(nodes);
};
if selections.len() != source.files.len() {
return unchanged(nodes);
}
let mut before = 0usize;
let mut after = 0usize;
let mut new_row_groups_by_file = Vec::with_capacity(source.files.len());
let mut row_group_rows_by_file = Vec::with_capacity(source.files.len());
for (file, selection) in source.files.iter().zip(selections) {
if selection.total_row_groups == 0 || selection.row_group_rows.len() != selection.total_row_groups {
return unchanged(nodes);
}
let candidates = file.selected_or_all_row_groups(selection.total_row_groups);
if candidates.iter().any(|row_group| *row_group >= selection.total_row_groups) {
return unchanged(nodes);
}
let candidate_set = candidates.iter().copied().collect::<BTreeSet<_>>();
let mut seen = BTreeSet::new();
let mut previous_candidate_position = None;
for row_group in &selection.row_groups {
if !seen.insert(*row_group) {
return unchanged(nodes);
}
if !candidate_set.contains(row_group) {
return unchanged(nodes);
}
let Some(candidate_position) = candidates.iter().position(|candidate| candidate == row_group) else {
return unchanged(nodes);
};
if previous_candidate_position.is_some_and(|previous| candidate_position <= previous) {
return unchanged(nodes);
}
previous_candidate_position = Some(candidate_position);
}
before += candidates.len();
after += selection.row_groups.len();
new_row_groups_by_file.push(selection.row_groups.clone());
row_group_rows_by_file.push(selection.row_group_rows.clone());
}
if after == 0 || after >= before {
return unchanged(nodes);
}
for ((file, row_groups), row_group_rows) in
source.files.iter_mut().zip(new_row_groups_by_file).zip(row_group_rows_by_file)
{
file.row_groups = Some(row_groups);
file.row_group_rows = Some(row_group_rows);
}
source.refresh_diagnostics();
Ok(LocalParquetRowGroupSelectionApplyResult {
plan: plan.rewrite(RewrittenNodes::refined_sources(nodes, root))?,
applied: true,
before,
after,
})
}
pub fn apply_dimension_derived_pruning_with_stats(
plan: QueryPlan,
target_source_node_id: NodeId,
per_file_row_group_key_ranges: &[Vec<Option<KeyRange>>],
filter: &DimensionKeyFilter,
) -> Result<DimensionDerivedApplyResult> {
let mut nodes = plan.nodes().to_vec();
let root = plan.root_id();
let unchanged = |nodes: Vec<QueryNode>| -> Result<DimensionDerivedApplyResult> {
Ok(DimensionDerivedApplyResult {
plan: plan.rewrite(RewrittenNodes::refined_sources(nodes, root))?,
applied: false,
before: 0,
after: 0,
})
};
let Some(source) = mutable_local_parquet_source(&mut nodes, target_source_node_id) else {
return unchanged(nodes);
};
if per_file_row_group_key_ranges.len() != source.files.len() {
return unchanged(nodes);
}
let mut before = 0usize;
let mut after = 0usize;
let mut saw_statistics = false;
let mut new_row_groups_by_file = Vec::with_capacity(source.files.len());
for (file, row_group_key_ranges) in source.files.iter().zip(per_file_row_group_key_ranges) {
let candidates = file.selected_or_all_row_groups(row_group_key_ranges.len());
if candidates.len() != row_group_key_ranges.len() {
return unchanged(nodes);
}
let selection = select_overlapping_row_groups(row_group_key_ranges, filter);
before += selection.before;
after += selection.kept.len();
saw_statistics |= selection.saw_statistics;
new_row_groups_by_file.push(selection.kept.iter().map(|position| candidates[*position]).collect::<Vec<_>>());
}
if !saw_statistics || after == 0 || after >= before {
return unchanged(nodes);
}
for (file, row_groups) in source.files.iter_mut().zip(new_row_groups_by_file) {
file.row_groups = Some(row_groups);
}
source.refresh_diagnostics();
Ok(DimensionDerivedApplyResult {
plan: plan.rewrite(RewrittenNodes::refined_sources(nodes, root))?,
applied: true,
before,
after,
})
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use arrow::datatypes::{DataType, Field, Schema};
use super::*;
use crate::{
dynamic_filter::{
DynamicFilterConsumer, DynamicFilterConsumerLowering, DynamicFilterId, DynamicFilterKind,
DynamicFilterProducer, DynamicFilterRoute,
},
plan::QueryPlanBuilder,
source::LocalParquetFile,
};
#[test]
fn apply_local_parquet_row_group_selections_narrows_existing_selection() {
let (plan, source_node_id) = test_plan_with_source_row_groups(Some(vec![0, 1, 2]));
let result = apply_local_parquet_row_group_selections(
plan,
source_node_id,
&[LocalParquetRowGroupSelection {
total_row_groups: 5,
row_groups: vec![1, 2],
row_group_rows: vec![10, 20, 30, 40, 50],
}],
)
.unwrap();
assert!(result.applied);
assert_eq!(result.before, 3);
assert_eq!(result.after, 2);
let source = local_parquet_source(&result.plan, source_node_id).unwrap();
assert_eq!(source.files[0].row_groups, Some(vec![1, 2]));
assert_eq!(source.files[0].row_group_rows, Some(vec![10, 20, 30, 40, 50]));
assert_eq!(source.exact_selected_row_count(), Some(50));
let stats = source
.diagnostics
.selected_row_group_stats
.as_ref()
.expect("row-count metadata should be reflected in diagnostics");
assert_eq!(stats.selected_rows, Some(50));
assert_eq!(stats.total_rows, Some(150));
}
#[test]
fn apply_local_parquet_row_group_selections_rejects_widening_existing_selection() {
let (plan, source_node_id) = test_plan_with_source_row_groups(Some(vec![0, 1, 2]));
let result = apply_local_parquet_row_group_selections(
plan,
source_node_id,
&[LocalParquetRowGroupSelection {
total_row_groups: 5,
row_groups: vec![1, 4],
row_group_rows: vec![10, 20, 30, 40, 50],
}],
)
.unwrap();
assert!(!result.applied);
let source = local_parquet_source(&result.plan, source_node_id).unwrap();
assert_eq!(source.files[0].row_groups, Some(vec![0, 1, 2]));
}
#[test]
fn apply_local_parquet_row_group_selections_keeps_explicitly_empty_source_empty() {
let (plan, source_node_id) = test_plan_with_source_row_groups(Some(Vec::new()));
let result = apply_local_parquet_row_group_selections(
plan,
source_node_id,
&[LocalParquetRowGroupSelection {
total_row_groups: 5,
row_groups: vec![1, 2],
row_group_rows: vec![10, 20, 30, 40, 50],
}],
)
.unwrap();
assert!(!result.applied);
let source = local_parquet_source(&result.plan, source_node_id).unwrap();
assert_eq!(source.files[0].row_groups, Some(Vec::new()));
}
#[test]
fn apply_local_parquet_row_group_selections_rejects_empty_selection() {
let (plan, source_node_id) = test_plan_with_source_row_groups(None);
let result = apply_local_parquet_row_group_selections(
plan,
source_node_id,
&[LocalParquetRowGroupSelection {
total_row_groups: 5,
row_groups: Vec::new(),
row_group_rows: vec![10, 20, 30, 40, 50],
}],
)
.unwrap();
assert!(!result.applied);
let source = local_parquet_source(&result.plan, source_node_id).unwrap();
assert_eq!(source.files[0].row_groups, None);
}
#[test]
fn apply_local_parquet_row_group_selections_rejects_duplicate_selection() {
let (plan, source_node_id) = test_plan_with_source_row_groups(Some(vec![0, 1, 2]));
let result = apply_local_parquet_row_group_selections(
plan,
source_node_id,
&[LocalParquetRowGroupSelection {
total_row_groups: 5,
row_groups: vec![1, 1],
row_group_rows: vec![10, 20, 30, 40, 50],
}],
)
.unwrap();
assert!(!result.applied);
let source = local_parquet_source(&result.plan, source_node_id).unwrap();
assert_eq!(source.files[0].row_groups, Some(vec![0, 1, 2]));
}
#[test]
fn apply_local_parquet_row_group_selections_rejects_noncanonical_order() {
let (plan, source_node_id) = test_plan_with_source_row_groups(Some(vec![0, 1, 2]));
let result = apply_local_parquet_row_group_selections(
plan,
source_node_id,
&[LocalParquetRowGroupSelection {
total_row_groups: 5,
row_groups: vec![2, 1],
row_group_rows: vec![10, 20, 30, 40, 50],
}],
)
.unwrap();
assert!(!result.applied);
let source = local_parquet_source(&result.plan, source_node_id).unwrap();
assert_eq!(source.files[0].row_groups, Some(vec![0, 1, 2]));
}
#[test]
fn apply_local_parquet_row_group_selections_rejects_misaligned_row_counts() {
let (plan, source_node_id) = test_plan_with_source_row_groups(Some(vec![0, 1, 2]));
let result = apply_local_parquet_row_group_selections(
plan,
source_node_id,
&[LocalParquetRowGroupSelection {
total_row_groups: 5,
row_groups: vec![1, 2],
row_group_rows: vec![10, 20],
}],
)
.unwrap();
assert!(!result.applied);
let source = local_parquet_source(&result.plan, source_node_id).unwrap();
assert_eq!(source.files[0].row_groups, Some(vec![0, 1, 2]));
assert_eq!(source.files[0].row_group_rows, None);
}
#[test]
fn apply_dimension_derived_pruning_preserves_dynamic_filter_routes_when_applied() {
let (plan, source_node_id) = test_plan_with_source_row_groups(None);
let route = test_dynamic_filter_route(source_node_id);
let plan = plan.with_dynamic_filter_routes(vec![route.clone()]);
let result = apply_dimension_derived_pruning_with_stats(
plan,
source_node_id,
&[vec![
Some(KeyRange { min: 0, max: 2 }),
Some(KeyRange { min: 5, max: 5 }),
Some(KeyRange { min: 10, max: 12 }),
]],
&DimensionKeyFilter::from_bounds(KeyRange { min: 5, max: 5 }),
)
.unwrap();
assert!(result.applied);
assert_eq!(result.plan.dynamic_filter_routes(), &[route]);
let source = local_parquet_source(&result.plan, source_node_id).unwrap();
assert_eq!(source.files[0].row_groups, Some(vec![1]));
}
#[test]
fn apply_dimension_derived_pruning_preserves_dynamic_filter_routes_when_unchanged() {
let (plan, source_node_id) = test_plan_with_source_row_groups(Some(vec![0, 1, 2]));
let route = test_dynamic_filter_route(source_node_id);
let plan = plan.with_dynamic_filter_routes(vec![route.clone()]);
let result = apply_dimension_derived_pruning_with_stats(
plan,
source_node_id,
&[vec![Some(KeyRange { min: 0, max: 2 })]],
&DimensionKeyFilter::from_bounds(KeyRange { min: 5, max: 5 }),
)
.unwrap();
assert!(!result.applied);
assert_eq!(result.plan.dynamic_filter_routes(), &[route]);
let source = local_parquet_source(&result.plan, source_node_id).unwrap();
assert_eq!(source.files[0].row_groups, Some(vec![0, 1, 2]));
}
fn test_plan_with_source_row_groups(row_groups: Option<Vec<usize>>) -> (QueryPlan, NodeId) {
let schema = Arc::new(Schema::new(vec![Field::new("x", DataType::Int64, true)]));
let mut file = LocalParquetFile::new("/tmp/test.parquet");
file.row_groups = row_groups;
let source = LocalParquetSource::from_files(vec![file], schema, vec!["x".to_string()]);
let mut builder = QueryPlanBuilder::new();
let source_node_id = builder.add_source(SourceNode::LocalParquet(source));
(builder.finish_with_host_sink(source_node_id).unwrap(), source_node_id)
}
fn test_dynamic_filter_route(source_node_id: NodeId) -> DynamicFilterRoute {
DynamicFilterRoute::new(
DynamicFilterId::new(0),
DynamicFilterProducer::AggregateGroupKeys { aggregate_node_id: source_node_id },
DynamicFilterConsumer::LocalParquetSource { source_node_id },
vec!["x".to_string()],
vec!["x".to_string()],
vec![DynamicFilterKind::Range],
DynamicFilterConsumerLowering::ReaderPredicate,
)
}
}