pub mod pull;
pub mod query;
pub mod result;
pub mod tx;
pub mod value;
mod local;
mod remote;
use std::collections::BTreeMap;
use std::sync::Arc;
use async_trait::async_trait;
use corium_core::EntityId;
use corium_query::edn::Edn;
use thiserror::Error;
pub use crate::local::LocalPeer;
pub use crate::pull::Pull;
pub use crate::query::Query;
pub use crate::remote::RemotePeer;
pub use crate::result::{QueryResult, ResultShape, Row};
pub use crate::tx::{TxBuilder, TxData};
pub use crate::value::{FromEdn, IntoEdn};
#[derive(Debug, Error)]
pub enum ClientError {
#[error(transparent)]
Peer(#[from] corium_peer::PeerError),
#[error(transparent)]
Query(#[from] corium_query::QueryError),
#[error(transparent)]
Rpc(#[from] tonic::Status),
#[error(transparent)]
Transport(#[from] tonic::transport::Error),
#[error(transparent)]
Codec(#[from] corium_protocol::codec::CodecError),
#[error("decode error: {0}")]
Decode(String),
#[error("protocol error: {0}")]
Protocol(String),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum View {
Current,
AsOf(u64),
Since(u64),
History,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Index {
Eavt,
Aevt,
Avet,
Vaet,
}
impl Index {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Eavt => "eavt",
Self::Aevt => "aevt",
Self::Avet => "avet",
Self::Vaet => "vaet",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DatomRow {
pub e: u64,
pub a: u64,
pub v: Edn,
pub tx: u64,
pub added: bool,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct DbStats {
pub basis_t: u64,
pub datoms: u64,
pub entities: u64,
pub attributes: u64,
}
#[derive(Clone, Debug)]
pub struct TxReport {
pub basis_before: u64,
pub basis_t: u64,
pub tx_instant: i64,
pub tempids: BTreeMap<String, EntityId>,
pub db_after: Db,
}
#[async_trait]
pub(crate) trait DbBackend: Send + Sync {
fn db_name(&self) -> &str;
async fn query(
&self,
view: View,
query: Edn,
args: Vec<Edn>,
fuel: Option<u64>,
) -> Result<QueryResult, ClientError>;
async fn pull(&self, view: View, pattern: Edn, eid: Edn) -> Result<Edn, ClientError>;
async fn datoms(
&self,
view: View,
index: Index,
components: Vec<Edn>,
limit: usize,
) -> Result<Vec<DatomRow>, ClientError>;
async fn stats(&self, view: View) -> Result<DbStats, ClientError>;
}
#[derive(Clone)]
pub struct Db {
backend: Arc<dyn DbBackend>,
view: View,
}
impl Db {
pub(crate) fn new(backend: Arc<dyn DbBackend>, view: View) -> Self {
Self { backend, view }
}
#[must_use]
pub fn db_name(&self) -> &str {
self.backend.db_name()
}
#[must_use]
pub fn view(&self) -> View {
self.view
}
#[must_use]
pub fn as_of(&self, t: u64) -> Self {
Self {
backend: Arc::clone(&self.backend),
view: View::AsOf(t),
}
}
#[must_use]
pub fn since(&self, t: u64) -> Self {
Self {
backend: Arc::clone(&self.backend),
view: View::Since(t),
}
}
#[must_use]
pub fn history(&self) -> Self {
Self {
backend: Arc::clone(&self.backend),
view: View::History,
}
}
pub async fn query(&self, query: &Query, args: Args) -> Result<QueryResult, ClientError> {
self.backend
.query(self.view, query.to_edn(), args.arg_forms(), args.fuel)
.await
}
pub async fn query_edn(&self, query: Edn, args: Vec<Edn>) -> Result<QueryResult, ClientError> {
self.backend.query(self.view, query, args, None).await
}
pub async fn pull(&self, pattern: &Pull, entity: impl IntoEdn) -> Result<Edn, ClientError> {
self.backend
.pull(self.view, pattern.to_edn(), entity.into_edn())
.await
}
pub async fn datoms(
&self,
index: Index,
components: Vec<Edn>,
limit: usize,
) -> Result<Vec<DatomRow>, ClientError> {
self.backend
.datoms(self.view, index, components, limit)
.await
}
pub async fn stats(&self) -> Result<DbStats, ClientError> {
self.backend.stats(self.view).await
}
pub async fn basis_t(&self) -> Result<u64, ClientError> {
Ok(self.stats().await?.basis_t)
}
}
impl std::fmt::Debug for Db {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Db")
.field("db_name", &self.backend.db_name())
.field("view", &self.view)
.finish()
}
}
#[derive(Clone, Debug, Default)]
pub struct Args {
values: Vec<Edn>,
fuel: Option<u64>,
}
impl Args {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn scalar(mut self, value: impl IntoEdn) -> Self {
self.values.push(value.into_edn());
self
}
#[must_use]
pub fn tuple<V: IntoEdn>(mut self, values: impl IntoIterator<Item = V>) -> Self {
self.values.push(Edn::Vector(
values.into_iter().map(IntoEdn::into_edn).collect(),
));
self
}
#[must_use]
pub fn coll<V: IntoEdn>(mut self, values: impl IntoIterator<Item = V>) -> Self {
self.values.push(Edn::Vector(
values.into_iter().map(IntoEdn::into_edn).collect(),
));
self
}
#[must_use]
pub fn relation<V: IntoEdn>(mut self, tuples: impl IntoIterator<Item = Vec<V>>) -> Self {
let rows = tuples
.into_iter()
.map(|tuple| Edn::Vector(tuple.into_iter().map(IntoEdn::into_edn).collect()))
.collect();
self.values.push(Edn::Vector(rows));
self
}
#[must_use]
pub fn arg(mut self, value: Edn) -> Self {
self.values.push(value);
self
}
#[must_use]
pub fn fuel(mut self, fuel: u64) -> Self {
self.fuel = Some(fuel);
self
}
fn arg_forms(&self) -> Vec<Edn> {
self.values.clone()
}
}
#[async_trait]
pub trait Peer: Send + Sync {
fn db_name(&self) -> &str;
async fn db(&self) -> Result<Db, ClientError>;
async fn transact(&self, tx: TxData) -> Result<TxReport, ClientError>;
async fn sync(&self) -> Result<Db, ClientError>;
}