1mod sealed {
15 pub trait Sealed {}
16}
17
18use std::time::SystemTime;
24
25use serde::Serialize;
27
28use 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
36pub struct Queued<'a>(&'a Operation);
45
46pub 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
66impl<'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 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
233impl 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
258fn optional(expected: &Option<Identifier>, actual: Option<&Identifier>) -> bool {
263 expected.as_ref().is_none_or(|expected| Some(expected) == actual)
264}
265
266fn 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