1pub mod cached;
9pub mod memory;
10pub mod recent_cache;
11#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
12pub mod sqlite;
13
14use std::{collections::Bound, sync};
15
16#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
17use cached::CachedCdcStorage;
18use memory::MemoryCdcStorage;
19use reifydb_core::{
20 common::CommitVersion,
21 encoded::key::EncodedKey,
22 interface::cdc::{Cdc, CdcBatch},
23};
24use reifydb_runtime::shutdown::Shutdown;
25#[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
26use reifydb_sqlite::SqliteConfig;
27use reifydb_value::value::datetime::DateTime;
28
29use crate::error::CdcError;
30
31pub type CdcStorageResult<T> = Result<T, CdcError>;
32
33enum ScanContinuation {
34 Done(CommitVersion),
35 Continue(Bound<CommitVersion>),
36}
37
38#[inline]
39fn scan_batch_for_cutoff(items: &[Cdc], cutoff: DateTime) -> Option<CommitVersion> {
40 for cdc in items {
41 if cdc.timestamp >= cutoff {
42 return Some(cdc.version);
43 }
44 }
45 None
46}
47
48#[inline]
49fn next_start_after_batch(batch: &CdcBatch, max: CommitVersion) -> ScanContinuation {
50 if !batch.has_more {
51 return ScanContinuation::Done(CommitVersion(max.0.saturating_add(1)));
52 }
53 let last = batch.items.last().unwrap().version;
54 ScanContinuation::Continue(Bound::Excluded(last))
55}
56
57#[inline]
58pub(crate) fn normalize_range_inclusive(
59 start: Bound<CommitVersion>,
60 end: Bound<CommitVersion>,
61) -> Option<(CommitVersion, CommitVersion)> {
62 let lo_inc = match start {
63 Bound::Included(v) => v,
64 Bound::Excluded(v) => CommitVersion(v.0.saturating_add(1)),
65 Bound::Unbounded => CommitVersion(0),
66 };
67 let hi_inc = match end {
68 Bound::Included(v) => v,
69 Bound::Excluded(v) => CommitVersion(v.0.saturating_sub(1)),
70 Bound::Unbounded => CommitVersion(u64::MAX),
71 };
72 if lo_inc > hi_inc {
73 None
74 } else {
75 Some((lo_inc, hi_inc))
76 }
77}
78
79#[derive(Debug, Clone)]
80pub struct DroppedCdcEntry {
81 pub key: EncodedKey,
82 pub value_bytes: u64,
83}
84
85#[derive(Debug, Clone, Default)]
86pub struct DropBeforeResult {
87 pub count: usize,
88 pub entries: Vec<DroppedCdcEntry>,
89 pub more_remaining: bool,
90}
91
92pub trait CdcStorage: Send + Sync + Clone + 'static {
93 fn write(&self, cdc: &Cdc) -> CdcStorageResult<()>;
94
95 fn read(&self, version: CommitVersion) -> CdcStorageResult<Option<Cdc>>;
96
97 fn read_range(
98 &self,
99 start: Bound<CommitVersion>,
100 end: Bound<CommitVersion>,
101 batch_size: u64,
102 ) -> CdcStorageResult<CdcBatch>;
103
104 fn count(&self, version: CommitVersion) -> CdcStorageResult<usize>;
105
106 fn min_version(&self) -> CdcStorageResult<Option<CommitVersion>>;
107
108 fn max_version(&self) -> CdcStorageResult<Option<CommitVersion>>;
109
110 fn exists(&self, version: CommitVersion) -> CdcStorageResult<bool> {
111 Ok(self.read(version)?.is_some())
112 }
113
114 fn drop_before(&self, version: CommitVersion, limit: usize) -> CdcStorageResult<DropBeforeResult>;
115
116 fn vacuum(&self) -> CdcStorageResult<()> {
117 Ok(())
118 }
119
120 fn find_ttl_cutoff(&self, cutoff: DateTime) -> CdcStorageResult<Option<CommitVersion>> {
121 let Some(min) = self.min_version()? else {
122 return Ok(None);
123 };
124 let Some(max) = self.max_version()? else {
125 return Ok(None);
126 };
127
128 let mut next_start = Bound::Included(min);
129 loop {
130 let batch = self.read_range(next_start, Bound::Unbounded, 256)?;
131 if batch.items.is_empty() {
132 return Ok(Some(CommitVersion(max.0.saturating_add(1))));
133 }
134 if let Some(version) = scan_batch_for_cutoff(&batch.items, cutoff) {
135 return Ok(Some(version));
136 }
137 match next_start_after_batch(&batch, max) {
138 ScanContinuation::Done(v) => return Ok(Some(v)),
139 ScanContinuation::Continue(start) => next_start = start,
140 }
141 }
142 }
143
144 fn range(&self, start: Bound<CommitVersion>, end: Bound<CommitVersion>) -> CdcStorageResult<CdcBatch> {
145 self.read_range(start, end, 1024)
146 }
147
148 fn scan(&self, batch_size: u64) -> CdcStorageResult<CdcBatch> {
149 self.read_range(Bound::Unbounded, Bound::Unbounded, batch_size)
150 }
151}
152
153impl<T: CdcStorage> CdcStorage for sync::Arc<T> {
154 fn write(&self, cdc: &Cdc) -> CdcStorageResult<()> {
155 (**self).write(cdc)
156 }
157
158 fn read(&self, version: CommitVersion) -> CdcStorageResult<Option<Cdc>> {
159 (**self).read(version)
160 }
161
162 fn read_range(
163 &self,
164 start: Bound<CommitVersion>,
165 end: Bound<CommitVersion>,
166 batch_size: u64,
167 ) -> CdcStorageResult<CdcBatch> {
168 (**self).read_range(start, end, batch_size)
169 }
170
171 fn count(&self, version: CommitVersion) -> CdcStorageResult<usize> {
172 (**self).count(version)
173 }
174
175 fn min_version(&self) -> CdcStorageResult<Option<CommitVersion>> {
176 (**self).min_version()
177 }
178
179 fn max_version(&self) -> CdcStorageResult<Option<CommitVersion>> {
180 (**self).max_version()
181 }
182
183 fn drop_before(&self, version: CommitVersion, limit: usize) -> CdcStorageResult<DropBeforeResult> {
184 (**self).drop_before(version, limit)
185 }
186
187 fn vacuum(&self) -> CdcStorageResult<()> {
188 (**self).vacuum()
189 }
190
191 fn find_ttl_cutoff(&self, cutoff: DateTime) -> CdcStorageResult<Option<CommitVersion>> {
192 (**self).find_ttl_cutoff(cutoff)
193 }
194}
195
196#[derive(Clone)]
197pub enum CdcStore {
198 Memory(MemoryCdcStorage),
199
200 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
201 Sqlite(CachedCdcStorage<sqlite::storage::SqliteCdcStorage>),
202}
203
204impl Shutdown for CdcStore {
205 fn shutdown(&self) {
206 match self {
207 Self::Memory(_) => {}
208 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
209 Self::Sqlite(s) => s.inner().shutdown(),
210 }
211 }
212}
213
214impl CdcStore {
215 pub fn memory() -> Self {
216 Self::Memory(MemoryCdcStorage::new())
217 }
218
219 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
220 pub fn sqlite(config: SqliteConfig, recent_cache_capacity: usize) -> Self {
221 Self::Sqlite(CachedCdcStorage::new(
222 sqlite::storage::SqliteCdcStorage::new(config),
223 recent_cache_capacity,
224 ))
225 }
226
227 pub fn write(&self, cdc: &Cdc) -> CdcStorageResult<()> {
228 match self {
229 Self::Memory(s) => s.write(cdc),
230 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
231 Self::Sqlite(s) => s.write(cdc),
232 }
233 }
234
235 pub fn read(&self, version: CommitVersion) -> CdcStorageResult<Option<Cdc>> {
236 match self {
237 Self::Memory(s) => s.read(version),
238 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
239 Self::Sqlite(s) => s.read(version),
240 }
241 }
242
243 pub fn read_range(
244 &self,
245 start: Bound<CommitVersion>,
246 end: Bound<CommitVersion>,
247 batch_size: u64,
248 ) -> CdcStorageResult<CdcBatch> {
249 match self {
250 Self::Memory(s) => s.read_range(start, end, batch_size),
251 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
252 Self::Sqlite(s) => s.read_range(start, end, batch_size),
253 }
254 }
255
256 pub fn count(&self, version: CommitVersion) -> CdcStorageResult<usize> {
257 match self {
258 Self::Memory(s) => s.count(version),
259 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
260 Self::Sqlite(s) => s.count(version),
261 }
262 }
263
264 pub fn min_version(&self) -> CdcStorageResult<Option<CommitVersion>> {
265 match self {
266 Self::Memory(s) => s.min_version(),
267 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
268 Self::Sqlite(s) => s.min_version(),
269 }
270 }
271
272 pub fn max_version(&self) -> CdcStorageResult<Option<CommitVersion>> {
273 match self {
274 Self::Memory(s) => s.max_version(),
275 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
276 Self::Sqlite(s) => s.max_version(),
277 }
278 }
279
280 pub fn delete_before(&self, version: CommitVersion, limit: usize) -> CdcStorageResult<DropBeforeResult> {
281 match self {
282 Self::Memory(s) => s.drop_before(version, limit),
283 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
284 Self::Sqlite(s) => s.drop_before(version, limit),
285 }
286 }
287
288 pub fn vacuum(&self) -> CdcStorageResult<()> {
289 match self {
290 Self::Memory(s) => s.vacuum(),
291 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
292 Self::Sqlite(s) => s.vacuum(),
293 }
294 }
295
296 pub fn find_ttl_cutoff(&self, cutoff: DateTime) -> CdcStorageResult<Option<CommitVersion>> {
297 match self {
298 Self::Memory(s) => s.find_ttl_cutoff(cutoff),
299 #[cfg(all(feature = "sqlite", not(target_arch = "wasm32")))]
300 Self::Sqlite(s) => s.find_ttl_cutoff(cutoff),
301 }
302 }
303}
304
305impl CdcStorage for CdcStore {
306 fn write(&self, cdc: &Cdc) -> CdcStorageResult<()> {
307 CdcStore::write(self, cdc)
308 }
309
310 fn read(&self, version: CommitVersion) -> CdcStorageResult<Option<Cdc>> {
311 CdcStore::read(self, version)
312 }
313
314 fn read_range(
315 &self,
316 start: Bound<CommitVersion>,
317 end: Bound<CommitVersion>,
318 batch_size: u64,
319 ) -> CdcStorageResult<CdcBatch> {
320 CdcStore::read_range(self, start, end, batch_size)
321 }
322
323 fn count(&self, version: CommitVersion) -> CdcStorageResult<usize> {
324 CdcStore::count(self, version)
325 }
326
327 fn min_version(&self) -> CdcStorageResult<Option<CommitVersion>> {
328 CdcStore::min_version(self)
329 }
330
331 fn max_version(&self) -> CdcStorageResult<Option<CommitVersion>> {
332 CdcStore::max_version(self)
333 }
334
335 fn drop_before(&self, version: CommitVersion, limit: usize) -> CdcStorageResult<DropBeforeResult> {
336 CdcStore::delete_before(self, version, limit)
337 }
338
339 fn vacuum(&self) -> CdcStorageResult<()> {
340 CdcStore::vacuum(self)
341 }
342
343 fn find_ttl_cutoff(&self, cutoff: DateTime) -> CdcStorageResult<Option<CommitVersion>> {
344 CdcStore::find_ttl_cutoff(self, cutoff)
345 }
346}