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