use std::{fmt, time::Duration};
use sim_kernel::{CapabilityName, Datum, Symbol};
use sim_relation_core::{Cell, Row, RowType};
use sim_relation_migrate::CheckedProgram;
use sim_relation_plan::{CheckedMutation, CheckedQuery};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StorageLocator {
Memory,
Preopened {
reference: Symbol,
access: StorageAccess,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StorageAccess {
ReadOnly,
ReadWrite,
}
impl StorageLocator {
pub fn from_datum(value: &Datum) -> Result<Self, SiteError> {
let Datum::Node { tag, fields } = value else {
return Err(SiteError::Locator);
};
if tag == &Symbol::qualified("relation", "memory") && fields.is_empty() {
return Ok(Self::Memory);
}
if tag != &Symbol::qualified("relation", "preopened") {
return Err(SiteError::Locator);
}
let field = |name: &str| {
fields
.iter()
.find(|(key, _)| key == &Symbol::new(name))
.map(|(_, value)| value)
};
if fields.len() != 2 {
return Err(SiteError::Locator);
}
let Some(Datum::Symbol(reference)) = field("ref") else {
return Err(SiteError::Locator);
};
let access = match field("access") {
Some(Datum::Symbol(value)) if value == &Symbol::new("read-only") => {
StorageAccess::ReadOnly
}
Some(Datum::Symbol(value)) if value == &Symbol::new("read-write") => {
StorageAccess::ReadWrite
}
_ => return Err(SiteError::Locator),
};
Ok(Self::Preopened {
reference: reference.clone(),
access,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DriverManifest {
pub site: Symbol,
pub provider: Symbol,
}
impl DriverManifest {
pub fn sqlite(site: Symbol, provider: Symbol) -> Result<Self, SiteError> {
if site != Symbol::qualified("relation/site", "sqlite")
|| provider != Symbol::qualified("relation/provider", "sqlite")
{
return Err(SiteError::Registration);
}
Ok(Self { site, provider })
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RelationPlacement {
pub(crate) site: Symbol,
pub(crate) locator: Datum,
}
impl RelationPlacement {
pub fn new(site: Symbol, locator: Datum) -> Self {
Self { site, locator }
}
pub fn site(&self) -> &Symbol {
&self.site
}
pub fn locator(&self) -> &Datum {
&self.locator
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Limits {
pub rows: u64,
pub cells: u64,
pub bytes: u64,
pub work: u64,
pub deadline: Option<Duration>,
pub buffer_bytes: Option<u64>,
}
impl Limits {
pub fn new(rows: u64, cells: u64, bytes: u64, work: u64) -> Result<Self, SiteError> {
if [rows, cells, bytes, work].contains(&0) {
return Err(SiteError::InvalidLimits);
}
Ok(Self {
rows,
cells,
bytes,
work,
deadline: None,
buffer_bytes: None,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Bindings(Row);
impl Bindings {
pub fn new(
expected: &RowType,
cells: impl IntoIterator<Item = Cell>,
) -> Result<Self, SiteError> {
Row::new(expected.clone(), cells)
.map(Self)
.map_err(|e| SiteError::Bindings(e.to_string()))
}
pub fn row(&self) -> &Row {
&self.0
}
}
pub trait RowSink {
fn push(&mut self, row: Row) -> Result<(), SiteError>;
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct VecRowSink {
rows: Vec<Row>,
}
impl VecRowSink {
pub fn rows(&self) -> &[Row] {
&self.rows
}
pub fn into_rows(self) -> Vec<Row> {
self.rows
}
}
impl RowSink for VecRowSink {
fn push(&mut self, row: Row) -> Result<(), SiteError> {
self.rows.push(row);
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ProviderStats {
pub work: u64,
pub affected: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LimitKind {
Rows,
Cells,
Bytes,
Work,
Deadline,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Receipt {
pub operation_id: String,
pub operation: Operation,
pub rows: u64,
pub cells: u64,
pub bytes: u64,
pub work: u64,
pub affected: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Operation {
Read,
Write,
Schema,
Migrate,
Transaction,
Attach,
}
impl Operation {
fn label(self) -> &'static str {
match self {
Self::Read => "read",
Self::Write => "write",
Self::Schema => "schema",
Self::Migrate => "migrate",
Self::Transaction => "transaction",
Self::Attach => "attach",
}
}
pub(crate) fn capability(self) -> CapabilityName {
CapabilityName::new(format!("relation.{}", self.label()))
}
pub(crate) fn effect(self) -> Symbol {
Symbol::qualified("effect/relation", self.label())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SiteError {
InvalidLimits,
Bindings(String),
RowType,
Limit(LimitKind),
Locator,
Registration,
Constraint,
Locked,
ReadOnly,
Interrupted,
Corruption,
Conversion,
Drift,
Provider,
Kernel(String),
}
impl fmt::Display for SiteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for SiteError {}
pub trait Session {
fn query(
&mut self,
plan: &CheckedQuery,
bindings: &Bindings,
limits: &Limits,
sink: &mut dyn RowSink,
) -> Result<ProviderStats, SiteError>;
fn mutate(
&mut self,
plan: &CheckedMutation,
bindings: &Bindings,
limits: &Limits,
sink: &mut dyn RowSink,
) -> Result<ProviderStats, SiteError>;
fn migrate(
&mut self,
program: &CheckedProgram,
limits: &Limits,
) -> Result<ProviderStats, SiteError>;
fn schema(
&mut self,
program: &CheckedProgram,
limits: &Limits,
) -> Result<ProviderStats, SiteError>;
fn transaction(
&mut self,
body: &mut dyn FnMut(&mut dyn Transaction) -> Result<(), SiteError>,
) -> Result<(), SiteError>;
fn attach(&mut self, locator: &Datum, limits: &Limits) -> Result<ProviderStats, SiteError>;
}
pub trait Transaction: Session {
fn savepoint(
&mut self,
body: &mut dyn FnMut(&mut dyn Transaction) -> Result<(), SiteError>,
) -> Result<(), SiteError>;
}
pub trait Driver: Send + Sync {
fn connect(&self, locator: &Datum, limits: &Limits) -> Result<Box<dyn Session>, SiteError>;
}