1use std::sync::Arc;
11use std::time::Duration;
12
13use async_trait::async_trait;
14use sbol_db_core::{
15 DomainError, GraphId, GraphRecord, ImportReport, JobId, NeighborhoodQuery, NeighborhoodResult,
16 ObjectId, SbolObjectRecord, SerializationFormat, Triple,
17};
18use sbol_db_storage::{
19 AccelSolutions, AcceleratedQuery, BatchSequenceMatch, ClassCount, CorpusCounts, EnqueueOutcome,
20 GraphFilter, GraphOverview, GraphStore, GraphTriplesPage, GraphWriteMode, ImportInput,
21 JobAttempt, JobLogRecord, JobQueue, JobStatus, LabStore, ListGraphsFilter, ListJobsFilter,
22 ListObjectsFilter, NeighborhoodStore, NewJob, ObjectStore, OldestQueuedAge, OntologyLoadReport,
23 OntologyRecord, OntologyStore, OntologyTermRecord, PatternObject, PatternSubject,
24 QueueDepthRow, SbolJob, SbolStore, SequenceMatch, SequenceSearchOptions, SequenceSearchStore,
25 TripleChange, TripleSource, TripleWriter, UpdateOutcome,
26};
27use serde_json::Value;
28use tokio::runtime::Handle;
29
30use crate::repo::db_err;
31use crate::{AccelRepository, JobRepository, PgPool, SbolObjectService, TripleRepository};
32
33const UPDATE_SOURCE: &str = "sparql-update";
35
36#[derive(Clone)]
43pub struct PostgresTripleSource {
44 triples: Arc<TripleRepository>,
45 accel: AccelRepository,
46}
47
48impl PostgresTripleSource {
49 pub fn new(triples: Arc<TripleRepository>, accel: AccelRepository) -> Self {
50 Self { triples, accel }
51 }
52}
53
54impl TripleSource for PostgresTripleSource {
55 fn scan_pattern(
56 &self,
57 subject: Option<&PatternSubject>,
58 predicate: Option<&str>,
59 object: Option<&PatternObject>,
60 graph: Option<&GraphFilter>,
61 limit: i64,
62 ) -> Result<Vec<Triple>, DomainError> {
63 Handle::current().block_on(
64 self.triples
65 .scan_pattern(subject, predicate, object, graph, limit),
66 )
67 }
68
69 fn distinct_named_graphs(&self) -> Result<Vec<String>, DomainError> {
70 Handle::current().block_on(self.triples.distinct_named_graphs())
71 }
72
73 fn triples_for_graph(
74 &self,
75 graph: Option<&str>,
76 limit: i64,
77 ) -> Result<Vec<Triple>, DomainError> {
78 Handle::current().block_on(self.triples.triples_for_graph(graph, limit))
79 }
80
81 fn triples_for_subject(&self, subject_iri: &str) -> Result<Vec<Triple>, DomainError> {
82 Handle::current().block_on(self.triples.triples_for_subject(subject_iri))
83 }
84
85 fn run_accelerated(
86 &self,
87 query: &AcceleratedQuery,
88 ) -> Result<Option<AccelSolutions>, DomainError> {
89 Handle::current().block_on(self.accel.run(query)).map(Some)
90 }
91}
92
93#[derive(Clone)]
100pub struct PostgresTripleWriter {
101 triples: Arc<TripleRepository>,
102 accel: AccelRepository,
103 pool: PgPool,
104}
105
106impl PostgresTripleWriter {
107 pub fn new(triples: Arc<TripleRepository>, accel: AccelRepository, pool: PgPool) -> Self {
108 Self {
109 triples,
110 accel,
111 pool,
112 }
113 }
114}
115
116fn touched_named_graphs(changes: &[TripleChange]) -> std::collections::HashSet<String> {
120 let mut graphs = std::collections::HashSet::new();
121 for change in changes {
122 match change {
123 TripleChange::Change { deletes, inserts } => {
124 for triple in deletes.iter().chain(inserts) {
125 if let Some(graph) = &triple.graph_iri {
126 graphs.insert(graph.as_str().to_owned());
127 }
128 }
129 }
130 TripleChange::Clear(graph) => {
131 if let Some(graph) = graph {
132 graphs.insert(graph.as_str().to_owned());
133 }
134 }
135 }
136 }
137 graphs
138}
139
140#[async_trait]
141impl TripleWriter for PostgresTripleWriter {
142 async fn apply_update(&self, changes: Vec<TripleChange>) -> Result<UpdateOutcome, DomainError> {
143 let mut outcome = UpdateOutcome::default();
144 let mut tx = self.pool.begin().await.map_err(db_err)?;
145 for change in &changes {
146 match change {
147 TripleChange::Change { deletes, inserts } => {
148 outcome.deleted += self.triples.delete_triples(&mut tx, deletes).await?;
149 let mut ensured = std::collections::HashSet::new();
150 for triple in inserts {
151 if let Some(graph) = &triple.graph_iri {
152 if ensured.insert(graph.as_str().to_owned()) {
153 self.triples
154 .ensure_graph(&mut tx, graph.as_str(), "verbatim")
155 .await?;
156 }
157 }
158 }
159 outcome.inserted += self
160 .triples
161 .insert_triples(&mut tx, inserts, UPDATE_SOURCE)
162 .await?;
163 }
164 TripleChange::Clear(graph) => {
165 outcome.deleted += self
166 .triples
167 .clear_graph(&mut tx, graph.as_ref().map(|i| i.as_str()))
168 .await?;
169 }
170 }
171 }
172 for graph in touched_named_graphs(&changes) {
173 self.accel.refresh_graph(&mut tx, &graph).await?;
174 }
175 tx.commit().await.map_err(db_err)?;
176 Ok(outcome)
177 }
178}
179
180impl SbolObjectService {
181 pub fn triple_source(&self) -> Arc<dyn TripleSource> {
184 Arc::new(PostgresTripleSource::new(
185 Arc::new(self.triples().clone()),
186 self.accel().clone(),
187 ))
188 }
189
190 pub fn triple_writer(&self) -> Arc<dyn TripleWriter> {
192 Arc::new(PostgresTripleWriter::new(
193 Arc::new(self.triples().clone()),
194 self.accel().clone(),
195 self.pool().clone(),
196 ))
197 }
198}
199
200#[async_trait]
201impl ObjectStore for SbolObjectService {
202 async fn get_object_by_iri(&self, iri: &str) -> Result<Option<SbolObjectRecord>, DomainError> {
203 self.objects().get_by_iri(iri).await
204 }
205
206 async fn get_objects_by_iris(
207 &self,
208 iris: &[&str],
209 ) -> Result<Vec<SbolObjectRecord>, DomainError> {
210 self.objects().get_by_iris(iris).await
211 }
212
213 async fn list_objects(
214 &self,
215 filter: &ListObjectsFilter,
216 ) -> Result<Vec<SbolObjectRecord>, DomainError> {
217 self.objects().list(filter).await
218 }
219
220 async fn get_object_iri_by_id(&self, id: ObjectId) -> Result<Option<String>, DomainError> {
221 self.objects().get_iri_by_id(id).await
222 }
223}
224
225#[async_trait]
226impl GraphStore for SbolObjectService {
227 async fn get_graph(&self, id: GraphId) -> Result<Option<GraphRecord>, DomainError> {
228 self.graphs().get(id).await
229 }
230
231 async fn list_graphs(
232 &self,
233 filter: &ListGraphsFilter,
234 ) -> Result<Vec<GraphRecord>, DomainError> {
235 self.graphs().list(filter).await
236 }
237
238 async fn delete_graph(&self, id: GraphId) -> Result<bool, DomainError> {
239 self.graphs().delete(id).await
240 }
241
242 async fn graph_exists_by_hash(&self, hash: &[u8]) -> Result<bool, DomainError> {
243 self.graphs().exists_by_hash(hash).await
244 }
245}
246
247#[async_trait]
248impl OntologyStore for SbolObjectService {
249 async fn load_ontology_from_url(
250 &self,
251 prefix: &str,
252 name: &str,
253 source_url: &str,
254 ) -> Result<OntologyLoadReport, DomainError> {
255 self.ontology()
256 .load_from_url(prefix, name, source_url)
257 .await
258 }
259
260 async fn load_ontology_from_text(
261 &self,
262 prefix: &str,
263 name: &str,
264 source_url: Option<&str>,
265 text: &str,
266 ) -> Result<OntologyLoadReport, DomainError> {
267 self.ontology()
268 .load_from_text(prefix, name, source_url, text)
269 .await
270 }
271
272 async fn list_ontologies(&self) -> Result<Vec<OntologyRecord>, DomainError> {
273 self.ontology().list_ontologies().await
274 }
275
276 async fn canonicalize(&self, iri: &str) -> Result<Option<String>, DomainError> {
277 self.ontology().canonicalize(iri).await
278 }
279
280 async fn descendants(&self, iri: &str) -> Result<Vec<(String, i16)>, DomainError> {
281 self.ontology().descendants(iri).await
282 }
283
284 async fn list_ontology_terms(
285 &self,
286 prefix: &str,
287 limit: i64,
288 offset: i64,
289 search: Option<&str>,
290 ) -> Result<(Vec<OntologyTermRecord>, i64), DomainError> {
291 self.ontology()
292 .list_terms(prefix, limit, offset, search)
293 .await
294 }
295
296 async fn get_ontology_term(
297 &self,
298 iri: &str,
299 ) -> Result<Option<OntologyTermRecord>, DomainError> {
300 self.ontology().get_term(iri).await
301 }
302}
303
304#[async_trait]
305impl LabStore for SbolObjectService {
306 async fn corpus_counts(&self) -> Result<CorpusCounts, DomainError> {
307 self.lab().corpus_counts().await
308 }
309
310 async fn recent_graphs(&self, limit: i64) -> Result<Vec<GraphOverview>, DomainError> {
311 self.lab().list_graph_overviews(None, limit, 0).await
312 }
313
314 async fn top_classes(&self, limit: i64) -> Result<Vec<ClassCount>, DomainError> {
315 self.lab().top_classes(limit).await
316 }
317
318 async fn count_graphs(&self, kind: Option<&str>) -> Result<i64, DomainError> {
319 self.lab().count_graphs(kind).await
320 }
321
322 async fn list_graph_overviews(
323 &self,
324 kind: Option<&str>,
325 limit: i64,
326 offset: i64,
327 ) -> Result<Vec<GraphOverview>, DomainError> {
328 self.lab().list_graph_overviews(kind, limit, offset).await
329 }
330
331 async fn get_graph_overview(&self, id: GraphId) -> Result<Option<GraphOverview>, DomainError> {
332 self.lab().get_graph_overview(id).await
333 }
334
335 async fn graph_triples(
336 &self,
337 id: GraphId,
338 limit: i64,
339 offset: i64,
340 ) -> Result<Option<GraphTriplesPage>, DomainError> {
341 self.lab().graph_triples(id, limit, offset).await
342 }
343}
344
345#[async_trait]
346impl NeighborhoodStore for SbolObjectService {
347 async fn walk(&self, query: &NeighborhoodQuery) -> Result<NeighborhoodResult, DomainError> {
348 self.neighborhood().walk(query).await
349 }
350}
351
352#[async_trait]
353impl SequenceSearchStore for SbolObjectService {
354 async fn search(
355 &self,
356 pattern: &str,
357 options: SequenceSearchOptions,
358 ) -> Result<Vec<SequenceMatch>, DomainError> {
359 self.sequence_search().search(pattern, options).await
360 }
361
362 async fn search_many(
363 &self,
364 patterns: &[String],
365 options: SequenceSearchOptions,
366 ) -> Result<Vec<BatchSequenceMatch>, DomainError> {
367 self.sequence_search().search_many(patterns, options).await
368 }
369}
370
371#[async_trait]
372impl SbolStore for SbolObjectService {
373 async fn import_document(&self, input: ImportInput) -> Result<ImportReport, DomainError> {
374 self.import_document(input).await
375 }
376
377 async fn import_documents(
378 &self,
379 inputs: Vec<ImportInput>,
380 ) -> Result<Vec<ImportReport>, DomainError> {
381 self.import_documents(inputs).await
382 }
383
384 async fn graph_store_write(
385 &self,
386 graph: &str,
387 body: &str,
388 format: SerializationFormat,
389 mode: GraphWriteMode,
390 ) -> Result<usize, DomainError> {
391 self.graph_store_write(graph, body, format, mode).await
392 }
393
394 async fn graph_store_clear(&self, graph: &str) -> Result<usize, DomainError> {
395 self.graph_store_clear(graph).await
396 }
397
398 async fn graph_store_read(&self, graph: &str) -> Result<Vec<Triple>, DomainError> {
399 self.graph_store_read(graph).await
400 }
401
402 async fn triples_for_subject(&self, subject_iri: &str) -> Result<Vec<Triple>, DomainError> {
403 self.triples().triples_for_subject(subject_iri).await
404 }
405
406 async fn ping(&self) -> Result<(), DomainError> {
407 self.ping().await
408 }
409}
410
411#[async_trait]
412impl JobQueue for JobRepository {
413 async fn enqueue(&self, input: NewJob) -> Result<EnqueueOutcome, DomainError> {
414 self.enqueue(input).await
415 }
416
417 async fn append_log(
418 &self,
419 job_id: JobId,
420 attempt_no: Option<i32>,
421 level: &str,
422 message: &str,
423 fields: Value,
424 ) -> Result<JobLogRecord, DomainError> {
425 self.append_log(job_id, attempt_no, level, message, fields)
426 .await
427 }
428
429 async fn list_logs(
430 &self,
431 id: JobId,
432 after_id: Option<i64>,
433 limit: u32,
434 ) -> Result<Vec<JobLogRecord>, DomainError> {
435 self.list_logs(id, after_id, limit).await
436 }
437
438 async fn dequeue(
439 &self,
440 queues: &[String],
441 worker_id: &str,
442 lease: Duration,
443 ) -> Result<Option<SbolJob>, DomainError> {
444 self.dequeue(queues, worker_id, lease).await
445 }
446
447 async fn renew_lease(
448 &self,
449 job_id: JobId,
450 worker_id: &str,
451 lease: Duration,
452 ) -> Result<bool, DomainError> {
453 self.renew_lease(job_id, worker_id, lease).await
454 }
455
456 async fn mark_succeeded(
457 &self,
458 job_id: JobId,
459 worker_id: &str,
460 result: Option<Value>,
461 ) -> Result<(), DomainError> {
462 self.mark_succeeded(job_id, worker_id, result).await
463 }
464
465 async fn mark_failed(
466 &self,
467 job_id: JobId,
468 worker_id: &str,
469 error: &str,
470 ) -> Result<JobStatus, DomainError> {
471 self.mark_failed(job_id, worker_id, error).await
472 }
473
474 async fn reap_expired_leases(&self) -> Result<u64, DomainError> {
475 self.reap_expired_leases().await
476 }
477
478 async fn get(&self, id: JobId) -> Result<Option<SbolJob>, DomainError> {
479 self.get(id).await
480 }
481
482 async fn list_attempts(&self, id: JobId) -> Result<Vec<JobAttempt>, DomainError> {
483 self.list_attempts(id).await
484 }
485
486 async fn list(&self, filter: &ListJobsFilter) -> Result<Vec<SbolJob>, DomainError> {
487 self.list(filter).await
488 }
489
490 async fn cancel(&self, id: JobId) -> Result<bool, DomainError> {
491 self.cancel(id).await
492 }
493
494 async fn current_status(&self, id: JobId) -> Result<Option<JobStatus>, DomainError> {
495 self.current_status(id).await
496 }
497
498 async fn queue_depth_snapshot(&self) -> Result<Vec<QueueDepthRow>, DomainError> {
499 self.queue_depth_snapshot().await
500 }
501
502 async fn oldest_queued_age(&self) -> Result<Vec<OldestQueuedAge>, DomainError> {
503 self.oldest_queued_age().await
504 }
505
506 async fn recent_failure_count(&self, within_hours: i64) -> Result<i64, DomainError> {
507 self.recent_failure_count(within_hours).await
508 }
509}