1use std::collections::{HashMap, HashSet};
2
3use rusqlite::{Connection, OptionalExtension, params, params_from_iter};
4
5use super::{
6 MerkleMode, MerkleProof, MerkleSummary, SqliteLayout, SqliteStoreOptions, StoredObjectRow, codec_to_str, hash_pair,
7 merkle_root_from_hashes, object_record_from_row, prepare_object, schema_statements, verified_object_from_row,
8};
9use crate::{Codec, Error, Hash32, ObjectKind, ObjectRecord, ObjectSource, Result, VerifiedObject};
10
11pub struct RusqliteStore<'connection> {
12 connection: &'connection Connection,
13 layout: SqliteLayout,
14 merkle: MerkleMode,
15}
16
17impl<'connection> RusqliteStore<'connection> {
18 pub fn from_connection(connection: &'connection Connection) -> Result<Self> {
19 Self::from_connection_with_options(connection, SqliteStoreOptions::default())
20 }
21
22 pub fn from_connection_with_namespace(connection: &'connection Connection, namespace: impl AsRef<str>) -> Result<Self> {
23 Self::from_connection_with_options(
24 connection,
25 SqliteStoreOptions {
26 namespace: Some(namespace.as_ref().to_string()),
27 ..Default::default()
28 },
29 )
30 }
31
32 pub fn from_connection_with_options(connection: &'connection Connection, options: SqliteStoreOptions) -> Result<Self> {
33 let layout = SqliteLayout::new(options.namespace.as_deref())?;
34 for statement in schema_statements(&layout, options.merkle) {
35 connection.execute_batch(&statement)?;
36 }
37 Ok(Self::view(connection, options, layout))
38 }
39
40 pub(in crate::sqlite) fn view(connection: &'connection Connection, options: SqliteStoreOptions, layout: SqliteLayout) -> Self {
41 Self {
42 connection,
43 layout,
44 merkle: options.merkle,
45 }
46 }
47
48 pub fn ensure_schema(connection: &Connection) -> Result<()> {
49 Self::ensure_schema_with_options(connection, &SqliteStoreOptions::default())
50 }
51
52 pub fn ensure_schema_with_options(connection: &Connection, options: &SqliteStoreOptions) -> Result<()> {
53 let layout = SqliteLayout::new(options.namespace.as_deref())?;
54 for statement in schema_statements(&layout, options.merkle) {
55 connection.execute_batch(&statement)?;
56 }
57 Ok(())
58 }
59
60 pub fn connection(&self) -> &Connection {
61 self.connection
62 }
63
64 pub fn namespace(&self) -> Option<&str> {
65 self.layout.namespace.as_deref()
66 }
67
68 pub fn merkle_mode(&self) -> MerkleMode {
69 self.merkle
70 }
71
72 pub fn put_objects_batch(&self, objects: &[ObjectRecord]) -> Result<()> {
73 if objects.is_empty() {
74 return Ok(());
75 }
76 let sql = self.layout.insert_object();
77 let mut statement = self.connection.prepare_cached(&sql)?;
78 for object in objects {
79 statement.execute(params![
80 object.hash.as_bytes().as_slice(),
81 object.kind as i64,
82 i64::try_from(object.decoded_len).map_err(|_| Error::Integrity("object size exceeds SQLite integer".into()))?,
83 i64::try_from(object.stored_bytes.len()).map_err(|_| Error::Integrity("stored size exceeds SQLite integer".into()))?,
84 codec_to_str(object.codec),
85 &object.stored_bytes,
86 unix_timestamp(),
87 ])?;
88 }
89 Ok(())
90 }
91
92 pub fn put_file_recipe_cache_batch(&self, entries: &[(Hash32, Hash32)]) -> Result<()> {
93 let sql = self.layout.upsert_recipe_cache();
94 let mut statement = self.connection.prepare_cached(&sql)?;
95 for (file_hash, recipe_hash) in entries {
96 statement.execute(params![
97 file_hash.as_bytes().as_slice(),
98 recipe_hash.as_bytes().as_slice(),
99 unix_timestamp()
100 ])?;
101 }
102 Ok(())
103 }
104
105 pub fn put_chunk(&self, hash: Hash32, content: &[u8], codec: Codec) -> Result<()> {
106 self.put_object(hash, ObjectKind::Chunk, content, codec)
107 }
108
109 pub fn put_recipe(&self, hash: Hash32, content: &[u8], codec: Codec) -> Result<()> {
110 self.put_object(hash, ObjectKind::Recipe, content, codec)
111 }
112
113 fn put_object(&self, hash: Hash32, kind: ObjectKind, content: &[u8], codec: Codec) -> Result<()> {
114 self.put_objects_batch(&[prepare_object(hash, kind, content, codec)?])
115 }
116
117 pub fn read_object_record(&self, hash: &Hash32) -> Result<Option<ObjectRecord>> {
118 let sql = self.layout.select_object();
119 self
120 .connection
121 .query_row(&sql, [hash.as_bytes().as_slice()], stored_row)
122 .optional()?
123 .map(object_record_from_row)
124 .transpose()
125 }
126
127 pub fn existing_hashes(&self, hashes: &[Hash32]) -> Result<HashSet<Hash32>> {
128 let mut found = HashSet::new();
129 for chunk in hashes.chunks(999) {
130 if chunk.is_empty() {
131 continue;
132 }
133 let placeholders = std::iter::repeat_n("?", chunk.len()).collect::<Vec<_>>().join(",");
134 let sql = format!("{}{placeholders})", self.layout.select_hashes_prefix());
135 let values = chunk.iter().map(|hash| hash.as_bytes().as_slice());
136 let mut statement = self.connection.prepare(&sql)?;
137 let rows = statement.query_map(params_from_iter(values), |row| row.get::<_, Vec<u8>>(0))?;
138 for row in rows {
139 found.insert(Hash32::from_bytes(&row?)?);
140 }
141 }
142 Ok(found)
143 }
144
145 pub fn chunk_stored_bytes(&self, hash: &Hash32) -> Result<Option<u64>> {
146 let sql = self.layout.select_chunk_stored_size();
147 let value = self
148 .connection
149 .query_row(&sql, [hash.as_bytes().as_slice()], |row| {
150 Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?))
151 })
152 .optional()?;
153 match value {
154 Some((kind, size)) if kind == ObjectKind::Chunk as i64 => {
155 Ok(Some(size.try_into().map_err(|_| Error::Integrity("negative stored size".into()))?))
156 }
157 _ => Ok(None),
158 }
159 }
160
161 pub fn chunk_sizes(&self) -> Result<Vec<(Hash32, u64)>> {
162 let sql = self.layout.select_chunk_sizes();
163 let mut statement = self.connection.prepare(&sql)?;
164 let rows = statement.query_map([ObjectKind::Chunk as i64], |row| {
165 Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, i64>(1)?))
166 })?;
167 rows
168 .map(|row| {
169 let (hash, size) = row?;
170 Ok((
171 Hash32::from_bytes(&hash)?,
172 size.try_into().map_err(|_| Error::Integrity("negative stored size".into()))?,
173 ))
174 })
175 .collect()
176 }
177
178 pub fn file_recipe_cache(&self) -> Result<HashMap<Hash32, Hash32>> {
179 let sql = self.layout.select_recipe_cache();
180 let mut statement = self.connection.prepare(&sql)?;
181 let rows = statement.query_map([], |row| Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, Vec<u8>>(1)?)))?;
182 let mut cache = HashMap::new();
183 for row in rows {
184 let (file, recipe) = row?;
185 cache.insert(Hash32::from_bytes(&file)?, Hash32::from_bytes(&recipe)?);
186 }
187 Ok(cache)
188 }
189
190 pub fn recipe_for_file_hash(&self, file_hash: &Hash32) -> Result<Option<Hash32>> {
191 let sql = self.layout.select_recipe_for_file();
192 self
193 .connection
194 .query_row(&sql, [file_hash.as_bytes().as_slice()], |row| row.get::<_, Vec<u8>>(0))
195 .optional()?
196 .map(|bytes| Hash32::from_bytes(&bytes))
197 .transpose()
198 }
199
200 pub fn all_objects(&self) -> Result<Vec<VerifiedObject>> {
201 let sql = self.layout.select_objects();
202 let mut statement = self.connection.prepare(&sql)?;
203 statement
204 .query_map([], stored_row)?
205 .map(|row| verified_object_from_row(row?))
206 .collect()
207 }
208
209 pub fn object_records(&self, offset: u64, limit: usize) -> Result<Vec<ObjectRecord>> {
210 let sql = self.layout.select_object_records();
211 let mut statement = self.connection.prepare(&sql)?;
212 statement
213 .query_map(params![limit as i64, offset as i64], stored_row)?
214 .map(|row| object_record_from_row(row?))
215 .collect()
216 }
217
218 pub fn integrity_check(&self) -> Result<()> {
219 let status: String = self
220 .connection
221 .query_row(super::statements::INTEGRITY_CHECK, [], |row| row.get(0))?;
222 if status.trim() == "ok" {
223 Ok(())
224 } else {
225 Err(Error::Integrity(status))
226 }
227 }
228
229 pub fn rebuild_merkle_index(&self) -> Result<MerkleSummary> {
230 self.ensure_merkle_enabled()?;
231 self.connection.execute(&self.layout.delete_merkle_leaves(), [])?;
232 self.connection.execute(&self.layout.delete_merkle_nodes(), [])?;
233 self.connection.execute(&self.layout.delete_merkle_meta(), [])?;
234 let hashes = self.object_hashes()?;
235 let leaf_sql = self.layout.insert_merkle_leaf();
236 let mut leaf_statement = self.connection.prepare_cached(&leaf_sql)?;
237 for (position, hash) in hashes.iter().enumerate() {
238 leaf_statement.execute(params![hash.as_bytes().as_slice(), position as i64])?;
239 }
240 let mut current = hashes.clone();
241 let node_sql = self.layout.insert_merkle_node();
242 let mut node_statement = self.connection.prepare_cached(&node_sql)?;
243 let mut level = 0i64;
244 while current.len() > 1 {
245 let next = current
246 .chunks(2)
247 .enumerate()
248 .map(|(position, pair)| {
249 let parent = hash_pair(&pair[0], pair.get(1).unwrap_or(&pair[0]));
250 node_statement.execute(params![level, position as i64, parent.as_bytes().as_slice()])?;
251 Ok(parent)
252 })
253 .collect::<Result<Vec<_>>>()?;
254 current = next;
255 level += 1;
256 }
257 let summary = merkle_root_from_hashes(&hashes);
258 let meta_sql = self.layout.upsert_merkle_meta();
259 let mut statement = self.connection.prepare_cached(&meta_sql)?;
260 for (key, value) in [
261 ("leaf_count", summary.leaf_count.to_string()),
262 ("root_hash", summary.root.to_hex()),
263 ("algo", "sha3-256".into()),
264 ("tree", "merkle-set-v1".into()),
265 ("sorted", "byte-lex".into()),
266 ] {
267 statement.execute(params![key, value])?;
268 }
269 Ok(summary)
270 }
271
272 pub fn merkle_summary(&self) -> Result<MerkleSummary> {
273 self.ensure_merkle_enabled()?;
274 let leaf_count = self
275 .meta("leaf_count")?
276 .parse()
277 .map_err(|_| Error::MissingMeta("leaf_count".into()))?;
278 let root = Hash32::from_hex(&self.meta("root_hash")?).map_err(|_| Error::MissingMeta("root_hash".into()))?;
279 Ok(MerkleSummary { root, leaf_count })
280 }
281
282 pub fn recompute_merkle_summary(&self) -> Result<MerkleSummary> {
283 self.ensure_merkle_enabled()?;
284 let sql = self.layout.select_merkle_hashes();
285 let mut statement = self.connection.prepare(&sql)?;
286 let rows = statement.query_map([], |row| row.get::<_, Vec<u8>>(0))?;
287 let hashes = rows.map(|row| Hash32::from_bytes(&row?)).collect::<Result<Vec<_>>>()?;
288 Ok(merkle_root_from_hashes(&hashes))
289 }
290
291 pub fn prove_membership(&self, hash: &Hash32) -> Result<Option<MerkleProof>> {
292 let summary = self.merkle_summary()?;
293 let position_sql = self.layout.select_merkle_position();
294 let Some(mut position) = self
295 .connection
296 .query_row(&position_sql, [hash.as_bytes().as_slice()], |row| row.get::<_, i64>(0))
297 .optional()?
298 else {
299 return Ok(None);
300 };
301 let leaf_pos = position as u64;
302 let mut count = summary.leaf_count;
303 let mut current = *hash;
304 let mut level = -1i64;
305 let mut siblings = Vec::new();
306 while count > 1 {
307 let sibling_pos = (position as u64) ^ 1;
308 let sibling = if sibling_pos >= count {
309 current
310 } else {
311 self.hash_at(level, sibling_pos)?
312 };
313 siblings.push(sibling);
314 current = if position % 2 == 0 {
315 hash_pair(¤t, &sibling)
316 } else {
317 hash_pair(&sibling, ¤t)
318 };
319 position /= 2;
320 count = count.div_ceil(2);
321 level += 1;
322 }
323 Ok(Some(MerkleProof { leaf_pos, siblings }))
324 }
325
326 pub fn prove_memberships_batch(&self, hashes: &[Hash32]) -> Result<Vec<Option<MerkleProof>>> {
327 hashes.iter().map(|hash| self.prove_membership(hash)).collect()
328 }
329
330 pub fn verify_proof(target: &Hash32, proof: &MerkleProof, expected_root: &Hash32, leaf_count: u64) -> bool {
331 if leaf_count == 0 {
332 return false;
333 }
334 let mut current = *target;
335 let mut position = proof.leaf_pos;
336 for sibling in &proof.siblings {
337 current = if position.is_multiple_of(2) {
338 hash_pair(¤t, sibling)
339 } else {
340 hash_pair(sibling, ¤t)
341 };
342 position /= 2;
343 }
344 current == *expected_root
345 }
346
347 pub fn verified_object_hashes(&self) -> Result<Vec<Hash32>> {
348 Ok(self.all_objects()?.into_iter().map(|object| object.hash).collect())
349 }
350
351 fn object_hashes(&self) -> Result<Vec<Hash32>> {
352 let sql = self.layout.select_object_hashes();
353 let mut statement = self.connection.prepare(&sql)?;
354 statement
355 .query_map([], |row| row.get::<_, Vec<u8>>(0))?
356 .map(|row| Hash32::from_bytes(&row?))
357 .collect()
358 }
359
360 fn meta(&self, key: &str) -> Result<String> {
361 let sql = self.layout.select_merkle_meta();
362 self
363 .connection
364 .query_row(&sql, [key], |row| row.get(0))
365 .optional()?
366 .ok_or_else(|| Error::MissingMeta(key.into()))
367 }
368
369 fn hash_at(&self, level: i64, position: u64) -> Result<Hash32> {
370 let (sql, values): (String, Vec<i64>) = if level < 0 {
371 (self.layout.select_leaf_hash_at(), vec![position as i64])
372 } else {
373 (self.layout.select_node_hash_at(), vec![level, position as i64])
374 };
375 let bytes: Vec<u8> = self.connection.query_row(&sql, params_from_iter(values), |row| row.get(0))?;
376 Hash32::from_bytes(&bytes)
377 }
378
379 fn ensure_merkle_enabled(&self) -> Result<()> {
380 if self.merkle == MerkleMode::Enabled {
381 Ok(())
382 } else {
383 Err(Error::Integrity("merkle disabled for sqlite store".into()))
384 }
385 }
386}
387
388impl ObjectSource for RusqliteStore<'_> {
389 fn read_object(&self, hash: &Hash32) -> Result<Option<VerifiedObject>> {
390 let sql = self.layout.select_object();
391 self
392 .connection
393 .query_row(&sql, [hash.as_bytes().as_slice()], stored_row)
394 .optional()?
395 .map(verified_object_from_row)
396 .transpose()
397 }
398}
399
400fn stored_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<StoredObjectRow> {
401 let hash = row.get::<_, Vec<u8>>(0)?;
402 Ok(StoredObjectRow {
403 hash: Hash32::from_bytes(&hash).map_err(|error| rusqlite::Error::ToSqlConversionFailure(Box::new(error)))?,
404 kind: row.get(1)?,
405 decoded_len: row.get(2)?,
406 codec: row.get(3)?,
407 stored_bytes: row.get(4)?,
408 })
409}
410
411fn unix_timestamp() -> i64 {
412 std::time::UNIX_EPOCH.elapsed().unwrap_or_default().as_secs() as i64
413}