1use sbol_db_core::{DomainError, ImportReport, IriString, SerializationFormat, Triple};
2use sbol_db_derive::{build_import_plan, to_rdf_format};
3use sbol_db_rdf::rdf_graph_to_triples;
4
5use crate::repo::{
6 AccelRepository, GraphRepository, LabRepository, NeighborhoodRepository, OntologyRepository,
7 ProjectionEvent, ProjectionEventRepository, SbolObjectRepository, SequenceSearchRepository,
8 TripleRepository, TypedProjectionRepository, ValidationRepository,
9};
10use crate::PgPool;
11
12use sbol_db_storage::{GraphWriteMode, ImportInput};
13
14pub struct SbolObjectService {
15 pool: PgPool,
16 graphs: GraphRepository,
17 objects: SbolObjectRepository,
18 triples: TripleRepository,
19 accel: AccelRepository,
20 validation: ValidationRepository,
21 projection: ProjectionEventRepository,
22 typed: TypedProjectionRepository,
23 neighborhood: NeighborhoodRepository,
24 sequence_search: SequenceSearchRepository,
25 ontology: OntologyRepository,
26 lab: LabRepository,
27}
28
29const GRAPH_READ_LIMIT: i64 = 5_000_000;
33
34impl SbolObjectService {
35 pub fn new(pool: PgPool) -> Self {
36 Self {
37 graphs: GraphRepository::new(pool.clone()),
38 objects: SbolObjectRepository::new(pool.clone()),
39 triples: TripleRepository::new(pool.clone()),
40 accel: AccelRepository::new(pool.clone(), TripleRepository::new(pool.clone())),
41 validation: ValidationRepository::new(pool.clone()),
42 projection: ProjectionEventRepository::new(pool.clone()),
43 typed: TypedProjectionRepository::new(pool.clone()),
44 neighborhood: NeighborhoodRepository::new(pool.clone()),
45 sequence_search: SequenceSearchRepository::new(pool.clone()),
46 ontology: OntologyRepository::new(pool.clone()),
47 lab: LabRepository::new(pool.clone()),
48 pool,
49 }
50 }
51
52 pub fn lab(&self) -> &LabRepository {
53 &self.lab
54 }
55
56 pub fn pool(&self) -> &PgPool {
57 &self.pool
58 }
59
60 pub async fn ping(&self) -> Result<(), DomainError> {
64 sqlx::query("SELECT 1")
65 .execute(&self.pool)
66 .await
67 .map(|_| ())
68 .map_err(|e| DomainError::Database(e.to_string()))
69 }
70
71 pub fn graphs(&self) -> &GraphRepository {
72 &self.graphs
73 }
74
75 pub fn objects(&self) -> &SbolObjectRepository {
76 &self.objects
77 }
78
79 pub fn triples(&self) -> &TripleRepository {
80 &self.triples
81 }
82
83 pub fn accel(&self) -> &AccelRepository {
84 &self.accel
85 }
86
87 pub fn neighborhood(&self) -> &NeighborhoodRepository {
88 &self.neighborhood
89 }
90
91 pub fn sequence_search(&self) -> &SequenceSearchRepository {
92 &self.sequence_search
93 }
94
95 pub fn ontology(&self) -> &OntologyRepository {
96 &self.ontology
97 }
98
99 pub async fn import_documents(
111 &self,
112 inputs: Vec<ImportInput>,
113 ) -> Result<Vec<ImportReport>, DomainError> {
114 if inputs.is_empty() {
115 return Ok(Vec::new());
116 }
117 let mut tx = self.pool.begin().await.map_err(db_err)?;
118 let mut reports = Vec::with_capacity(inputs.len());
119 for input in inputs {
120 reports.push(self.import_into_conn(&mut tx, input).await?);
121 }
122 tx.commit().await.map_err(db_err)?;
123 Ok(reports)
124 }
125
126 pub async fn import_document(&self, input: ImportInput) -> Result<ImportReport, DomainError> {
127 let mut tx = self.pool.begin().await.map_err(db_err)?;
128 let report = self.import_into_conn(&mut tx, input).await?;
129 tx.commit().await.map_err(db_err)?;
130 Ok(report)
131 }
132
133 pub async fn graph_store_write(
147 &self,
148 graph: &str,
149 body: &str,
150 format: SerializationFormat,
151 mode: GraphWriteMode,
152 ) -> Result<usize, DomainError> {
153 let rdf_format = to_rdf_format(format)?;
154 let parsed = sbol_rdf::Graph::parse(body, rdf_format)
155 .map_err(|e| DomainError::Parse(e.to_string()))?;
156 let triples = rdf_graph_to_triples(&parsed, &IriString::unchecked(graph));
157
158 let mut tx = self.pool.begin().await.map_err(db_err)?;
159 self.triples
160 .ensure_graph(&mut tx, graph, "verbatim")
161 .await?;
162 if mode == GraphWriteMode::Replace {
163 self.triples.clear_graph(&mut tx, Some(graph)).await?;
164 }
165 let inserted = self
166 .triples
167 .insert_triples(&mut tx, &triples, "graph-store")
168 .await?;
169 self.accel.refresh_graph(&mut tx, graph).await?;
170 tx.commit().await.map_err(db_err)?;
171 Ok(inserted)
172 }
173
174 pub async fn graph_store_clear(&self, graph: &str) -> Result<usize, DomainError> {
177 let mut tx = self.pool.begin().await.map_err(db_err)?;
178 let deleted = self.triples.clear_graph(&mut tx, Some(graph)).await?;
179 self.triples.delete_graph(&mut tx, graph).await?;
180 self.accel.refresh_graph(&mut tx, graph).await?;
181 tx.commit().await.map_err(db_err)?;
182 Ok(deleted)
183 }
184
185 pub async fn graph_store_read(&self, graph: &str) -> Result<Vec<Triple>, DomainError> {
187 self.triples
188 .triples_for_graph(Some(graph), GRAPH_READ_LIMIT)
189 .await
190 }
191
192 async fn import_into_conn(
193 &self,
194 conn: &mut sqlx::PgConnection,
195 input: ImportInput,
196 ) -> Result<ImportReport, DomainError> {
197 let plan = build_import_plan(&input)?;
198
199 self.graphs
203 .insert(&mut *conn, plan.graph_id, plan.new_graph)
204 .await?;
205
206 let triple_count = self
207 .triples
208 .insert_triples(&mut *conn, &plan.triples, "sbol")
209 .await?;
210 self.accel
211 .refresh_graph(&mut *conn, plan.graph_iri.as_str())
212 .await?;
213
214 let object_count = plan.summaries.len();
215 for summary in &plan.summaries {
216 self.objects
217 .upsert(&mut *conn, summary, Some(plan.graph_id))
218 .await?;
219 }
220 let typed_counts = self.typed.upsert_all(&mut *conn, &plan.projections).await?;
221 let recorded = self
222 .validation
223 .record_run(
224 &mut *conn,
225 &plan.target_iri,
226 Some(plan.graph_id),
227 "sbol-rs",
228 Some(sbol::SPEC_VERSION),
229 "sbol3-3.1.0",
230 &plan.validation,
231 )
232 .await?;
233
234 self.projection
235 .append(
236 &mut *conn,
237 ProjectionEvent {
238 event_type: "document_imported".to_owned(),
239 subject_iri: Some(IriString::unchecked(plan.target_iri.clone())),
240 graph_iri: Some(plan.graph_iri.clone()),
241 payload: serde_json::json!({
242 "graph_id": plan.graph_id.as_uuid(),
243 "object_count": object_count,
244 "triple_count": triple_count,
245 "typed_counts": typed_counts,
246 }),
247 },
248 )
249 .await?;
250
251 Ok(ImportReport {
252 graph_id: plan.graph_id,
253 object_count,
254 triple_count,
255 validation_status: recorded.status,
256 validation_issue_count: recorded.issue_count,
257 })
258 }
259}
260
261fn db_err<E: std::fmt::Display>(e: E) -> DomainError {
262 DomainError::Database(e.to_string())
263}