xmtp_db/encrypted_store/
stream_storage.rs1use diesel::{Connection, SqliteConnection, connection::TransactionManager};
4use thiserror::Error;
5use xmtp_common::{ErrorCode, RetryableError};
6
7use crate::{ConnectionExt, StorageError};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum BudgetScope {
12 Batch,
14 Topic,
16 Kind,
18}
19
20#[derive(Debug, Error, ErrorCode)]
22pub enum StreamStorageError {
23 #[error("The ordered envelope batch is invalid")]
25 InvalidBatch,
26 #[error("The source starts at {after}, beyond received position {received}")]
28 MissingPrefix { after: u64, received: u64 },
29 #[error("Network progress has no proven received prefix")]
31 UninitializedNetworkProgress,
32 #[error("The {scope:?} pending budget is full")]
34 Capacity { scope: BudgetScope },
35 #[error("The pending envelope is no longer the topic head")]
37 HeadChanged,
38 #[error("The join anchor does not advance processed progress")]
40 StaleJoinAnchor,
41 #[error("A default message consumer is already active")]
43 AlreadyActive,
44 #[error("The default message consumer no longer owns delivery")]
46 NotCurrentOwner,
47 #[error("The delivery cursor belongs to another database")]
49 ForeignCursor,
50 #[error("The delivery sequence allocator is exhausted")]
52 DeliveryExhausted,
53 #[error("The next local message needs {bytes} bytes, above the {limit} byte limit")]
55 LocalReadCapacity { bytes: u64, limit: u64 },
56 #[error("The delivery cursor or lease is invalid")]
58 InvalidDeliveryPosition,
59}
60
61impl RetryableError for StreamStorageError {
62 fn is_retryable(&self) -> bool {
63 matches!(self, Self::Capacity { .. } | Self::HeadChanged)
64 }
65}
66
67pub(crate) fn stream_transaction<C, T>(
70 connection: &C,
71 work: impl FnOnce(&mut SqliteConnection) -> Result<T, StorageError>,
72) -> Result<T, StorageError>
73where
74 C: ConnectionExt,
75{
76 connection.raw_query(|conn| {
77 let nested =
78 <SqliteConnection as Connection>::TransactionManager::transaction_manager_status_mut(
79 conn,
80 )
81 .transaction_depth()?
82 .is_some();
83 Ok(if nested {
84 conn.transaction(work)
85 } else {
86 conn.immediate_transaction(work)
87 })
88 })?
89}