Skip to main content

harmos_storage/
selection.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10// Stored Entry Kinds Shared By Scans And Live Subscriptions
11
12/* === Modules === */
13
14mod sealed {
15    pub trait Sealed {}
16}
17
18/* === Re-Exports === */
19
20/* === Imports === */
21
22// Standard Library Imports
23use std::time::SystemTime;
24
25// Third Party Imports
26use serde::Serialize;
27
28// Local Imports
29use super::backend::{BackendSnapshot, ScanPage, StorageBackend};
30use super::manager::Operation;
31use super::query::{FileSelection, ObjectSelection, RecordSelection, SchemaSelection, StreamSelection};
32use super::{FileReference, ObjectEntry, StorageCursor, StorageFuture, StorageSchema, StoredRecord, StoredRow};
33use crate::Identifier;
34use harmos_domain::model::storage::{IdentitySelection, StoredIdentity};
35
36/* === Constants === */
37
38/* === Macros === */
39
40/* === Type Aliases === */
41
42/* === Structs === */
43
44pub struct Queued<'a>(&'a Operation);
45
46/* === Enums === */
47
48/* === Traits === */
49
50pub trait Entry: Clone + Serialize + Send + Sync + 'static + sealed::Sealed {
51    type Selection: Send + Sync;
52
53    fn position(&self) -> &StorageCursor;
54    fn matches(&self, selection: &Self::Selection) -> bool;
55
56    fn start(_selection: &Self::Selection) -> u64 {
57        0
58    }
59
60    fn pending(queued: Queued<'_>) -> &[Self];
61    fn scan<'a>(
62        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a Self::Selection, page: ScanPage,
63    ) -> StorageFuture<'a, Vec<Self>>;
64}
65
66/* === Implementations === */
67
68impl<'a> Queued<'a> {
69    pub(super) fn new(operation: &'a Operation) -> Self {
70        Self(operation)
71    }
72}
73
74impl sealed::Sealed for ObjectEntry {}
75impl sealed::Sealed for StoredRecord {}
76impl sealed::Sealed for StoredRow {}
77impl sealed::Sealed for StorageSchema {}
78impl sealed::Sealed for FileReference {}
79impl sealed::Sealed for StoredIdentity {}
80
81impl Entry for ObjectEntry {
82    type Selection = ObjectSelection;
83
84    fn position(&self) -> &StorageCursor {
85        &self.position
86    }
87
88    // Latest selection is resolved by the backend; matching checks identity only.
89    fn matches(&self, selection: &ObjectSelection) -> bool {
90        self.version.check_position(&self.position).is_ok()
91            && selection.schema.as_ref().is_none_or(|key| key == &self.value.schema)
92            && selection.key.as_ref().is_none_or(|key| key == &self.value.item)
93    }
94
95    fn pending(queued: Queued<'_>) -> &[Self] {
96        match queued.0 {
97            Operation::ObjectGroup { objects, .. } => objects.as_slice(),
98            _ => &[],
99        }
100    }
101
102    fn scan<'a>(
103        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a ObjectSelection, page: ScanPage,
104    ) -> StorageFuture<'a, Vec<Self>> {
105        let future = backend.read_objects(snapshot, selection, page);
106        let span = tracing::trace_span!("storage.backend.scan", method = "primary.read_objects");
107        Box::pin(tracing::Instrument::instrument(future, span))
108    }
109}
110
111impl Entry for StoredRecord {
112    type Selection = RecordSelection;
113
114    fn position(&self) -> &StorageCursor {
115        &self.position
116    }
117
118    fn matches(&self, selection: &RecordSelection) -> bool {
119        self.schema == selection.schema
120            && optional(&selection.scope, self.scope.as_ref())
121            && optional(&selection.item, Some(&self.item))
122            && within(selection.source_start, selection.source_end, self.source_time)
123    }
124
125    fn pending(queued: Queued<'_>) -> &[Self] {
126        match queued.0 {
127            Operation::Records(values) => values.as_slice(),
128            _ => &[],
129        }
130    }
131
132    fn scan<'a>(
133        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a RecordSelection, page: ScanPage,
134    ) -> StorageFuture<'a, Vec<Self>> {
135        let future = backend.read_records(snapshot, selection, page);
136        let span = tracing::trace_span!("storage.backend.scan", method = "primary.read_records");
137        Box::pin(tracing::Instrument::instrument(future, span))
138    }
139}
140
141impl Entry for StoredRow {
142    type Selection = StreamSelection;
143
144    fn position(&self) -> &StorageCursor {
145        &self.record.position
146    }
147
148    fn matches(&self, selection: &StreamSelection) -> bool {
149        let record = &self.record;
150        record.schema == selection.schema
151            && record.position.sequence > selection.after
152            && optional(&selection.scope, record.scope.as_ref())
153            && optional(&selection.item, Some(&record.item))
154            && optional(&selection.stream, Some(&self.stream))
155            && within(selection.source_start, selection.source_end, record.source_time)
156    }
157
158    fn start(selection: &StreamSelection) -> u64 {
159        selection.after
160    }
161
162    fn pending(queued: Queued<'_>) -> &[Self] {
163        match queued.0 {
164            Operation::Stream(values) => values.as_slice(),
165            _ => &[],
166        }
167    }
168
169    fn scan<'a>(
170        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a StreamSelection, page: ScanPage,
171    ) -> StorageFuture<'a, Vec<Self>> {
172        let future = backend.read_stream(snapshot, selection, page);
173        let span = tracing::trace_span!("storage.backend.scan", method = "primary.read_stream");
174        Box::pin(tracing::Instrument::instrument(future, span))
175    }
176}
177
178impl Entry for StorageSchema {
179    type Selection = SchemaSelection;
180
181    fn position(&self) -> &StorageCursor {
182        &self.position
183    }
184
185    fn matches(&self, selection: &SchemaSelection) -> bool {
186        selection.schema.as_ref().is_none_or(|key| key == &self.key)
187    }
188
189    fn pending(queued: Queued<'_>) -> &[Self] {
190        match queued.0 {
191            Operation::Schema(value) => value.as_slice(),
192            Operation::ObjectGroup { schemas, .. } => schemas.as_slice(),
193            _ => &[],
194        }
195    }
196
197    fn scan<'a>(
198        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a SchemaSelection, page: ScanPage,
199    ) -> StorageFuture<'a, Vec<Self>> {
200        let future = backend.read_schemas(snapshot, selection, page);
201        let span = tracing::trace_span!("storage.backend.scan", method = "primary.read_schemas");
202        Box::pin(tracing::Instrument::instrument(future, span))
203    }
204}
205
206impl Entry for FileReference {
207    type Selection = FileSelection;
208
209    fn position(&self) -> &StorageCursor {
210        &self.position
211    }
212
213    fn matches(&self, selection: &FileSelection) -> bool {
214        optional(&selection.scope, self.metadata.scope.as_ref()) && optional(&selection.item, Some(&self.metadata.id))
215    }
216
217    fn pending(queued: Queued<'_>) -> &[Self] {
218        match queued.0 {
219            Operation::File(value) => value.as_slice(),
220            _ => &[],
221        }
222    }
223
224    fn scan<'a>(
225        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a FileSelection, page: ScanPage,
226    ) -> StorageFuture<'a, Vec<Self>> {
227        let future = backend.read_files(snapshot, selection, page);
228        let span = tracing::trace_span!("storage.backend.scan", method = "primary.read_files");
229        Box::pin(tracing::Instrument::instrument(future, span))
230    }
231}
232
233// Identities Are Catalogued From Durable Storage Only; None Wait In The Queue.
234impl Entry for StoredIdentity {
235    type Selection = IdentitySelection;
236
237    fn position(&self) -> &StorageCursor {
238        self.first()
239    }
240
241    fn matches(&self, selection: &IdentitySelection) -> bool {
242        selection.matches(self)
243    }
244
245    fn pending(_: Queued<'_>) -> &[Self] {
246        &[]
247    }
248
249    fn scan<'a>(
250        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a IdentitySelection, page: ScanPage,
251    ) -> StorageFuture<'a, Vec<Self>> {
252        let future = backend.read_identities(snapshot, selection, page);
253        let span = tracing::trace_span!("storage.backend.scan", method = "primary.read_identities");
254        Box::pin(tracing::Instrument::instrument(future, span))
255    }
256}
257
258/* === Public Functions === */
259
260/* === Private Functions === */
261
262fn optional(expected: &Option<Identifier>, actual: Option<&Identifier>) -> bool {
263    expected.as_ref().is_none_or(|expected| Some(expected) == actual)
264}
265
266// Half-Open Source Windows Select Only Records Carrying A Source Time.
267fn within(start: Option<SystemTime>, end: Option<SystemTime>, time: Option<SystemTime>) -> bool {
268    match (start, end) {
269        (Some(start), Some(end)) => time.is_some_and(|time| time >= start && time < end),
270        _ => true,
271    }
272}
273
274/* === Tests === */