use super::{
sort_ordering_keys, PhysicalPlan, PhysicalProperties, SortDirection, SortItem, SortKey,
};
use crate::{OptimizerCatalog, RequiredProperties};
use hawdb_plan_cypher::{NodeProjectionAccess, Projection, ProjectionExpression};
use std::collections::BTreeSet;
fn unique_projection_names(items: &[Projection]) -> bool {
let mut names = BTreeSet::new();
items.iter().all(|item| names.insert(&item.name))
}
fn canonical_key(key: &SortKey) -> SortKey {
match key {
SortKey::Expression(ProjectionExpression::Property { variable, property }) => {
SortKey::Property {
variable: variable.clone(),
property: property.clone(),
}
}
SortKey::Expression(ProjectionExpression::Column(column)) => {
SortKey::Column(column.clone())
}
_ => key.clone(),
}
}
pub(super) fn selected_ordering(plan: &PhysicalPlan, catalog: &OptimizerCatalog) -> Vec<SortItem> {
match plan {
PhysicalPlan::IndexNodeRangeSeek {
variable,
label,
property,
..
} if catalog
.ordered_range_indexes
.contains(&(label.clone(), property.clone())) =>
{
vec![SortItem {
key: SortKey::Property {
variable: variable.clone(),
property: property.clone(),
},
direction: SortDirection::Asc,
}]
}
PhysicalPlan::NodeProjectionScanExec {
variable,
label,
access: NodeProjectionAccess::PropertyRange { property, .. },
items,
..
} if catalog
.ordered_range_indexes
.contains(&(label.clone(), property.clone())) =>
{
let key = if items.is_empty() {
Some(SortKey::Property {
variable: variable.clone(),
property: property.clone(),
})
} else if !unique_projection_names(items) {
None
} else {
items.iter().find(|item| {
matches!(&item.expression, ProjectionExpression::Property { variable: source, property: key } if source == variable && key == property)
}).map(|item| SortKey::Column(item.name.clone()))
};
key.into_iter()
.map(|key| SortItem {
key,
direction: SortDirection::Asc,
})
.collect()
}
PhysicalPlan::FilterExec { input, .. } | PhysicalPlan::LimitExec { input, .. } => {
selected_ordering(input, catalog)
}
PhysicalPlan::ProjectExec { items, input } => {
let mut ordering = Vec::new();
for mut key in selected_ordering(input, catalog) {
if let SortKey::Column(column) = &key.key {
if !unique_projection_names(items) {
break;
}
let Some(item) = items.iter().find(|item| {
matches!(&item.expression, ProjectionExpression::Column(source) if source == column)
}) else { break };
key.key = SortKey::Column(item.name.clone());
} else if !matches!(key.key, SortKey::Property { .. } | SortKey::Id { .. }) {
break;
}
ordering.push(key);
}
ordering
}
PhysicalPlan::SortExec { items, .. } | PhysicalPlan::TopNExec { items, .. } => items
.iter()
.map(|item| SortItem {
key: canonical_key(&item.key),
direction: item.direction,
})
.collect(),
_ => Vec::new(),
}
}
pub(in crate::graph) fn satisfies_ordering(
plan: &PhysicalPlan,
required: &[SortItem],
catalog: &OptimizerCatalog,
) -> bool {
match plan {
PhysicalPlan::FilterExec { input, .. } | PhysicalPlan::LimitExec { input, .. } => {
return satisfies_ordering(input, required, catalog)
}
PhysicalPlan::ProjectExec { items, input } => {
let unique_names = unique_projection_names(items);
let remapped: Option<Vec<_>> = required
.iter()
.map(|item| {
let key = match canonical_key(&item.key) {
SortKey::Expression(ProjectionExpression::ColumnProperty {
column,
property,
}) => {
if !unique_names {
return None;
}
let projection = items.iter().rev().find(|item| item.name == column)?;
match &projection.expression {
ProjectionExpression::Variable { variable }
if !matches!(
property.as_str(),
"_id" | "labels" | "source_id" | "target_id" | "type"
) =>
{
SortKey::Property {
variable: variable.clone(),
property,
}
}
ProjectionExpression::Column(source) => {
SortKey::Expression(ProjectionExpression::ColumnProperty {
column: source.clone(),
property,
})
}
_ => return None,
}
}
SortKey::Column(column) => {
if !unique_names {
return None;
}
let projection = items.iter().rev().find(|item| item.name == column)?;
match &projection.expression {
ProjectionExpression::Property { variable, property } => {
SortKey::Property {
variable: variable.clone(),
property: property.clone(),
}
}
ProjectionExpression::Column(column) => {
SortKey::Column(column.clone())
}
_ => return None,
}
}
key @ (SortKey::Property { .. } | SortKey::Id { .. }) => key,
_ => return None,
};
Some(SortItem {
key,
direction: item.direction,
})
})
.collect();
return remapped.is_some_and(|required| satisfies_ordering(input, &required, catalog));
}
_ => {}
}
let provided = selected_ordering(plan, catalog);
let required: Vec<_> = required
.iter()
.map(|item| SortItem {
key: canonical_key(&item.key),
direction: item.direction,
})
.collect();
if !provided.starts_with(&required) {
return false;
}
PhysicalProperties {
ordering: sort_ordering_keys(&provided),
..PhysicalProperties::default()
}
.satisfies(&RequiredProperties {
ordering: sort_ordering_keys(&required),
..RequiredProperties::default()
})
}