sbol_db_postgres/repo/
sketch.rs1use async_trait::async_trait;
11use sbol_db_core::DomainError;
12use sbol_db_storage::{Signature, SketchStore};
13use sqlx::Row;
14
15use crate::repo::db_err;
16use crate::PgPool;
17
18#[derive(Clone)]
19pub struct PgSketchStore {
20 pool: PgPool,
21}
22
23impl PgSketchStore {
24 pub fn new(pool: PgPool) -> Self {
25 Self { pool }
26 }
27}
28
29fn to_i64(band: u64) -> i64 {
32 i64::from_le_bytes(band.to_le_bytes())
33}
34
35fn decode_sig(bytes: &[u8]) -> Result<Signature, DomainError> {
36 Signature::from_bytes(bytes).ok_or_else(|| {
37 DomainError::Database("stored sketch signature has an invalid length".into())
38 })
39}
40
41#[async_trait]
42impl SketchStore for PgSketchStore {
43 async fn put_sketch(
44 &self,
45 iri: &str,
46 signature: &Signature,
47 bands: &[u64],
48 ) -> Result<(), DomainError> {
49 let mut tx = self.pool.begin().await.map_err(db_err)?;
50 sqlx::query("DELETE FROM sbol_sequence_lsh_band WHERE sequence_iri = $1")
51 .bind(iri)
52 .execute(&mut *tx)
53 .await
54 .map_err(db_err)?;
55 sqlx::query(
56 r#"
57 INSERT INTO sbol_sequence_sketch (sequence_iri, signature)
58 VALUES ($1, $2)
59 ON CONFLICT (sequence_iri) DO UPDATE SET signature = EXCLUDED.signature
60 "#,
61 )
62 .bind(iri)
63 .bind(signature.to_bytes())
64 .execute(&mut *tx)
65 .await
66 .map_err(db_err)?;
67 if !bands.is_empty() {
68 let hashes: Vec<i64> = bands.iter().map(|b| to_i64(*b)).collect();
69 let iris: Vec<String> = vec![iri.to_owned(); bands.len()];
70 sqlx::query(
71 r#"
72 INSERT INTO sbol_sequence_lsh_band (band_hash, sequence_iri)
73 SELECT * FROM UNNEST($1::bigint[], $2::text[])
74 ON CONFLICT DO NOTHING
75 "#,
76 )
77 .bind(&hashes)
78 .bind(&iris)
79 .execute(&mut *tx)
80 .await
81 .map_err(db_err)?;
82 }
83 tx.commit().await.map_err(db_err)?;
84 Ok(())
85 }
86
87 async fn sketch_of(&self, iri: &str) -> Result<Option<Signature>, DomainError> {
88 let row = sqlx::query("SELECT signature FROM sbol_sequence_sketch WHERE sequence_iri = $1")
89 .bind(iri)
90 .fetch_optional(&self.pool)
91 .await
92 .map_err(db_err)?;
93 match row {
94 Some(r) => {
95 let bytes: Vec<u8> = r.try_get("signature").map_err(db_err)?;
96 Ok(Some(decode_sig(&bytes)?))
97 }
98 None => Ok(None),
99 }
100 }
101
102 async fn candidates_by_bands(&self, bands: &[u64]) -> Result<Vec<String>, DomainError> {
103 if bands.is_empty() {
104 return Ok(Vec::new());
105 }
106 let hashes: Vec<i64> = bands.iter().map(|b| to_i64(*b)).collect();
107 let rows = sqlx::query(
108 "SELECT DISTINCT sequence_iri FROM sbol_sequence_lsh_band WHERE band_hash = ANY($1::bigint[])",
109 )
110 .bind(&hashes)
111 .fetch_all(&self.pool)
112 .await
113 .map_err(db_err)?;
114 let mut out = Vec::with_capacity(rows.len());
115 for r in rows {
116 out.push(r.try_get::<String, _>("sequence_iri").map_err(db_err)?);
117 }
118 Ok(out)
119 }
120
121 async fn all_sketches(&self) -> Result<Vec<(String, Signature)>, DomainError> {
122 let rows = sqlx::query("SELECT sequence_iri, signature FROM sbol_sequence_sketch")
123 .fetch_all(&self.pool)
124 .await
125 .map_err(db_err)?;
126 let mut out = Vec::with_capacity(rows.len());
127 for r in rows {
128 let iri: String = r.try_get("sequence_iri").map_err(db_err)?;
129 let bytes: Vec<u8> = r.try_get("signature").map_err(db_err)?;
130 out.push((iri, decode_sig(&bytes)?));
131 }
132 Ok(out)
133 }
134
135 async fn replace_all_sketches(
136 &self,
137 entries: Vec<(String, Signature, Vec<u64>)>,
138 ) -> Result<(), DomainError> {
139 let mut tx = self.pool.begin().await.map_err(db_err)?;
140 sqlx::query("DELETE FROM sbol_sequence_lsh_band")
141 .execute(&mut *tx)
142 .await
143 .map_err(db_err)?;
144 sqlx::query("DELETE FROM sbol_sequence_sketch")
145 .execute(&mut *tx)
146 .await
147 .map_err(db_err)?;
148
149 let mut sketch_iris: Vec<String> = Vec::with_capacity(entries.len());
150 let mut sketch_sigs: Vec<Vec<u8>> = Vec::with_capacity(entries.len());
151 let mut band_hashes: Vec<i64> = Vec::new();
152 let mut band_iris: Vec<String> = Vec::new();
153 for (iri, signature, bands) in &entries {
154 sketch_iris.push(iri.clone());
155 sketch_sigs.push(signature.to_bytes());
156 for band in bands {
157 band_hashes.push(to_i64(*band));
158 band_iris.push(iri.clone());
159 }
160 }
161
162 if !sketch_iris.is_empty() {
163 sqlx::query(
164 r#"
165 INSERT INTO sbol_sequence_sketch (sequence_iri, signature)
166 SELECT * FROM UNNEST($1::text[], $2::bytea[])
167 "#,
168 )
169 .bind(&sketch_iris)
170 .bind(&sketch_sigs)
171 .execute(&mut *tx)
172 .await
173 .map_err(db_err)?;
174 }
175 if !band_hashes.is_empty() {
176 sqlx::query(
177 r#"
178 INSERT INTO sbol_sequence_lsh_band (band_hash, sequence_iri)
179 SELECT * FROM UNNEST($1::bigint[], $2::text[])
180 ON CONFLICT DO NOTHING
181 "#,
182 )
183 .bind(&band_hashes)
184 .bind(&band_iris)
185 .execute(&mut *tx)
186 .await
187 .map_err(db_err)?;
188 }
189 tx.commit().await.map_err(db_err)?;
190 Ok(())
191 }
192}