1mod access;
15
16use std::collections::{BTreeMap, BTreeSet};
17use std::sync::Arc;
18
19use crate::{ManagedConnection, SQLiteError, SQLiteStorageBackend};
20use uqa_core::{Edge, Value, Vertex};
21use uqa_storage::PersistentStorageBackend;
22
23use uqa_graph::{begin_graph_write, GraphStorage};
24use uqa_graph::{Direction, GraphStore, GraphStoreError, GraphStoreResult, PersistentGraphStore};
25
26const LEGACY_PROPERTIES_FORMAT: i64 = 1;
27const TAGGED_PROPERTIES_FORMAT: i64 = 2;
28
29pub struct SQLiteGraphStore {
31 inner: PersistentGraphStore,
32 backend: Arc<dyn PersistentStorageBackend>,
33 operation_gate: parking_lot::ReentrantMutex<()>,
34}
35
36fn graph_store_error(error: &GraphStoreError) -> SQLiteError {
37 SQLiteError::StorageBackend(error.to_string())
38}
39
40fn sqlite_graph_error(error: &SQLiteError) -> GraphStoreError {
41 GraphStoreError::Storage(error.to_string())
42}
43
44impl SQLiteGraphStore {
45 pub fn open(conn: ManagedConnection, table_name: Option<&str>) -> Result<Self, SQLiteError> {
48 let suffix = table_name.unwrap_or("");
49 if !suffix
50 .chars()
51 .all(|character| character.is_ascii_alphanumeric() || character == '_')
52 {
53 return Err(SQLiteError::StorageBackend(format!(
54 "invalid graph table suffix {suffix:?}"
55 )));
56 }
57 let suffix = if suffix.is_empty() {
58 String::new()
59 } else {
60 format!("_{suffix}")
61 };
62 let backend: Arc<dyn PersistentStorageBackend> =
63 Arc::new(SQLiteStorageBackend::new(conn.clone()));
64 let storage = Arc::new(access::SQLiteGraphStorage {
65 conn,
66 backend: Arc::clone(&backend),
67 vtx_table: format!("_graph_vertices{suffix}"),
68 edge_table: format!("_graph_edges{suffix}"),
69 member_table: format!("_graph_membership{suffix}"),
70 catalog_table: format!("_graph_catalog{suffix}"),
71 metadata_table: format!("_graph_metadata{suffix}"),
72 });
73 let mut inner = PersistentGraphStore::from_storage(storage.clone());
74 let mut checkpoint =
75 begin_graph_write(Arc::clone(&backend)).map_err(|error| graph_store_error(&error))?;
76 let opened = (|| {
77 storage.ensure_tables()?;
78 match storage.metadata("direct_access_version")?.as_deref() {
79 Some("1") => {}
80 None => {
81 for graph in storage
82 .graph_names()
83 .map_err(|error| graph_store_error(&error))?
84 {
85 inner
86 .rebuild_label_registry_from_ids(&graph)
87 .map_err(|error| graph_store_error(&error))?;
88 }
89 storage.save_metadata("direct_access_version", "1")?;
90 }
91 Some(other) => {
92 return Err(SQLiteError::StorageBackend(format!(
93 "unsupported standalone graph access version {other}"
94 )))
95 }
96 }
97 Ok(())
98 })();
99 if let Err(error) = opened {
100 checkpoint
101 .rollback()
102 .map_err(|error| graph_store_error(&error))?;
103 return Err(error);
104 }
105 checkpoint
106 .commit()
107 .map_err(|error| graph_store_error(&error))?;
108 Ok(Self {
109 inner,
110 backend,
111 operation_gate: parking_lot::ReentrantMutex::new(()),
112 })
113 }
114
115 pub fn read_snapshot<T>(
118 &self,
119 read: impl FnOnce(&PersistentGraphStore) -> GraphStoreResult<T>,
120 ) -> GraphStoreResult<T> {
121 struct ReadCheckpoint(Option<Arc<dyn PersistentStorageBackend>>);
122 impl Drop for ReadCheckpoint {
123 fn drop(&mut self) {
124 if let Some(backend) = &self.0 {
125 let _ = backend.rollback_transaction();
126 }
127 }
128 }
129 let _operation = self.operation_gate.lock();
130 if self.backend.in_transaction() {
131 return read(&self.inner);
132 }
133 self.backend.begin_read_transaction()?;
134 let mut checkpoint = ReadCheckpoint(Some(Arc::clone(&self.backend)));
135 let result = read(&self.inner);
136 self.backend.rollback_transaction()?;
137 checkpoint.0 = None;
138 result
139 }
140
141 pub fn as_graph_store(&self) -> &PersistentGraphStore {
143 &self.inner
144 }
145
146 pub fn create_graph(&mut self, name: &str) -> Result<(), SQLiteError> {
147 self.inner
148 .create_graph(name)
149 .map_err(|error| graph_store_error(&error))
150 }
151
152 pub fn drop_graph(&mut self, name: &str) -> Result<(), SQLiteError> {
153 self.inner
154 .drop_graph(name)
155 .map_err(|error| graph_store_error(&error))
156 }
157
158 pub fn graph_names(&self) -> Result<Vec<String>, SQLiteError> {
159 self.read_snapshot(GraphStore::graph_names)
160 .map_err(|error| graph_store_error(&error))
161 }
162
163 pub fn has_graph(&self, name: &str) -> Result<bool, SQLiteError> {
164 self.read_snapshot(|store| store.has_graph(name))
165 .map_err(|error| graph_store_error(&error))
166 }
167
168 pub fn union_graphs(&mut self, g1: &str, g2: &str, target: &str) -> Result<(), SQLiteError> {
169 self.inner
170 .union_graphs(g1, g2, target)
171 .map_err(|error| graph_store_error(&error))
172 }
173
174 pub fn intersect_graphs(
175 &mut self,
176 g1: &str,
177 g2: &str,
178 target: &str,
179 ) -> Result<(), SQLiteError> {
180 self.inner
181 .intersect_graphs(g1, g2, target)
182 .map_err(|error| graph_store_error(&error))
183 }
184
185 pub fn difference_graphs(
186 &mut self,
187 g1: &str,
188 g2: &str,
189 target: &str,
190 ) -> Result<(), SQLiteError> {
191 self.inner
192 .difference_graphs(g1, g2, target)
193 .map_err(|error| graph_store_error(&error))
194 }
195
196 pub fn copy_graph(&mut self, source: &str, target: &str) -> Result<(), SQLiteError> {
197 self.inner
198 .copy_graph(source, target)
199 .map_err(|error| graph_store_error(&error))
200 }
201
202 pub fn add_vertex(&mut self, vertex: Vertex, graph: &str) -> Result<(), SQLiteError> {
203 self.inner
204 .add_vertex(vertex, graph)
205 .map_err(|error| graph_store_error(&error))
206 }
207
208 pub fn add_edge(&mut self, edge: Edge, graph: &str) -> Result<(), SQLiteError> {
209 self.inner
210 .add_edge(edge, graph)
211 .map_err(|error| graph_store_error(&error))
212 }
213
214 pub fn remove_vertex(&mut self, vertex_id: u64, graph: &str) -> Result<(), SQLiteError> {
215 self.inner
216 .remove_vertex(vertex_id, graph)
217 .map_err(|error| graph_store_error(&error))
218 }
219
220 pub fn remove_edge(&mut self, edge_id: u64, graph: &str) -> Result<(), SQLiteError> {
221 self.inner
222 .remove_edge(edge_id, graph)
223 .map_err(|error| graph_store_error(&error))
224 }
225
226 pub fn neighbors(
227 &self,
228 vertex_id: u64,
229 label: Option<&str>,
230 direction: Direction,
231 graph: &str,
232 ) -> Result<Vec<u64>, SQLiteError> {
233 self.read_snapshot(|store| store.neighbors(vertex_id, label, direction, graph))
234 .map_err(|error| graph_store_error(&error))
235 }
236
237 pub fn vertices_by_label(&self, label: &str, graph: &str) -> Result<Vec<Vertex>, SQLiteError> {
238 self.read_snapshot(|store| store.vertices_by_label(label, graph))
239 .map_err(|error| graph_store_error(&error))
240 }
241
242 pub fn vertex_ids_by_label(&self, label: &str, graph: &str) -> Result<Vec<u64>, SQLiteError> {
243 self.read_snapshot(|store| store.vertex_ids_by_label(label, graph))
244 .map_err(|error| graph_store_error(&error))
245 }
246
247 pub fn vertices_in_graph(&self, graph: &str) -> Result<Vec<Vertex>, SQLiteError> {
248 self.read_snapshot(|store| store.vertices_in_graph(graph))
249 .map_err(|error| graph_store_error(&error))
250 }
251
252 pub fn edges_in_graph(&self, graph: &str) -> Result<Vec<Edge>, SQLiteError> {
253 self.read_snapshot(|store| store.edges_in_graph(graph))
254 .map_err(|error| graph_store_error(&error))
255 }
256
257 pub fn vertex_graphs(&self, vertex_id: u64) -> Result<BTreeSet<String>, SQLiteError> {
258 self.read_snapshot(|store| store.vertex_graphs(vertex_id))
259 .map_err(|error| graph_store_error(&error))
260 }
261
262 pub fn out_edge_ids(&self, vertex_id: u64, graph: &str) -> Result<BTreeSet<u64>, SQLiteError> {
263 self.read_snapshot(|store| store.out_edge_ids(vertex_id, graph))
264 .map_err(|error| graph_store_error(&error))
265 }
266
267 pub fn in_edge_ids(&self, vertex_id: u64, graph: &str) -> Result<BTreeSet<u64>, SQLiteError> {
268 self.read_snapshot(|store| store.in_edge_ids(vertex_id, graph))
269 .map_err(|error| graph_store_error(&error))
270 }
271
272 pub fn edge_ids_by_label(
273 &self,
274 label: &str,
275 graph: &str,
276 ) -> Result<BTreeSet<u64>, SQLiteError> {
277 self.read_snapshot(|store| store.edge_ids_by_label(label, graph))
278 .map_err(|error| graph_store_error(&error))
279 }
280
281 pub fn vertex_ids_in_graph(&self, graph: &str) -> Result<BTreeSet<u64>, SQLiteError> {
282 self.read_snapshot(|store| store.vertex_ids_in_graph(graph))
283 .map_err(|error| graph_store_error(&error))
284 }
285
286 pub fn require_vertex_in_graph(&self, vertex_id: u64, graph: &str) -> Result<(), SQLiteError> {
287 self.read_snapshot(|store| store.require_vertex_in_graph(vertex_id, graph))
288 .map_err(|error| graph_store_error(&error))
289 }
290
291 pub fn degree_distribution(&self, graph: &str) -> Result<BTreeMap<u64, u64>, SQLiteError> {
292 self.read_snapshot(|store| store.degree_distribution(graph))
293 .map_err(|error| graph_store_error(&error))
294 }
295
296 pub fn label_degree(&self, label: &str, graph: &str) -> Result<f64, SQLiteError> {
297 self.read_snapshot(|store| store.label_degree(label, graph))
298 .map_err(|error| graph_store_error(&error))
299 }
300
301 pub fn vertex_label_counts(&self, graph: &str) -> Result<BTreeMap<String, u64>, SQLiteError> {
302 self.read_snapshot(|store| store.vertex_label_counts(graph))
303 .map_err(|error| graph_store_error(&error))
304 }
305
306 pub fn get_vertex(&self, vertex_id: u64) -> Result<Option<Vertex>, SQLiteError> {
307 self.read_snapshot(|store| store.get_vertex(vertex_id))
308 .map_err(|error| graph_store_error(&error))
309 }
310
311 pub fn get_edge(&self, edge_id: u64) -> Result<Option<Edge>, SQLiteError> {
312 self.read_snapshot(|store| store.get_edge(edge_id))
313 .map_err(|error| graph_store_error(&error))
314 }
315
316 pub fn next_vertex_id(&mut self) -> Result<u64, SQLiteError> {
317 self.inner
318 .next_vertex_id()
319 .map_err(|error| graph_store_error(&error))
320 }
321
322 pub fn next_edge_id(&mut self) -> Result<u64, SQLiteError> {
323 self.inner
324 .next_edge_id()
325 .map_err(|error| graph_store_error(&error))
326 }
327
328 pub fn allocate_vertex_id(&mut self, label: &str, graph: &str) -> Result<u64, SQLiteError> {
329 self.inner
330 .allocate_vertex_id(label, graph)
331 .map_err(|error| graph_store_error(&error))
332 }
333
334 pub fn allocate_edge_id(&mut self, label: &str, graph: &str) -> Result<u64, SQLiteError> {
335 self.inner
336 .allocate_edge_id(label, graph)
337 .map_err(|error| graph_store_error(&error))
338 }
339
340 pub fn clear(&mut self) -> Result<(), SQLiteError> {
341 self.inner
342 .clear()
343 .map_err(|error| graph_store_error(&error))
344 }
345
346 pub fn vertices(&self) -> Result<BTreeMap<u64, Vertex>, SQLiteError> {
347 self.read_snapshot(GraphStore::vertices)
348 .map_err(|error| graph_store_error(&error))
349 }
350
351 pub fn edges(&self) -> Result<BTreeMap<u64, Edge>, SQLiteError> {
352 self.read_snapshot(GraphStore::edges)
353 .map_err(|error| graph_store_error(&error))
354 }
355}
356
357impl GraphStore for SQLiteGraphStore {
358 fn vertex_id_page(
359 &self,
360 graph: &str,
361 after: Option<u64>,
362 limit: usize,
363 ) -> GraphStoreResult<Vec<u64>> {
364 self.read_snapshot(|store| store.vertex_id_page(graph, after, limit))
365 }
366 fn edge_id_page(
367 &self,
368 graph: &str,
369 after: Option<u64>,
370 limit: usize,
371 ) -> GraphStoreResult<Vec<u64>> {
372 self.read_snapshot(|store| store.edge_id_page(graph, after, limit))
373 }
374 fn transaction<T>(
375 &mut self,
376 operation: impl FnOnce(&mut Self) -> GraphStoreResult<T>,
377 ) -> GraphStoreResult<T> {
378 let mut checkpoint = self.inner.clone();
379 checkpoint.transaction(|_| operation(self))
380 }
381
382 fn create_graph(&mut self, name: &str) -> GraphStoreResult<()> {
383 self.inner.create_graph(name)
384 }
385
386 fn drop_graph(&mut self, name: &str) -> GraphStoreResult<()> {
387 self.inner.drop_graph(name)
388 }
389
390 fn graph_names(&self) -> GraphStoreResult<Vec<String>> {
391 self.read_snapshot(GraphStore::graph_names)
392 }
393
394 fn has_graph(&self, name: &str) -> GraphStoreResult<bool> {
395 self.read_snapshot(|store| store.has_graph(name))
396 }
397
398 fn union_graphs(&mut self, g1: &str, g2: &str, target: &str) -> GraphStoreResult<()> {
399 self.inner.union_graphs(g1, g2, target)
400 }
401
402 fn intersect_graphs(&mut self, g1: &str, g2: &str, target: &str) -> GraphStoreResult<()> {
403 self.inner.intersect_graphs(g1, g2, target)
404 }
405
406 fn difference_graphs(&mut self, g1: &str, g2: &str, target: &str) -> GraphStoreResult<()> {
407 self.inner.difference_graphs(g1, g2, target)
408 }
409
410 fn copy_graph(&mut self, source: &str, target: &str) -> GraphStoreResult<()> {
411 self.inner.copy_graph(source, target)
412 }
413
414 fn add_vertex(&mut self, vertex: Vertex, graph: &str) -> GraphStoreResult<()> {
415 self.inner.add_vertex(vertex, graph)
416 }
417
418 fn add_edge(&mut self, edge: Edge, graph: &str) -> GraphStoreResult<()> {
419 self.inner.add_edge(edge, graph)
420 }
421
422 fn remove_vertex(&mut self, vertex_id: u64, graph: &str) -> GraphStoreResult<()> {
423 self.inner.remove_vertex(vertex_id, graph)
424 }
425
426 fn remove_edge(&mut self, edge_id: u64, graph: &str) -> GraphStoreResult<()> {
427 self.inner.remove_edge(edge_id, graph)
428 }
429
430 fn neighbors(
431 &self,
432 vertex_id: u64,
433 label: Option<&str>,
434 direction: Direction,
435 graph: &str,
436 ) -> GraphStoreResult<Vec<u64>> {
437 self.read_snapshot(|store| store.neighbors(vertex_id, label, direction, graph))
438 }
439
440 fn vertices_by_label(&self, label: &str, graph: &str) -> GraphStoreResult<Vec<Vertex>> {
441 self.read_snapshot(|store| store.vertices_by_label(label, graph))
442 }
443
444 fn vertex_ids_by_label(&self, label: &str, graph: &str) -> GraphStoreResult<Vec<u64>> {
445 self.read_snapshot(|store| store.vertex_ids_by_label(label, graph))
446 }
447
448 fn vertices_in_graph(&self, graph: &str) -> GraphStoreResult<Vec<Vertex>> {
449 self.read_snapshot(|store| store.vertices_in_graph(graph))
450 }
451
452 fn edges_in_graph(&self, graph: &str) -> GraphStoreResult<Vec<Edge>> {
453 self.read_snapshot(|store| store.edges_in_graph(graph))
454 }
455
456 fn vertex_graphs(&self, vertex_id: u64) -> GraphStoreResult<BTreeSet<String>> {
457 self.read_snapshot(|store| store.vertex_graphs(vertex_id))
458 }
459 fn edge_graphs(&self, edge_id: u64) -> GraphStoreResult<BTreeSet<String>> {
460 self.read_snapshot(|store| store.edge_graphs(edge_id))
461 }
462 fn edges_by_label(&self, label: &str, graph: &str) -> GraphStoreResult<Vec<Edge>> {
463 self.read_snapshot(|store| store.edges_by_label(label, graph))
464 }
465
466 fn out_edge_ids(&self, vertex_id: u64, graph: &str) -> GraphStoreResult<BTreeSet<u64>> {
467 self.read_snapshot(|store| store.out_edge_ids(vertex_id, graph))
468 }
469
470 fn in_edge_ids(&self, vertex_id: u64, graph: &str) -> GraphStoreResult<BTreeSet<u64>> {
471 self.read_snapshot(|store| store.in_edge_ids(vertex_id, graph))
472 }
473
474 fn edge_ids_by_label(&self, label: &str, graph: &str) -> GraphStoreResult<BTreeSet<u64>> {
475 self.read_snapshot(|store| store.edge_ids_by_label(label, graph))
476 }
477
478 fn vertex_ids_in_graph(&self, graph: &str) -> GraphStoreResult<BTreeSet<u64>> {
479 self.read_snapshot(|store| store.vertex_ids_in_graph(graph))
480 }
481
482 fn require_vertex_in_graph(&self, vertex_id: u64, graph: &str) -> GraphStoreResult<()> {
483 self.read_snapshot(|store| store.require_vertex_in_graph(vertex_id, graph))
484 }
485
486 fn degree_distribution(&self, graph: &str) -> GraphStoreResult<BTreeMap<u64, u64>> {
487 self.read_snapshot(|store| store.degree_distribution(graph))
488 }
489
490 fn label_degree(&self, label: &str, graph: &str) -> GraphStoreResult<f64> {
491 self.read_snapshot(|store| store.label_degree(label, graph))
492 }
493
494 fn vertex_label_counts(&self, graph: &str) -> GraphStoreResult<BTreeMap<String, u64>> {
495 self.read_snapshot(|store| store.vertex_label_counts(graph))
496 }
497
498 fn get_vertex(&self, vertex_id: u64) -> GraphStoreResult<Option<Vertex>> {
499 self.read_snapshot(|store| store.get_vertex(vertex_id))
500 }
501
502 fn get_edge(&self, edge_id: u64) -> GraphStoreResult<Option<Edge>> {
503 self.read_snapshot(|store| store.get_edge(edge_id))
504 }
505
506 fn next_vertex_id(&mut self) -> GraphStoreResult<u64> {
507 self.inner.next_vertex_id()
508 }
509
510 fn next_edge_id(&mut self) -> GraphStoreResult<u64> {
511 self.inner.next_edge_id()
512 }
513
514 fn allocate_vertex_id(&mut self, label: &str, graph: &str) -> GraphStoreResult<u64> {
515 self.inner.allocate_vertex_id(label, graph)
516 }
517
518 fn allocate_edge_id(&mut self, label: &str, graph: &str) -> GraphStoreResult<u64> {
519 self.inner.allocate_edge_id(label, graph)
520 }
521
522 fn clear(&mut self) -> GraphStoreResult<()> {
523 self.inner.clear()
524 }
525
526 fn vertices(&self) -> GraphStoreResult<BTreeMap<u64, Vertex>> {
527 self.read_snapshot(GraphStore::vertices)
528 }
529
530 fn edges(&self) -> GraphStoreResult<BTreeMap<u64, Edge>> {
531 self.read_snapshot(GraphStore::edges)
532 }
533}
534
535fn decode_graph_id(kind: &str, id: i64) -> Result<u64, SQLiteError> {
536 u64::try_from(id).map_err(|_| {
537 SQLiteError::StorageBackend(format!("invalid negative {kind} id {id} in graph store"))
538 })
539}
540
541fn decode_properties(
542 properties_json: &str,
543 properties_format: i64,
544) -> Result<BTreeMap<String, Value>, SQLiteError> {
545 match properties_format {
546 LEGACY_PROPERTIES_FORMAT => {
547 let raw: BTreeMap<String, serde_json::Value> =
548 serde_json::from_str(properties_json).map_err(SQLiteError::from)?;
549 raw.into_iter()
550 .map(|(key, value)| decode_legacy_value(value).map(|value| (key, value)))
551 .collect()
552 }
553 TAGGED_PROPERTIES_FORMAT => {
554 serde_json::from_str(properties_json).map_err(SQLiteError::from)
555 }
556 other => Err(SQLiteError::StorageBackend(format!(
557 "unsupported graph properties format version {other}"
558 ))),
559 }
560}
561
562fn decode_legacy_value(raw: serde_json::Value) -> Result<Value, SQLiteError> {
568 match raw {
569 serde_json::Value::Array(items) => {
570 let bytes = items
571 .iter()
572 .map(|item| item.as_u64().and_then(|number| u8::try_from(number).ok()))
573 .collect::<Option<Vec<_>>>();
574 if let Some(bytes) = bytes {
575 return Ok(Value::Bytes(bytes));
576 }
577 items
578 .into_iter()
579 .map(decode_legacy_value)
580 .collect::<Result<Vec<_>, _>>()
581 .map(Value::List)
582 }
583 serde_json::Value::Object(map) => {
584 if map
585 .get("$uqa_type")
586 .and_then(serde_json::Value::as_str)
587 .is_some()
588 {
589 let tagged: Value = serde_json::from_value(serde_json::Value::Object(map.clone()))
590 .map_err(SQLiteError::from)?;
591 if !matches!(tagged, Value::Map(_)) {
592 return Ok(tagged);
593 }
594 }
595 map.into_iter()
596 .map(|(key, value)| decode_legacy_value(value).map(|value| (key, value)))
597 .collect::<Result<BTreeMap<_, _>, _>>()
598 .map(Value::Map)
599 }
600 scalar => serde_json::from_value(scalar).map_err(SQLiteError::from),
601 }
602}
603
604fn encode_graph_id(kind: &str, id: u64) -> Result<i64, SQLiteError> {
605 i64::try_from(id).map_err(|_| {
606 SQLiteError::StorageBackend(format!("{kind} id {id} exceeds SQLite INTEGER range"))
607 })
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613 use uqa_core::{Value, Vertex};
614
615 #[test]
616 fn round_trip_through_sqlite() {
617 let conn = ManagedConnection::open_in_memory().unwrap();
618 let mut store = SQLiteGraphStore::open(conn.clone(), None).unwrap();
619 store.create_graph("g").unwrap();
620 store.add_vertex(Vertex::new(1, "person"), "g").unwrap();
621 store.add_vertex(Vertex::new(2, "person"), "g").unwrap();
622 store.add_edge(Edge::new(1, 1, 2, "knows"), "g").unwrap();
623 let other = SQLiteGraphStore::open(conn, None).unwrap();
624 assert!(other.has_graph("g").unwrap());
625 let vs = other.vertices_in_graph("g").unwrap();
626 assert_eq!(vs.len(), 2);
627 let es = other.edges_in_graph("g").unwrap();
628 assert_eq!(es.len(), 1);
629 }
630
631 #[test]
632 fn legacy_raw_bytes_and_edge_first_memberships_survive_reopen() {
633 let conn = ManagedConnection::open_in_memory().unwrap();
634 conn.with(|connection| {
635 connection.execute_batch(
638 r#"
639 CREATE TABLE _graph_vertices (
640 vertex_id INTEGER PRIMARY KEY,
641 label TEXT NOT NULL DEFAULT '',
642 properties_json TEXT NOT NULL
643 );
644 CREATE TABLE _graph_edges (
645 edge_id INTEGER PRIMARY KEY,
646 source_id INTEGER NOT NULL,
647 target_id INTEGER NOT NULL,
648 label TEXT NOT NULL,
649 properties_json TEXT NOT NULL
650 );
651 CREATE TABLE _graph_membership (
652 graph TEXT NOT NULL,
653 entity_kind TEXT NOT NULL,
654 entity_id INTEGER NOT NULL,
655 PRIMARY KEY (graph, entity_kind, entity_id)
656 );
657 CREATE TABLE _graph_catalog (name TEXT PRIMARY KEY);
658 INSERT INTO _graph_catalog (name) VALUES ('g');
659 INSERT INTO _graph_vertices
660 (vertex_id, label, properties_json)
661 VALUES
662 (1, 'person', '{"bytes":[1,2],"nested":[[3,4]],"list":[256]}'),
663 (2, 'person', '{}');
664 INSERT INTO _graph_edges
665 (edge_id, source_id, target_id, label, properties_json)
666 VALUES (10, 1, 2, 'knows', '{"bytes":[5,6]}');
667 INSERT INTO _graph_membership
668 (graph, entity_kind, entity_id) VALUES ('g', 'e', 10);
669 INSERT INTO _graph_membership
670 (graph, entity_kind, entity_id) VALUES ('g', 'v', 1);
671 INSERT INTO _graph_membership
672 (graph, entity_kind, entity_id) VALUES ('g', 'v', 2);
673 "#,
674 )?;
675 Ok(())
676 })
677 .unwrap();
678
679 let reopened = SQLiteGraphStore::open(conn, None).unwrap();
680 let vertex = reopened.get_vertex(1).unwrap().unwrap();
681 assert_eq!(vertex.properties["bytes"], Value::Bytes(vec![1, 2]));
682 assert_eq!(
683 vertex.properties["nested"],
684 Value::List(vec![Value::Bytes(vec![3, 4])])
685 );
686 assert_eq!(
687 vertex.properties["list"],
688 Value::List(vec![Value::Int(256)])
689 );
690 assert_eq!(
691 reopened.get_edge(10).unwrap().unwrap().properties["bytes"],
692 Value::Bytes(vec![5, 6])
693 );
694 }
695
696 #[test]
697 fn list_and_explicit_bytes_properties_remain_distinct_after_reopen() {
698 let conn = ManagedConnection::open_in_memory().unwrap();
699 let mut store = SQLiteGraphStore::open(conn.clone(), None).unwrap();
700 store.create_graph("g").unwrap();
701 let mut vertex = Vertex::new(1, "payload");
702 vertex.properties.insert(
703 "list".into(),
704 Value::List(vec![Value::Int(1), Value::Int(2)]),
705 );
706 vertex
707 .properties
708 .insert("bytes".into(), Value::Bytes(vec![1, 2]));
709 store.add_vertex(vertex, "g").unwrap();
710 drop(store);
711
712 let reopened = SQLiteGraphStore::open(conn, None).unwrap();
713 let restored = reopened.get_vertex(1).unwrap().unwrap();
714 assert_eq!(
715 restored.properties["list"],
716 Value::List(vec![Value::Int(1), Value::Int(2)])
717 );
718 assert_eq!(restored.properties["bytes"], Value::Bytes(vec![1, 2]));
719 }
720
721 #[test]
722 fn failed_persistence_does_not_publish_partial_disk_state() {
723 let conn = ManagedConnection::open_in_memory().unwrap();
724 let mut store = SQLiteGraphStore::open(conn.clone(), None).unwrap();
725 store.create_graph("g").unwrap();
726 store.add_vertex(Vertex::new(1, "person"), "g").unwrap();
727 conn.with(|connection| {
728 connection.execute_batch(
729 r#"
730 CREATE TRIGGER fail_graph_membership
731 BEFORE INSERT ON "_graph_membership"
732 WHEN NEW.entity_id = 2
733 BEGIN
734 SELECT RAISE(ABORT, 'forced graph persistence failure');
735 END;
736 "#,
737 )?;
738 Ok(())
739 })
740 .unwrap();
741
742 assert!(store.add_vertex(Vertex::new(2, "person"), "g").is_err());
743 assert!(store.get_vertex(1).unwrap().is_some());
744 assert!(store.get_vertex(2).unwrap().is_none());
745
746 conn.with(|connection| {
747 connection.execute_batch("DROP TRIGGER fail_graph_membership")?;
748 Ok(())
749 })
750 .unwrap();
751 let reopened = SQLiteGraphStore::open(conn, None).unwrap();
752 assert_eq!(reopened.vertices_in_graph("g").unwrap().len(), 1);
753 assert!(reopened.get_vertex(2).unwrap().is_none());
754 }
755
756 #[test]
757 fn unrelated_corrupt_payload_is_not_loaded_until_queried() {
758 let conn = ManagedConnection::open_in_memory().unwrap();
759 let mut store = SQLiteGraphStore::open(conn.clone(), None).unwrap();
760 store.create_graph("g").unwrap();
761 store.add_vertex(Vertex::new(1, "person"), "g").unwrap();
762 conn.with(|connection| {
763 connection.execute(
764 "UPDATE _graph_vertices SET properties_json = '{' WHERE vertex_id = 1",
765 [],
766 )?;
767 Ok(())
768 })
769 .unwrap();
770
771 store.add_vertex(Vertex::new(2, "person"), "g").unwrap();
772 let reopened = SQLiteGraphStore::open(conn, None).unwrap();
773 assert!(reopened.get_vertex(2).unwrap().is_some());
774 assert!(reopened.get_vertex(1).is_err());
775 }
776
777 #[test]
778 fn allocated_label_sequence_survives_reopen() {
779 let conn = ManagedConnection::open_in_memory().unwrap();
780 let mut store = SQLiteGraphStore::open(conn.clone(), None).unwrap();
781 store.create_graph("g").unwrap();
782 let first = store.allocate_vertex_id("person", "g").unwrap();
783 drop(store);
784
785 let mut reopened = SQLiteGraphStore::open(conn, None).unwrap();
786 let second = reopened.allocate_vertex_id("person", "g").unwrap();
787 assert_ne!(first, second);
788 assert!(second > first);
789 }
790}