Skip to main content

coven_database/store/store_session/
materialized_commit_index.rs

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        // Read once from the connection's verified baseline; every reference the
64        // walk finds below the snapshot cut resolves against this same one.
65        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    /// The baseline a history walk stops at, with the device states it keeps
338    /// for the covered positions commits above it still name.
339    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    /// The exact registration this device is activated under, or `None` before
389    /// it has one. This is the identity a signed artifact names when it names a
390    /// device, so it is what a role check compares against.
391    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
492/// The exact commit each stream's installed snapshot image reaches. The image
493/// records one tip per stream and nothing below it, so this is the whole of
494/// what a snapshot says it materialized.
495pub(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;