use crate::ir::*;
use crate::types::*;
#[derive(Debug, Clone)]
pub enum PhysicalOp {
NodeScan {
label: Label,
as_: String,
props: Option<Properties>,
},
IndexScan {
label: Label,
as_: String,
index: String,
value: Value,
},
Filter {
pred: Predicate,
input: Box<PhysicalOp>,
},
Expand {
edge: EdgePattern,
to_as: String,
input: Box<PhysicalOp>,
},
NestedLoopJoin {
left: Box<PhysicalOp>,
right: Box<PhysicalOp>,
on: Vec<String>,
},
HashJoin {
left: Box<PhysicalOp>,
right: Box<PhysicalOp>,
on: Vec<String>,
},
Project {
cols: Vec<String>,
input: Box<PhysicalOp>,
},
Group {
keys: Vec<String>,
aggregations: Vec<Aggregation>,
input: Box<PhysicalOp>,
},
Sort {
keys: Vec<SortKey>,
input: Box<PhysicalOp>,
},
Limit {
count: usize,
input: Box<PhysicalOp>,
},
Distinct {
input: Box<PhysicalOp>,
},
}
#[derive(Debug, Clone)]
pub struct PhysicalPlan {
pub op: PhysicalOp,
pub estimated_cost: f64,
}
#[derive(Debug)]
pub struct PhysicalPlanner;
impl PhysicalPlanner {
pub fn new() -> Self {
Self
}
pub fn plan_to_physical(&self, logical: &PlanIR, catalog: &Catalog) -> Result<PhysicalPlan> {
let op = self.convert_logical_op(&logical.plan, catalog)?;
let cost = self.estimate_cost(&op, catalog);
Ok(PhysicalPlan {
op,
estimated_cost: cost,
})
}
fn convert_logical_op(&self, logical: &LogicalOp, catalog: &Catalog) -> Result<PhysicalOp> {
match logical {
LogicalOp::NodeScan { label, as_, props } => {
if self.has_index(catalog, label, props) {
Ok(PhysicalOp::IndexScan {
label: label.clone(),
as_: as_.clone(),
index: format!("{}_index", label),
value: Value::String("dummy".to_string()), })
} else {
Ok(PhysicalOp::NodeScan {
label: label.clone(),
as_: as_.clone(),
props: props.clone(),
})
}
}
LogicalOp::IndexScan { label, as_, index, value } => {
Ok(PhysicalOp::IndexScan {
label: label.clone(),
as_: as_.clone(),
index: index.clone(),
value: value.clone(),
})
}
LogicalOp::Filter { pred, input } => {
let input_op = self.convert_logical_op(input, catalog)?;
Ok(PhysicalOp::Filter {
pred: pred.clone(),
input: Box::new(input_op),
})
}
LogicalOp::Expand { edge, to_as, from } => {
let input_op = self.convert_logical_op(from, catalog)?;
Ok(PhysicalOp::Expand {
edge: edge.clone(),
to_as: to_as.clone(),
input: Box::new(input_op),
})
}
LogicalOp::Join { left, right, on } => {
let left_op = self.convert_logical_op(left, catalog)?;
let right_op = self.convert_logical_op(right, catalog)?;
let left_cost = self.estimate_cost(&left_op, catalog);
let right_cost = self.estimate_cost(&right_op, catalog);
if left_cost < right_cost && left_cost < 1000.0 {
Ok(PhysicalOp::NestedLoopJoin {
left: Box::new(left_op),
right: Box::new(right_op),
on: on.clone(),
})
} else {
Ok(PhysicalOp::HashJoin {
left: Box::new(left_op),
right: Box::new(right_op),
on: on.clone(),
})
}
}
LogicalOp::Project { cols, input } => {
let input_op = self.convert_logical_op(input, catalog)?;
Ok(PhysicalOp::Project {
cols: cols.clone(),
input: Box::new(input_op),
})
}
LogicalOp::Group { keys, aggregations, input } => {
let input_op = self.convert_logical_op(input, catalog)?;
Ok(PhysicalOp::Group {
keys: keys.clone(),
aggregations: aggregations.clone(),
input: Box::new(input_op),
})
}
LogicalOp::Sort { keys, input } => {
let input_op = self.convert_logical_op(input, catalog)?;
Ok(PhysicalOp::Sort {
keys: keys.clone(),
input: Box::new(input_op),
})
}
LogicalOp::Limit { count, input } => {
let input_op = self.convert_logical_op(input, catalog)?;
Ok(PhysicalOp::Limit {
count: *count,
input: Box::new(input_op),
})
}
LogicalOp::Distinct { input } => {
let input_op = self.convert_logical_op(input, catalog)?;
Ok(PhysicalOp::Distinct {
input: Box::new(input_op),
})
}
}
}
fn has_index(&self, catalog: &Catalog, label: &Label, props: &Option<Properties>) -> bool {
if let Some(props) = props {
catalog.indexes.iter().any(|idx| {
idx.label == *label &&
props.contains_key(&idx.properties[0])
})
} else {
false
}
}
fn estimate_cost(&self, op: &PhysicalOp, catalog: &Catalog) -> f64 {
match op {
PhysicalOp::NodeScan { .. } => 100.0,
PhysicalOp::IndexScan { .. } => 10.0,
PhysicalOp::Filter { input, .. } => self.estimate_cost(input, catalog) + 10.0,
PhysicalOp::Expand { input, .. } => self.estimate_cost(input, catalog) + 50.0,
PhysicalOp::NestedLoopJoin { left, right, .. } => {
let left_cost = self.estimate_cost(left, catalog);
let right_cost = self.estimate_cost(right, catalog);
left_cost * right_cost * 2.0
}
PhysicalOp::HashJoin { left, right, .. } => {
let left_cost = self.estimate_cost(left, catalog);
let right_cost = self.estimate_cost(right, catalog);
left_cost + right_cost + 100.0
}
PhysicalOp::Project { input, .. } => self.estimate_cost(input, catalog) + 5.0,
PhysicalOp::Group { input, .. } => self.estimate_cost(input, catalog) + 150.0,
PhysicalOp::Sort { input, .. } => self.estimate_cost(input, catalog) + 100.0,
PhysicalOp::Limit { input, .. } => self.estimate_cost(input, catalog) + 1.0,
PhysicalOp::Distinct { input, .. } => self.estimate_cost(input, catalog) + 50.0,
}
}
}