use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use crate::ast::ast::{Expression, InsertStatement, PatternElement};
use crate::plan::logical::{
EntityType, InsertPattern, LogicalNode, LogicalPlan, NodeIdentifier, VariableInfo,
};
pub struct InsertPlanner {
identifier_mappings: HashMap<String, NodeIdentifier>,
defined_identifiers: HashSet<String>,
}
impl InsertPlanner {
pub fn new() -> Self {
Self {
identifier_mappings: HashMap::new(),
defined_identifiers: HashSet::new(),
}
}
pub fn plan_insert(
&mut self,
statement: &InsertStatement,
) -> Result<LogicalPlan, PlanningError> {
log::debug!(
"Planning INSERT statement with {} patterns",
statement.graph_patterns.len()
);
let mut patterns = Vec::new();
let mut variables = HashMap::new();
for (pattern_idx, graph_pattern) in statement.graph_patterns.iter().enumerate() {
log::debug!("Processing pattern {}", pattern_idx);
for (element_idx, element) in graph_pattern.elements.iter().enumerate() {
match element {
PatternElement::Node(node_pattern) => {
let logical_pattern =
self.process_node_pattern(node_pattern, &mut variables)?;
if let Some(pattern) = logical_pattern {
patterns.push(pattern);
}
}
PatternElement::Edge(edge_pattern) => {
let logical_patterns = self.process_edge_pattern(
edge_pattern,
&graph_pattern.elements,
element_idx,
&mut variables,
)?;
patterns.extend(logical_patterns);
}
}
}
}
log::debug!(
"Created {} logical patterns with {} identifier mappings",
patterns.len(),
self.identifier_mappings.len()
);
let root = LogicalNode::Insert {
patterns,
identifier_mappings: self.identifier_mappings.clone(),
};
Ok(LogicalPlan { root, variables })
}
fn process_node_pattern(
&mut self,
node_pattern: &crate::ast::ast::Node,
variables: &mut HashMap<String, VariableInfo>,
) -> Result<Option<InsertPattern>, PlanningError> {
if let Some(ref identifier) = node_pattern.identifier {
if self.defined_identifiers.contains(identifier) {
log::debug!(
"Node '{}' is a reference to existing definition, skipping node creation",
identifier
);
return Ok(None); }
}
let properties = if let Some(ref prop_map) = node_pattern.properties {
self.extract_properties(prop_map)?
} else {
HashMap::new()
};
let storage_id = Self::generate_node_content_id(&node_pattern.labels, &properties);
if let Some(ref identifier) = node_pattern.identifier {
self.identifier_mappings.insert(
identifier.clone(),
NodeIdentifier {
storage_id: storage_id.clone(),
labels: node_pattern.labels.clone(),
is_reference: false,
},
);
self.defined_identifiers.insert(identifier.clone());
variables.insert(
identifier.clone(),
VariableInfo {
name: identifier.clone(),
entity_type: EntityType::Node,
labels: node_pattern.labels.clone(),
required_properties: properties.keys().cloned().collect(),
},
);
}
log::debug!("Creating node pattern with storage_id: {}", storage_id);
Ok(Some(InsertPattern::CreateNode {
storage_id,
labels: node_pattern.labels.clone(),
properties,
original_identifier: node_pattern.identifier.clone(),
}))
}
fn process_edge_pattern(
&mut self,
edge_pattern: &crate::ast::ast::Edge,
all_elements: &[PatternElement],
current_idx: usize,
variables: &mut HashMap<String, VariableInfo>,
) -> Result<Vec<InsertPattern>, PlanningError> {
let source_node_id = self.resolve_adjacent_node(all_elements, current_idx, -1)?;
let target_node_id = self.resolve_adjacent_node(all_elements, current_idx, 1)?;
let properties = if let Some(ref prop_map) = edge_pattern.properties {
self.extract_properties(prop_map)?
} else {
HashMap::new()
};
let edge_label = edge_pattern
.labels
.first()
.cloned()
.unwrap_or_else(|| "CONNECTED".to_string());
let edge_storage_id = Self::generate_edge_content_id(
&source_node_id,
&target_node_id,
&edge_label,
&properties,
);
if let Some(ref identifier) = edge_pattern.identifier {
variables.insert(
identifier.clone(),
VariableInfo {
name: identifier.clone(),
entity_type: EntityType::Edge,
labels: edge_pattern.labels.clone(),
required_properties: properties.keys().cloned().collect(),
},
);
}
log::debug!(
"Creating edge pattern: {} -[{}]-> {}",
source_node_id,
edge_label,
target_node_id
);
Ok(vec![InsertPattern::CreateEdge {
storage_id: edge_storage_id,
from_node_id: source_node_id,
to_node_id: target_node_id,
label: edge_label,
properties,
original_identifier: edge_pattern.identifier.clone(),
}])
}
fn resolve_adjacent_node(
&self,
all_elements: &[PatternElement],
edge_idx: usize,
direction: i32, ) -> Result<String, PlanningError> {
let node_idx = if direction < 0 {
if edge_idx == 0 {
return Err(PlanningError::InvalidPattern(
"Edge pattern must be preceded by a source node".to_string(),
));
}
edge_idx - 1
} else {
if edge_idx >= all_elements.len() - 1 {
return Err(PlanningError::InvalidPattern(
"Edge pattern must be followed by a target node".to_string(),
));
}
edge_idx + 1
};
match &all_elements[node_idx] {
PatternElement::Node(node_pattern) => {
if let Some(ref identifier) = node_pattern.identifier {
if let Some(node_info) = self.identifier_mappings.get(identifier) {
Ok(node_info.storage_id.clone())
} else {
Err(PlanningError::IdentifierNotFound(identifier.clone()))
}
} else {
let properties = if let Some(ref prop_map) = node_pattern.properties {
self.extract_properties(prop_map)?
} else {
HashMap::new()
};
if node_pattern.labels.is_empty() && properties.is_empty() {
return Err(PlanningError::InvalidPattern(
"Cannot use empty anonymous node in edge pattern".to_string(),
));
}
Ok(Self::generate_node_content_id(
&node_pattern.labels,
&properties,
))
}
}
_ => Err(PlanningError::InvalidPattern(
"Expected node pattern adjacent to edge".to_string(),
)),
}
}
fn extract_properties(
&self,
prop_map: &crate::ast::ast::PropertyMap,
) -> Result<HashMap<String, Expression>, PlanningError> {
let mut properties = HashMap::new();
for property in &prop_map.properties {
properties.insert(property.key.clone(), property.value.clone());
}
Ok(properties)
}
fn generate_node_content_id(
labels: &[String],
properties: &HashMap<String, Expression>,
) -> String {
let mut hasher = DefaultHasher::new();
let mut sorted_labels = labels.to_vec();
sorted_labels.sort();
for label in &sorted_labels {
label.hash(&mut hasher);
}
let mut sorted_properties: Vec<_> = properties.iter().collect();
sorted_properties.sort_by_key(|(k, _)| *k);
for (key, value) in sorted_properties {
key.hash(&mut hasher);
format!("{:?}", value).hash(&mut hasher);
}
let hash = hasher.finish();
format!("node_{:x}", hash)
}
fn generate_edge_content_id(
from_node_id: &str,
to_node_id: &str,
label: &str,
properties: &HashMap<String, Expression>,
) -> String {
let mut hasher = DefaultHasher::new();
from_node_id.hash(&mut hasher);
to_node_id.hash(&mut hasher);
label.hash(&mut hasher);
let mut sorted_properties: Vec<_> = properties.iter().collect();
sorted_properties.sort_by_key(|(k, _)| *k);
for (key, value) in sorted_properties {
key.hash(&mut hasher);
format!("{:?}", value).hash(&mut hasher);
}
let hash = hasher.finish();
format!("edge_{:x}", hash)
}
}
#[derive(Debug, thiserror::Error)]
pub enum PlanningError {
#[error("Invalid pattern: {0}")]
InvalidPattern(String),
#[error("Identifier not found: {0}")]
IdentifierNotFound(String),
}