use super::super::ast::Expression;
use super::super::result::ResultRow;
use super::write_scope::enforce_bound_edge_write_scope as enforce_edge_write_scope;
use super::CypherExecutor;
use crate::datatypes::values::Value;
use crate::graph::languages::cypher::result::MutationStats;
use crate::graph::schema::{DirGraph, EdgeData};
use crate::graph::storage::{GraphRead, GraphWrite};
use std::collections::{HashMap, HashSet};
pub(super) fn set_edge_property(
graph: &mut DirGraph,
row: &ResultRow,
item: (&String, &String, &Expression),
params: &HashMap<String, Value>,
stats: &mut MutationStats,
edges_to_stamp: &mut HashSet<petgraph::graph::EdgeIndex>,
) -> Result<bool, String> {
let (variable, property, expression) = item;
if row.node_bindings.contains_key(variable) {
return Ok(false);
}
let Some(edge_binding) = row.edge_bindings.get(variable) else {
return Ok(false);
};
enforce_edge_write_scope(graph, edge_binding)?;
let edge_index = edge_binding.edge_index;
let value = {
let executor = CypherExecutor::with_params(graph, params, None);
executor.evaluate_expression(expression, row)?
};
if let Some(rel_type) = constrained_edge_type(graph, edge_index) {
graph.check_rel_property_write(&rel_type, property, Some(&value))?;
}
let key = graph.interner.get_or_intern(property);
if let Some(EdgeData {
properties: edge_props,
..
}) = GraphWrite::edge_weight_mut(&mut graph.graph, edge_index)
{
if matches!(value, Value::Null) {
edge_props.retain(|(ek, _)| *ek != key);
} else if let Some((_, existing)) = edge_props.iter_mut().find(|(ek, _)| *ek == key) {
*existing = value;
} else {
edge_props.push((key, value));
}
stats.properties_set += 1;
}
if property != "updated_at" {
let ct_key = {
let _arena_guard = graph.graph.begin_query();
graph
.graph
.edge_weight(edge_index)
.map(|e| e.connection_type)
};
if let Some(ct_key) = ct_key {
let ct = graph.interner.resolve(ct_key).to_string();
if graph.auto_timestamp_for_connection(&ct) {
edges_to_stamp.insert(edge_index);
}
}
}
Ok(true)
}
pub(super) fn remove_edge_property(
graph: &mut DirGraph,
row: &ResultRow,
variable: &str,
property: &str,
stats: &mut MutationStats,
) -> Result<bool, String> {
if row.node_bindings.contains_key(variable) {
return Ok(false);
}
let Some(edge_binding) = row.edge_bindings.get(variable) else {
return Ok(false);
};
enforce_edge_write_scope(graph, edge_binding)?;
let edge_index = edge_binding.edge_index;
if let Some(rel_type) = constrained_edge_type(graph, edge_index) {
graph.check_rel_property_write(&rel_type, property, None)?;
}
let key = graph.interner.get_or_intern(property);
if let Some(EdgeData {
properties: edge_props,
..
}) = GraphWrite::edge_weight_mut(&mut graph.graph, edge_index)
{
let before = edge_props.len();
edge_props.retain(|(ek, _)| *ek != key);
if edge_props.len() != before {
stats.properties_removed += 1;
}
}
Ok(true)
}
pub(super) fn constrained_edge_type(
graph: &DirGraph,
edge_index: petgraph::graph::EdgeIndex,
) -> Option<String> {
if !graph.has_rel_constraints() {
return None;
}
let connection_type = {
let _arena_guard = graph.graph.begin_query();
graph
.graph
.edge_weight(edge_index)
.map(|e| e.connection_type)
}?;
let rel_type = graph.interner.resolve(connection_type).to_string();
graph
.type_has_rel_constraints(&rel_type)
.then_some(rel_type)
}