1use std::collections::BTreeMap;
2use std::path::Path;
3
4use marsdb_storage::{ReadableMultimapTable, ReadableTable, StorageEngine, WriteTransaction};
5
6use crate::encode::{decode, encode, EdgeRecord, NodeRecord};
7use crate::error::GraphError;
8use crate::id::next_id;
9use crate::labels::{intern_label, lookup_label_id, resolve_label};
10use crate::model::{AdjEntry, Direction, Edge, EdgeId, Node, NodeId, PropertyValue};
11
12pub struct GraphStore {
13 storage: StorageEngine,
14}
15
16impl GraphStore {
17 pub fn open_file(path: impl AsRef<Path>) -> Result<Self, GraphError> {
18 Ok(Self {
19 storage: StorageEngine::open_file(path)?,
20 })
21 }
22
23 pub fn open_memory() -> Result<Self, GraphError> {
24 Ok(Self {
25 storage: StorageEngine::open_memory()?,
26 })
27 }
28
29 pub fn begin_write(&self) -> Result<WriteTransaction, GraphError> {
44 Ok(self.storage.begin_write()?)
45 }
46
47 pub fn commit(write_txn: WriteTransaction) -> Result<(), GraphError> {
49 write_txn.commit()?;
50 Ok(())
51 }
52
53 pub fn abort(write_txn: WriteTransaction) -> Result<(), GraphError> {
57 write_txn.abort()?;
58 Ok(())
59 }
60
61 pub fn create_node(
62 &self,
63 label: &str,
64 props: BTreeMap<String, PropertyValue>,
65 ) -> Result<NodeId, GraphError> {
66 let write_txn = self.begin_write()?;
67 let id = Self::create_node_in_txn(&write_txn, label, props)?;
68 write_txn.commit()?;
69 Ok(id)
70 }
71
72 pub fn create_node_in_txn(
73 write_txn: &WriteTransaction,
74 label: &str,
75 props: BTreeMap<String, PropertyValue>,
76 ) -> Result<NodeId, GraphError> {
77 let label_id = intern_label(write_txn, label)?;
78 let id = next_id(write_txn, "next_node_id")?;
79 let record = NodeRecord { label_id, props };
80 let bytes = encode(&record)?;
81 let mut nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
82 nodes.insert(id, bytes.as_slice())?;
83 Ok(NodeId(id))
84 }
85
86 pub fn get_node(&self, id: NodeId) -> Result<Option<Node>, GraphError> {
87 let write_txn = self.begin_write()?;
88 let node = Self::get_node_in_txn(&write_txn, id)?;
89 write_txn.abort()?;
90 Ok(node)
91 }
92
93 pub fn get_node_in_txn(write_txn: &WriteTransaction, id: NodeId) -> Result<Option<Node>, GraphError> {
94 let record: Option<NodeRecord> = {
95 let nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
96 let found = match nodes.get(id.0)? {
97 Some(guard) => Some(decode(guard.value())?),
98 None => None,
99 };
100 found
101 };
102 let Some(record) = record else { return Ok(None) };
103 let label = resolve_label(write_txn, record.label_id)?;
104 Ok(Some(Node {
105 id,
106 label,
107 props: record.props,
108 }))
109 }
110
111 pub fn create_edge(
112 &self,
113 label: &str,
114 src: NodeId,
115 dst: NodeId,
116 props: BTreeMap<String, PropertyValue>,
117 ) -> Result<EdgeId, GraphError> {
118 let write_txn = self.begin_write()?;
119 let id = Self::create_edge_in_txn(&write_txn, label, src, dst, props)?;
120 write_txn.commit()?;
121 Ok(id)
122 }
123
124 pub fn create_edge_in_txn(
125 write_txn: &WriteTransaction,
126 label: &str,
127 src: NodeId,
128 dst: NodeId,
129 props: BTreeMap<String, PropertyValue>,
130 ) -> Result<EdgeId, GraphError> {
131 let label_id = intern_label(write_txn, label)?;
132 let id = next_id(write_txn, "next_edge_id")?;
133 let record = EdgeRecord {
134 label_id,
135 src: src.0,
136 dst: dst.0,
137 props,
138 };
139 let bytes = encode(&record)?;
140 let mut edges = write_txn.open_table(marsdb_storage::tables::EDGES)?;
141 edges.insert(id, bytes.as_slice())?;
142
143 let out_entry = AdjEntry {
144 edge_id: EdgeId(id),
145 other: dst,
146 label_id,
147 }
148 .encode();
149 let in_entry = AdjEntry {
150 edge_id: EdgeId(id),
151 other: src,
152 label_id,
153 }
154 .encode();
155 let mut adj_out = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_OUT)?;
156 adj_out.insert(src.0, out_entry.as_slice())?;
157 let mut adj_in = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_IN)?;
158 adj_in.insert(dst.0, in_entry.as_slice())?;
159 Ok(EdgeId(id))
160 }
161
162 pub fn get_edge(&self, id: EdgeId) -> Result<Option<Edge>, GraphError> {
163 let write_txn = self.begin_write()?;
164 let edge = Self::get_edge_in_txn(&write_txn, id)?;
165 write_txn.abort()?;
166 Ok(edge)
167 }
168
169 pub fn get_edge_in_txn(write_txn: &WriteTransaction, id: EdgeId) -> Result<Option<Edge>, GraphError> {
170 let record: Option<EdgeRecord> = {
171 let edges = write_txn.open_table(marsdb_storage::tables::EDGES)?;
172 let found = match edges.get(id.0)? {
173 Some(guard) => Some(decode(guard.value())?),
174 None => None,
175 };
176 found
177 };
178 let Some(record) = record else { return Ok(None) };
179 let label = resolve_label(write_txn, record.label_id)?;
180 Ok(Some(Edge {
181 id,
182 label,
183 src: NodeId(record.src),
184 dst: NodeId(record.dst),
185 props: record.props,
186 }))
187 }
188
189 pub fn neighbors(
192 &self,
193 node: NodeId,
194 dir: Direction,
195 label_filter: Option<&str>,
196 ) -> Result<Vec<AdjEntry>, GraphError> {
197 let write_txn = self.begin_write()?;
198 let result = Self::neighbors_in_txn(&write_txn, node, dir, label_filter)?;
199 write_txn.abort()?;
200 Ok(result)
201 }
202
203 pub fn neighbors_in_txn(
204 write_txn: &WriteTransaction,
205 node: NodeId,
206 dir: Direction,
207 label_filter: Option<&str>,
208 ) -> Result<Vec<AdjEntry>, GraphError> {
209 let label_id_filter = match label_filter {
210 Some(l) => match lookup_label_id(write_txn, l)? {
211 Some(id) => Some(id),
212 None => return Ok(Vec::new()),
213 },
214 None => None,
215 };
216 let mut result = Vec::new();
217 let table_def = match dir {
218 Direction::Out => marsdb_storage::tables::ADJ_OUT,
219 Direction::In => marsdb_storage::tables::ADJ_IN,
220 };
221 let table = write_txn.open_multimap_table(table_def)?;
222 for item in table.get(node.0)? {
223 let entry = AdjEntry::decode(item?.value());
224 if label_id_filter.is_none_or(|lid| lid == entry.label_id) {
225 result.push(entry);
226 }
227 }
228 Ok(result)
229 }
230
231 pub fn delete_edge(&self, id: EdgeId) -> Result<bool, GraphError> {
232 let write_txn = self.begin_write()?;
233 let removed = Self::delete_edge_in_txn(&write_txn, id)?;
234 write_txn.commit()?;
235 Ok(removed)
236 }
237
238 pub fn delete_edge_in_txn(write_txn: &WriteTransaction, id: EdgeId) -> Result<bool, GraphError> {
239 let record_bytes: Option<Vec<u8>> = {
240 let mut edges = write_txn.open_table(marsdb_storage::tables::EDGES)?;
241 let removed = edges.remove(id.0)?.map(|guard| guard.value().to_vec());
242 removed
243 };
244 let Some(record_bytes) = record_bytes else {
245 return Ok(false);
246 };
247 let record: EdgeRecord = decode(&record_bytes)?;
248 let out_entry = AdjEntry {
249 edge_id: id,
250 other: NodeId(record.dst),
251 label_id: record.label_id,
252 }
253 .encode();
254 let in_entry = AdjEntry {
255 edge_id: id,
256 other: NodeId(record.src),
257 label_id: record.label_id,
258 }
259 .encode();
260 {
261 let mut adj_out = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_OUT)?;
262 adj_out.remove(record.src, out_entry.as_slice())?;
263 }
264 {
265 let mut adj_in = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_IN)?;
266 adj_in.remove(record.dst, in_entry.as_slice())?;
267 }
268 Ok(true)
269 }
270
271 pub fn delete_node(&self, id: NodeId, detach: bool) -> Result<bool, GraphError> {
274 let write_txn = self.begin_write()?;
275 let existed = Self::delete_node_in_txn(&write_txn, id, detach)?;
276 write_txn.commit()?;
277 Ok(existed)
278 }
279
280 pub fn delete_node_in_txn(write_txn: &WriteTransaction, id: NodeId, detach: bool) -> Result<bool, GraphError> {
281 let mut incident: Vec<EdgeId> = Vec::new();
282 {
283 let adj_out = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_OUT)?;
284 for item in adj_out.get(id.0)? {
285 incident.push(AdjEntry::decode(item?.value()).edge_id);
286 }
287 let adj_in = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_IN)?;
288 for item in adj_in.get(id.0)? {
289 incident.push(AdjEntry::decode(item?.value()).edge_id);
290 }
291 }
292 if !incident.is_empty() && !detach {
293 return Err(GraphError::NodeHasEdges(id));
294 }
295 for edge_id in incident {
296 Self::delete_edge_in_txn(write_txn, edge_id)?;
297 }
298 let mut nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
299 let existed = nodes.remove(id.0)?.is_some();
300 Ok(existed)
301 }
302
303 pub fn set_node_prop(&self, id: NodeId, key: &str, value: PropertyValue) -> Result<bool, GraphError> {
304 let write_txn = self.begin_write()?;
305 let updated = Self::set_node_prop_in_txn(&write_txn, id, key, value)?;
306 if updated {
307 write_txn.commit()?;
308 } else {
309 write_txn.abort()?;
310 }
311 Ok(updated)
312 }
313
314 pub fn set_node_prop_in_txn(
315 write_txn: &WriteTransaction,
316 id: NodeId,
317 key: &str,
318 value: PropertyValue,
319 ) -> Result<bool, GraphError> {
320 let bytes_opt: Option<Vec<u8>> = {
321 let nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
322 let found = nodes.get(id.0)?.map(|g| g.value().to_vec());
323 found
324 };
325 let Some(bytes) = bytes_opt else { return Ok(false) };
326 let mut record: NodeRecord = decode(&bytes)?;
327 record.props.insert(key.to_string(), value);
328 let new_bytes = encode(&record)?;
329 let mut nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
330 nodes.insert(id.0, new_bytes.as_slice())?;
331 Ok(true)
332 }
333
334 pub fn set_edge_prop(&self, id: EdgeId, key: &str, value: PropertyValue) -> Result<bool, GraphError> {
335 let write_txn = self.begin_write()?;
336 let updated = Self::set_edge_prop_in_txn(&write_txn, id, key, value)?;
337 if updated {
338 write_txn.commit()?;
339 } else {
340 write_txn.abort()?;
341 }
342 Ok(updated)
343 }
344
345 pub fn set_edge_prop_in_txn(
346 write_txn: &WriteTransaction,
347 id: EdgeId,
348 key: &str,
349 value: PropertyValue,
350 ) -> Result<bool, GraphError> {
351 let bytes_opt: Option<Vec<u8>> = {
352 let edges = write_txn.open_table(marsdb_storage::tables::EDGES)?;
353 let found = edges.get(id.0)?.map(|g| g.value().to_vec());
354 found
355 };
356 let Some(bytes) = bytes_opt else { return Ok(false) };
357 let mut record: EdgeRecord = decode(&bytes)?;
358 record.props.insert(key.to_string(), value);
359 let new_bytes = encode(&record)?;
360 let mut edges = write_txn.open_table(marsdb_storage::tables::EDGES)?;
361 edges.insert(id.0, new_bytes.as_slice())?;
362 Ok(true)
363 }
364
365 pub fn all_nodes(&self, label_filter: Option<&str>) -> Result<Vec<Node>, GraphError> {
368 let write_txn = self.begin_write()?;
369 let result = Self::all_nodes_in_txn(&write_txn, label_filter)?;
370 write_txn.abort()?;
371 Ok(result)
372 }
373
374 pub fn all_nodes_in_txn(write_txn: &WriteTransaction, label_filter: Option<&str>) -> Result<Vec<Node>, GraphError> {
375 let label_id_filter = match label_filter {
376 Some(l) => match lookup_label_id(write_txn, l)? {
377 Some(id) => Some(id),
378 None => return Ok(Vec::new()),
379 },
380 None => None,
381 };
382 let mut result = Vec::new();
383 let nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
384 for item in nodes.iter()? {
385 let (key, value) = item?;
386 let record: NodeRecord = decode(value.value())?;
387 if label_id_filter.is_none_or(|lid| lid == record.label_id) {
388 let label = resolve_label(write_txn, record.label_id)?;
389 result.push(Node {
390 id: NodeId(key.value()),
391 label,
392 props: record.props,
393 });
394 }
395 }
396 Ok(result)
397 }
398}