1use std::collections::BTreeMap;
2use std::path::Path;
3
4use marsdb_storage::{ReadTransaction, ReadableMultimapTable, ReadableTable, StorageEngine, Txn, 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 begin_read(&self) -> Result<ReadTransaction, GraphError> {
54 Ok(self.storage.begin_read()?)
55 }
56
57 pub fn commit(write_txn: WriteTransaction) -> Result<(), GraphError> {
59 write_txn.commit()?;
60 Ok(())
61 }
62
63 pub fn abort(write_txn: WriteTransaction) -> Result<(), GraphError> {
67 write_txn.abort()?;
68 Ok(())
69 }
70
71 pub fn create_node(
72 &self,
73 labels: &[&str],
74 props: BTreeMap<String, PropertyValue>,
75 ) -> Result<NodeId, GraphError> {
76 let write_txn = self.begin_write()?;
77 let id = Self::create_node_in_txn(&write_txn, labels, props)?;
78 write_txn.commit()?;
79 Ok(id)
80 }
81
82 pub fn create_node_in_txn(
83 write_txn: &WriteTransaction,
84 labels: &[&str],
85 props: BTreeMap<String, PropertyValue>,
86 ) -> Result<NodeId, GraphError> {
87 let label_ids = labels
88 .iter()
89 .map(|l| intern_label(write_txn, l))
90 .collect::<Result<Vec<_>, _>>()?;
91 let id = next_id(write_txn, "next_node_id")?;
92 let record = NodeRecord {
93 label_ids: label_ids.clone(),
94 props,
95 };
96 let bytes = encode(&record)?;
97 let mut nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
98 nodes.insert(id, bytes.as_slice())?;
99 drop(nodes);
100 let mut label_index = write_txn.open_multimap_table(marsdb_storage::tables::NODE_LABEL_INDEX)?;
101 for label_id in label_ids {
102 label_index.insert(label_id, id)?;
103 }
104 Ok(NodeId(id))
105 }
106
107 pub fn get_node(&self, id: NodeId) -> Result<Option<Node>, GraphError> {
108 let read_txn = self.begin_read()?;
109 Self::get_node_in_txn(Txn::Read(&read_txn), id)
110 }
111
112 pub fn get_node_in_txn(txn: Txn, id: NodeId) -> Result<Option<Node>, GraphError> {
113 let record: Option<NodeRecord> = {
114 let nodes = txn.open_table(marsdb_storage::tables::NODES)?;
115 let found = match nodes.get(id.0)? {
116 Some(guard) => Some(decode(guard.value())?),
117 None => None,
118 };
119 found
120 };
121 let Some(record) = record else { return Ok(None) };
122 let labels = record
123 .label_ids
124 .iter()
125 .map(|&lid| resolve_label(txn, lid))
126 .collect::<Result<Vec<_>, _>>()?;
127 Ok(Some(Node {
128 id,
129 labels,
130 props: record.props,
131 }))
132 }
133
134 pub fn create_edge(
135 &self,
136 label: &str,
137 src: NodeId,
138 dst: NodeId,
139 props: BTreeMap<String, PropertyValue>,
140 ) -> Result<EdgeId, GraphError> {
141 let write_txn = self.begin_write()?;
142 let id = Self::create_edge_in_txn(&write_txn, label, src, dst, props)?;
143 write_txn.commit()?;
144 Ok(id)
145 }
146
147 pub fn create_edge_in_txn(
148 write_txn: &WriteTransaction,
149 label: &str,
150 src: NodeId,
151 dst: NodeId,
152 props: BTreeMap<String, PropertyValue>,
153 ) -> Result<EdgeId, GraphError> {
154 let label_id = intern_label(write_txn, label)?;
155 let id = next_id(write_txn, "next_edge_id")?;
156 let record = EdgeRecord {
157 label_id,
158 src: src.0,
159 dst: dst.0,
160 props,
161 };
162 let bytes = encode(&record)?;
163 let mut edges = write_txn.open_table(marsdb_storage::tables::EDGES)?;
164 edges.insert(id, bytes.as_slice())?;
165
166 let out_entry = AdjEntry {
167 edge_id: EdgeId(id),
168 other: dst,
169 label_id,
170 }
171 .encode();
172 let in_entry = AdjEntry {
173 edge_id: EdgeId(id),
174 other: src,
175 label_id,
176 }
177 .encode();
178 let mut adj_out = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_OUT)?;
179 adj_out.insert(src.0, out_entry.as_slice())?;
180 let mut adj_in = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_IN)?;
181 adj_in.insert(dst.0, in_entry.as_slice())?;
182 Ok(EdgeId(id))
183 }
184
185 pub fn get_edge(&self, id: EdgeId) -> Result<Option<Edge>, GraphError> {
186 let read_txn = self.begin_read()?;
187 Self::get_edge_in_txn(Txn::Read(&read_txn), id)
188 }
189
190 pub fn get_edge_in_txn(txn: Txn, id: EdgeId) -> Result<Option<Edge>, GraphError> {
191 let record: Option<EdgeRecord> = {
192 let edges = txn.open_table(marsdb_storage::tables::EDGES)?;
193 let found = match edges.get(id.0)? {
194 Some(guard) => Some(decode(guard.value())?),
195 None => None,
196 };
197 found
198 };
199 let Some(record) = record else { return Ok(None) };
200 let label = resolve_label(txn, record.label_id)?;
201 Ok(Some(Edge {
202 id,
203 label,
204 src: NodeId(record.src),
205 dst: NodeId(record.dst),
206 props: record.props,
207 }))
208 }
209
210 pub fn neighbors(
213 &self,
214 node: NodeId,
215 dir: Direction,
216 label_filter: Option<&str>,
217 ) -> Result<Vec<AdjEntry>, GraphError> {
218 let read_txn = self.begin_read()?;
219 Self::neighbors_in_txn(Txn::Read(&read_txn), node, dir, label_filter)
220 }
221
222 pub fn neighbors_in_txn(
223 txn: Txn,
224 node: NodeId,
225 dir: Direction,
226 label_filter: Option<&str>,
227 ) -> Result<Vec<AdjEntry>, GraphError> {
228 let label_id_filter = match label_filter {
229 Some(l) => match lookup_label_id(txn, l)? {
230 Some(id) => Some(id),
231 None => return Ok(Vec::new()),
232 },
233 None => None,
234 };
235 let mut result = Vec::new();
236 let table_def = match dir {
237 Direction::Out => marsdb_storage::tables::ADJ_OUT,
238 Direction::In => marsdb_storage::tables::ADJ_IN,
239 };
240 let table = txn.open_multimap_table(table_def)?;
241 for item in table.get(node.0)? {
242 let entry = AdjEntry::decode(item?.value());
243 if label_id_filter.is_none_or(|lid| lid == entry.label_id) {
244 result.push(entry);
245 }
246 }
247 Ok(result)
248 }
249
250 pub fn delete_edge(&self, id: EdgeId) -> Result<bool, GraphError> {
251 let write_txn = self.begin_write()?;
252 let removed = Self::delete_edge_in_txn(&write_txn, id)?;
253 write_txn.commit()?;
254 Ok(removed)
255 }
256
257 pub fn delete_edge_in_txn(write_txn: &WriteTransaction, id: EdgeId) -> Result<bool, GraphError> {
258 let record_bytes: Option<Vec<u8>> = {
259 let mut edges = write_txn.open_table(marsdb_storage::tables::EDGES)?;
260 let removed = edges.remove(id.0)?.map(|guard| guard.value().to_vec());
261 removed
262 };
263 let Some(record_bytes) = record_bytes else {
264 return Ok(false);
265 };
266 let record: EdgeRecord = decode(&record_bytes)?;
267 let out_entry = AdjEntry {
268 edge_id: id,
269 other: NodeId(record.dst),
270 label_id: record.label_id,
271 }
272 .encode();
273 let in_entry = AdjEntry {
274 edge_id: id,
275 other: NodeId(record.src),
276 label_id: record.label_id,
277 }
278 .encode();
279 {
280 let mut adj_out = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_OUT)?;
281 adj_out.remove(record.src, out_entry.as_slice())?;
282 }
283 {
284 let mut adj_in = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_IN)?;
285 adj_in.remove(record.dst, in_entry.as_slice())?;
286 }
287 Ok(true)
288 }
289
290 pub fn delete_node(&self, id: NodeId, detach: bool) -> Result<bool, GraphError> {
293 let write_txn = self.begin_write()?;
294 let existed = Self::delete_node_in_txn(&write_txn, id, detach)?;
295 write_txn.commit()?;
296 Ok(existed)
297 }
298
299 pub fn delete_node_in_txn(write_txn: &WriteTransaction, id: NodeId, detach: bool) -> Result<bool, GraphError> {
300 let mut incident: Vec<EdgeId> = Vec::new();
301 {
302 let adj_out = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_OUT)?;
303 for item in adj_out.get(id.0)? {
304 incident.push(AdjEntry::decode(item?.value()).edge_id);
305 }
306 let adj_in = write_txn.open_multimap_table(marsdb_storage::tables::ADJ_IN)?;
307 for item in adj_in.get(id.0)? {
308 incident.push(AdjEntry::decode(item?.value()).edge_id);
309 }
310 }
311 if !incident.is_empty() && !detach {
312 return Err(GraphError::NodeHasEdges(id));
313 }
314 for edge_id in incident {
315 Self::delete_edge_in_txn(write_txn, edge_id)?;
316 }
317 let removed_bytes: Option<Vec<u8>> = {
318 let mut nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
319 let removed = nodes.remove(id.0)?.map(|guard| guard.value().to_vec());
320 removed
321 };
322 let Some(removed_bytes) = removed_bytes else {
323 return Ok(false);
324 };
325 let record: NodeRecord = decode(&removed_bytes)?;
326 let mut label_index = write_txn.open_multimap_table(marsdb_storage::tables::NODE_LABEL_INDEX)?;
327 for label_id in record.label_ids {
328 label_index.remove(label_id, id.0)?;
329 }
330 Ok(true)
331 }
332
333 pub fn set_node_prop(&self, id: NodeId, key: &str, value: PropertyValue) -> Result<bool, GraphError> {
334 let write_txn = self.begin_write()?;
335 let updated = Self::set_node_prop_in_txn(&write_txn, id, key, value)?;
336 if updated {
337 write_txn.commit()?;
338 } else {
339 write_txn.abort()?;
340 }
341 Ok(updated)
342 }
343
344 pub fn set_node_prop_in_txn(
345 write_txn: &WriteTransaction,
346 id: NodeId,
347 key: &str,
348 value: PropertyValue,
349 ) -> Result<bool, GraphError> {
350 let bytes_opt: Option<Vec<u8>> = {
351 let nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
352 let found = nodes.get(id.0)?.map(|g| g.value().to_vec());
353 found
354 };
355 let Some(bytes) = bytes_opt else { return Ok(false) };
356 let mut record: NodeRecord = decode(&bytes)?;
357 record.props.insert(key.to_string(), value);
358 let new_bytes = encode(&record)?;
359 let mut nodes = write_txn.open_table(marsdb_storage::tables::NODES)?;
360 nodes.insert(id.0, new_bytes.as_slice())?;
361 Ok(true)
362 }
363
364 pub fn set_edge_prop(&self, id: EdgeId, key: &str, value: PropertyValue) -> Result<bool, GraphError> {
365 let write_txn = self.begin_write()?;
366 let updated = Self::set_edge_prop_in_txn(&write_txn, id, key, value)?;
367 if updated {
368 write_txn.commit()?;
369 } else {
370 write_txn.abort()?;
371 }
372 Ok(updated)
373 }
374
375 pub fn set_edge_prop_in_txn(
376 write_txn: &WriteTransaction,
377 id: EdgeId,
378 key: &str,
379 value: PropertyValue,
380 ) -> Result<bool, GraphError> {
381 let bytes_opt: Option<Vec<u8>> = {
382 let edges = write_txn.open_table(marsdb_storage::tables::EDGES)?;
383 let found = edges.get(id.0)?.map(|g| g.value().to_vec());
384 found
385 };
386 let Some(bytes) = bytes_opt else { return Ok(false) };
387 let mut record: EdgeRecord = decode(&bytes)?;
388 record.props.insert(key.to_string(), value);
389 let new_bytes = encode(&record)?;
390 let mut edges = write_txn.open_table(marsdb_storage::tables::EDGES)?;
391 edges.insert(id.0, new_bytes.as_slice())?;
392 Ok(true)
393 }
394
395 pub fn all_nodes(&self, label_filter: Option<&str>) -> Result<Vec<Node>, GraphError> {
398 let read_txn = self.begin_read()?;
399 Self::all_nodes_in_txn(Txn::Read(&read_txn), label_filter)
400 }
401
402 pub fn all_nodes_in_txn(txn: Txn, label_filter: Option<&str>) -> Result<Vec<Node>, GraphError> {
403 let Some(label_filter) = label_filter else {
409 let mut result = Vec::new();
410 let nodes = txn.open_table(marsdb_storage::tables::NODES)?;
411 for item in nodes.iter()? {
412 let (key, value) = item?;
413 let record: NodeRecord = decode(value.value())?;
414 let labels = record
415 .label_ids
416 .iter()
417 .map(|&lid| resolve_label(txn, lid))
418 .collect::<Result<Vec<_>, _>>()?;
419 result.push(Node {
420 id: NodeId(key.value()),
421 labels,
422 props: record.props,
423 });
424 }
425 return Ok(result);
426 };
427 let Some(label_id) = lookup_label_id(txn, label_filter)? else {
428 return Ok(Vec::new());
429 };
430 let node_ids: Vec<u64> = {
431 let label_index = txn.open_multimap_table(marsdb_storage::tables::NODE_LABEL_INDEX)?;
432 let ids: Vec<u64> = label_index
433 .get(label_id)?
434 .map(|item| item.map(|g| g.value()))
435 .collect::<Result<_, _>>()?;
436 ids
437 };
438 let mut result = Vec::with_capacity(node_ids.len());
439 let nodes = txn.open_table(marsdb_storage::tables::NODES)?;
440 for id in node_ids {
441 let guard = nodes
442 .get(id)?
443 .expect("node_label_index entry must reference a live node");
444 let record: NodeRecord = decode(guard.value())?;
445 drop(guard);
446 let labels = record
447 .label_ids
448 .iter()
449 .map(|&lid| resolve_label(txn, lid))
450 .collect::<Result<Vec<_>, _>>()?;
451 result.push(Node {
452 id: NodeId(id),
453 labels,
454 props: record.props,
455 });
456 }
457 Ok(result)
458 }
459}