Skip to main content

klukai_types/
base.rs

1use std::{
2    fmt,
3    ops::{Add, RangeInclusive, Sub},
4};
5
6use rangemap::StepLite;
7use rusqlite::{ToSql, types::FromSql};
8use serde::{Deserialize, Serialize};
9use speedy::{Context, Readable, Writable};
10
11#[macro_export]
12macro_rules! dbvr {
13    ($start:literal, $end:literal) => {
14        $crate::base::CrsqlDbVersionRange::new(
15            $crate::base::CrsqlDbVersion($start),
16            $crate::base::CrsqlDbVersion($end),
17        )
18    };
19}
20
21#[macro_export]
22macro_rules! dbvri {
23    ($start:literal, $end:literal) => {
24        $crate::base::CrsqlDbVersion($start)..=$crate::base::CrsqlDbVersion($end)
25    };
26}
27
28#[macro_export]
29macro_rules! dbsr {
30    ($start:literal, $end:literal) => {
31        $crate::base::CrsqlSeqRange::new(
32            $crate::base::CrsqlSeq($start),
33            $crate::base::CrsqlSeq($end),
34        )
35    };
36}
37
38#[macro_export]
39macro_rules! dbsri {
40    ($start:literal, $end:literal) => {
41        $crate::base::CrsqlSeq($start)..=$crate::base::CrsqlSeq($end)
42    };
43}
44
45#[derive(
46    Copy, Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize,
47)]
48#[serde(transparent)]
49pub struct CrsqlDbVersion(pub u64);
50
51impl CrsqlDbVersion {
52    #[inline]
53    pub fn chunked_iter(
54        range: CrsqlDbVersionRange,
55        chunk_size: usize,
56    ) -> ChunkedCrsqlDbVersionIterator {
57        ChunkedCrsqlDbVersionIterator {
58            current: range.start_int(),
59            end: range.end_int(),
60            chunk_size: chunk_size as u64,
61        }
62    }
63}
64
65pub struct ChunkedCrsqlDbVersionIterator {
66    current: u64,
67    end: u64,
68    chunk_size: u64,
69}
70
71impl Iterator for ChunkedCrsqlDbVersionIterator {
72    type Item = CrsqlDbVersionRange;
73
74    #[inline]
75    fn next(&mut self) -> Option<Self::Item> {
76        if self.current > self.end {
77            return None;
78        }
79
80        // Avoid overflows if the end of the range happens to be within chunk_size
81        // of u64::MAX
82        let next_step = self.current.saturating_add(self.chunk_size);
83
84        let next = CrsqlDbVersionRange::new(
85            CrsqlDbVersion(self.current),
86            CrsqlDbVersion(next_step.min(self.end)),
87        );
88
89        self.current = next_step;
90
91        Some(next)
92    }
93}
94
95impl StepLite for CrsqlDbVersion {
96    fn add_one(&self) -> Self {
97        Self(self.0 + 1)
98    }
99
100    fn sub_one(&self) -> Self {
101        Self(self.0 - 1)
102    }
103}
104
105impl From<CrsqlDbVersion> for u64 {
106    fn from(v: CrsqlDbVersion) -> Self {
107        v.0
108    }
109}
110
111impl ToSql for CrsqlDbVersion {
112    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
113        self.0.to_sql()
114    }
115}
116
117impl FromSql for CrsqlDbVersion {
118    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
119        u64::column_result(value).map(Self)
120    }
121}
122
123impl Add<u64> for CrsqlDbVersion {
124    type Output = Self;
125
126    fn add(self, rhs: u64) -> Self::Output {
127        Self(self.0.add(rhs))
128    }
129}
130
131impl Sub<u64> for CrsqlDbVersion {
132    type Output = Self;
133
134    fn sub(self, rhs: u64) -> Self::Output {
135        Self(self.0.sub(rhs))
136    }
137}
138
139impl<'a, C> Readable<'a, C> for CrsqlDbVersion
140where
141    C: Context,
142{
143    fn read_from<R: speedy::Reader<'a, C>>(reader: &mut R) -> Result<Self, <C as Context>::Error> {
144        u64::read_from(reader).map(Self)
145    }
146}
147
148impl<C> Writable<C> for CrsqlDbVersion
149where
150    C: Context,
151{
152    fn write_to<T: ?Sized + speedy::Writer<C>>(
153        &self,
154        writer: &mut T,
155    ) -> Result<(), <C as Context>::Error> {
156        self.0.write_to(writer)
157    }
158}
159
160impl fmt::Display for CrsqlDbVersion {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        self.0.fmt(f)
163    }
164}
165
166#[derive(
167    Copy, Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize,
168)]
169#[serde(transparent)]
170pub struct CrsqlSeq(pub u64);
171
172impl StepLite for CrsqlSeq {
173    fn add_one(&self) -> Self {
174        Self(self.0 + 1)
175    }
176
177    fn sub_one(&self) -> Self {
178        Self(self.0 - 1)
179    }
180}
181
182impl ToSql for CrsqlSeq {
183    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
184        self.0.to_sql()
185    }
186}
187
188impl FromSql for CrsqlSeq {
189    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
190        u64::column_result(value).map(Self)
191    }
192}
193
194impl Add<u64> for CrsqlSeq {
195    type Output = Self;
196
197    fn add(self, rhs: u64) -> Self::Output {
198        Self(self.0.add(rhs))
199    }
200}
201
202impl Sub<u64> for CrsqlSeq {
203    type Output = Self;
204
205    fn sub(self, rhs: u64) -> Self::Output {
206        Self(self.0.sub(rhs))
207    }
208}
209
210impl<'a, C> Readable<'a, C> for CrsqlSeq
211where
212    C: Context,
213{
214    fn read_from<R: speedy::Reader<'a, C>>(reader: &mut R) -> Result<Self, <C as Context>::Error> {
215        u64::read_from(reader).map(Self)
216    }
217}
218
219impl<C> Writable<C> for CrsqlSeq
220where
221    C: Context,
222{
223    fn write_to<T: ?Sized + speedy::Writer<C>>(
224        &self,
225        writer: &mut T,
226    ) -> Result<(), <C as Context>::Error> {
227        self.0.write_to(writer)
228    }
229}
230
231impl fmt::Display for CrsqlSeq {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        self.0.fmt(f)
234    }
235}
236
237macro_rules! range {
238    ($inner:ident, $name:ident, $iter:ident) => {
239        #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
240        pub struct $name {
241            start: u64,
242            end: Option<std::num::NonZeroU64>,
243        }
244
245        impl $name {
246            /// Equivalent of `(<start>..=<end>).into()`
247            #[inline]
248            pub fn new(start: $inner, end: $inner) -> Self {
249                Self {
250                    start: start.0,
251                    end: std::num::NonZeroU64::new(end.0),
252                }
253            }
254
255            /// Creates an empty range
256            #[inline]
257            pub fn empty() -> Self {
258                Self {
259                    start: 0,
260                    end: None,
261                }
262            }
263
264            /// Creates a range that starts and ends at the specified value
265            #[inline]
266            pub fn single(single: $inner) -> Self {
267                Self {
268                    start: single.0,
269                    end: std::num::NonZeroU64::new(single.0),
270                }
271            }
272
273            /// The typed start of the range
274            #[inline]
275            pub fn start(&self) -> $inner {
276                $inner(self.start)
277            }
278
279            /// The typed end of the range
280            #[inline]
281            pub fn end(&self) -> $inner {
282                $inner(self.end_int())
283            }
284
285            /// The u64 at the beginning of the range
286            #[inline]
287            pub fn start_int(&self) -> u64 {
288                self.start
289            }
290
291            /// The u64 at the end of the range
292            #[inline]
293            pub fn end_int(&self) -> u64 {
294                self.end.map_or(0, |e| e.get())
295            }
296
297            /// The length of this range, ie the number of values
298            /// that would be yielded on iteration
299            #[inline]
300            pub fn len(&self) -> usize {
301                let end = self.end.map_or(0, |e| e.get());
302                if end >= self.start {
303                    (end - self.start) as usize + 1
304                } else {
305                    0
306                }
307            }
308
309            #[inline]
310            pub fn is_empty(&self) -> bool {
311                self.len() == 0
312            }
313        }
314
315        impl From<RangeInclusive<$inner>> for $name {
316            #[inline]
317            fn from(r: RangeInclusive<$inner>) -> Self {
318                let (start, end) = r.into_inner();
319                Self {
320                    start: start.0,
321                    end: std::num::NonZeroU64::new(end.0),
322                }
323            }
324        }
325
326        impl<'s> From<&'s RangeInclusive<$inner>> for $name {
327            #[inline]
328            fn from(r: &'s RangeInclusive<$inner>) -> Self {
329                Self {
330                    start: r.start().0,
331                    end: std::num::NonZeroU64::new(r.end().0),
332                }
333            }
334        }
335
336        impl From<$name> for RangeInclusive<$inner> {
337            #[inline]
338            fn from(r: $name) -> Self {
339                Self::new(r.start(), r.end())
340            }
341        }
342
343        impl<'s> From<&'s $name> for RangeInclusive<$inner> {
344            #[inline]
345            fn from(r: &'s $name) -> Self {
346                Self::new(r.start(), r.end())
347            }
348        }
349
350        impl Iterator for $name {
351            type Item = $inner;
352
353            #[inline]
354            fn next(&mut self) -> Option<Self::Item> {
355                let end = self.end?;
356
357                let next = if self.start < end.get() {
358                    let n = $inner(self.start);
359                    self.start += 1;
360                    n
361                } else {
362                    self.end = None;
363                    $inner(self.start)
364                };
365
366                Some(next)
367            }
368
369            #[inline]
370            fn size_hint(&self) -> (usize, Option<usize>) {
371                let len = self.len();
372                (len, Some(len))
373            }
374        }
375
376        impl<'a, C: speedy::Context> Readable<'a, C> for $name {
377            #[inline]
378            fn read_from<R: speedy::Reader<'a, C>>(reader: &mut R) -> Result<Self, C::Error> {
379                let start = reader.read_value()?;
380                let end: u64 = reader.read_value()?;
381                Ok(Self {
382                    start,
383                    end: std::num::NonZeroU64::new(end),
384                })
385            }
386
387            #[inline]
388            fn minimum_bytes_needed() -> usize {
389                16
390            }
391        }
392
393        impl<C: speedy::Context> Writable<C> for $name {
394            #[inline]
395            fn write_to<W: ?Sized + speedy::Writer<C>>(
396                &self,
397                writer: &mut W,
398            ) -> Result<(), C::Error> {
399                self.start.write_to(writer)?;
400                self.end.map_or(0, |e| e.get()).write_to(writer)
401            }
402
403            #[inline]
404            fn bytes_needed(&self) -> Result<usize, C::Error> {
405                Ok(16)
406            }
407        }
408    };
409}
410
411range!(CrsqlDbVersion, CrsqlDbVersionRange, CrsqlDbVersionIter);
412range!(CrsqlSeq, CrsqlSeqRange, CrsqlSeqIter);
413
414#[cfg(test)]
415mod test {
416    use super::*;
417
418    #[test]
419    fn chunked_iter() {
420        /// This was the original implementation in corro-agent::api::peer, reproduced
421        /// here in stable form to validate the range implementation matches
422        fn chunk_range(
423            range: RangeInclusive<u64>,
424            chunk_size: usize,
425        ) -> impl Iterator<Item = RangeInclusive<u64>> {
426            let end = *range.end();
427            range.step_by(chunk_size).map(move |block_start| {
428                let block_end = (block_start + chunk_size as u64).min(end);
429                block_start..=block_end
430            })
431        }
432
433        #[track_caller]
434        fn matches_step_by(range: RangeInclusive<u64>, chunk_size: usize) {
435            let mut ours = CrsqlDbVersion::chunked_iter(
436                CrsqlDbVersionRange::new(
437                    CrsqlDbVersion(*range.start()),
438                    CrsqlDbVersion(*range.end()),
439                ),
440                chunk_size,
441            );
442            let mut stds = chunk_range(range, chunk_size);
443
444            loop {
445                match (ours.next(), stds.next()) {
446                    (Some(o), Some(s)) => {
447                        assert_eq!(o.start_int(), *s.start());
448                        assert_eq!(o.end_int(), *s.end());
449                    }
450                    (None, None) => {
451                        break;
452                    }
453                    (Some(o), None) => {
454                        panic!("step_by was done but we still had {o:?}");
455                    }
456                    (None, Some(s)) => {
457                        panic!("we were done but step_by still had {s:?}");
458                    }
459                }
460            }
461        }
462
463        // a range is always emitted, even on empty ranges
464        {
465            matches_step_by(0..=0, 10);
466            matches_step_by(1987..=1987, 1);
467
468            // ...unless the start > end
469            #[allow(clippy::reversed_empty_ranges)]
470            matches_step_by(2..=0, 1);
471        }
472
473        // exact
474        {
475            matches_step_by(1..=10, 11);
476            matches_step_by(0..=1, 1);
477            matches_step_by(1..=13, 14);
478        }
479
480        // remainder
481        {
482            matches_step_by(2001..=11000, 13);
483        }
484    }
485
486    #[test]
487    fn ranges() {
488        let r = dbsr!(1, 1000);
489        assert_eq!(r.len(), 1000);
490
491        let ri = RangeInclusive::from(r);
492        assert_eq!(ri.start(), &r.start());
493        assert_eq!(ri.end(), &r.end());
494
495        for (i, (ours, theirs)) in r.zip(r.start_int()..=r.end_int()).enumerate() {
496            assert_eq!(ours.0, theirs, "oops {i}");
497        }
498
499        let max =
500            CrsqlDbVersionRange::from(CrsqlDbVersion(u64::MAX - 1)..=CrsqlDbVersion(u64::MAX));
501        assert_eq!(max.len(), 2);
502        assert_eq!(max.into_iter().count(), 2);
503    }
504
505    #[test]
506    fn serialization() {
507        #[track_caller]
508        fn speedy(input: CrsqlDbVersionRange) {
509            let ser = input.write_to_vec().unwrap();
510            let deser = CrsqlDbVersionRange::read_from_buffer(&ser).unwrap();
511
512            assert_eq!(input, deser);
513        }
514
515        speedy(CrsqlDbVersionRange::empty());
516        speedy(CrsqlDbVersionRange::new(
517            CrsqlDbVersion(0),
518            CrsqlDbVersion(u64::MAX),
519        ));
520        speedy(CrsqlDbVersionRange::new(
521            CrsqlDbVersion(110),
522            CrsqlDbVersion(0),
523        ));
524    }
525}