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,
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        backend.read_objects(snapshot, selection, page)
106    }
107}
108
109impl Entry for StoredRecord {
110    type Selection = RecordSelection;
111
112    fn position(&self) -> &StorageCursor {
113        &self.position
114    }
115
116    fn matches(&self, selection: &RecordSelection) -> bool {
117        self.schema == selection.schema
118            && optional(&selection.scope, self.scope.as_ref())
119            && optional(&selection.item, Some(&self.item))
120            && within(selection.source_start, selection.source_end, self.source_time)
121    }
122
123    fn pending(queued: Queued<'_>) -> &[Self] {
124        match queued.0 {
125            Operation::Records(values) => values,
126            _ => &[],
127        }
128    }
129
130    fn scan<'a>(
131        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a RecordSelection, page: ScanPage,
132    ) -> StorageFuture<'a, Vec<Self>> {
133        backend.read_records(snapshot, selection, page)
134    }
135}
136
137impl Entry for StoredRow {
138    type Selection = StreamSelection;
139
140    fn position(&self) -> &StorageCursor {
141        &self.record.position
142    }
143
144    fn matches(&self, selection: &StreamSelection) -> bool {
145        let record = &self.record;
146        record.schema == selection.schema
147            && record.position.sequence > selection.after
148            && optional(&selection.scope, record.scope.as_ref())
149            && optional(&selection.item, Some(&record.item))
150            && optional(&selection.stream, Some(&self.stream))
151            && within(selection.source_start, selection.source_end, record.source_time)
152    }
153
154    fn start(selection: &StreamSelection) -> u64 {
155        selection.after
156    }
157
158    fn pending(queued: Queued<'_>) -> &[Self] {
159        match queued.0 {
160            Operation::Stream(values) => values,
161            _ => &[],
162        }
163    }
164
165    fn scan<'a>(
166        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a StreamSelection, page: ScanPage,
167    ) -> StorageFuture<'a, Vec<Self>> {
168        backend.read_stream(snapshot, selection, page)
169    }
170}
171
172impl Entry for StorageSchema {
173    type Selection = SchemaSelection;
174
175    fn position(&self) -> &StorageCursor {
176        &self.position
177    }
178
179    fn matches(&self, selection: &SchemaSelection) -> bool {
180        selection.schema.as_ref().is_none_or(|key| key == &self.key)
181    }
182
183    fn pending(queued: Queued<'_>) -> &[Self] {
184        match queued.0 {
185            Operation::Schema(value) => std::slice::from_ref(value),
186            Operation::ObjectGroup { schemas, .. } => schemas,
187            _ => &[],
188        }
189    }
190
191    fn scan<'a>(
192        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a SchemaSelection, page: ScanPage,
193    ) -> StorageFuture<'a, Vec<Self>> {
194        backend.read_schemas(snapshot, selection, page)
195    }
196}
197
198impl Entry for FileReference {
199    type Selection = FileSelection;
200
201    fn position(&self) -> &StorageCursor {
202        &self.position
203    }
204
205    fn matches(&self, selection: &FileSelection) -> bool {
206        optional(&selection.scope, self.metadata.scope.as_ref()) && optional(&selection.item, Some(&self.metadata.id))
207    }
208
209    fn pending(queued: Queued<'_>) -> &[Self] {
210        match queued.0 {
211            Operation::File(value) => std::slice::from_ref(value),
212            _ => &[],
213        }
214    }
215
216    fn scan<'a>(
217        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a FileSelection, page: ScanPage,
218    ) -> StorageFuture<'a, Vec<Self>> {
219        backend.read_files(snapshot, selection, page)
220    }
221}
222
223// Identities Are Catalogued From Durable Storage Only; None Wait In The Queue.
224impl Entry for StoredIdentity {
225    type Selection = IdentitySelection;
226
227    fn position(&self) -> &StorageCursor {
228        self.first()
229    }
230
231    fn matches(&self, selection: &IdentitySelection) -> bool {
232        selection.matches(self)
233    }
234
235    fn pending(_: Queued<'_>) -> &[Self] {
236        &[]
237    }
238
239    fn scan<'a>(
240        backend: &'a dyn StorageBackend, snapshot: &'a BackendSnapshot, selection: &'a IdentitySelection, page: ScanPage,
241    ) -> StorageFuture<'a, Vec<Self>> {
242        backend.read_identities(snapshot, selection, page)
243    }
244}
245
246/* === Public Functions === */
247
248/* === Private Functions === */
249
250fn optional(expected: &Option<Identifier>, actual: Option<&Identifier>) -> bool {
251    expected.as_ref().is_none_or(|expected| Some(expected) == actual)
252}
253
254// Half-Open Source Windows Select Only Records Carrying A Source Time.
255fn within(start: Option<SystemTime>, end: Option<SystemTime>, time: Option<SystemTime>) -> bool {
256    match (start, end) {
257        (Some(start), Some(end)) => time.is_some_and(|time| time >= start && time < end),
258        _ => true,
259    }
260}
261
262/* === Tests === */