mod memory;
use std::{fmt, ops::ControlFlow, sync::Arc};
use hidane_proto::google::firestore::v1::{ArrayValue, MapValue, Value, value::ValueType};
use hidane_proto::ordered::Fields;
use prost::Message as _;
use prost_types::Timestamp;
pub use memory::MemoryStore;
use crate::path::ResourcePath;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ReadTime(pub i64);
impl ReadTime {
pub const MAX: Self = Self(i64::MAX);
pub fn from_timestamp(ts: &Timestamp) -> Self {
Self(ts.seconds * 1_000_000 + i64::from(ts.nanos) / 1_000)
}
pub fn to_timestamp(self) -> Timestamp {
#[allow(clippy::cast_possible_truncation)]
Timestamp {
seconds: self.0.div_euclid(1_000_000),
nanos: (self.0.rem_euclid(1_000_000) * 1_000) as i32,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StoredDocument {
pub path: Arc<ResourcePath>,
encoded_fields: Box<[u8]>,
pub create_time: Timestamp,
pub update_time: Timestamp,
}
impl StoredDocument {
pub fn new(
path: Arc<ResourcePath>,
fields: Fields,
create_time: Timestamp,
update_time: Timestamp,
) -> Self {
Self {
path,
encoded_fields: MapValue { fields }.encode_to_vec().into_boxed_slice(),
create_time,
update_time,
}
}
pub fn fields(&self) -> Fields {
MapValue::decode(&*self.encoded_fields)
.expect("fields were encoded by StoredDocument::new")
.fields
}
pub fn encoded_len(&self) -> usize {
self.encoded_fields.len()
}
pub fn has_fields(&self, fields: &Fields) -> bool {
let canonical = |fields: &Fields| {
MapValue {
fields: canonical(fields),
}
.encode_to_vec()
};
fields.len() == self.fields().len() && canonical(fields) == canonical(&self.fields())
}
}
pub fn identical(a: &Value, b: &Value) -> bool {
canonical_value(a).encode_to_vec() == canonical_value(b).encode_to_vec()
}
fn canonical(fields: &Fields) -> Fields {
fields
.sorted()
.into_iter()
.map(|(key, value)| (key.clone(), canonical_value(value)))
.collect()
}
fn canonical_value(value: &Value) -> Value {
let value_type = match &value.value_type {
Some(ValueType::MapValue(map)) => ValueType::MapValue(MapValue {
fields: canonical(&map.fields),
}),
Some(ValueType::ArrayValue(array)) => ValueType::ArrayValue(ArrayValue {
values: array.values.iter().map(canonical_value).collect(),
}),
_ => return value.clone(),
};
Value {
value_type: Some(value_type),
}
}
#[derive(Debug, Clone)]
pub struct Change {
pub path: Arc<ResourcePath>,
pub before: Option<Arc<StoredDocument>>,
pub after: Option<Arc<StoredDocument>>,
}
#[derive(Debug, Clone)]
pub struct Commit {
pub commit_time: ReadTime,
pub changes: Vec<Change>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StoreError {
InvalidArgument(String),
NotFound(String),
AlreadyExists(String),
FailedPrecondition(String),
Aborted(String),
}
impl fmt::Display for StoreError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidArgument(m)
| Self::NotFound(m)
| Self::AlreadyExists(m)
| Self::FailedPrecondition(m)
| Self::Aborted(m) => f.write_str(m),
}
}
}
impl std::error::Error for StoreError {}
pub trait WriteBatch {
fn commit_time(&self) -> ReadTime;
fn get(&self, path: &ResourcePath) -> Option<Arc<StoredDocument>>;
fn set(&mut self, path: &ResourcePath, fields: Fields);
fn delete(&mut self, path: &ResourcePath);
}
pub type Visit<'a> = dyn FnMut(&Arc<StoredDocument>) -> ControlFlow<()> + 'a;
#[derive(Debug)]
pub enum ListItem<'a> {
Document(&'a Arc<StoredDocument>),
Missing(&'a ResourcePath),
}
#[derive(Debug, Default, Clone, Copy)]
pub struct ListOptions<'a> {
pub after: Option<&'a ResourcePath>,
pub include_missing: bool,
}
pub trait Store: Send + Sync {
fn latest_read_time(&self, database: &str) -> ReadTime;
fn earliest_read_time(&self, database: &str) -> ReadTime;
fn get(&self, database: &str, path: &ResourcePath, at: ReadTime)
-> Option<Arc<StoredDocument>>;
fn scan_collection(
&self,
database: &str,
collection: &ResourcePath,
at: ReadTime,
visit: &mut Visit<'_>,
) {
self.list_collection(
database,
collection,
at,
ListOptions::default(),
&mut |item| match item {
ListItem::Document(doc) => visit(doc),
ListItem::Missing(_) => ControlFlow::Continue(()),
},
);
}
fn list_collection(
&self,
database: &str,
collection: &ResourcePath,
at: ReadTime,
options: ListOptions<'_>,
visit: &mut dyn FnMut(ListItem<'_>) -> ControlFlow<()>,
);
fn scan_collection_group(
&self,
database: &str,
parent: &ResourcePath,
collection_id: &str,
at: ReadTime,
visit: &mut Visit<'_>,
);
fn scan_descendants(
&self,
database: &str,
parent: &ResourcePath,
at: ReadTime,
visit: &mut Visit<'_>,
);
fn list_collection_ids(
&self,
database: &str,
parent: &ResourcePath,
at: ReadTime,
) -> Vec<String>;
fn commit(
&self,
database: &str,
write: &mut dyn FnMut(&mut dyn WriteBatch) -> Result<(), StoreError>,
) -> Result<Commit, StoreError>;
fn clear_database(&self, database: &str) -> ReadTime;
fn database_names(&self) -> Vec<String>;
fn clear(&self);
}