1mod memory;
18
19use std::{fmt, ops::ControlFlow, sync::Arc};
20
21use hidane_proto::google::firestore::v1::{ArrayValue, MapValue, Value, value::ValueType};
22use hidane_proto::ordered::Fields;
23use prost::Message as _;
24use prost_types::Timestamp;
25
26pub use memory::MemoryStore;
27
28use crate::path::ResourcePath;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct ReadTime(pub i64);
34
35impl ReadTime {
36 pub const MAX: Self = Self(i64::MAX);
37
38 pub fn from_timestamp(ts: &Timestamp) -> Self {
40 Self(ts.seconds * 1_000_000 + i64::from(ts.nanos) / 1_000)
41 }
42
43 pub fn to_timestamp(self) -> Timestamp {
44 #[allow(clippy::cast_possible_truncation)]
45 Timestamp {
46 seconds: self.0.div_euclid(1_000_000),
47 nanos: (self.0.rem_euclid(1_000_000) * 1_000) as i32,
48 }
49 }
50}
51
52#[derive(Debug, Clone, PartialEq)]
58pub struct StoredDocument {
59 pub path: Arc<ResourcePath>,
61 encoded_fields: Box<[u8]>,
62 pub create_time: Timestamp,
63 pub update_time: Timestamp,
64}
65
66impl StoredDocument {
67 pub fn new(
68 path: Arc<ResourcePath>,
69 fields: Fields,
70 create_time: Timestamp,
71 update_time: Timestamp,
72 ) -> Self {
73 Self {
74 path,
75 encoded_fields: MapValue { fields }.encode_to_vec().into_boxed_slice(),
76 create_time,
77 update_time,
78 }
79 }
80
81 pub fn fields(&self) -> Fields {
83 MapValue::decode(&*self.encoded_fields)
84 .expect("fields were encoded by StoredDocument::new")
85 .fields
86 }
87
88 pub fn encoded_len(&self) -> usize {
90 self.encoded_fields.len()
91 }
92
93 pub fn has_fields(&self, fields: &Fields) -> bool {
97 let canonical = |fields: &Fields| {
98 MapValue {
99 fields: canonical(fields),
100 }
101 .encode_to_vec()
102 };
103 fields.len() == self.fields().len() && canonical(fields) == canonical(&self.fields())
104 }
105}
106
107fn canonical(fields: &Fields) -> Fields {
109 fields
110 .sorted()
111 .into_iter()
112 .map(|(key, value)| (key.clone(), canonical_value(value)))
113 .collect()
114}
115
116fn canonical_value(value: &Value) -> Value {
117 let value_type = match &value.value_type {
118 Some(ValueType::MapValue(map)) => ValueType::MapValue(MapValue {
119 fields: canonical(&map.fields),
120 }),
121 Some(ValueType::ArrayValue(array)) => ValueType::ArrayValue(ArrayValue {
122 values: array.values.iter().map(canonical_value).collect(),
123 }),
124 _ => return value.clone(),
125 };
126 Value {
127 value_type: Some(value_type),
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct Change {
134 pub path: Arc<ResourcePath>,
135 pub before: Option<Arc<StoredDocument>>,
136 pub after: Option<Arc<StoredDocument>>,
137}
138
139#[derive(Debug, Clone)]
141pub struct Commit {
142 pub commit_time: ReadTime,
143 pub changes: Vec<Change>,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum StoreError {
149 InvalidArgument(String),
150 NotFound(String),
151 AlreadyExists(String),
152 FailedPrecondition(String),
153 Aborted(String),
154}
155
156impl fmt::Display for StoreError {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 match self {
159 Self::InvalidArgument(m)
160 | Self::NotFound(m)
161 | Self::AlreadyExists(m)
162 | Self::FailedPrecondition(m)
163 | Self::Aborted(m) => f.write_str(m),
164 }
165 }
166}
167
168impl std::error::Error for StoreError {}
169
170pub trait WriteBatch {
172 fn commit_time(&self) -> ReadTime;
174
175 fn get(&self, path: &ResourcePath) -> Option<Arc<StoredDocument>>;
178
179 fn set(&mut self, path: &ResourcePath, fields: Fields);
181
182 fn delete(&mut self, path: &ResourcePath);
184}
185
186pub type Visit<'a> = dyn FnMut(&Arc<StoredDocument>) -> ControlFlow<()> + 'a;
188
189#[derive(Debug)]
191pub enum ListItem<'a> {
192 Document(&'a Arc<StoredDocument>),
193 Missing(&'a ResourcePath),
195}
196
197#[derive(Debug, Default, Clone, Copy)]
199pub struct ListOptions<'a> {
200 pub after: Option<&'a ResourcePath>,
202 pub include_missing: bool,
204}
205
206pub trait Store: Send + Sync {
209 fn latest_read_time(&self, database: &str) -> ReadTime;
211
212 fn earliest_read_time(&self, database: &str) -> ReadTime;
214
215 fn get(&self, database: &str, path: &ResourcePath, at: ReadTime)
216 -> Option<Arc<StoredDocument>>;
217
218 fn scan_collection(
220 &self,
221 database: &str,
222 collection: &ResourcePath,
223 at: ReadTime,
224 visit: &mut Visit<'_>,
225 ) {
226 self.list_collection(
227 database,
228 collection,
229 at,
230 ListOptions::default(),
231 &mut |item| match item {
232 ListItem::Document(doc) => visit(doc),
233 ListItem::Missing(_) => ControlFlow::Continue(()),
234 },
235 );
236 }
237
238 fn list_collection(
240 &self,
241 database: &str,
242 collection: &ResourcePath,
243 at: ReadTime,
244 options: ListOptions<'_>,
245 visit: &mut dyn FnMut(ListItem<'_>) -> ControlFlow<()>,
246 );
247
248 fn scan_collection_group(
251 &self,
252 database: &str,
253 parent: &ResourcePath,
254 collection_id: &str,
255 at: ReadTime,
256 visit: &mut Visit<'_>,
257 );
258
259 fn scan_descendants(
262 &self,
263 database: &str,
264 parent: &ResourcePath,
265 at: ReadTime,
266 visit: &mut Visit<'_>,
267 );
268
269 fn list_collection_ids(
272 &self,
273 database: &str,
274 parent: &ResourcePath,
275 at: ReadTime,
276 ) -> Vec<String>;
277
278 fn commit(
280 &self,
281 database: &str,
282 write: &mut dyn FnMut(&mut dyn WriteBatch) -> Result<(), StoreError>,
283 ) -> Result<Commit, StoreError>;
284
285 fn clear_database(&self, database: &str) -> ReadTime;
289
290 fn database_names(&self) -> Vec<String>;
292
293 fn clear(&self);
295}