use crate::datatypes::values::Value;
use crate::datatypes::PropMap;
use crate::graph::languages::cypher::ast::SetPathStep;
use crate::graph::languages::cypher::result::ResultRow;
use super::CypherExecutor;
pub(super) fn read_modify(
graph: &crate::graph::dir_graph::DirGraph,
node_idx: petgraph::graph::NodeIndex,
property: &str,
steps: &[SetPathStep],
new_value: Value,
params: &std::collections::HashMap<String, Value>,
row: &ResultRow,
) -> Result<Value, String> {
let resolved = {
let executor = CypherExecutor::with_params(graph, params, None);
resolve_steps(&executor, row, steps)?
};
let current = {
use crate::graph::storage::GraphRead;
let _guard = graph.graph.begin_query();
let key = crate::graph::schema::InternedKey::from_str(property);
graph
.graph
.get_node_property(node_idx, key)
.unwrap_or(Value::Null)
};
apply_path(property, current, &resolved, new_value)
}
pub(super) enum ResolvedStep {
Field(String),
Index(i64),
}
pub(super) fn resolve_steps(
executor: &CypherExecutor<'_>,
row: &ResultRow,
steps: &[SetPathStep],
) -> Result<Vec<ResolvedStep>, String> {
steps
.iter()
.map(|step| match step {
SetPathStep::Field(name) => Ok(ResolvedStep::Field(name.clone())),
SetPathStep::Index(expr) => match executor.evaluate_expression(expr, row)? {
Value::Int64(i) => Ok(ResolvedStep::Index(i)),
other => Err(format!(
"SET path index must be an integer, got {}",
other.type_name()
)),
},
})
.collect()
}
pub(super) fn apply_path(
property: &str,
current: Value,
steps: &[ResolvedStep],
new_value: Value,
) -> Result<Value, String> {
descend(property.to_string(), current, steps, new_value)
}
fn descend(
path: String,
current: Value,
steps: &[ResolvedStep],
new_value: Value,
) -> Result<Value, String> {
let Some((head, rest)) = steps.split_first() else {
return Ok(new_value);
};
match head {
ResolvedStep::Index(raw) => {
let Value::List(mut items) = current else {
return Err(format!(
"{path}: expected a list to index into, found {}",
current.type_name()
));
};
let len = items.len() as i64;
let idx = if *raw < 0 { len + raw } else { *raw };
if idx < 0 || idx >= len {
return Err(format!(
"{path}[{raw}]: index out of bounds (list has {len} entries)"
));
}
let idx = idx as usize;
let child = std::mem::replace(&mut items[idx], Value::Null);
items[idx] = descend(format!("{path}[{raw}]"), child, rest, new_value)?;
Ok(Value::List(items))
}
ResolvedStep::Field(name) => {
let map = match current {
Value::Map(map) => map,
Value::Null => PropMap::from_pairs(Vec::new()),
other => {
return Err(format!(
"{path}: expected a map for '.{name}', found {}",
other.type_name()
));
}
};
let mut pairs: Vec<(crate::datatypes::PropKey, Value)> =
map.iter().map(|(k, v)| (k.into(), v.clone())).collect();
let child = map.get(name).cloned().unwrap_or(Value::Null);
let updated = descend(format!("{path}.{name}"), child, rest, new_value)?;
match pairs
.iter_mut()
.find(|(k, _)| AsRef::<str>::as_ref(k) == name.as_str())
{
Some(slot) => slot.1 = updated,
None => pairs.push((name.as_str().into(), updated)),
}
Ok(Value::Map(PropMap::from_pairs(pairs)))
}
}
}