1use std::time::Duration;
9
10use async_trait::async_trait;
11use sbol_db_core::{
12 DomainError, GraphId, GraphRecord, ImportReport, JobId, NeighborhoodQuery, NeighborhoodResult,
13 ObjectId, SbolObjectRecord, SerializationFormat, Triple,
14};
15use serde_json::Value;
16
17use crate::{
18 AccelSolutions, AcceleratedQuery, BatchSequenceMatch, EnqueueOutcome, GraphFilter,
19 GraphWriteMode, IdGraphFilter, IdQuad, ImportInput, JobAttempt, JobLogRecord, JobStatus,
20 ListGraphsFilter, ListJobsFilter, ListObjectsFilter, NewJob, OldestQueuedAge,
21 OntologyLoadReport, OntologyRecord, OntologyTermRecord, PatternObject, PatternSubject,
22 QueueDepthRow, SbolJob, SequenceMatch, SequenceSearchOptions, TermId, TermKey, TermValue,
23 TripleChange, UpdateOutcome,
24};
25
26pub trait TripleSource: Send + Sync {
28 fn scan_pattern(
31 &self,
32 subject: Option<&PatternSubject>,
33 predicate: Option<&str>,
34 object: Option<&PatternObject>,
35 graph: Option<&GraphFilter>,
36 limit: i64,
37 ) -> Result<Vec<Triple>, DomainError>;
38
39 fn distinct_named_graphs(&self) -> Result<Vec<String>, DomainError>;
41
42 fn triples_for_graph(
45 &self,
46 graph: Option<&str>,
47 limit: i64,
48 ) -> Result<Vec<Triple>, DomainError>;
49
50 fn triples_for_subject(&self, subject_iri: &str) -> Result<Vec<Triple>, DomainError>;
52
53 fn supports_id_scan(&self) -> bool {
58 false
59 }
60
61 fn id_scan(
65 &self,
66 _subject: Option<TermId>,
67 _predicate: Option<TermId>,
68 _object: Option<TermId>,
69 _graph: &IdGraphFilter,
70 _limit: i64,
71 ) -> Result<Vec<IdQuad>, DomainError> {
72 Err(DomainError::Database(
73 "id_scan is not supported by this backend".into(),
74 ))
75 }
76
77 fn term_to_id(&self, _key: TermKey<'_>) -> Result<TermId, DomainError> {
80 Err(DomainError::Database(
81 "term_to_id is not supported by this backend".into(),
82 ))
83 }
84
85 fn id_to_term(&self, _id: TermId) -> Result<TermValue, DomainError> {
88 Err(DomainError::Database(
89 "id_to_term is not supported by this backend".into(),
90 ))
91 }
92
93 fn run_accelerated(
97 &self,
98 _query: &AcceleratedQuery,
99 ) -> Result<Option<AccelSolutions>, DomainError> {
100 Ok(None)
101 }
102}
103
104#[async_trait]
106pub trait TripleWriter: Send + Sync {
107 async fn apply_update(&self, changes: Vec<TripleChange>) -> Result<UpdateOutcome, DomainError>;
110}
111
112#[async_trait]
114pub trait ObjectStore: Send + Sync {
115 async fn get_object_by_iri(&self, iri: &str) -> Result<Option<SbolObjectRecord>, DomainError>;
116 async fn get_objects_by_iris(
117 &self,
118 iris: &[&str],
119 ) -> Result<Vec<SbolObjectRecord>, DomainError>;
120 async fn list_objects(
121 &self,
122 filter: &ListObjectsFilter,
123 ) -> Result<Vec<SbolObjectRecord>, DomainError>;
124 async fn get_object_iri_by_id(&self, id: ObjectId) -> Result<Option<String>, DomainError>;
125}
126
127#[async_trait]
129pub trait GraphStore: Send + Sync {
130 async fn get_graph(&self, id: GraphId) -> Result<Option<GraphRecord>, DomainError>;
131 async fn list_graphs(&self, filter: &ListGraphsFilter)
132 -> Result<Vec<GraphRecord>, DomainError>;
133 async fn delete_graph(&self, id: GraphId) -> Result<bool, DomainError>;
134 async fn graph_exists_by_hash(&self, hash: &[u8]) -> Result<bool, DomainError>;
135}
136
137#[async_trait]
139pub trait OntologyStore: Send + Sync {
140 async fn load_ontology_from_url(
141 &self,
142 prefix: &str,
143 name: &str,
144 source_url: &str,
145 ) -> Result<OntologyLoadReport, DomainError>;
146 async fn load_ontology_from_text(
147 &self,
148 prefix: &str,
149 name: &str,
150 source_url: Option<&str>,
151 text: &str,
152 ) -> Result<OntologyLoadReport, DomainError>;
153 async fn list_ontologies(&self) -> Result<Vec<OntologyRecord>, DomainError>;
154 async fn canonicalize(&self, iri: &str) -> Result<Option<String>, DomainError>;
155 async fn descendants(&self, iri: &str) -> Result<Vec<(String, i16)>, DomainError>;
156 async fn list_ontology_terms(
157 &self,
158 prefix: &str,
159 limit: i64,
160 offset: i64,
161 search: Option<&str>,
162 ) -> Result<(Vec<OntologyTermRecord>, i64), DomainError>;
163 async fn get_ontology_term(&self, iri: &str)
164 -> Result<Option<OntologyTermRecord>, DomainError>;
165}
166
167#[async_trait]
169pub trait NeighborhoodStore: Send + Sync {
170 async fn walk(&self, query: &NeighborhoodQuery) -> Result<NeighborhoodResult, DomainError>;
171}
172
173#[async_trait]
175pub trait SequenceSearchStore: Send + Sync {
176 async fn search(
177 &self,
178 pattern: &str,
179 options: SequenceSearchOptions,
180 ) -> Result<Vec<SequenceMatch>, DomainError>;
181 async fn search_many(
182 &self,
183 patterns: &[String],
184 options: SequenceSearchOptions,
185 ) -> Result<Vec<BatchSequenceMatch>, DomainError>;
186}
187
188#[async_trait]
190pub trait SbolStore:
191 ObjectStore + GraphStore + OntologyStore + NeighborhoodStore + SequenceSearchStore
192{
193 async fn import_document(&self, input: ImportInput) -> Result<ImportReport, DomainError>;
194 async fn import_documents(
195 &self,
196 inputs: Vec<ImportInput>,
197 ) -> Result<Vec<ImportReport>, DomainError>;
198 async fn graph_store_write(
199 &self,
200 graph: &str,
201 body: &str,
202 format: SerializationFormat,
203 mode: GraphWriteMode,
204 ) -> Result<usize, DomainError>;
205 async fn graph_store_clear(&self, graph: &str) -> Result<usize, DomainError>;
206 async fn graph_store_read(&self, graph: &str) -> Result<Vec<Triple>, DomainError>;
207 async fn triples_for_subject(&self, subject_iri: &str) -> Result<Vec<Triple>, DomainError>;
209 async fn ping(&self) -> Result<(), DomainError>;
210}
211
212#[async_trait]
215pub trait JobQueue: Send + Sync {
216 async fn enqueue(&self, input: NewJob) -> Result<EnqueueOutcome, DomainError>;
217 async fn append_log(
218 &self,
219 job_id: JobId,
220 attempt_no: Option<i32>,
221 level: &str,
222 message: &str,
223 fields: Value,
224 ) -> Result<JobLogRecord, DomainError>;
225 async fn list_logs(
226 &self,
227 id: JobId,
228 after_id: Option<i64>,
229 limit: u32,
230 ) -> Result<Vec<JobLogRecord>, DomainError>;
231 async fn dequeue(
232 &self,
233 queues: &[String],
234 worker_id: &str,
235 lease: Duration,
236 ) -> Result<Option<SbolJob>, DomainError>;
237 async fn renew_lease(
238 &self,
239 job_id: JobId,
240 worker_id: &str,
241 lease: Duration,
242 ) -> Result<bool, DomainError>;
243 async fn mark_succeeded(
244 &self,
245 job_id: JobId,
246 worker_id: &str,
247 result: Option<Value>,
248 ) -> Result<(), DomainError>;
249 async fn mark_failed(
250 &self,
251 job_id: JobId,
252 worker_id: &str,
253 error: &str,
254 ) -> Result<JobStatus, DomainError>;
255 async fn reap_expired_leases(&self) -> Result<u64, DomainError>;
256 async fn get(&self, id: JobId) -> Result<Option<SbolJob>, DomainError>;
257 async fn list_attempts(&self, id: JobId) -> Result<Vec<JobAttempt>, DomainError>;
258 async fn list(&self, filter: &ListJobsFilter) -> Result<Vec<SbolJob>, DomainError>;
259 async fn cancel(&self, id: JobId) -> Result<bool, DomainError>;
260 async fn current_status(&self, id: JobId) -> Result<Option<JobStatus>, DomainError>;
261 async fn queue_depth_snapshot(&self) -> Result<Vec<QueueDepthRow>, DomainError>;
262 async fn oldest_queued_age(&self) -> Result<Vec<OldestQueuedAge>, DomainError>;
263
264 async fn recent_failure_count(&self, within_hours: i64) -> Result<i64, DomainError> {
269 let since = chrono::Utc::now() - chrono::Duration::hours(within_hours.max(0));
270 let mut total = 0i64;
271 for status in [JobStatus::Failed, JobStatus::Dead] {
272 let filter = ListJobsFilter {
273 kind: None,
274 status: Some(status),
275 queue: None,
276 correlation_id: None,
277 since: Some(since),
278 limit: 10_000,
279 };
280 total += self.list(&filter).await?.len() as i64;
281 }
282 Ok(total)
283 }
284}