1use sbol_db_core::{
8 ComponentProjection, ConstraintProjection, DomainError, FeatureProjection,
9 InteractionProjection, IriString, LocationProjection, ParticipationProjection,
10 SequenceProjection, TypedProjections,
11};
12
13use crate::repo::db_err;
14use crate::PgPool;
15
16#[derive(Clone)]
17pub struct TypedProjectionRepository {
18 _pool: PgPool,
19}
20
21impl TypedProjectionRepository {
22 pub fn new(pool: PgPool) -> Self {
23 Self { _pool: pool }
24 }
25
26 pub async fn upsert_all(
29 &self,
30 conn: &mut sqlx::PgConnection,
31 projections: &TypedProjections,
32 ) -> Result<TypedProjectionCounts, DomainError> {
33 let mut counts = TypedProjectionCounts::default();
34 for c in &projections.components {
35 upsert_component(conn, c).await?;
36 counts.components += 1;
37 }
38 for s in &projections.sequences {
39 upsert_sequence(conn, s).await?;
40 counts.sequences += 1;
41 }
42 for f in &projections.features {
43 upsert_feature(conn, f).await?;
44 counts.features += 1;
45 }
46 for l in &projections.locations {
47 upsert_location(conn, l).await?;
48 counts.locations += 1;
49 }
50 for c in &projections.constraints {
51 upsert_constraint(conn, c).await?;
52 counts.constraints += 1;
53 }
54 for i in &projections.interactions {
55 upsert_interaction(conn, i).await?;
56 counts.interactions += 1;
57 }
58 for p in &projections.participations {
59 upsert_participation(conn, p).await?;
60 counts.participations += 1;
61 }
62 Ok(counts)
63 }
64}
65
66#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
67pub struct TypedProjectionCounts {
68 pub components: usize,
69 pub sequences: usize,
70 pub features: usize,
71 pub locations: usize,
72 pub constraints: usize,
73 pub interactions: usize,
74 pub participations: usize,
75}
76
77fn as_str_opt(value: &Option<IriString>) -> Option<&str> {
78 value.as_ref().map(|i| i.as_str())
79}
80
81async fn upsert_component(
82 conn: &mut sqlx::PgConnection,
83 p: &ComponentProjection,
84) -> Result<(), DomainError> {
85 sqlx::query(
86 r#"
87 INSERT INTO sbol_components (
88 object_id, iri, types, roles, sequence_iris, feature_iris,
89 interaction_iris, model_iris
90 )
91 SELECT id, $1,
92 $2::text[]::sbol_ontology_term[],
93 $3::text[]::sbol_ontology_term[],
94 $4::text[]::sbol_iri[],
95 $5::text[]::sbol_iri[],
96 $6::text[]::sbol_iri[],
97 $7::text[]::sbol_iri[]
98 FROM sbol_objects WHERE iri = $1
99 ON CONFLICT (object_id) DO UPDATE SET
100 types = EXCLUDED.types,
101 roles = EXCLUDED.roles,
102 sequence_iris = EXCLUDED.sequence_iris,
103 feature_iris = EXCLUDED.feature_iris,
104 interaction_iris = EXCLUDED.interaction_iris,
105 model_iris = EXCLUDED.model_iris,
106 updated_at = now()
107 "#,
108 )
109 .bind(p.iri.as_str())
110 .bind(&p.types)
111 .bind(&p.roles)
112 .bind(&p.sequence_iris)
113 .bind(&p.feature_iris)
114 .bind(&p.interaction_iris)
115 .bind(&p.model_iris)
116 .execute(&mut *conn)
117 .await
118 .map_err(db_err)?;
119 Ok(())
120}
121
122async fn upsert_sequence(
123 conn: &mut sqlx::PgConnection,
124 p: &SequenceProjection,
125) -> Result<(), DomainError> {
126 let alphabet_str = p.alphabet.map(|a| a.as_db_str());
127 let row = sqlx::query(
128 r#"
129 INSERT INTO sbol_sequences (
130 object_id, iri, encoding_iri, elements, alphabet, content_hash
131 )
132 SELECT id, $1, $2, $3, $4, $5
133 FROM sbol_objects WHERE iri = $1
134 ON CONFLICT (object_id) DO UPDATE SET
135 encoding_iri = EXCLUDED.encoding_iri,
136 elements = EXCLUDED.elements,
137 alphabet = EXCLUDED.alphabet,
138 content_hash = EXCLUDED.content_hash
139 RETURNING object_id
140 "#,
141 )
142 .bind(p.iri.as_str())
143 .bind(as_str_opt(&p.encoding_iri))
144 .bind(p.elements.as_deref())
145 .bind(alphabet_str)
146 .bind(p.content_hash.as_deref())
147 .fetch_optional(&mut *conn)
148 .await
149 .map_err(db_err)?;
150 if let Some(row) = row {
151 use sqlx::Row;
152 let object_id: uuid::Uuid = row.try_get("object_id").map_err(db_err)?;
153 crate::repo::sequence_search::reindex_kmers(
154 conn,
155 object_id,
156 p.elements.as_deref(),
157 alphabet_str,
158 )
159 .await?;
160 }
161 Ok(())
162}
163
164async fn upsert_feature(
165 conn: &mut sqlx::PgConnection,
166 p: &FeatureProjection,
167) -> Result<(), DomainError> {
168 sqlx::query(
169 r#"
170 INSERT INTO sbol_features (
171 object_id, iri, parent_component_iri, feature_kind,
172 instance_of_iri, roles, orientation_iri
173 )
174 SELECT id, $1, $2, $3, $4, $5::text[]::sbol_ontology_term[], $6
175 FROM sbol_objects WHERE iri = $1
176 ON CONFLICT (object_id) DO UPDATE SET
177 parent_component_iri = EXCLUDED.parent_component_iri,
178 feature_kind = EXCLUDED.feature_kind,
179 instance_of_iri = EXCLUDED.instance_of_iri,
180 roles = EXCLUDED.roles,
181 orientation_iri = EXCLUDED.orientation_iri
182 "#,
183 )
184 .bind(p.iri.as_str())
185 .bind(as_str_opt(&p.parent_component_iri))
186 .bind(&p.feature_kind)
187 .bind(as_str_opt(&p.instance_of_iri))
188 .bind(&p.roles)
189 .bind(as_str_opt(&p.orientation_iri))
190 .execute(&mut *conn)
191 .await
192 .map_err(db_err)?;
193 Ok(())
194}
195
196async fn upsert_location(
197 conn: &mut sqlx::PgConnection,
198 p: &LocationProjection,
199) -> Result<(), DomainError> {
200 sqlx::query(
201 r#"
202 INSERT INTO sbol_locations (
203 object_id, iri, feature_iri, sequence_iri, location_kind,
204 start_pos, end_pos, cut_pos, orientation_iri, data
205 )
206 SELECT id, $1, $2, $3, $4, $5, $6, $7, $8, $9
207 FROM sbol_objects WHERE iri = $1
208 ON CONFLICT (object_id) DO UPDATE SET
209 feature_iri = EXCLUDED.feature_iri,
210 sequence_iri = EXCLUDED.sequence_iri,
211 location_kind = EXCLUDED.location_kind,
212 start_pos = EXCLUDED.start_pos,
213 end_pos = EXCLUDED.end_pos,
214 cut_pos = EXCLUDED.cut_pos,
215 orientation_iri = EXCLUDED.orientation_iri,
216 data = EXCLUDED.data
217 "#,
218 )
219 .bind(p.iri.as_str())
220 .bind(as_str_opt(&p.feature_iri))
221 .bind(as_str_opt(&p.sequence_iri))
222 .bind(&p.location_kind)
223 .bind(p.start_pos)
224 .bind(p.end_pos)
225 .bind(p.cut_pos)
226 .bind(as_str_opt(&p.orientation_iri))
227 .bind(&p.data)
228 .execute(&mut *conn)
229 .await
230 .map_err(db_err)?;
231 Ok(())
232}
233
234async fn upsert_constraint(
235 conn: &mut sqlx::PgConnection,
236 p: &ConstraintProjection,
237) -> Result<(), DomainError> {
238 sqlx::query(
239 r#"
240 INSERT INTO sbol_constraints (
241 object_id, iri, parent_component_iri, restriction_iri,
242 subject_iri, object_iri
243 )
244 SELECT id, $1, $2, $3, $4, $5
245 FROM sbol_objects WHERE iri = $1
246 ON CONFLICT (object_id) DO UPDATE SET
247 parent_component_iri = EXCLUDED.parent_component_iri,
248 restriction_iri = EXCLUDED.restriction_iri,
249 subject_iri = EXCLUDED.subject_iri,
250 object_iri = EXCLUDED.object_iri
251 "#,
252 )
253 .bind(p.iri.as_str())
254 .bind(as_str_opt(&p.parent_component_iri))
255 .bind(as_str_opt(&p.restriction_iri))
256 .bind(as_str_opt(&p.subject_iri))
257 .bind(as_str_opt(&p.object_iri))
258 .execute(&mut *conn)
259 .await
260 .map_err(db_err)?;
261 Ok(())
262}
263
264async fn upsert_interaction(
265 conn: &mut sqlx::PgConnection,
266 p: &InteractionProjection,
267) -> Result<(), DomainError> {
268 sqlx::query(
269 r#"
270 INSERT INTO sbol_interactions (
271 object_id, iri, parent_component_iri, interaction_types
272 )
273 SELECT id, $1, $2, $3::text[]::sbol_ontology_term[]
274 FROM sbol_objects WHERE iri = $1
275 ON CONFLICT (object_id) DO UPDATE SET
276 parent_component_iri = EXCLUDED.parent_component_iri,
277 interaction_types = EXCLUDED.interaction_types
278 "#,
279 )
280 .bind(p.iri.as_str())
281 .bind(as_str_opt(&p.parent_component_iri))
282 .bind(&p.interaction_types)
283 .execute(&mut *conn)
284 .await
285 .map_err(db_err)?;
286 Ok(())
287}
288
289async fn upsert_participation(
290 conn: &mut sqlx::PgConnection,
291 p: &ParticipationProjection,
292) -> Result<(), DomainError> {
293 sqlx::query(
294 r#"
295 INSERT INTO sbol_participations (
296 object_id, iri, interaction_iri, participant_iri, roles
297 )
298 SELECT id, $1, $2, $3, $4::text[]::sbol_ontology_term[]
299 FROM sbol_objects WHERE iri = $1
300 ON CONFLICT (object_id) DO UPDATE SET
301 interaction_iri = EXCLUDED.interaction_iri,
302 participant_iri = EXCLUDED.participant_iri,
303 roles = EXCLUDED.roles
304 "#,
305 )
306 .bind(p.iri.as_str())
307 .bind(as_str_opt(&p.interaction_iri))
308 .bind(as_str_opt(&p.participant_iri))
309 .bind(&p.roles)
310 .execute(&mut *conn)
311 .await
312 .map_err(db_err)?;
313 Ok(())
314}