1use std::collections::HashSet;
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use sbol_db_core::{
11 DomainError, GraphId, GraphRecord, ImportReport, IriString, NeighborhoodQuery,
12 NeighborhoodResult, ObjectId, SbolObjectRecord, SerializationFormat, Triple,
13};
14use sbol_db_derive::{build_import_plan, to_rdf_format};
15use sbol_db_rdf::rdf_graph_to_triples;
16use sbol_db_storage::{
17 AccelSolutions, AcceleratedQuery, BatchSequenceMatch, ClassCount, CorpusCounts, GraphFilter,
18 GraphOverview, GraphStore, GraphTriplesPage, GraphWriteMode, ImportInput, LabStore,
19 ListGraphsFilter, ListObjectsFilter, NeighborhoodStore, ObjectStore, OntologyLoadReport,
20 OntologyRecord, OntologyStore, OntologyTermRecord, PatternObject, PatternSubject, SbolStore,
21 SequenceMatch, SequenceSearchOptions, SequenceSearchStore, TripleChange, TripleSource,
22 TripleWriter, UpdateOutcome,
23};
24use tokio::runtime::Handle;
25
26use crate::pool::db_err;
27use crate::repo::{
28 AccelRepository, GraphRepository, LabRepository, OntologyRepository, SbolObjectRepository,
29 SequenceSearchRepository, TripleRepository,
30};
31use crate::SqlitePool;
32
33const GRAPH_READ_LIMIT: i64 = 5_000_000;
35const UPDATE_SOURCE: &str = "sparql-update";
36
37#[derive(Clone)]
39pub struct SqliteStore {
40 pool: SqlitePool,
41 graphs: GraphRepository,
42 objects: SbolObjectRepository,
43 triples: TripleRepository,
44 accel: AccelRepository,
45 ontology: OntologyRepository,
46 sequences: SequenceSearchRepository,
47 lab: LabRepository,
48}
49
50impl SqliteStore {
51 pub fn new(pool: SqlitePool) -> Self {
52 Self {
53 graphs: GraphRepository::new(pool.clone()),
54 objects: SbolObjectRepository::new(pool.clone()),
55 triples: TripleRepository::new(pool.clone()),
56 accel: AccelRepository::new(pool.clone(), TripleRepository::new(pool.clone())),
57 ontology: OntologyRepository::new(pool.clone()),
58 sequences: SequenceSearchRepository::new(pool.clone()),
59 lab: LabRepository::new(pool.clone()),
60 pool,
61 }
62 }
63
64 pub fn pool(&self) -> &SqlitePool {
65 &self.pool
66 }
67
68 pub async fn ping(&self) -> Result<(), DomainError> {
69 sqlx::query("SELECT 1")
70 .execute(&self.pool)
71 .await
72 .map(|_| ())
73 .map_err(db_err)
74 }
75
76 pub fn triple_source(&self) -> Arc<dyn TripleSource> {
77 Arc::new(SqliteTripleSource {
78 triples: Arc::new(self.triples.clone()),
79 accel: self.accel.clone(),
80 })
81 }
82
83 pub fn triple_writer(&self) -> Arc<dyn TripleWriter> {
84 Arc::new(SqliteTripleWriter {
85 triples: Arc::new(self.triples.clone()),
86 pool: self.pool.clone(),
87 })
88 }
89
90 pub async fn import_document(&self, input: ImportInput) -> Result<ImportReport, DomainError> {
91 let mut tx = self.pool.begin().await.map_err(db_err)?;
92 let report = self.import_into_conn(&mut tx, input).await?;
93 tx.commit().await.map_err(db_err)?;
94 Ok(report)
95 }
96
97 pub async fn import_documents(
98 &self,
99 inputs: Vec<ImportInput>,
100 ) -> Result<Vec<ImportReport>, DomainError> {
101 if inputs.is_empty() {
102 return Ok(Vec::new());
103 }
104 let mut tx = self.pool.begin().await.map_err(db_err)?;
105 let mut reports = Vec::with_capacity(inputs.len());
106 for input in inputs {
107 reports.push(self.import_into_conn(&mut tx, input).await?);
108 }
109 tx.commit().await.map_err(db_err)?;
110 Ok(reports)
111 }
112
113 async fn import_into_conn(
114 &self,
115 conn: &mut sqlx::SqliteConnection,
116 input: ImportInput,
117 ) -> Result<ImportReport, DomainError> {
118 let plan = build_import_plan(&input)?;
119
120 self.graphs
121 .insert(&mut *conn, plan.graph_id, plan.new_graph)
122 .await?;
123 let triple_count = self
124 .triples
125 .insert_triples(&mut *conn, &plan.triples, "sbol")
126 .await?;
127 AccelRepository::mark_dirty(&mut *conn, plan.graph_iri.as_str()).await?;
128 let object_count = plan.summaries.len();
129 for summary in &plan.summaries {
130 self.objects
131 .upsert(&mut *conn, summary, Some(plan.graph_id))
132 .await?;
133 }
134
135 for sequence in &plan.projections.sequences {
138 self.sequences.upsert_sequence(&mut *conn, sequence).await?;
139 }
140
141 Ok(ImportReport {
142 graph_id: plan.graph_id,
143 object_count,
144 triple_count,
145 validation_status: plan.validation_status,
146 validation_issue_count: plan.validation_issue_count,
147 })
148 }
149
150 pub async fn graph_store_write(
151 &self,
152 graph: &str,
153 body: &str,
154 format: SerializationFormat,
155 mode: GraphWriteMode,
156 ) -> Result<usize, DomainError> {
157 let rdf_format = to_rdf_format(format)?;
158 let parsed = sbol_rdf::Graph::parse(body, rdf_format)
159 .map_err(|e| DomainError::Parse(e.to_string()))?;
160 let triples = rdf_graph_to_triples(&parsed, &IriString::unchecked(graph));
161
162 let mut tx = self.pool.begin().await.map_err(db_err)?;
163 self.triples
164 .ensure_graph(&mut tx, graph, "verbatim")
165 .await?;
166 if mode == GraphWriteMode::Replace {
167 self.triples.clear_graph(&mut tx, Some(graph)).await?;
168 }
169 let inserted = self
170 .triples
171 .insert_triples(&mut tx, &triples, "graph-store")
172 .await?;
173 AccelRepository::mark_dirty(&mut tx, graph).await?;
174 tx.commit().await.map_err(db_err)?;
175 Ok(inserted)
176 }
177
178 pub async fn graph_store_clear(&self, graph: &str) -> Result<usize, DomainError> {
179 let mut tx = self.pool.begin().await.map_err(db_err)?;
180 let deleted = self.triples.clear_graph(&mut tx, Some(graph)).await?;
181 self.triples.delete_graph(&mut tx, graph).await?;
182 AccelRepository::mark_dirty(&mut tx, graph).await?;
183 tx.commit().await.map_err(db_err)?;
184 Ok(deleted)
185 }
186
187 pub async fn graph_store_read(&self, graph: &str) -> Result<Vec<Triple>, DomainError> {
188 self.triples
189 .triples_for_graph(Some(graph), GRAPH_READ_LIMIT)
190 .await
191 }
192}
193
194#[derive(Clone)]
199struct SqliteTripleSource {
200 triples: Arc<TripleRepository>,
201 accel: AccelRepository,
202}
203
204impl TripleSource for SqliteTripleSource {
205 fn scan_pattern(
206 &self,
207 subject: Option<&PatternSubject>,
208 predicate: Option<&str>,
209 object: Option<&PatternObject>,
210 graph: Option<&GraphFilter>,
211 limit: i64,
212 ) -> Result<Vec<Triple>, DomainError> {
213 Handle::current().block_on(
214 self.triples
215 .scan_pattern(subject, predicate, object, graph, limit),
216 )
217 }
218
219 fn distinct_named_graphs(&self) -> Result<Vec<String>, DomainError> {
220 Handle::current().block_on(self.triples.distinct_named_graphs())
221 }
222
223 fn triples_for_graph(
224 &self,
225 graph: Option<&str>,
226 limit: i64,
227 ) -> Result<Vec<Triple>, DomainError> {
228 Handle::current().block_on(self.triples.triples_for_graph(graph, limit))
229 }
230
231 fn triples_for_subject(&self, subject_iri: &str) -> Result<Vec<Triple>, DomainError> {
232 Handle::current().block_on(self.triples.triples_for_subject(subject_iri))
233 }
234
235 fn run_accelerated(
236 &self,
237 query: &AcceleratedQuery,
238 ) -> Result<Option<AccelSolutions>, DomainError> {
239 Handle::current().block_on(self.accel.run(query)).map(Some)
240 }
241}
242
243#[derive(Clone)]
246struct SqliteTripleWriter {
247 triples: Arc<TripleRepository>,
248 pool: SqlitePool,
249}
250
251fn touched_named_graphs(changes: &[TripleChange]) -> HashSet<String> {
255 let mut graphs = HashSet::new();
256 for change in changes {
257 match change {
258 TripleChange::Change { deletes, inserts } => {
259 for triple in deletes.iter().chain(inserts) {
260 if let Some(graph) = &triple.graph_iri {
261 graphs.insert(graph.as_str().to_owned());
262 }
263 }
264 }
265 TripleChange::Clear(graph) => {
266 if let Some(graph) = graph {
267 graphs.insert(graph.as_str().to_owned());
268 }
269 }
270 }
271 }
272 graphs
273}
274
275#[async_trait]
276impl TripleWriter for SqliteTripleWriter {
277 async fn apply_update(&self, changes: Vec<TripleChange>) -> Result<UpdateOutcome, DomainError> {
278 let mut outcome = UpdateOutcome::default();
279 let mut tx = self.pool.begin().await.map_err(db_err)?;
280 for change in &changes {
281 match change {
282 TripleChange::Change { deletes, inserts } => {
283 outcome.deleted += self.triples.delete_triples(&mut tx, deletes).await?;
284 let mut ensured = HashSet::new();
285 for triple in inserts {
286 if let Some(graph) = &triple.graph_iri {
287 if ensured.insert(graph.as_str().to_owned()) {
288 self.triples
289 .ensure_graph(&mut tx, graph.as_str(), "verbatim")
290 .await?;
291 }
292 }
293 }
294 outcome.inserted += self
295 .triples
296 .insert_triples(&mut tx, inserts, UPDATE_SOURCE)
297 .await?;
298 }
299 TripleChange::Clear(graph) => {
300 outcome.deleted += self
301 .triples
302 .clear_graph(&mut tx, graph.as_ref().map(|i| i.as_str()))
303 .await?;
304 }
305 }
306 }
307 for graph in touched_named_graphs(&changes) {
308 AccelRepository::mark_dirty(&mut tx, &graph).await?;
309 }
310 tx.commit().await.map_err(db_err)?;
311 Ok(outcome)
312 }
313}
314
315#[async_trait]
316impl ObjectStore for SqliteStore {
317 async fn get_object_by_iri(&self, iri: &str) -> Result<Option<SbolObjectRecord>, DomainError> {
318 self.objects.get_by_iri(iri).await
319 }
320
321 async fn get_objects_by_iris(
322 &self,
323 iris: &[&str],
324 ) -> Result<Vec<SbolObjectRecord>, DomainError> {
325 self.objects.get_by_iris(iris).await
326 }
327
328 async fn list_objects(
329 &self,
330 filter: &ListObjectsFilter,
331 ) -> Result<Vec<SbolObjectRecord>, DomainError> {
332 self.objects.list(filter).await
333 }
334
335 async fn get_object_iri_by_id(&self, id: ObjectId) -> Result<Option<String>, DomainError> {
336 self.objects.get_iri_by_id(id).await
337 }
338}
339
340#[async_trait]
341impl GraphStore for SqliteStore {
342 async fn get_graph(&self, id: GraphId) -> Result<Option<GraphRecord>, DomainError> {
343 self.graphs.get(id).await
344 }
345
346 async fn list_graphs(
347 &self,
348 filter: &ListGraphsFilter,
349 ) -> Result<Vec<GraphRecord>, DomainError> {
350 self.graphs.list(filter).await
351 }
352
353 async fn delete_graph(&self, id: GraphId) -> Result<bool, DomainError> {
354 self.graphs.delete(id).await
355 }
356
357 async fn graph_exists_by_hash(&self, hash: &[u8]) -> Result<bool, DomainError> {
358 self.graphs.exists_by_hash(hash).await
359 }
360}
361
362#[async_trait]
363impl OntologyStore for SqliteStore {
364 async fn load_ontology_from_url(
365 &self,
366 prefix: &str,
367 name: &str,
368 source_url: &str,
369 ) -> Result<OntologyLoadReport, DomainError> {
370 let client = reqwest::Client::builder()
371 .user_agent("sbol-db/0.1 (+https://github.com/marpaia/sbol-db)")
372 .build()
373 .map_err(|e| DomainError::InvalidInput(format!("reqwest client: {e}")))?;
374 let body = client
375 .get(source_url)
376 .send()
377 .await
378 .map_err(|e| DomainError::InvalidInput(format!("fetch {source_url}: {e}")))?
379 .error_for_status()
380 .map_err(|e| DomainError::InvalidInput(format!("HTTP {source_url}: {e}")))?
381 .text()
382 .await
383 .map_err(|e| DomainError::InvalidInput(format!("decode {source_url}: {e}")))?;
384 self.ontology
385 .load_from_text(prefix, name, Some(source_url), &body)
386 .await
387 }
388
389 async fn load_ontology_from_text(
390 &self,
391 prefix: &str,
392 name: &str,
393 source_url: Option<&str>,
394 text: &str,
395 ) -> Result<OntologyLoadReport, DomainError> {
396 self.ontology
397 .load_from_text(prefix, name, source_url, text)
398 .await
399 }
400
401 async fn list_ontologies(&self) -> Result<Vec<OntologyRecord>, DomainError> {
402 self.ontology.list_ontologies().await
403 }
404
405 async fn canonicalize(&self, iri: &str) -> Result<Option<String>, DomainError> {
406 self.ontology.canonicalize(iri).await
407 }
408
409 async fn descendants(&self, iri: &str) -> Result<Vec<(String, i16)>, DomainError> {
410 self.ontology.descendants(iri).await
411 }
412
413 async fn list_ontology_terms(
414 &self,
415 prefix: &str,
416 limit: i64,
417 offset: i64,
418 search: Option<&str>,
419 ) -> Result<(Vec<OntologyTermRecord>, i64), DomainError> {
420 self.ontology
421 .list_terms(prefix, limit, offset, search)
422 .await
423 }
424
425 async fn get_ontology_term(
426 &self,
427 iri: &str,
428 ) -> Result<Option<OntologyTermRecord>, DomainError> {
429 self.ontology.get_term(iri).await
430 }
431}
432
433#[async_trait]
434impl NeighborhoodStore for SqliteStore {
435 async fn walk(&self, query: &NeighborhoodQuery) -> Result<NeighborhoodResult, DomainError> {
436 crate::repo::neighborhood::walk(&self.triples, &self.objects, query).await
437 }
438}
439
440#[async_trait]
441impl SequenceSearchStore for SqliteStore {
442 async fn search(
443 &self,
444 pattern: &str,
445 options: SequenceSearchOptions,
446 ) -> Result<Vec<SequenceMatch>, DomainError> {
447 self.sequences.search(pattern, options).await
448 }
449
450 async fn search_many(
451 &self,
452 patterns: &[String],
453 options: SequenceSearchOptions,
454 ) -> Result<Vec<BatchSequenceMatch>, DomainError> {
455 self.sequences.search_many(patterns, options).await
456 }
457}
458
459#[async_trait]
460impl LabStore for SqliteStore {
461 async fn corpus_counts(&self) -> Result<CorpusCounts, DomainError> {
462 self.lab.corpus_counts().await
463 }
464
465 async fn recent_graphs(&self, limit: i64) -> Result<Vec<GraphOverview>, DomainError> {
466 self.lab.list_graph_overviews(None, limit, 0).await
467 }
468
469 async fn top_classes(&self, limit: i64) -> Result<Vec<ClassCount>, DomainError> {
470 self.lab.top_classes(limit).await
471 }
472
473 async fn count_graphs(&self, kind: Option<&str>) -> Result<i64, DomainError> {
474 self.lab.count_graphs(kind).await
475 }
476
477 async fn list_graph_overviews(
478 &self,
479 kind: Option<&str>,
480 limit: i64,
481 offset: i64,
482 ) -> Result<Vec<GraphOverview>, DomainError> {
483 self.lab.list_graph_overviews(kind, limit, offset).await
484 }
485
486 async fn get_graph_overview(&self, id: GraphId) -> Result<Option<GraphOverview>, DomainError> {
487 self.lab.get_graph_overview(id).await
488 }
489
490 async fn graph_triples(
491 &self,
492 id: GraphId,
493 limit: i64,
494 offset: i64,
495 ) -> Result<Option<GraphTriplesPage>, DomainError> {
496 self.lab.graph_triples(id, limit, offset).await
497 }
498}
499
500#[async_trait]
501impl SbolStore for SqliteStore {
502 async fn import_document(&self, input: ImportInput) -> Result<ImportReport, DomainError> {
503 self.import_document(input).await
504 }
505
506 async fn import_documents(
507 &self,
508 inputs: Vec<ImportInput>,
509 ) -> Result<Vec<ImportReport>, DomainError> {
510 self.import_documents(inputs).await
511 }
512
513 async fn graph_store_write(
514 &self,
515 graph: &str,
516 body: &str,
517 format: SerializationFormat,
518 mode: GraphWriteMode,
519 ) -> Result<usize, DomainError> {
520 self.graph_store_write(graph, body, format, mode).await
521 }
522
523 async fn graph_store_clear(&self, graph: &str) -> Result<usize, DomainError> {
524 self.graph_store_clear(graph).await
525 }
526
527 async fn graph_store_read(&self, graph: &str) -> Result<Vec<Triple>, DomainError> {
528 self.graph_store_read(graph).await
529 }
530
531 async fn triples_for_subject(&self, subject_iri: &str) -> Result<Vec<Triple>, DomainError> {
532 self.triples.triples_for_subject(subject_iri).await
533 }
534
535 async fn ping(&self) -> Result<(), DomainError> {
536 self.ping().await
537 }
538}