use super::{
Distribution, MemoryBudgetClass, PhysicalPlan, PhysicalProperties, ScanPruningSupport,
VectorPrecision,
};
use hawdb_plan_cypher::write_projection_expression;
use hawdb_plan_cypher::{SortDirection, SortItem, SortKey};
mod ordering;
pub(super) use ordering::satisfies_ordering;
pub(super) fn selected_plan_properties(
plan: &PhysicalPlan,
catalog: &super::OptimizerCatalog,
) -> PhysicalProperties {
let mut properties = non_ordering_properties(plan);
properties.ordering = sort_ordering_keys(&ordering::selected_ordering(plan, catalog));
properties
}
fn non_ordering_properties(plan: &PhysicalPlan) -> PhysicalProperties {
match plan {
PhysicalPlan::GraphMatchExec { .. } => PhysicalProperties {
distribution: Distribution::Single,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::RowLinear,
..PhysicalProperties::default()
},
PhysicalPlan::EmptyExec => PhysicalProperties {
distribution: Distribution::Single,
scan_pruning: ScanPruningSupport::ExactEmpty,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::Constant,
..PhysicalProperties::default()
},
PhysicalPlan::NodeCountExec { .. } | PhysicalPlan::RelationshipCountExec { .. } => {
PhysicalProperties {
distribution: Distribution::Single,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::Constant,
..PhysicalProperties::default()
}
}
PhysicalPlan::FilterExec { input, .. }
| PhysicalPlan::ProjectExec { input, .. }
| PhysicalPlan::LimitExec { input, .. }
| PhysicalPlan::NodeColumnLookupExec { input, .. }
| PhysicalPlan::AdjacencyExpandExec { input, .. }
| PhysicalPlan::AdjacencyExistsExec { input, .. }
| PhysicalPlan::OptionalDegreeExec { input, .. } => non_ordering_properties(input),
PhysicalPlan::SortExec { input, .. } => {
let mut properties = non_ordering_properties(input);
properties.memory_budget = MemoryBudgetClass::Blocking;
properties
}
PhysicalPlan::TopNExec { input, .. } => {
let mut properties = non_ordering_properties(input);
properties.memory_budget = MemoryBudgetClass::RowLinear;
properties
}
PhysicalPlan::AggregateExec { input, .. } | PhysicalPlan::DistinctExec { input } => {
let mut properties = non_ordering_properties(input);
properties.memory_budget = MemoryBudgetClass::Blocking;
properties
}
PhysicalPlan::SeqNodeScan { .. } => PhysicalProperties {
distribution: Distribution::Single,
scan_pruning: ScanPruningSupport::Label,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::RowLinear,
..PhysicalProperties::default()
},
PhysicalPlan::NodeProjectionScanExec {
label,
access,
required_properties,
..
} => {
let scan_pruning = if access.is_label_scan() {
ScanPruningSupport::Label
} else {
ScanPruningSupport::Index
};
PhysicalProperties {
distribution: Distribution::Single,
covering_fields: required_properties
.iter()
.map(|property| plan_property_key(label, property))
.collect(),
scan_pruning,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::RowLinear,
..PhysicalProperties::default()
}
}
PhysicalPlan::SourceSegmentScan { .. } => PhysicalProperties {
distribution: Distribution::Single,
scan_pruning: ScanPruningSupport::Segment,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::RowLinear,
..PhysicalProperties::default()
},
PhysicalPlan::IndexNodeSeek {
label, property, ..
}
| PhysicalPlan::IndexNodeMultiSeek {
label, property, ..
}
| PhysicalPlan::IndexNodeRangeSeek {
label, property, ..
}
| PhysicalPlan::IndexNodeTextSeek {
label, property, ..
} => PhysicalProperties {
distribution: Distribution::Single,
covering_fields: vec![plan_property_key(label, property)],
scan_pruning: ScanPruningSupport::Index,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::RowLinear,
..PhysicalProperties::default()
},
PhysicalPlan::IndexNodeCompositeSeek {
label, predicates, ..
} => PhysicalProperties {
distribution: Distribution::Single,
covering_fields: predicates
.iter()
.map(|(property, _)| plan_property_key(label, property))
.collect(),
scan_pruning: ScanPruningSupport::Index,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::RowLinear,
..PhysicalProperties::default()
},
PhysicalPlan::IndexNodeCompositeRangeSeek { label, seek, .. } => PhysicalProperties {
distribution: Distribution::Single,
covering_fields: seek
.equality_prefix
.iter()
.map(|(property, _)| plan_property_key(label, property))
.chain(std::iter::once(plan_property_key(
label,
&seek.range_property,
)))
.collect(),
scan_pruning: ScanPruningSupport::Index,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::RowLinear,
..PhysicalProperties::default()
},
PhysicalPlan::IndexNodeUnionSeek {
label, branches, ..
} => PhysicalProperties {
distribution: Distribution::Single,
covering_fields: branches
.iter()
.map(|branch| plan_property_key(label, &branch.property))
.collect(),
scan_pruning: ScanPruningSupport::Index,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::RowLinear,
..PhysicalProperties::default()
},
PhysicalPlan::NodeCartesianProductExec { left, right }
| PhysicalPlan::HashJoinExec { left, right, .. } => {
let left = non_ordering_properties(left);
let right = non_ordering_properties(right);
PhysicalProperties {
distribution: Distribution::Single,
covering_fields: left
.covering_fields
.into_iter()
.chain(right.covering_fields)
.collect(),
scan_pruning: combine_scan_pruning(left.scan_pruning, right.scan_pruning),
vector_precision: combine_vector_precision(
left.vector_precision,
right.vector_precision,
),
memory_budget: if matches!(plan, PhysicalPlan::HashJoinExec { .. }) {
MemoryBudgetClass::Blocking
} else {
combine_memory_budget(left.memory_budget, right.memory_budget)
},
..PhysicalProperties::default()
}
}
PhysicalPlan::OptionalRelationshipCountSumExec { .. }
| PhysicalPlan::ThreadRepairStatsExec { .. }
| PhysicalPlan::ShortestPathExec { .. } => PhysicalProperties {
distribution: Distribution::Single,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::RowLinear,
..PhysicalProperties::default()
},
_ => PhysicalProperties {
distribution: Distribution::Single,
vector_precision: VectorPrecision::NotVector,
memory_budget: MemoryBudgetClass::Unknown,
..PhysicalProperties::default()
},
}
}
fn plan_property_key(label: &str, property: &str) -> String {
format!("{label}.{property}")
}
fn combine_scan_pruning(left: ScanPruningSupport, right: ScanPruningSupport) -> ScanPruningSupport {
if left == ScanPruningSupport::ExactEmpty || right == ScanPruningSupport::ExactEmpty {
ScanPruningSupport::ExactEmpty
} else if left == ScanPruningSupport::Index || right == ScanPruningSupport::Index {
ScanPruningSupport::Index
} else if left == ScanPruningSupport::Segment || right == ScanPruningSupport::Segment {
ScanPruningSupport::Segment
} else if left == ScanPruningSupport::Label || right == ScanPruningSupport::Label {
ScanPruningSupport::Label
} else if left == ScanPruningSupport::None && right == ScanPruningSupport::None {
ScanPruningSupport::None
} else {
ScanPruningSupport::Unknown
}
}
fn combine_vector_precision(left: VectorPrecision, right: VectorPrecision) -> VectorPrecision {
match (left, right) {
(VectorPrecision::ApproximateCandidate, _) | (_, VectorPrecision::ApproximateCandidate) => {
VectorPrecision::ApproximateCandidate
}
(VectorPrecision::RawReranked, _) | (_, VectorPrecision::RawReranked) => {
VectorPrecision::RawReranked
}
(VectorPrecision::Exact, _) | (_, VectorPrecision::Exact) => VectorPrecision::Exact,
_ => VectorPrecision::NotVector,
}
}
fn combine_memory_budget(left: MemoryBudgetClass, right: MemoryBudgetClass) -> MemoryBudgetClass {
match (left, right) {
(MemoryBudgetClass::Blocking, _) | (_, MemoryBudgetClass::Blocking) => {
MemoryBudgetClass::Blocking
}
(MemoryBudgetClass::RowLinear, _) | (_, MemoryBudgetClass::RowLinear) => {
MemoryBudgetClass::RowLinear
}
(MemoryBudgetClass::Constant, _) | (_, MemoryBudgetClass::Constant) => {
MemoryBudgetClass::Constant
}
_ => MemoryBudgetClass::Unknown,
}
}
fn sort_ordering_keys(items: &[SortItem]) -> Vec<String> {
items.iter().map(sort_ordering_key).collect()
}
fn sort_ordering_key(item: &SortItem) -> String {
let mut output = String::new();
match &item.key {
SortKey::Property { variable, property } => {
output.push_str(variable);
output.push('.');
output.push_str(property);
}
SortKey::Id { variable } => {
output.push_str("id(");
output.push_str(variable);
output.push(')');
}
SortKey::Expression(expression) => write_projection_expression(&mut output, expression),
SortKey::Column(column) => output.push_str(column),
}
output.push(' ');
match item.direction {
SortDirection::Asc => output.push_str("asc"),
SortDirection::Desc => output.push_str("desc"),
}
output
}