use std::collections::HashMap;
use crate::planning::plan::logical::LogicalNodeEnum;
use super::subquery_graph::SubqueryGraph;
#[derive(Debug, Clone)]
pub struct JoinOrderPlan {
pub plan: LogicalNodeEnum,
pub cost: u64,
pub cardinality: u64,
pub encoding: u64,
}
impl JoinOrderPlan {
pub fn new(plan: LogicalNodeEnum, cost: u64, cardinality: u64, encoding: u64) -> Self {
Self {
plan,
cost,
cardinality,
encoding,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SubgraphPlans {
pub max_cost: u64,
pub encoded_plan_to_idx: HashMap<u64, usize>,
pub plans: Vec<JoinOrderPlan>,
}
impl SubgraphPlans {
pub const MAX_NUM_PLANS: usize = 10;
pub fn new() -> Self {
Self::default()
}
pub fn add_plan(&mut self, plan: JoinOrderPlan) {
if let Some(idx) = self.encoded_plan_to_idx.get(&plan.encoding).copied() {
if plan.cost < self.plans[idx].cost {
self.plans[idx] = plan;
}
self.refresh_max_cost();
return;
}
if self.plans.len() >= Self::MAX_NUM_PLANS {
if plan.cost >= self.max_cost {
return;
}
if let Some(pos) = self.plans.iter().position(|p| p.cost == self.max_cost) {
let removed = self.plans.remove(pos);
self.encoded_plan_to_idx.remove(&removed.encoding);
for idx in self.encoded_plan_to_idx.values_mut() {
if *idx > pos {
*idx -= 1;
}
}
}
}
self.encoded_plan_to_idx
.insert(plan.encoding, self.plans.len());
if plan.cost > self.max_cost {
self.max_cost = plan.cost;
}
self.plans.push(plan);
}
pub fn best_plan(&self) -> Option<&JoinOrderPlan> {
self.plans.iter().min_by_key(|p| p.cost)
}
fn refresh_max_cost(&mut self) {
self.max_cost = self.plans.iter().map(|p| p.cost).max().unwrap_or(0);
}
}
#[derive(Debug, Default)]
pub struct SubPlansTable {
pub dp_levels: Vec<Vec<(SubqueryGraph, SubgraphPlans)>>,
}
impl SubPlansTable {
pub fn new() -> Self {
Self::default()
}
fn ensure_level(&mut self, level: usize) {
while self.dp_levels.len() <= level {
self.dp_levels.push(Vec::new());
}
}
fn find_entry_mut(&mut self, subgraph: &SubqueryGraph) -> Option<&mut SubgraphPlans> {
let level = subgraph.total_num_variables();
let key = subgraph.key();
self.dp_levels
.get_mut(level)?
.iter_mut()
.find_map(
|(sg, plans)| {
if sg.key() == key {
Some(plans)
} else {
None
}
},
)
}
pub fn add_plan(&mut self, subgraph: &SubqueryGraph, plan: JoinOrderPlan) {
let level = subgraph.total_num_variables();
self.ensure_level(level);
match self.find_entry_mut(subgraph) {
Some(entry) => entry.add_plan(plan),
None => {
let mut plans = SubgraphPlans::new();
plans.add_plan(plan);
self.dp_levels[level].push((subgraph.clone(), plans));
}
}
}
pub fn get_best_plan(&self, subgraph: &SubqueryGraph) -> Option<&JoinOrderPlan> {
let level = subgraph.total_num_variables();
let key = subgraph.key();
self.dp_levels.get(level)?.iter().find_map(|(sg, plans)| {
if sg.key() == key {
plans.best_plan()
} else {
None
}
})
}
pub fn get_plans(&self, subgraph: &SubqueryGraph) -> &[JoinOrderPlan] {
let level = subgraph.total_num_variables();
let key = subgraph.key();
self.dp_levels
.get(level)
.and_then(|entries| {
entries.iter().find_map(|(sg, plans)| {
if sg.key() == key {
Some(plans.plans.as_slice())
} else {
None
}
})
})
.unwrap_or(&[])
}
pub fn get_subgraphs(&self, level: usize) -> &[(SubqueryGraph, SubgraphPlans)] {
self.dp_levels.get(level).map(Vec::as_slice).unwrap_or(&[])
}
pub fn num_levels(&self) -> usize {
self.dp_levels.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use crate::planning::join_order::query_graph::QueryGraph;
use crate::planning::join_order::query_graph::{QueryNode, QueryRel};
use crate::planning::plan::core::next_node_id;
use crate::planning::plan::logical::logical_nodes::access::LogicalScanVerticesNode;
fn scan_node(var: &str) -> LogicalNodeEnum {
LogicalNodeEnum::ScanVertices(LogicalScanVerticesNode {
id: next_node_id(),
space_id: 1,
space_name: "default".to_string(),
tag: None,
expression: None,
limit: None,
projected_properties: vec![],
index_hint: None,
estimated_cardinality: None,
output_var: Some(var.to_string()),
col_names: vec![var.to_string()],
column_types: vec![],
})
}
fn triangle() -> Arc<QueryGraph> {
let mut qg = QueryGraph::new();
qg.add_node(QueryNode::new("a", "a"));
qg.add_node(QueryNode::new("b", "b"));
qg.add_node(QueryNode::new("c", "c"));
qg.add_rel(QueryRel::new("e1", "e1", "a", "b"));
qg.add_rel(QueryRel::new("e2", "e2", "a", "c"));
qg.add_rel(QueryRel::new("e3", "e3", "b", "c"));
Arc::new(qg)
}
#[test]
fn add_and_best_plan() {
let qg = triangle();
let mut table = SubPlansTable::new();
let sg = SubqueryGraph::single_node(&qg, 0);
table.add_plan(&sg, JoinOrderPlan::new(scan_node("a"), 100, 1_000, 0b1));
table.add_plan(&sg, JoinOrderPlan::new(scan_node("a"), 50, 1_000, 0b1));
let best = table.get_best_plan(&sg).expect("best plan");
assert_eq!(best.cost, 50);
}
#[test]
fn distinct_encodings_coexist() {
let qg = triangle();
let mut table = SubPlansTable::new();
let sg = SubqueryGraph::single_node(&qg, 0);
table.add_plan(&sg, JoinOrderPlan::new(scan_node("a"), 100, 1_000, 0b01));
table.add_plan(&sg, JoinOrderPlan::new(scan_node("a"), 200, 1_000, 0b10));
assert_eq!(table.get_plans(&sg).len(), 2);
}
#[test]
fn plan_cap_drops_most_expensive() {
let qg = triangle();
let mut table = SubPlansTable::new();
let sg = SubqueryGraph::single_node(&qg, 0);
for i in 0..(SubgraphPlans::MAX_NUM_PLANS as u64 + 2) {
table.add_plan(&sg, JoinOrderPlan::new(scan_node("a"), 10 + i, 1_000, i));
}
assert_eq!(table.get_plans(&sg).len(), SubgraphPlans::MAX_NUM_PLANS);
let best = table.get_best_plan(&sg).expect("best plan");
assert_eq!(best.cost, 10);
}
#[test]
fn missing_subgraph_returns_none() {
let qg = triangle();
let table = SubPlansTable::new();
let sg = SubqueryGraph::single_node(&qg, 1);
assert!(table.get_best_plan(&sg).is_none());
assert!(table.get_plans(&sg).is_empty());
assert!(table.get_subgraphs(1).is_empty());
}
}