1use crate::query_mapped_rows;
2use crate::*;
3#[cfg(any(test, feature = "test-utils"))]
4use coven_protocol::store_commit::StoreDeviceRegistration;
5use coven_protocol::store_commit::{
6 ActivatedStoreDeviceRegistration, CommitFrontier, ReferencedStoreDeviceRegistration,
7 ResolvedStoreDeviceState, StoreBatchCommitRef, StoreDeviceRegistrationRef, StoreDeviceStateRef,
8 StoreHistoryCut,
9};
10use rusqlite::{Connection, OptionalExtension};
11use std::collections::BTreeMap;
12
13use super::*;
14
15impl StoreSession<'_> {
16 fn materialized_frontier(&mut self) -> Result<BTreeMap<String, StoreBatchCommitRef>, DbError> {
17 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
18 .materialized_frontier()
19 }
20
21 pub(super) fn retained_merge_replay_inputs(
22 &mut self,
23 root: coven_protocol::store_commit::StoreRootRef,
24 ) -> Result<Vec<OwnedVerifiedMergeMaterialization>, DbError> {
25 self.verified_store_authority.retained_replay_inputs_on(
26 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir),
27 &root,
28 )
29 }
30
31 fn retained_merge_materialization_refs(&mut self) -> Result<Vec<StoreBatchCommitRef>, DbError> {
32 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
33 .retained_merge_materialization_refs()
34 }
35
36 fn retained_merge_materialization(
37 &mut self,
38 root: coven_protocol::store_commit::StoreRootRef,
39 reference: StoreBatchCommitRef,
40 ) -> Result<OwnedVerifiedMergeMaterialization, DbError> {
41 self.verified_store_authority
42 .retained_replay_inputs_on(
43 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir),
44 &root,
45 )?
46 .into_iter()
47 .find(|materialization| materialization.commit_ref() == &reference)
48 .ok_or_else(|| {
49 DbError::Message(
50 "retained Merge materialization is absent at its exact coordinate".to_string(),
51 )
52 })
53 }
54
55 fn retained_merge_history_frontier(
56 &mut self,
57 root: coven_protocol::store_commit::StoreRootRef,
58 references: Vec<StoreBatchCommitRef>,
59 ) -> Result<Vec<RetainedMergeHistoryCheckpoint>, DbError> {
60 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
61 let authority = &mut *self.verified_store_authority;
62 let retained = authority.retained_replay_inputs_on(records, &root)?;
63 let baseline = authority.retained_replay_baseline_on(records)?.clone();
66 let by_reference = retained
67 .iter()
68 .map(|materialization| (materialization.commit_ref().clone(), materialization))
69 .collect::<BTreeMap<_, _>>();
70 let mut pending = references;
71 let mut visited = std::collections::BTreeSet::new();
72 let mut checkpoints = Vec::new();
73 while let Some(reference) = pending.pop() {
74 if !visited.insert(reference.clone()) {
75 continue;
76 }
77 match by_reference.get(&reference) {
78 Some(materialization) => {
79 pending.extend(
80 materialization
81 .commit()
82 .order
83 .predecessor_cut()
84 .map_err(DbError::from)?
85 .0
86 .into_values(),
87 );
88 checkpoints
89 .push(authority.retained_history_checkpoint_on(records, &reference)?);
90 }
91 None => checkpoints.push(StoreDatabase::load_retained_merge_history_checkpoint_on(
92 records, &root, authority, &baseline, &reference,
93 )?),
94 }
95 }
96 Ok(checkpoints)
97 }
98
99 fn exact_materialized_ref(
100 &mut self,
101 stream_id: String,
102 sequence: u64,
103 ) -> Result<Option<StoreBatchCommitRef>, DbError> {
104 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
105 .materialized_commit_ref(&stream_id, sequence)
106 }
107
108 fn snapshot_coverage_frontier(&mut self) -> Result<CommitFrontier, DbError> {
109 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
110 .snapshot_coverage_frontier()
111 }
112
113 pub(super) fn installed_replay_baseline(
114 &mut self,
115 ) -> Result<crate::InstalledReplayBaseline, DbError> {
116 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
117 let coverage = self.snapshot_coverage_frontier()?;
118 let baseline = self
119 .verified_store_authority
120 .retained_replay_baseline_on(records)?;
121 if &coverage != baseline.coverage() {
122 return Err(DbError::Message(
123 "installed snapshot coverage differs from its replay authority".into(),
124 ));
125 }
126 let covered_states =
127 crate::store::store_device_state::load_covered_store_device_snapshots_on(
128 self.conn, &coverage,
129 )?;
130 match &baseline.authority {
131 crate::RetainedReplayAuthority::InstalledSnapshot(authority) => {
132 let metadata =
133 crate::StoreDatabase::validated_installed_baseline_metadata(records, baseline)?;
134 Ok(crate::InstalledReplayBaseline::from_snapshot(
135 crate::PublishedStoreSnapshot {
136 reference: authority.snapshot.clone(),
137 meta: metadata.clone(),
138 },
139 covered_states,
140 ))
141 }
142 crate::RetainedReplayAuthority::Genesis(_) => {
143 Ok(crate::InstalledReplayBaseline::default())
144 }
145 }
146 }
147
148 fn store_device_state_for_history_cut(
149 &mut self,
150 cut: StoreHistoryCut,
151 ) -> Result<(StoreDeviceStateRef, ResolvedStoreDeviceState), DbError> {
152 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
153 .store_device_state_for_history_cut(&cut)
154 }
155
156 fn resolved_store_device_state(
157 &mut self,
158 reference: StoreDeviceStateRef,
159 ) -> Result<ResolvedStoreDeviceState, DbError> {
160 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
161 .declared_store_device_state(&reference)
162 }
163
164 fn activated_store_device_registration_records(
165 &mut self,
166 ) -> Result<Vec<ReferencedStoreDeviceRegistration>, DbError> {
167 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
168 let root = self
169 .verified_store_authority
170 .root_authority_on(records)?
171 .map(|(reference, _)| reference)
172 .ok_or_else(|| {
173 DbError::Message("Store root is absent while loading activated devices".to_string())
174 })?;
175 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
176 .activated_registration_references()?
177 .into_iter()
178 .map(|reference| {
179 let device_id = reference.device_id;
180 let registration = self
181 .verified_store_authority
182 .activated_registration_on(records, &root, &reference)?;
183 ReferencedStoreDeviceRegistration::verified(reference, registration).map_err(
184 |error| {
185 DbError::context(
186 format!(
187 "activated Store device registration {device_id} exact reference"
188 ),
189 error,
190 )
191 },
192 )
193 })
194 .collect::<Result<Vec<_>, DbError>>()
195 }
196
197 fn activated_store_device_registration(
198 &mut self,
199 reference: StoreDeviceRegistrationRef,
200 ) -> Result<ReferencedStoreDeviceRegistration, DbError> {
201 let root = self
202 .root_authority()?
203 .map(|(reference, _)| reference)
204 .ok_or_else(|| {
205 DbError::Message(
206 "Store root is absent while loading an activated device".to_string(),
207 )
208 })?;
209 let registration = self.verified_store_authority.activated_registration_on(
210 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir),
211 &root,
212 &reference,
213 )?;
214 ReferencedStoreDeviceRegistration::verified(reference, registration).map_err(DbError::from)
215 }
216
217 fn local_activated_registration_ref(
218 &mut self,
219 ) -> Result<Option<StoreDeviceRegistrationRef>, DbError> {
220 crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
221 .local_activated_registration_ref()
222 }
223
224 pub(super) fn activated_store_device_registration_with_authority(
225 &mut self,
226 root: coven_protocol::store_commit::StoreRootRef,
227 reference: StoreDeviceRegistrationRef,
228 ) -> Result<ActivatedStoreDeviceRegistration, DbError> {
229 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
230 let registration = self
231 .verified_store_authority
232 .activated_registration_on(records, &root, &reference)?;
233 let authority = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
234 .activated_registration_authority(&reference)?;
235 let authority = serde_json::from_str(&authority)
236 .map_err(|error| DbError::context("activated Store registration authority", error))?;
237 let registration = ReferencedStoreDeviceRegistration::verified(reference, registration)
238 .map_err(DbError::from)?;
239 ActivatedStoreDeviceRegistration::verified(registration, authority).map_err(DbError::from)
240 }
241
242 fn activated_store_device_registration_for_device(
243 &mut self,
244 device_id: coven_protocol::store_commit::StoreDeviceId,
245 ) -> Result<Option<ActivatedStoreDeviceRegistration>, DbError> {
246 let records = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir);
247 let root = self
248 .verified_store_authority
249 .root_authority_on(records)?
250 .map(|(reference, _)| reference)
251 .ok_or_else(|| {
252 DbError::Message(
253 "Store root is absent while loading an activated device".to_string(),
254 )
255 })?;
256 let stored = crate::store::store_session::StoreRecords::new(self.conn, self.store_dir)
257 .activated_registration_row_for_device(device_id)?;
258 let Some((reference, authority)) = stored else {
259 return Ok(None);
260 };
261 let reference: StoreDeviceRegistrationRef = serde_json::from_str(&reference)
262 .map_err(|error| DbError::context("activated Store registration ref", error))?;
263 if reference.device_id != device_id {
264 return Err(DbError::Message(
265 "activated Store registration row names another device".to_string(),
266 ));
267 }
268 let registration = self
269 .verified_store_authority
270 .activated_registration_on(records, &root, &reference)?;
271 let authority = serde_json::from_str(&authority)
272 .map_err(|error| DbError::context("activated Store registration authority", error))?;
273 let registration = ReferencedStoreDeviceRegistration::verified(reference, registration)
274 .map_err(DbError::from)?;
275 ActivatedStoreDeviceRegistration::verified(registration, authority)
276 .map(Some)
277 .map_err(DbError::from)
278 }
279}
280
281impl StoreDatabase {
282 pub async fn materialized_frontier(
283 &self,
284 ) -> Result<BTreeMap<String, StoreBatchCommitRef>, DbError> {
285 self.call_store(|session| session.materialized_frontier())
286 .await
287 }
288
289 pub async fn retained_merge_replay_inputs(
290 &self,
291 root: coven_protocol::store_commit::StoreRootRef,
292 ) -> Result<Vec<OwnedVerifiedMergeMaterialization>, DbError> {
293 self.call_store(move |session| session.retained_merge_replay_inputs(root))
294 .await
295 }
296
297 pub async fn retained_merge_materialization_refs(
298 &self,
299 ) -> Result<Vec<StoreBatchCommitRef>, DbError> {
300 self.call_store(|session| session.retained_merge_materialization_refs())
301 .await
302 }
303
304 pub async fn retained_merge_materialization(
305 &self,
306 root: coven_protocol::store_commit::StoreRootRef,
307 reference: StoreBatchCommitRef,
308 ) -> Result<OwnedVerifiedMergeMaterialization, DbError> {
309 self.call_store(move |session| session.retained_merge_materialization(root, reference))
310 .await
311 }
312
313 pub async fn retained_merge_history_frontier(
314 &self,
315 root: coven_protocol::store_commit::StoreRootRef,
316 references: Vec<StoreBatchCommitRef>,
317 ) -> Result<Vec<RetainedMergeHistoryCheckpoint>, DbError> {
318 self.call_store(move |session| session.retained_merge_history_frontier(root, references))
319 .await
320 }
321
322 pub async fn exact_materialized_ref(
323 &self,
324 stream_id: &str,
325 sequence: u64,
326 ) -> Result<Option<StoreBatchCommitRef>, DbError> {
327 let stream_id = stream_id.to_string();
328 self.call_store(move |session| session.exact_materialized_ref(stream_id, sequence))
329 .await
330 }
331
332 pub async fn snapshot_coverage_frontier(&self) -> Result<CommitFrontier, DbError> {
333 self.call_store(|session| session.snapshot_coverage_frontier())
334 .await
335 }
336
337 pub async fn installed_replay_baseline(
340 &self,
341 ) -> Result<crate::InstalledReplayBaseline, DbError> {
342 self.call_store(|session| session.installed_replay_baseline())
343 .await
344 }
345
346 pub async fn store_device_state_for_order(
347 &self,
348 order: &coven_protocol::store_commit::StoreCommitOrder,
349 ) -> Result<(StoreDeviceStateRef, ResolvedStoreDeviceState), DbError> {
350 let cut = order.predecessor_cut().map_err(DbError::from)?;
351 self.call_store(move |session| session.store_device_state_for_history_cut(cut))
352 .await
353 }
354
355 pub async fn store_device_state_for_history_cut(
356 &self,
357 cut: &StoreHistoryCut,
358 ) -> Result<(StoreDeviceStateRef, ResolvedStoreDeviceState), DbError> {
359 let cut = cut.clone();
360 self.call_store(move |session| session.store_device_state_for_history_cut(cut))
361 .await
362 }
363
364 pub async fn resolved_store_device_state(
365 &self,
366 reference: &StoreDeviceStateRef,
367 ) -> Result<ResolvedStoreDeviceState, DbError> {
368 let reference = reference.clone();
369 self.call_store(move |session| session.resolved_store_device_state(reference))
370 .await
371 }
372
373 pub async fn activated_store_device_registration_records(
374 &self,
375 ) -> Result<Vec<ReferencedStoreDeviceRegistration>, DbError> {
376 self.call_store(|session| session.activated_store_device_registration_records())
377 .await
378 }
379
380 pub async fn activated_store_device_registration(
381 &self,
382 reference: StoreDeviceRegistrationRef,
383 ) -> Result<ReferencedStoreDeviceRegistration, DbError> {
384 self.call_store(move |session| session.activated_store_device_registration(reference))
385 .await
386 }
387
388 pub async fn local_activated_registration_ref(
392 &self,
393 ) -> Result<Option<StoreDeviceRegistrationRef>, DbError> {
394 self.call_store(|session| session.local_activated_registration_ref())
395 .await
396 }
397
398 pub async fn local_blob_write_authority(
399 &self,
400 ) -> Result<ReferencedStoreDeviceRegistration, DbError> {
401 self.call_store(|session| session.local_store_authority())
402 .await
403 }
404
405 pub async fn activated_store_device_registration_with_authority(
406 &self,
407 root: &coven_protocol::store_commit::StoreRootRef,
408 reference: StoreDeviceRegistrationRef,
409 ) -> Result<ActivatedStoreDeviceRegistration, DbError> {
410 let root = root.clone();
411 self.call_store(move |session| {
412 session.activated_store_device_registration_with_authority(root, reference)
413 })
414 .await
415 }
416
417 pub async fn activated_store_device_registration_for_device(
418 &self,
419 device_id: coven_protocol::store_commit::StoreDeviceId,
420 ) -> Result<Option<ActivatedStoreDeviceRegistration>, DbError> {
421 self.call_store(move |session| {
422 session.activated_store_device_registration_for_device(device_id)
423 })
424 .await
425 }
426
427 #[cfg(any(test, feature = "test-utils"))]
428 pub async fn activated_store_device_registrations(
429 &self,
430 ) -> Result<Vec<StoreDeviceRegistration>, DbError> {
431 Ok(self
432 .activated_store_device_registration_records()
433 .await?
434 .into_iter()
435 .map(|registration| registration.value().clone())
436 .collect())
437 }
438}
439
440pub(crate) fn materialized_frontier_on(
441 conn: &Connection,
442 exclude_device: Option<&str>,
443) -> Result<BTreeMap<String, StoreBatchCommitRef>, DbError> {
444 let mut frontier = BTreeMap::new();
445 let rows = query_mapped_rows(
446 conn,
447 "SELECT m.device_id, m.seq, m.commit_ref,
448 m.retained_commit_ref, m.retained_input_hash \
449 FROM materialized_commits m \
450 JOIN (SELECT device_id, MAX(seq) AS seq FROM materialized_commits \
451 GROUP BY device_id) latest \
452 ON latest.device_id = m.device_id AND latest.seq = m.seq",
453 [],
454 |row| {
455 Ok((
456 row.get::<_, String>(0)?,
457 row.get::<_, i64>(1)?,
458 row.get::<_, String>(2)?,
459 row.get::<_, Option<String>>(3)?,
460 row.get::<_, Option<String>>(4)?,
461 ))
462 },
463 )?;
464 for row in rows {
465 let (device_id, seq, reference, retained_commit_ref, retained_input_hash) = row;
466 if exclude_device == Some(device_id.as_str()) {
467 continue;
468 }
469 let seq = Database::sequence_from_sqlite(&device_id, seq)?;
470 frontier.insert(
471 device_id.clone(),
472 parse_materialized_commit_row_on(
473 &device_id,
474 seq,
475 &reference,
476 retained_commit_ref.as_deref(),
477 retained_input_hash.as_deref(),
478 )?,
479 );
480 }
481
482 let coverage = snapshot_coverage_on(conn)?
483 .into_iter()
484 .filter(|(device_id, _)| exclude_device != Some(device_id.as_str()))
485 .collect();
486 CommitFrontier::from_refs(frontier)?
487 .join(CommitFrontier::from_refs(coverage)?)
488 .map(CommitFrontier::into_refs)
489 .map_err(|error| DbError::context("join materialized history and snapshot coverage", error))
490}
491
492pub(crate) fn snapshot_coverage_on(
496 conn: &Connection,
497) -> Result<BTreeMap<String, StoreBatchCommitRef>, DbError> {
498 let rows = query_mapped_rows(
499 conn,
500 "SELECT device_id, seq, commit_ref FROM snapshot_coverage",
501 [],
502 |row| {
503 Ok((
504 row.get::<_, String>(0)?,
505 row.get::<_, i64>(1)?,
506 row.get::<_, String>(2)?,
507 ))
508 },
509 )?;
510 let mut coverage = BTreeMap::new();
511 for (device_id, seq, reference) in rows {
512 let seq = Database::sequence_from_sqlite(&device_id, seq)?;
513 let reference = parse_stored_commit_ref(&device_id, seq, &reference)?;
514 coverage.insert(device_id, reference);
515 }
516 Ok(coverage)
517}
518
519pub(crate) fn parse_stored_commit_ref(
520 stream_id: &str,
521 sequence: u64,
522 encoded: &str,
523) -> Result<StoreBatchCommitRef, DbError> {
524 let reference: StoreBatchCommitRef = serde_json::from_str(encoded)
525 .map_err(|error| DbError::context("stored exact Store commit ref", error))?;
526 let coordinate_matches =
527 reference.coord.stream_id.to_string() == stream_id && reference.coord.sequence == sequence;
528 if !coordinate_matches {
529 return Err(DbError::Message(format!(
530 "stored exact Store commit ref differs from {stream_id}/{sequence}"
531 )));
532 }
533 Ok(reference)
534}
535
536fn parse_materialized_commit_row_on(
537 stream_id: &str,
538 sequence: u64,
539 encoded: &str,
540 retained_commit_ref: Option<&str>,
541 retained_input_hash: Option<&str>,
542) -> Result<StoreBatchCommitRef, DbError> {
543 let reference = parse_stored_commit_ref(stream_id, sequence, encoded)?;
544 if retained_commit_ref != Some(encoded) {
545 return Err(DbError::Message(format!(
546 "materialized coordinate {stream_id}/{sequence} does not bind its exact retained commit"
547 )));
548 }
549 let input_hash = retained_input_hash.ok_or_else(|| {
550 DbError::Message(format!(
551 "materialized coordinate {stream_id}/{sequence} has no retained input hash"
552 ))
553 })?;
554 input_hash
555 .parse::<coven_protocol::store_commit::ObjectHash>()
556 .map_err(|error| {
557 DbError::context(
558 format!(
559 "materialized coordinate {stream_id}/{sequence} retained input hash is invalid"
560 ),
561 error,
562 )
563 })?;
564 Ok(reference)
565}
566
567pub(crate) fn materialized_commit_ref_on(
568 conn: &Connection,
569 stream_id: &str,
570 sequence: u64,
571) -> Result<Option<StoreBatchCommitRef>, DbError> {
572 let seq = Database::sequence_to_sqlite(stream_id, sequence)?;
573 conn.query_row(
574 "SELECT commit_ref, retained_commit_ref, retained_input_hash
575 FROM materialized_commits WHERE device_id = ?1 AND seq = ?2",
576 (stream_id, seq),
577 |row| {
578 Ok((
579 row.get::<_, String>(0)?,
580 row.get::<_, Option<String>>(1)?,
581 row.get::<_, Option<String>>(2)?,
582 ))
583 },
584 )
585 .optional()
586 .map_err(DbError::from)?
587 .map(|(encoded, retained_commit_ref, retained_input_hash)| {
588 parse_materialized_commit_row_on(
589 stream_id,
590 sequence,
591 &encoded,
592 retained_commit_ref.as_deref(),
593 retained_input_hash.as_deref(),
594 )
595 })
596 .transpose()
597}
598
599pub(crate) fn latest_position_for_device_on(
600 conn: &Connection,
601 device_id: &str,
602) -> Result<Option<StoreBatchCommitRef>, DbError> {
603 let materialized = conn
604 .query_row(
605 "SELECT seq, commit_ref, retained_commit_ref, retained_input_hash
606 FROM materialized_commits
607 WHERE device_id = ?1 ORDER BY seq DESC LIMIT 1",
608 [device_id],
609 |row| {
610 Ok((
611 row.get::<_, i64>(0)?,
612 row.get::<_, String>(1)?,
613 row.get::<_, Option<String>>(2)?,
614 row.get::<_, Option<String>>(3)?,
615 ))
616 },
617 )
618 .optional()
619 .map_err(DbError::from)?;
620 let coverage = conn
621 .query_row(
622 "SELECT seq, commit_ref FROM snapshot_coverage WHERE device_id = ?1",
623 [device_id],
624 |row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)),
625 )
626 .optional()
627 .map_err(DbError::from)?;
628 let mut references = Vec::new();
629 if let Some((seq, reference, retained_commit_ref, retained_input_hash)) = materialized {
630 let seq = Database::sequence_from_sqlite(device_id, seq)?;
631 references.push(parse_materialized_commit_row_on(
632 device_id,
633 seq,
634 &reference,
635 retained_commit_ref.as_deref(),
636 retained_input_hash.as_deref(),
637 )?);
638 }
639 if let Some((seq, reference)) = coverage {
640 let seq = Database::sequence_from_sqlite(device_id, seq)?;
641 references.push(parse_stored_commit_ref(device_id, seq, &reference)?);
642 }
643 let frontier = references
644 .into_iter()
645 .try_fold(CommitFrontier(BTreeMap::new()), |frontier, reference| {
646 frontier.join(CommitFrontier(BTreeMap::from([(
647 reference.coord.stream_id,
648 reference,
649 )])))
650 })
651 .map_err(|error| {
652 DbError::context("join materialized history and snapshot coverage", error)
653 })?;
654 Ok(frontier.0.into_values().next())
655}
656
657#[cfg(test)]
658#[path = "materialized_commit_index_tests.rs"]
659mod tests;