1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Copyright (c) 2024-2025 DeepGraph Inc.
// SPDX-License-Identifier: Apache-2.0
//
use std::collections::HashMap;
use crate::ast::ast::{LabelFactor, RemoveItem, RemoveStatement};
use crate::exec::write_stmt::data_stmt::DataStatementExecutor;
use crate::exec::write_stmt::{ExecutionContext, StatementExecutor};
use crate::exec::ExecutionError;
use crate::storage::GraphCache;
use crate::txn::{state::OperationType, UndoOperation};
/// Executor for REMOVE statements
pub struct RemoveExecutor {
statement: RemoveStatement,
}
impl RemoveExecutor {
/// Create a new RemoveExecutor
pub fn new(statement: RemoveStatement) -> Self {
Self { statement }
}
}
impl StatementExecutor for RemoveExecutor {
fn operation_type(&self) -> OperationType {
OperationType::Remove
}
fn operation_description(&self, context: &ExecutionContext) -> String {
let graph_name = context
.get_graph_name()
.unwrap_or_else(|_| "unknown".to_string());
format!("REMOVE properties/labels in graph '{}'", graph_name)
}
}
impl DataStatementExecutor for RemoveExecutor {
fn execute_modification(
&self,
graph: &mut GraphCache,
context: &mut ExecutionContext,
) -> Result<(UndoOperation, usize), ExecutionError> {
let graph_name = context.get_graph_name()?;
let mut undo_operations = Vec::new();
let mut removed_count = 0;
// Process each REMOVE item
for item in &self.statement.items {
match item {
RemoveItem::Property(property_access) => {
// Handle property removal (e.g., REMOVE n.name)
let var_name = &property_access.object;
// Find nodes with this variable identifier
let node_ids_to_update: Vec<String> = graph
.get_all_nodes()
.iter()
.filter(|node| node.id == *var_name || node.labels.contains(var_name))
.map(|node| node.id.clone())
.collect();
for node_id in node_ids_to_update {
// Get old property value and labels for undo
let (old_properties, old_labels, has_property) = if let Some(node) =
graph.get_node(&node_id)
{
let mut old_props = HashMap::new();
let has_prop = if let Some(old_val) =
node.properties.get(&property_access.property)
{
old_props.insert(property_access.property.clone(), old_val.clone());
true
} else {
false
};
(old_props, node.labels.clone(), has_prop)
} else {
(HashMap::new(), Vec::new(), false)
};
if has_property {
// Remove the property
if let Some(node_mut) = graph.get_node_mut(&node_id) {
node_mut.remove_property(&property_access.property);
log::debug!(
"Removed property {} from node {}",
property_access.property,
node_id
);
removed_count += 1;
// Add undo operation
undo_operations.push(UndoOperation::UpdateNode {
graph_path: graph_name.clone(),
node_id: node_id.clone(),
old_properties,
old_labels,
});
}
}
}
}
RemoveItem::Label { variable, labels } => {
// Find nodes with this variable identifier
let node_ids_to_update: Vec<String> = graph
.get_all_nodes()
.iter()
.filter(|node| node.id == *variable || node.labels.contains(variable))
.map(|node| node.id.clone())
.collect();
for node_id in node_ids_to_update {
// Get original labels for undo
let old_labels = if let Some(node) = graph.get_node(&node_id) {
node.labels.clone()
} else {
continue;
};
let mut removed_any = false;
// Extract labels to remove
for term in &labels.terms {
for factor in &term.factors {
if let LabelFactor::Identifier(label_name) = factor {
if let Some(node_mut) = graph.get_node_mut(&node_id) {
let original_len = node_mut.labels.len();
node_mut.labels.retain(|l| l != label_name);
if node_mut.labels.len() < original_len {
log::debug!(
"Removed label {} from node {}",
label_name,
node_id
);
removed_any = true;
}
}
}
}
}
if removed_any {
removed_count += 1;
// Add undo operation
undo_operations.push(UndoOperation::UpdateNode {
graph_path: graph_name.clone(),
node_id: node_id.clone(),
old_properties: HashMap::new(),
old_labels,
});
}
}
}
RemoveItem::Variable(variable) => {
log::warn!("Variable removal in REMOVE not yet supported: {}", variable);
// This would typically remove variable bindings from execution context
}
}
}
// Return the first undo operation if any
let undo_op =
undo_operations
.into_iter()
.next()
.unwrap_or_else(|| UndoOperation::UpdateNode {
graph_path: graph_name,
node_id: "no_operations".to_string(),
old_properties: HashMap::new(),
old_labels: vec![],
});
Ok((undo_op, removed_count))
}
}