1use std::collections::BTreeMap;
13
14use uqa_core::{Edge, EdgeId, Value, Vertex, VertexId};
15
16use crate::cypher::ast::{
17 CreateClause, CypherClause, CypherExpr, CypherQuery, DeleteClause, MergeClause, NodePattern,
18 PathElement, PathPattern, PropertyAccess, RelDirection, RelPattern, SetClause, SetItem,
19 SetOperator, UnwindClause, Variable,
20};
21use crate::cypher::executor::{Binding, BindingRow, CypherError, CypherExecutor, ResultRow};
22use crate::store::GraphStore;
23
24pub struct CypherWriter<'a, G: GraphStore> {
28 pub store: &'a mut G,
29 pub graph: String,
30 pub params: BTreeMap<String, Value>,
31}
32
33impl<'a, G: GraphStore> CypherWriter<'a, G> {
34 pub fn new(store: &'a mut G, graph: impl Into<String>) -> Self {
35 Self {
36 store,
37 graph: graph.into(),
38 params: BTreeMap::new(),
39 }
40 }
41
42 pub fn with_params(mut self, params: BTreeMap<String, Value>) -> Self {
43 self.params = params;
44 self
45 }
46
47 fn reader(&self) -> CypherExecutor<'_, G> {
48 let mut exec = CypherExecutor::new(&*self.store, &self.graph);
49 exec.params = self.params.clone();
50 exec
51 }
52
53 pub fn execute(
54 &mut self,
55 query: &CypherQuery,
56 ) -> Result<(Vec<String>, Vec<ResultRow>), CypherError> {
57 let mut bindings: Vec<BindingRow> = vec![BTreeMap::new()];
58 let mut columns: Vec<String> = Vec::new();
59 let mut rows: Vec<ResultRow> = Vec::new();
60 for clause in &query.clauses {
61 match clause {
62 CypherClause::Match(m) => {
63 bindings = self.reader().exec_match(m, &bindings)?;
64 }
65 CypherClause::Create(c) => {
66 bindings = self.exec_create(c, bindings)?;
67 }
68 CypherClause::Merge(m) => {
69 bindings = self.exec_merge(m, bindings)?;
70 }
71 CypherClause::Set(s) => {
72 bindings = self.exec_set(s, bindings)?;
73 }
74 CypherClause::Delete(d) => {
75 bindings = self.exec_delete(d, bindings)?;
76 }
77 CypherClause::Unwind(u) => {
78 bindings = self.exec_unwind(u, bindings)?;
79 }
80 CypherClause::With(w) => {
81 let (cols, projected) = self.reader().exec_return_like(
82 &w.items,
83 w.distinct,
84 w.order_by.as_deref(),
85 w.skip.as_ref(),
86 w.limit.as_ref(),
87 &bindings,
88 )?;
89 let reader = self.reader();
90 let mut next = Vec::with_capacity(projected.len());
91 for row in projected {
92 if let Some(filter) = &w.r#where {
93 if !reader.where_passes(filter, &row)? {
94 continue;
95 }
96 }
97 next.push(CypherExecutor::<G>::row_to_bindings(&cols, &row));
98 }
99 drop(reader);
100 bindings = next;
101 }
102 CypherClause::Return(r) => {
103 let (cols, ret_rows) = self.reader().exec_return_like(
104 &r.items,
105 r.distinct,
106 r.order_by.as_deref(),
107 r.skip.as_ref(),
108 r.limit.as_ref(),
109 &bindings,
110 )?;
111 columns = cols;
112 rows = ret_rows;
113 }
114 }
115 }
116 Ok((columns, rows))
117 }
118
119 fn exec_create(
124 &mut self,
125 clause: &CreateClause,
126 bindings: Vec<BindingRow>,
127 ) -> Result<Vec<BindingRow>, CypherError> {
128 let mut next = Vec::with_capacity(bindings.len());
129 for mut row in bindings {
130 for pattern in &clause.patterns {
131 self.create_path(pattern, &mut row)?;
132 }
133 next.push(row);
134 }
135 Ok(next)
136 }
137
138 fn create_path(
139 &mut self,
140 pattern: &PathPattern,
141 row: &mut BindingRow,
142 ) -> Result<(), CypherError> {
143 let elements = &pattern.elements;
144 let mut position: Option<VertexId> = None;
147 let mut idx = 0;
148 while idx < elements.len() {
149 match &elements[idx] {
150 PathElement::Node(np) => {
151 position = Some(self.resolve_or_create_vertex(np, row)?);
152 idx += 1;
153 }
154 PathElement::Rel(rp) => {
155 let Some(PathElement::Node(next_np)) = elements.get(idx + 1) else {
156 return Err(CypherError::Unsupported("path must end on a node".into()));
157 };
158 let src_id = position.ok_or_else(|| {
159 CypherError::Unsupported("relationship without prior node".into())
160 })?;
161 let tgt_id = self.resolve_or_create_vertex(next_np, row)?;
162 let edge = self.create_edge(rp, row, src_id, tgt_id)?;
163 if let Some(var) = &rp.variable {
164 row.insert(var.clone(), Binding::Edge(edge));
165 }
166 position = Some(tgt_id);
167 idx += 2;
168 }
169 }
170 }
171 Ok(())
172 }
173
174 fn resolve_or_create_vertex(
178 &mut self,
179 np: &NodePattern,
180 row: &mut BindingRow,
181 ) -> Result<VertexId, CypherError> {
182 if let Some(var) = &np.variable {
183 match row.get(var) {
184 Some(Binding::Vertex(v)) => return Ok(v.vertex_id),
185 Some(_) => {
186 return Err(CypherError::TypeError(format!("{var:?} is not a vertex")));
187 }
188 None => {}
189 }
190 }
191 let vertex = self.create_vertex(np, row)?;
192 let id = vertex.vertex_id;
193 if let Some(var) = &np.variable {
194 row.insert(var.clone(), Binding::Vertex(vertex));
195 }
196 Ok(id)
197 }
198
199 fn create_vertex(
200 &mut self,
201 pat: &NodePattern,
202 row: &BindingRow,
203 ) -> Result<Vertex, CypherError> {
204 let label = pat.labels.first().cloned().unwrap_or_default();
205 let mut props: BTreeMap<String, Value> = BTreeMap::new();
206 if let Some(map) = &pat.properties {
207 let reader = self.reader();
208 for (k, expr) in map {
209 props.insert(k.clone(), reader.eval(expr, row)?);
210 }
211 }
212 let vid = self
214 .store
215 .allocate_vertex_id(&label, &self.graph)
216 .map_err(|error| CypherError::Storage(error.to_string()))?;
217 let vertex = Vertex {
218 vertex_id: vid,
219 label,
220 properties: props,
221 };
222 self.store.add_vertex(vertex.clone(), &self.graph)?;
223 Ok(vertex)
224 }
225
226 fn create_edge(
227 &mut self,
228 pat: &RelPattern,
229 row: &BindingRow,
230 mut src_id: VertexId,
231 mut tgt_id: VertexId,
232 ) -> Result<Edge, CypherError> {
233 if pat.direction == RelDirection::Left {
234 std::mem::swap(&mut src_id, &mut tgt_id);
235 }
236 let label = pat.types.first().cloned().unwrap_or_default();
237 let mut props: BTreeMap<String, Value> = BTreeMap::new();
238 if let Some(map) = &pat.properties {
239 let reader = self.reader();
240 for (k, expr) in map {
241 props.insert(k.clone(), reader.eval(expr, row)?);
242 }
243 }
244 let eid = self
246 .store
247 .allocate_edge_id(&label, &self.graph)
248 .map_err(|error| CypherError::Storage(error.to_string()))?;
249 let edge = Edge {
250 edge_id: eid,
251 source_id: src_id,
252 target_id: tgt_id,
253 label,
254 properties: props,
255 };
256 self.store.add_edge(edge.clone(), &self.graph)?;
257 Ok(edge)
258 }
259
260 fn exec_set(
265 &mut self,
266 clause: &SetClause,
267 bindings: Vec<BindingRow>,
268 ) -> Result<Vec<BindingRow>, CypherError> {
269 let mut next = Vec::with_capacity(bindings.len());
270 for mut row in bindings {
271 for item in &clause.items {
272 self.apply_set_item(item, &mut row)?;
273 }
274 next.push(row);
275 }
276 Ok(next)
277 }
278
279 fn apply_set_item(&mut self, item: &SetItem, row: &mut BindingRow) -> Result<(), CypherError> {
280 let value = self.reader().eval(&item.value, row)?;
281 match &item.target {
282 CypherExpr::PropertyAccess(PropertyAccess { variable, keys }) => {
283 let Some(binding) = row.get(variable).cloned() else {
284 return Err(CypherError::UndefinedVariable(variable.clone()));
285 };
286 match binding {
287 Binding::Vertex(vertex) => {
288 let mut new_props = vertex.properties.clone();
289 apply_property_update(&mut new_props, keys, &value, item.operator)?;
290 let updated = Vertex {
291 vertex_id: vertex.vertex_id,
292 label: vertex.label.clone(),
293 properties: new_props,
294 };
295 self.store.add_vertex(updated.clone(), &self.graph)?;
296 row.insert(variable.clone(), Binding::Vertex(updated));
297 }
298 Binding::Edge(edge) => {
299 let mut new_props = edge.properties.clone();
300 apply_property_update(&mut new_props, keys, &value, item.operator)?;
301 let updated = Edge {
302 edge_id: edge.edge_id,
303 source_id: edge.source_id,
304 target_id: edge.target_id,
305 label: edge.label.clone(),
306 properties: new_props,
307 };
308 self.store.add_edge(updated.clone(), &self.graph)?;
309 row.insert(variable.clone(), Binding::Edge(updated));
310 }
311 _ => {
312 return Err(CypherError::TypeError(format!(
313 "SET target {variable:?} is not a vertex or edge"
314 )));
315 }
316 }
317 }
318 CypherExpr::Variable(Variable { name }) => {
319 let Value::Map(replacement) = value else {
321 return Err(CypherError::TypeError(
322 "SET <var> = <expr> requires a map RHS".into(),
323 ));
324 };
325 let Some(binding) = row.get(name).cloned() else {
326 return Err(CypherError::UndefinedVariable(name.clone()));
327 };
328 if let Binding::Vertex(vertex) = binding {
329 let mut new_props = match item.operator {
330 SetOperator::Assign => BTreeMap::new(),
331 SetOperator::Update => vertex.properties.clone(),
332 };
333 new_props.extend(replacement);
334 let updated = Vertex {
335 vertex_id: vertex.vertex_id,
336 label: vertex.label.clone(),
337 properties: new_props,
338 };
339 self.store.add_vertex(updated.clone(), &self.graph)?;
340 row.insert(name.clone(), Binding::Vertex(updated));
341 } else {
342 return Err(CypherError::TypeError(format!(
343 "SET {name:?} target must be a vertex"
344 )));
345 }
346 }
347 other => {
348 return Err(CypherError::Unsupported(format!("SET target {other:?}")));
349 }
350 }
351 Ok(())
352 }
353
354 fn exec_delete(
359 &mut self,
360 clause: &DeleteClause,
361 bindings: Vec<BindingRow>,
362 ) -> Result<Vec<BindingRow>, CypherError> {
363 let mut to_delete_vertices: Vec<VertexId> = Vec::new();
364 let mut to_delete_edges: Vec<EdgeId> = Vec::new();
365 for row in &bindings {
366 for expr in &clause.expressions {
367 let CypherExpr::Variable(Variable { name }) = expr else {
368 return Err(CypherError::Unsupported(
369 "DELETE only supports bare variable references".into(),
370 ));
371 };
372 let Some(binding) = row.get(name) else {
373 return Err(CypherError::UndefinedVariable(name.clone()));
374 };
375 match binding {
376 Binding::Vertex(v) => to_delete_vertices.push(v.vertex_id),
377 Binding::Edge(e) => to_delete_edges.push(e.edge_id),
378 _ => {
379 return Err(CypherError::TypeError(format!(
380 "DELETE {name:?} is not a vertex or edge"
381 )));
382 }
383 }
384 }
385 }
386 to_delete_edges.sort_unstable();
388 to_delete_edges.dedup();
389 for eid in &to_delete_edges {
390 self.store.remove_edge(*eid, &self.graph)?;
391 }
392 to_delete_vertices.sort_unstable();
393 to_delete_vertices.dedup();
394 for vid in &to_delete_vertices {
395 if !clause.detach {
396 let has_out = !self.store.out_edge_ids(*vid, &self.graph)?.is_empty();
397 let has_in = !self.store.in_edge_ids(*vid, &self.graph)?.is_empty();
398 if has_out || has_in {
399 return Err(CypherError::TypeError(format!(
400 "cannot delete vertex {vid}: has incident edges, use DETACH DELETE"
401 )));
402 }
403 }
404 self.store.remove_vertex(*vid, &self.graph)?;
405 }
406 Ok(bindings)
407 }
408
409 fn exec_merge(
414 &mut self,
415 clause: &MergeClause,
416 bindings: Vec<BindingRow>,
417 ) -> Result<Vec<BindingRow>, CypherError> {
418 let mut next: Vec<BindingRow> = Vec::new();
419 for row in bindings {
420 let matches = self.reader().match_path_pattern(&clause.pattern, &row)?;
421 if matches.is_empty() {
422 let mut row = row;
423 self.create_path(&clause.pattern, &mut row)?;
424 if let Some(items) = &clause.on_create_set {
425 for item in items {
426 self.apply_set_item(item, &mut row)?;
427 }
428 }
429 next.push(row);
430 } else {
431 for mut matched in matches {
432 if let Some(items) = &clause.on_match_set {
433 for item in items {
434 self.apply_set_item(item, &mut matched)?;
435 }
436 }
437 next.push(matched);
438 }
439 }
440 }
441 Ok(next)
442 }
443
444 fn exec_unwind(
449 &mut self,
450 clause: &UnwindClause,
451 bindings: Vec<BindingRow>,
452 ) -> Result<Vec<BindingRow>, CypherError> {
453 let mut next = Vec::new();
454 for row in bindings {
455 let value = self.reader().eval(&clause.expr, &row)?;
456 let items = match value {
460 Value::List(items) => items,
461 Value::Null => continue,
462 other => vec![other],
463 };
464 for item in items {
465 let mut new_row = row.clone();
466 new_row.insert(clause.variable.clone(), Binding::Value(item));
467 next.push(new_row);
468 }
469 }
470 Ok(next)
471 }
472}
473
474fn apply_property_update(
475 props: &mut BTreeMap<String, Value>,
476 keys: &[String],
477 value: &Value,
478 op: SetOperator,
479) -> Result<(), CypherError> {
480 if keys.is_empty() {
481 return Err(CypherError::TypeError(
482 "SET property path must contain at least one key".to_string(),
483 ));
484 }
485 if keys.len() == 1 {
486 let key = keys[0].clone();
487 match (op, value) {
488 (SetOperator::Update, Value::Map(rhs)) => {
489 let mut existing = match props.remove(&key) {
490 Some(Value::Map(m)) => m,
491 _ => BTreeMap::new(),
492 };
493 existing.extend(rhs.clone());
494 props.insert(key, Value::Map(existing));
495 }
496 _ => {
497 props.insert(key, value.clone());
498 }
499 }
500 return Ok(());
501 }
502 let mut cursor = props;
504 for key in &keys[..keys.len() - 1] {
505 let entry = cursor
506 .entry(key.clone())
507 .or_insert_with(|| Value::Map(BTreeMap::new()));
508 if !matches!(entry, Value::Map(_)) {
509 *entry = Value::Map(BTreeMap::new());
510 }
511 let Value::Map(inner) = entry else {
512 return Err(CypherError::TypeError(format!(
513 "SET property path component {key:?} is not a map"
514 )));
515 };
516 cursor = inner;
517 }
518 let Some(leaf) = keys.last().cloned() else {
519 return Err(CypherError::TypeError(
520 "SET property path must contain at least one key".to_string(),
521 ));
522 };
523 cursor.insert(leaf, value.clone());
524 Ok(())
525}