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 {
98 let canonical = |fields: &Fields| {
99 MapValue {
100 fields: canonical(fields),
101 }
102 .encode_to_vec()
103 };
104 fields.len() == self.fields().len() && canonical(fields) == canonical(&self.fields())
105 }
106}
107
108pub fn identical(a: &Value, b: &Value) -> bool {
112 canonical_value(a).encode_to_vec() == canonical_value(b).encode_to_vec()
113}
114
115fn canonical(fields: &Fields) -> Fields {
117 fields
118 .sorted()
119 .into_iter()
120 .map(|(key, value)| (key.clone(), canonical_value(value)))
121 .collect()
122}
123
124fn canonical_value(value: &Value) -> Value {
125 let value_type = match &value.value_type {
126 Some(ValueType::MapValue(map)) => ValueType::MapValue(MapValue {
127 fields: canonical(&map.fields),
128 }),
129 Some(ValueType::ArrayValue(array)) => ValueType::ArrayValue(ArrayValue {
130 values: array.values.iter().map(canonical_value).collect(),
131 }),
132 _ => return value.clone(),
133 };
134 Value {
135 value_type: Some(value_type),
136 }
137}
138
139#[derive(Debug, Clone)]
141pub struct Change {
142 pub path: Arc<ResourcePath>,
143 pub before: Option<Arc<StoredDocument>>,
144 pub after: Option<Arc<StoredDocument>>,
145}
146
147#[derive(Debug, Clone)]
149pub struct Commit {
150 pub commit_time: ReadTime,
151 pub changes: Vec<Change>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
156pub enum StoreError {
157 InvalidArgument(String),
158 NotFound(String),
159 AlreadyExists(String),
160 FailedPrecondition(String),
161 Aborted(String),
162}
163
164impl fmt::Display for StoreError {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 match self {
167 Self::InvalidArgument(m)
168 | Self::NotFound(m)
169 | Self::AlreadyExists(m)
170 | Self::FailedPrecondition(m)
171 | Self::Aborted(m) => f.write_str(m),
172 }
173 }
174}
175
176impl std::error::Error for StoreError {}
177
178pub trait WriteBatch {
180 fn commit_time(&self) -> ReadTime;
182
183 fn get(&self, path: &ResourcePath) -> Option<Arc<StoredDocument>>;
186
187 fn set(&mut self, path: &ResourcePath, fields: Fields);
189
190 fn delete(&mut self, path: &ResourcePath);
192}
193
194pub type Visit<'a> = dyn FnMut(&Arc<StoredDocument>) -> ControlFlow<()> + 'a;
196
197#[derive(Debug)]
199pub enum ListItem<'a> {
200 Document(&'a Arc<StoredDocument>),
201 Missing(&'a ResourcePath),
203}
204
205#[derive(Debug, Default, Clone, Copy)]
207pub struct ListOptions<'a> {
208 pub after: Option<&'a ResourcePath>,
210 pub include_missing: bool,
212}
213
214pub trait Store: Send + Sync {
217 fn latest_read_time(&self, database: &str) -> ReadTime;
219
220 fn earliest_read_time(&self, database: &str) -> ReadTime;
222
223 fn get(&self, database: &str, path: &ResourcePath, at: ReadTime)
224 -> Option<Arc<StoredDocument>>;
225
226 fn scan_collection(
228 &self,
229 database: &str,
230 collection: &ResourcePath,
231 at: ReadTime,
232 visit: &mut Visit<'_>,
233 ) {
234 self.list_collection(
235 database,
236 collection,
237 at,
238 ListOptions::default(),
239 &mut |item| match item {
240 ListItem::Document(doc) => visit(doc),
241 ListItem::Missing(_) => ControlFlow::Continue(()),
242 },
243 );
244 }
245
246 fn list_collection(
248 &self,
249 database: &str,
250 collection: &ResourcePath,
251 at: ReadTime,
252 options: ListOptions<'_>,
253 visit: &mut dyn FnMut(ListItem<'_>) -> ControlFlow<()>,
254 );
255
256 fn scan_collection_group(
259 &self,
260 database: &str,
261 parent: &ResourcePath,
262 collection_id: &str,
263 at: ReadTime,
264 visit: &mut Visit<'_>,
265 );
266
267 fn scan_descendants(
270 &self,
271 database: &str,
272 parent: &ResourcePath,
273 at: ReadTime,
274 visit: &mut Visit<'_>,
275 );
276
277 fn list_collection_ids(
280 &self,
281 database: &str,
282 parent: &ResourcePath,
283 at: ReadTime,
284 ) -> Vec<String>;
285
286 fn commit(
288 &self,
289 database: &str,
290 write: &mut dyn FnMut(&mut dyn WriteBatch) -> Result<(), StoreError>,
291 ) -> Result<Commit, StoreError>;
292
293 fn clear_database(&self, database: &str) -> ReadTime;
297
298 fn database_names(&self) -> Vec<String>;
300
301 fn clear(&self);
303}