use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use spacedb_access::{
authorize, AccessRequest, Decision, Identity, MemKeyDirectory, Ops, RevocationSet, Scope,
SignedCapability,
};
use spacedb_consistency::{Outcome, QuorumGroup, StrongResult, Tier};
use spacedb_crdt::{CrdtDoc, CrdtError, Watcher};
use spacedb_meter::{MeterError, RateCard, Usage};
use crate::error::{SdkError, SdkResult};
use crate::schema::{CrdtType, FieldSpec, Schema};
use crate::session::Session;
const WRITE_FUEL: u64 = 1_000;
pub struct Database {
node: Identity,
actor_id: u64,
directory: MemKeyDirectory,
revocations: RevocationSet,
rate_card: RateCard,
clock: u64,
schemas: HashMap<String, Schema>,
docs: HashMap<String, CrdtDoc>,
quorum: QuorumGroup,
}
impl Database {
pub fn open(node: Identity) -> Self {
let actor_id = actor_id_for(&node.did().0);
Self {
node,
actor_id,
directory: MemKeyDirectory::new(),
revocations: RevocationSet::new(),
rate_card: default_rate_card(),
clock: 0,
schemas: HashMap::new(),
docs: HashMap::new(),
quorum: QuorumGroup::new(["q0", "q1", "q2"]),
}
}
pub fn node(&self) -> &Identity {
&self.node
}
pub fn register_identity(&self, identity: &Identity) -> SdkResult<()> {
self.directory
.publish(identity)
.map_err(|e| SdkError::Auth(e.to_string()))
}
pub fn define(&mut self, schema: Schema) {
self.schemas.insert(schema.collection().to_string(), schema);
}
pub fn set_clock(&mut self, now_unix: u64) {
self.clock = now_unix;
}
pub fn write_cost(&self) -> u64 {
self.rate_card.price(&Usage::compute(WRITE_FUEL, 1))
}
pub fn session(&self, capability: SignedCapability) -> Session {
Session::from_capability(capability)
}
pub fn revoke(&mut self, capability_id: [u8; 16]) {
self.revocations.revoke(capability_id);
}
pub fn put_register(
&mut self,
session: &mut Session,
collection: &str,
field: &str,
value: &str,
) -> SdkResult<Outcome> {
let spec = self.require_field(collection, field, CrdtType::Register)?;
let tier = self.begin_write(session, collection, spec)?;
let doc = self.doc_mut(collection);
doc.set_register(field, &value.to_string()).map_err(crdt_err)?;
Ok(local_outcome(tier, session, doc))
}
pub fn increment(
&mut self,
session: &mut Session,
collection: &str,
field: &str,
delta: i64,
) -> SdkResult<Outcome> {
let spec = self.require_field(collection, field, CrdtType::Counter)?;
let tier = self.begin_write(session, collection, spec)?;
let doc = self.doc_mut(collection);
doc.increment(field, delta);
Ok(local_outcome(tier, session, doc))
}
pub fn append_text(
&mut self,
session: &mut Session,
collection: &str,
field: &str,
text: &str,
) -> SdkResult<Outcome> {
let spec = self.require_field(collection, field, CrdtType::Text)?;
let tier = self.begin_write(session, collection, spec)?;
let doc = self.doc_mut(collection);
doc.text_push(field, text);
Ok(local_outcome(tier, session, doc))
}
pub fn add_to_set(
&mut self,
session: &mut Session,
collection: &str,
field: &str,
element: &str,
) -> SdkResult<Outcome> {
let spec = self.require_field(collection, field, CrdtType::Set)?;
let tier = self.begin_write(session, collection, spec)?;
let doc = self.doc_mut(collection);
doc.set_add(field, element);
Ok(local_outcome(tier, session, doc))
}
pub fn claim_unique(
&mut self,
session: &mut Session,
collection: &str,
field: &str,
value: &str,
) -> SdkResult<StrongResult> {
let spec = self.require_field(collection, field, CrdtType::Register)?;
if spec.tier != Tier::Strong {
return Err(SdkError::WrongType {
field: field.to_string(),
expected: CrdtType::Register,
found: spec.crdt,
});
}
self.authorize_op(session, collection, Ops::WRITE)?;
self.charge(session)?;
let key = format!("{collection}/{field}/{value}");
let owner = session.actor.0.as_bytes().to_vec();
Ok(self.quorum.claim_unique(&key, &owner))
}
pub fn unique_owner(
&self,
collection: &str,
field: &str,
value: &str,
) -> Option<String> {
let key = format!("{collection}/{field}/{value}");
match self.quorum.read(&key) {
Ok((Some(bytes), _)) => Some(String::from_utf8_lossy(&bytes).into_owned()),
_ => None,
}
}
pub fn quorum_partition(&mut self, member: &str) -> bool {
self.quorum.partition(member)
}
pub fn quorum_heal(&mut self, member: &str) -> bool {
self.quorum.heal(member)
}
pub fn read_register(
&self,
session: &mut Session,
collection: &str,
field: &str,
) -> SdkResult<(Option<String>, Outcome)> {
let spec = self.require_field(collection, field, CrdtType::Register)?;
self.authorize_op(session, collection, Ops::READ)?;
let doc = self.docs.get(collection);
let value = match doc {
Some(d) => d.get_register::<String>(field).map_err(crdt_err)?,
None => None,
};
let outcome = match (spec.tier, doc) {
(Tier::Causal, Some(d)) => session.causal.read(d),
(Tier::Causal, None) => Outcome::Committed(Tier::Causal),
(tier, _) => Outcome::Committed(tier),
};
Ok((value, outcome))
}
pub fn counter(&self, collection: &str, field: &str) -> i64 {
self.docs.get(collection).map_or(0, |d| d.counter(field))
}
pub fn text(&self, collection: &str, field: &str) -> String {
self.docs.get(collection).map_or_else(String::new, |d| d.text(field))
}
pub fn set_members(&self, collection: &str, field: &str) -> Vec<String> {
self.docs
.get(collection)
.map_or_else(Vec::new, |d| d.set_members(field))
}
pub fn watch(&mut self, collection: &str) -> Watcher {
self.doc_mut(collection).watch()
}
pub fn export(&self, collection: &str) -> Vec<u8> {
self.docs
.get(collection)
.map_or_else(Vec::new, |d| d.encode_full())
}
pub fn import(&mut self, collection: &str, update: &[u8]) -> SdkResult<()> {
self.doc_mut(collection).apply_update(update).map_err(crdt_err)
}
fn require_field(
&self,
collection: &str,
field: &str,
expected: CrdtType,
) -> SdkResult<FieldSpec> {
let schema = self
.schemas
.get(collection)
.ok_or_else(|| SdkError::UnknownCollection(collection.to_string()))?;
let spec = schema.spec(field).ok_or_else(|| SdkError::UnknownField {
collection: collection.to_string(),
field: field.to_string(),
})?;
if spec.crdt != expected {
return Err(SdkError::WrongType {
field: field.to_string(),
expected,
found: spec.crdt,
});
}
Ok(spec)
}
fn begin_write(
&self,
session: &mut Session,
collection: &str,
spec: FieldSpec,
) -> SdkResult<Tier> {
if spec.tier == Tier::Strong {
return Err(SdkError::StrongFieldNeedsClaim(collection.to_string()));
}
self.authorize_op(session, collection, Ops::WRITE)?;
self.charge(session)?;
Ok(spec.tier)
}
fn authorize_op(&self, session: &Session, collection: &str, op: Ops) -> SdkResult<()> {
let scope = Scope::Collection(collection.to_string());
let request = AccessRequest {
bearer: &session.actor,
scope: &scope,
op,
};
match authorize(
&session.capability,
&request,
&self.directory,
self.clock,
&self.revocations,
) {
Ok(Decision::Allow) => Ok(()),
Ok(Decision::Deny(reason)) => Err(SdkError::Denied(reason)),
Err(e) => Err(SdkError::Auth(e.to_string())),
}
}
fn charge(&self, session: &mut Session) -> SdkResult<()> {
let cost = self.write_cost();
session.budget.charge(cost).map_err(|e| match e {
MeterError::OverBudget { cost, remaining } => SdkError::OverBudget { cost, remaining },
other => SdkError::Auth(other.to_string()),
})
}
fn doc_mut(&mut self, collection: &str) -> &CrdtDoc {
let actor = self.actor_id;
self.docs
.entry(collection.to_string())
.or_insert_with(|| CrdtDoc::new(actor))
}
}
fn local_outcome(tier: Tier, session: &mut Session, doc: &CrdtDoc) -> Outcome {
match tier {
Tier::Causal => session.causal.record_write(doc),
_ => Outcome::Local,
}
}
fn actor_id_for(did: &str) -> u64 {
let mut hasher = DefaultHasher::new();
did.hash(&mut hasher);
hasher.finish() | 1 }
fn default_rate_card() -> RateCard {
RateCard {
storage_per_gib_month: 5_000_000,
compute_per_megafuel: 1_000_000,
compute_per_invocation: 1_000,
transit_per_gib: 1_000_000,
}
}
fn crdt_err(e: CrdtError) -> SdkError {
SdkError::Crdt(e.to_string())
}