Skip to main content

harmos_domain/model/storage/query/
contract.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10// Storage Arrow Query Contract
11
12/* === Modules === */
13
14/* === Re-Exports === */
15
16/* === Imports === */
17
18// Standard Library Imports
19use std::time::SystemTime;
20
21// Third Party Imports
22
23// Local Imports
24use crate::model::storage::ArrowIpc;
25use crate::model::storage::codec::{Code, Id, Nested, Optional, Time, Unsigned, coded, product, variants};
26use crate::model::storage::objects::Key;
27use crate::model::storage::{ObjectKey, SchemaKey, StorageCursor};
28use crate::{Identifier, MemorySize, Result};
29
30/* === Constants === */
31
32/* === Macros === */
33
34/* === Type Aliases === */
35
36/* === Structs === */
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
39pub struct RowSlice {
40    pub offset: u64,
41    pub limit: Option<u64>,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct ObjectSelection {
46    pub schema: Option<SchemaKey>,
47    pub key: Option<ObjectKey>,
48    pub versions: ObjectVersions,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct RecordSelection {
53    pub schema: SchemaKey,
54    pub scope: Option<Identifier>,
55    pub item: Option<Identifier>,
56    pub source_start: Option<SystemTime>,
57    pub source_end: Option<SystemTime>,
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
61pub struct StreamSelection {
62    pub schema: SchemaKey,
63    pub scope: Option<Identifier>,
64    pub item: Option<Identifier>,
65    pub stream: Option<Identifier>,
66    pub source_start: Option<SystemTime>,
67    pub source_end: Option<SystemTime>,
68
69    pub after: u64,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
73pub struct SchemaSelection {
74    pub schema: Option<SchemaKey>,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
78pub struct FileSelection {
79    pub scope: Option<Identifier>,
80    pub item: Option<Identifier>,
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct QueryCompletion {
85    pub at: StorageCursor,
86    pub rows: u64,
87}
88
89/* === Enums === */
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
92pub enum ObjectVersions {
93    Latest,
94    Revisions,
95}
96
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub enum Source {
99    Objects(ObjectSelection),
100    Records(RecordSelection),
101    Stream(StreamSelection),
102    Schemas(SchemaSelection),
103    Files(FileSelection),
104}
105
106#[derive(Clone, Debug)]
107pub enum QueryOutput {
108    Schema(ArrowIpc),
109    Batch(ArrowIpc),
110}
111
112/* === Traits === */
113
114/* === Implementations === */
115
116impl ObjectSelection {
117    pub fn follows_revisions(&self) -> bool {
118        self.versions == ObjectVersions::Revisions
119    }
120}
121
122impl Source {
123    pub fn grant_scope(&self) -> String {
124        match self {
125            Self::Objects(selection) => {
126                selection.schema.as_ref().map_or_else(|| "objects:*".to_owned(), |schema| format!("objects:schema:{}", schema.id))
127            }
128            Self::Records(selection) => format!("records:schema:{}", selection.schema.id),
129            Self::Stream(selection) => selection
130                .stream
131                .as_ref()
132                .map_or_else(|| format!("streams:schema:{}", selection.schema.id), |stream| format!("streams:id:{stream}")),
133            Self::Schemas(selection) => {
134                selection.schema.as_ref().map_or_else(|| "schemas:*".to_owned(), |schema| format!("schemas:id:{}", schema.id))
135            }
136            Self::Files(selection) => match (&selection.scope, &selection.item) {
137                (Some(scope), _) => format!("files:scope:{scope}"),
138                (None, Some(item)) => format!("files:id:{item}"),
139                (None, None) => "files:*".to_owned(),
140            },
141        }
142    }
143
144    pub fn validate(&self) -> Result<()> {
145        match self {
146            Self::Records(selection) => window(selection.source_start, selection.source_end),
147            Self::Stream(selection) => window(selection.source_start, selection.source_end),
148            Self::Objects(_) | Self::Schemas(_) | Self::Files(_) => Ok(()),
149        }
150    }
151}
152
153product!(RowSlice { offset: Unsigned => 1, limit: Optional<Unsigned> => 2 });
154product!(ObjectSelection {
155    schema: Optional<Nested<SchemaKey>> => 1,
156    key: Optional<Key> => 2,
157    versions: Code<ObjectVersions> => 3,
158});
159product!(RecordSelection {
160    schema: Nested<SchemaKey> => 1,
161    scope: Optional<Id> => 2,
162    item: Optional<Id> => 3,
163    source_start: Optional<Time> => 4,
164    source_end: Optional<Time> => 5,
165});
166product!(StreamSelection {
167    schema: Nested<SchemaKey> => 1,
168    scope: Optional<Id> => 2,
169    item: Optional<Id> => 3,
170    stream: Optional<Id> => 4,
171    source_start: Optional<Time> => 5,
172    source_end: Optional<Time> => 6,
173    after: Unsigned => 7,
174});
175product!(SchemaSelection { schema: Optional<Nested<SchemaKey>> => 1 });
176product!(FileSelection { scope: Optional<Id> => 1, item: Optional<Id> => 2 });
177product!(QueryCompletion { at: Nested<StorageCursor> => 1, rows: Unsigned => 2 });
178coded!(ObjectVersions { Latest, Revisions });
179variants!(Source {
180    0, 1, "objects": Self::Objects(value) => { value: Nested<ObjectSelection> => 1 } => Self::Objects(value);
181    1, 2, "records": Self::Records(value) => { value: Nested<RecordSelection> => 1 } => Self::Records(value);
182    2, 3, "stream": Self::Stream(value) => { value: Nested<StreamSelection> => 1 } => Self::Stream(value);
183    3, 4, "schemas": Self::Schemas(value) => { value: Nested<SchemaSelection> => 1 } => Self::Schemas(value);
184    4, 5, "files": Self::Files(value) => { value: Nested<FileSelection> => 1 } => Self::Files(value);
185});
186variants!(QueryOutput {
187    0, 1, "schema": Self::Schema(value) => { value: Nested<ArrowIpc> => 1 } => Self::Schema(value);
188    1, 2, "batch": Self::Batch(value) => { value: Nested<ArrowIpc> => 1 } => Self::Batch(value);
189});
190
191macro_rules! memory {
192    ($ty:ty, $($field:ident),+ $(,)?) => {
193        impl MemorySize for $ty {
194            fn memory_size(&self) -> usize {
195                std::mem::size_of::<Self>()$(.saturating_add(self.$field.memory_size()
196                    .saturating_sub(std::mem::size_of_val(&self.$field))))+
197            }
198        }
199    };
200}
201memory!(RowSlice, offset, limit);
202memory!(ObjectSelection, schema, key);
203memory!(RecordSelection, schema, scope, item);
204memory!(StreamSelection, schema, scope, item, stream);
205memory!(SchemaSelection, schema);
206memory!(FileSelection, scope, item);
207memory!(QueryCompletion, at);
208impl MemorySize for Source {
209    fn memory_size(&self) -> usize {
210        std::mem::size_of::<Self>()
211            + match self {
212                Self::Objects(value) => value.memory_size().saturating_sub(std::mem::size_of_val(value)),
213                Self::Records(value) => value.memory_size().saturating_sub(std::mem::size_of_val(value)),
214                Self::Stream(value) => value.memory_size().saturating_sub(std::mem::size_of_val(value)),
215                Self::Schemas(value) => value.memory_size().saturating_sub(std::mem::size_of_val(value)),
216                Self::Files(value) => value.memory_size().saturating_sub(std::mem::size_of_val(value)),
217            }
218    }
219}
220impl MemorySize for QueryOutput {
221    fn memory_size(&self) -> usize {
222        std::mem::size_of::<Self>()
223            + match self {
224                Self::Schema(value) => value.memory_size().saturating_sub(std::mem::size_of_val(value)),
225                Self::Batch(value) => value.memory_size().saturating_sub(std::mem::size_of_val(value)),
226            }
227    }
228}
229
230/* === Public Functions === */
231
232/* === Private Functions === */
233
234fn window(start: Option<SystemTime>, end: Option<SystemTime>) -> Result<()> {
235    match (start, end) {
236        (None, None) => Ok(()),
237        (Some(start), Some(end)) if start < end => Ok(()),
238        _ => Err(crate::model::storage::query::transform::invalid("source window requires increasing start and end")),
239    }
240}
241
242/* === Tests === */