#![doc = include_str!("../README.md")]
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::cast_sign_loss)]
mod stores;
pub use stores::{
artifact::{ArtifactKind, ArtifactStore},
assets::AssetStore,
cache::{CacheDependency, CacheKind, CacheStore, Lookup},
database::UpdateError,
secrets::SecretsStore,
};
use flexbuffers::Reader;
use ordinary_config::{DatabaseModelConfig, StorageLimits};
use ordinary_types::{Field, Kind, TimeUnit};
use saferlmdb::{
ConstAccessor, Environment, ReadTransaction, Stat, WriteAccessor, WriteTransaction,
};
use std::sync::Arc;
pub use bytes;
use bytes::{BufMut, Bytes, BytesMut};
use crate::stores::database::DatabaseStore;
pub use saferlmdb;
fn field_to_bytes(field: &Field, reader: &Reader<&[u8]>) -> Bytes {
let mut out = BytesMut::new();
match &field.kind {
Kind::Uuid => {
out.put(reader.as_blob().0);
}
Kind::Bool => {
if reader.as_bool() {
out.put_u8(1);
} else {
out.put_u8(0);
}
}
Kind::F32 => out.put_f32(reader.as_f32()),
Kind::F64 => out.put_f64(reader.as_f64()),
Kind::U8 => out.put_u8(reader.as_u8()),
Kind::U16 => out.put_u16(reader.as_u16()),
Kind::U32 => out.put_u32(reader.as_u32()),
Kind::U64 => out.put_u64(reader.as_u64()),
Kind::I8 => out.put_i8(reader.as_i8()),
Kind::I16 => out.put_i16(reader.as_i16()),
Kind::I32 => out.put_i32(reader.as_i32()),
Kind::I64 => out.put_i64(reader.as_i64()),
Kind::String | Kind::Markdown | Kind::Json | Kind::Url => {
out.put(reader.as_str().as_bytes());
}
Kind::Timestamp { unit, .. } => match unit {
TimeUnit::Seconds => out.put_i64(reader.as_i64()),
},
_ => {
tracing::error!(
"kind '{:?}' does not support encrypted/compressed",
field.kind
);
}
}
out.into()
}
fn push_field_from_bytes(
field: &Field,
bytes: &[u8],
dest: &mut flexbuffers::VectorBuilder,
) -> anyhow::Result<()> {
match &field.kind {
Kind::Uuid => dest.push(flexbuffers::Blob(bytes)),
Kind::Bool => dest.push(if bytes.len() == 1 {
bytes[0] == 1
} else {
false
}),
Kind::F32 => dest.push(f32::from_be_bytes(bytes.try_into()?)),
Kind::F64 => dest.push(f64::from_be_bytes(bytes.try_into()?)),
Kind::U8 => dest.push(u8::from_be_bytes(bytes.try_into()?)),
Kind::U16 => dest.push(u16::from_be_bytes(bytes.try_into()?)),
Kind::U32 => dest.push(u32::from_be_bytes(bytes.try_into()?)),
Kind::U64 => dest.push(u64::from_be_bytes(bytes.try_into()?)),
Kind::I8 => dest.push(i8::from_be_bytes(bytes.try_into()?)),
Kind::I16 => dest.push(i16::from_be_bytes(bytes.try_into()?)),
Kind::I32 => dest.push(i32::from_be_bytes(bytes.try_into()?)),
Kind::I64 => dest.push(i64::from_be_bytes(bytes.try_into()?)),
Kind::String | Kind::Markdown | Kind::Json | Kind::Url => {
dest.push(std::str::from_utf8(bytes)?);
}
Kind::Timestamp { unit, .. } => match unit {
TimeUnit::Seconds => dest.push(i64::from_be_bytes(bytes.try_into()?)),
},
_ => {
tracing::error!(
"kind '{:?}' does not support encrypted/compressed",
field.kind
);
}
}
Ok(())
}
pub enum Transaction<'a> {
Read(&'a ReadTransaction<'a>),
Write(WriteTransaction<'a>),
}
enum Accessor<'a> {
Const(&'a ConstAccessor<'a>),
Write(&'a WriteAccessor<'a>),
}
#[derive(Clone, Debug)]
#[allow(clippy::type_complexity)]
pub struct RefDepth(pub Vec<((u8, u8, Option<[u8; 16]>), RefDepth)>);
#[derive(Clone, Debug)]
pub enum QueryExpression {
Gte,
Gt,
Lte,
Lt,
Eq,
BeginsWith,
}
impl QueryExpression {
#[must_use]
pub fn as_byte(&self) -> u8 {
match self {
Self::Gte => 0,
Self::Lte => 1,
Self::Eq => 2,
Self::Gt => 3,
Self::Lt => 4,
Self::BeginsWith => 5,
}
}
}
pub struct OrdinaryStorage {
env: Arc<Environment>,
pub artifact: ArtifactStore,
pub asset: AssetStore,
pub cache: CacheStore,
pub secrets: SecretsStore,
pub database: DatabaseStore,
}
impl OrdinaryStorage {
#[allow(clippy::too_many_lines)]
pub fn new(
limits: StorageLimits,
model_configs: Vec<DatabaseModelConfig>,
encryption_key: [u8; 32],
env: &Arc<Environment>,
log_sizes: bool,
) -> anyhow::Result<Self> {
Ok(Self {
env: env.clone(),
database: DatabaseStore::new(
limits.database,
model_configs,
encryption_key,
env,
log_sizes,
)?,
artifact: ArtifactStore::new(limits.artifact, env, log_sizes)?,
asset: AssetStore::new(limits.assets, env, log_sizes)?,
cache: CacheStore::new(limits.cache, env, log_sizes)?,
secrets: SecretsStore::new(env, encryption_key)?,
})
}
pub fn stat(&self) -> anyhow::Result<Stat> {
let stat = self.env.stat()?;
Ok(stat)
}
pub fn write_txn(&self) -> anyhow::Result<WriteTransaction<'_>> {
let txn = WriteTransaction::new(self.env.clone())?;
Ok(txn)
}
pub fn read_txn(&self) -> anyhow::Result<ReadTransaction<'_>> {
let txn = ReadTransaction::new(self.env.clone())?;
Ok(txn)
}
}