Skip to main content

harmos_storage/
batch.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17use std::num::NonZeroUsize;
18// Third Party Imports
19
20// Local Imports
21use super::manager::encoded_size;
22use super::{FileReference, ObjectEntry, StorageCursor, StorageSchema, StoredRecord, StoredRow};
23use crate::{Error, Result};
24
25/* === Constants === */
26
27/* === Macros === */
28
29/* === Type Aliases === */
30
31/* === Structs === */
32
33#[derive(Clone, Copy)]
34pub struct StorageBatch<'a> {
35    parts: &'a [StoragePart<'a>],
36    first: &'a StorageCursor,
37    through: &'a StorageCursor,
38    len: usize,
39    bytes: usize,
40}
41
42/* === Enums === */
43
44#[derive(Clone, Copy)]
45pub enum StoragePart<'a> {
46    Schemas(&'a [StorageSchema]),
47    Objects(&'a [ObjectEntry]),
48    Records(&'a [StoredRecord]),
49    Stream(&'a [StoredRow]),
50    Files(&'a [FileReference]),
51}
52
53#[derive(Clone, Copy)]
54pub enum StorageEntry<'a> {
55    Schema(&'a StorageSchema),
56    Object(&'a ObjectEntry),
57    Record(&'a StoredRecord),
58    Row(&'a StoredRow),
59    File(&'a FileReference),
60}
61
62/* === Traits === */
63
64/* === Implementations === */
65
66impl<'a> StorageBatch<'a> {
67    pub fn new(parts: &'a [StoragePart<'a>], max_bytes: NonZeroUsize) -> Result<Self> {
68        let mut entries = parts.iter().copied().flat_map(StoragePart::entries);
69        let first = entries.next().ok_or(invalid("batch is empty"))?;
70        let mut through = first.position();
71        if through.sequence == 0 {
72            return Err(invalid("position zero is not a data item"));
73        }
74        let mut bytes = first.encoded_bytes()?;
75        let mut len = 1usize;
76        for entry in entries {
77            through.history.check(&entry.position().history)?;
78            if through.sequence.checked_add(1) != Some(entry.position().sequence) {
79                return Err(invalid("batch positions are not contiguous"));
80            }
81            bytes = bytes.checked_add(entry.encoded_bytes()?).ok_or(invalid("batch byte count overflow"))?;
82            if bytes > max_bytes.get() {
83                return Err(invalid("batch exceeds the encoded byte bound"));
84            }
85            len = len.checked_add(1).ok_or(invalid("batch entry count overflow"))?;
86            through = entry.position();
87        }
88        if bytes > max_bytes.get() {
89            return Err(invalid("batch exceeds the encoded byte bound"));
90        }
91        Ok(Self { parts, first: first.position(), through, len, bytes })
92    }
93
94    pub fn parts(&self) -> impl Iterator<Item = StoragePart<'a>> + '_ {
95        self.parts.iter().copied().filter_map(|part| {
96            let start = part.partition(self.first.sequence, false);
97            let end = part.partition(self.through.sequence, true);
98            (end > start).then(|| part.slice(start, end))
99        })
100    }
101
102    pub fn entries(&self) -> impl Iterator<Item = StorageEntry<'a>> + '_ {
103        self.parts().flat_map(StoragePart::entries)
104    }
105
106    pub fn first(&self) -> &'a StorageCursor {
107        self.first
108    }
109
110    pub fn through(&self) -> &'a StorageCursor {
111        self.through
112    }
113
114    pub fn len(&self) -> usize {
115        self.len
116    }
117
118    pub fn is_empty(&self) -> bool {
119        false
120    }
121
122    pub fn encoded_bytes(&self) -> usize {
123        self.bytes
124    }
125}
126
127impl<'a> StoragePart<'a> {
128    // Admitted spans are in cursor order. Seek a saved prefix logarithmically;
129    // retrying a large group must not rescan its whole saved prefix each time.
130    fn partition(self, sequence: u64, inclusive: bool) -> usize {
131        let mut start = 0;
132        let mut end = self.len();
133        while start < end {
134            let middle = start + (end - start) / 2;
135            let position = self.slice(middle, middle + 1).entries().next().unwrap().position().sequence;
136            if position < sequence || (inclusive && position == sequence) {
137                start = middle + 1;
138            } else {
139                end = middle;
140            }
141        }
142        start
143    }
144
145    fn slice(self, start: usize, end: usize) -> Self {
146        match self {
147            Self::Schemas(values) => Self::Schemas(&values[start..end]),
148            Self::Objects(values) => Self::Objects(&values[start..end]),
149            Self::Records(values) => Self::Records(&values[start..end]),
150            Self::Stream(values) => Self::Stream(&values[start..end]),
151            Self::Files(values) => Self::Files(&values[start..end]),
152        }
153    }
154
155    pub fn len(&self) -> usize {
156        match self {
157            Self::Schemas(values) => values.len(),
158            Self::Objects(values) => values.len(),
159            Self::Records(values) => values.len(),
160            Self::Stream(values) => values.len(),
161            Self::Files(values) => values.len(),
162        }
163    }
164
165    pub fn is_empty(&self) -> bool {
166        self.len() == 0
167    }
168
169    pub fn entries(self) -> impl Iterator<Item = StorageEntry<'a>> + 'a {
170        (0..self.len()).map(move |index| match self {
171            Self::Schemas(values) => StorageEntry::Schema(&values[index]),
172            Self::Objects(values) => StorageEntry::Object(&values[index]),
173            Self::Records(values) => StorageEntry::Record(&values[index]),
174            Self::Stream(values) => StorageEntry::Row(&values[index]),
175            Self::Files(values) => StorageEntry::File(&values[index]),
176        })
177    }
178}
179
180impl<'a> StorageEntry<'a> {
181    pub fn position(self) -> &'a StorageCursor {
182        match self {
183            Self::Schema(value) => &value.position,
184            Self::Object(value) => &value.position,
185            Self::Record(value) => &value.position,
186            Self::Row(value) => &value.record.position,
187            Self::File(value) => &value.position,
188        }
189    }
190
191    pub(super) fn encoded_bytes(self) -> Result<usize> {
192        match self {
193            Self::Schema(value) => value.encoded_bytes(),
194            Self::Object(value) => {
195                value.version.check_position(&value.position)?;
196                encoded_size(value)
197            }
198            Self::Record(value) => encoded_size(value),
199            Self::Row(value) => encoded_size(value),
200            Self::File(value) => encoded_size(value),
201        }
202    }
203}
204
205/* === Public Functions === */
206
207/* === Private Functions === */
208
209fn invalid(reason: &'static str) -> Error {
210    Error::InvalidInput { field: "storage batch", reason }
211}
212
213/* === Tests === */