liminal_server/server/participant/production/
committed_operations.rs1use std::sync::Arc;
18
19use liminal::durability::{DurabilityError, DurableStore, StoredEntry};
20
21use super::log::OperationLog;
22use super::restore_checkpoint_locator::checkpoint_conversation_route;
23use super::routed_stream::{self, RoutedStreamError, RoutedStreamRefusal};
24
25pub const COMMITTED_OPERATION_PAGE_MAX: usize = 256;
27
28#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct CommittedOperation {
32 pub sequence: u64,
34 pub payload: Vec<u8>,
36}
37
38#[derive(Debug, thiserror::Error)]
40#[non_exhaustive]
41pub enum CommittedOperationReadError {
42 #[error("committed operation page limit {limit} is outside 1..={max}")]
44 Limit {
45 limit: usize,
47 max: usize,
49 },
50 #[error(transparent)]
52 Store(#[from] DurabilityError),
53 #[error("committed operation routed stream refused: {0}")]
55 RoutedStream(#[source] RoutedStreamRefusal),
56 #[error("committed operation flat row {sequence} is absent below native head {native_head}")]
58 MissingFlatRow {
59 sequence: u64,
61 native_head: u64,
63 },
64 #[error("committed operation read expected sequence {expected}, read {actual}")]
66 Sequence {
67 expected: u64,
69 actual: u64,
71 },
72 #[error("committed operation sequence overflowed after {from_sequence}")]
74 SequenceOverflow {
75 from_sequence: u64,
77 },
78}
79
80impl From<RoutedStreamError> for CommittedOperationReadError {
81 fn from(error: RoutedStreamError) -> Self {
82 match error {
83 RoutedStreamError::Store(original) => Self::Store(original),
84 RoutedStreamError::Refusal(refusal) => Self::RoutedStream(refusal),
85 }
86 }
87}
88
89pub async fn read_committed_operations(
108 store: &Arc<dyn DurableStore>,
109 conversation_id: u64,
110 from_sequence: u64,
111 limit: usize,
112) -> Result<Vec<CommittedOperation>, CommittedOperationReadError> {
113 if limit == 0 || limit > COMMITTED_OPERATION_PAGE_MAX {
114 return Err(CommittedOperationReadError::Limit {
115 limit,
116 max: COMMITTED_OPERATION_PAGE_MAX,
117 });
118 }
119 let stream = OperationLog::new(Arc::clone(store), conversation_id)
120 .stream_key()
121 .await?;
122 let native_head = store.read_stream_next_seq(&stream).await?.unwrap_or(0);
123 let entries = if from_sequence < native_head {
124 read_flat(store.as_ref(), &stream, native_head, from_sequence, limit).await?
125 } else {
126 let route = checkpoint_conversation_route(conversation_id);
127 read_routed(store.as_ref(), &route, &stream, from_sequence, limit).await?
128 };
129 contiguous(entries, from_sequence)
130}
131
132async fn read_flat(
134 store: &dyn DurableStore,
135 stream: &str,
136 native_head: u64,
137 from_sequence: u64,
138 limit: usize,
139) -> Result<Vec<StoredEntry>, CommittedOperationReadError> {
140 let remaining = native_head.saturating_sub(from_sequence);
141 let bounded = usize::try_from(remaining).map_or(limit, |remaining| remaining.min(limit));
142 let entries = store.read_from(stream, from_sequence, bounded).await?;
143 if entries.is_empty() {
144 return Err(CommittedOperationReadError::MissingFlatRow {
145 sequence: from_sequence,
146 native_head,
147 });
148 }
149 Ok(entries)
150}
151
152async fn read_routed(
155 store: &dyn DurableStore,
156 route: &str,
157 stream: &str,
158 from_sequence: u64,
159 limit: usize,
160) -> Result<Vec<StoredEntry>, CommittedOperationReadError> {
161 let Some(head) = routed_stream::read_head(store, route, stream).await? else {
162 return Ok(Vec::new());
163 };
164 Ok(routed_stream::read_page(store, route, stream, &head, from_sequence, limit).await?)
165}
166
167fn contiguous(
169 entries: Vec<StoredEntry>,
170 from_sequence: u64,
171) -> Result<Vec<CommittedOperation>, CommittedOperationReadError> {
172 let mut operations = Vec::with_capacity(entries.len());
173 for (offset, entry) in entries.into_iter().enumerate() {
174 let expected = u64::try_from(offset)
175 .ok()
176 .and_then(|offset| from_sequence.checked_add(offset))
177 .ok_or(CommittedOperationReadError::SequenceOverflow { from_sequence })?;
178 if entry.sequence != expected {
179 return Err(CommittedOperationReadError::Sequence {
180 expected,
181 actual: entry.sequence,
182 });
183 }
184 operations.push(CommittedOperation {
185 sequence: entry.sequence,
186 payload: entry.payload,
187 });
188 }
189 Ok(operations)
190}