lora_database/database/
explain.rs1use std::any::Any;
13use std::collections::BTreeMap;
14
15use lora_compiler::{plan_tree_from_compiled, PlanTree, PlanTreeNode};
16use lora_executor::{classify_stream, plan_result_columns, LoraValue};
17use lora_store::{GraphStats, GraphStorage, GraphStorageMut};
18
19use crate::database::Database;
20use crate::error::LoraError;
21use crate::explain::{PlanShape, QueryPlan};
22
23impl<S> Database<S>
24where
25 S: GraphStorage + GraphStorageMut + Any + Clone + Send + Sync + 'static,
26{
27 pub fn explain(
39 &self,
40 query: &str,
41 _params: Option<BTreeMap<String, LoraValue>>,
42 ) -> Result<QueryPlan, LoraError> {
43 let (store, store_epoch) = self.read_store_with_epoch_deadline(None)?;
44 let compiled = self
45 .compile_query_cached(query, &*store, store_epoch)
46 .map_err(LoraError::from_anyhow)?;
47 let mut tree = plan_tree_from_compiled(&compiled);
48 let stats = store.graph_stats();
49 annotate_estimated_rows(&mut tree, &stats);
50 let shape: PlanShape = classify_stream(&compiled).into();
51 let result_columns = plan_result_columns(&compiled.physical);
52 Ok(QueryPlan {
53 query: query.to_string(),
54 tree,
55 shape,
56 result_columns,
57 })
58 }
59}
60
61pub(crate) fn annotate_estimated_rows(tree: &mut PlanTree, stats: &GraphStats) {
76 annotate_node(&mut tree.root, stats);
77}
78
79fn annotate_node(node: &mut PlanTreeNode, stats: &GraphStats) {
80 node.estimated_rows = match node.operator.as_str() {
81 "NodeScan" => Some(stats.node_count as u64),
82 "NodeByLabelScan" => labels_estimate(node, stats),
83 "NodeByPropertyScan" => property_equality_estimate(node, stats),
84 "NodeByPropertyRangeScan" => {
87 let two_sided = node.details.contains_key("lo") && node.details.contains_key("hi");
88 labels_estimate(node, stats).map(|n| n.div_ceil(if two_sided { 4 } else { 3 }))
89 }
90 "NodeByTextScan" => {
91 let denom = match node.details.get("predicate").map(String::as_str) {
92 Some("CONTAINS") => 2,
93 _ => 4,
94 };
95 labels_estimate(node, stats).map(|n| n.div_ceil(denom))
96 }
97 "NodeByPointScan" => labels_estimate(node, stats).map(|n| n.div_ceil(5)),
98 _ => None,
99 };
100 for child in &mut node.children {
101 annotate_node(child, stats);
102 }
103}
104
105fn labels_estimate(node: &PlanTreeNode, stats: &GraphStats) -> Option<u64> {
106 let labels = node.details.get("labels")?;
107 let trimmed = labels.trim_start_matches(':');
110 let bare = trimmed.split('|').next()?.trim();
111 stats.label_count(bare)
112}
113
114fn property_equality_estimate(node: &PlanTreeNode, stats: &GraphStats) -> Option<u64> {
115 let property = node.details.get("key")?;
116 let labels = node.details.get("labels")?;
117 let trimmed = labels.trim_start_matches(':');
118 let bare = trimmed.split('|').next()?.trim();
119 stats.estimate_node_property_equality(bare, property)
120}