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