use std::io::Write;
use std::path::Path;
use std::sync::Arc;
use time::OffsetDateTime;
use uuid::Uuid;
use crate::audit::{Action, AuditError, Entry};
use crate::clock::Clock;
use crate::envelope::Key;
use crate::ids::{FileId, KeyId, TenantId, VersionId};
use crate::store::{OpenError, Store, StoreError};
use crate::tenants::TenantError;
#[cfg(test)]
mod conformance;
pub mod local;
#[cfg(feature = "s3")]
pub(crate) mod migrate;
pub(crate) mod pack;
#[cfg(feature = "s3")]
pub(crate) mod s3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VconRow {
pub current: Option<VersionId>,
pub deleted_at: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VersionRow {
pub form: String,
pub gzip: bool,
pub wrapped: Vec<u8>,
pub place: Option<Packed>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Packed {
pub file: FileId,
pub date: String,
pub bytes: u64,
pub place: pack::Place,
}
#[derive(Debug, Clone, Copy)]
pub struct NewVersion<'a> {
pub version: VersionId,
pub uuid: Uuid,
pub form: &'static str,
pub gzip: bool,
pub size: i64,
pub stored_at: i64,
pub key: KeyId,
pub wrapped: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HiddenRow {
pub uuid: Uuid,
pub stored_at: i64,
pub held: bool,
pub deleted: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeptRow {
pub version: VersionId,
pub stored_at: i64,
pub hidden_at: Option<i64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ListedRow {
pub uuid: Uuid,
pub version: VersionId,
pub stored_at: i64,
pub updated_at: Option<i64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PageQuery {
pub before: Option<(i64, Uuid)>,
pub since: Option<i64>,
pub until: Option<i64>,
pub rows: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExaminedRow {
pub version: VersionId,
pub uuid: Uuid,
pub stored_at: i64,
pub hidden_at: Option<i64>,
pub held: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExportedRow {
pub version: VersionId,
pub uuid: Uuid,
pub form: String,
pub gzip: bool,
pub size: i64,
pub stored_at: i64,
pub live: bool,
pub wrapped: Vec<u8>,
pub place: Option<Packed>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnpackedRow {
pub version: VersionId,
pub stored_at: i64,
pub size: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PackFileRow {
pub date: String,
pub bytes: u64,
pub unnamed_at: Option<i64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NewPackFile<'a> {
pub file: FileId,
pub date: &'a str,
pub rows: i64,
pub bytes: i64,
pub created_at: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WrappedRow {
pub version: VersionId,
pub uuid: Uuid,
pub wrapped: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use]
pub enum CommitError {
RolledBack,
Unknown,
}
impl std::fmt::Display for CommitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
CommitError::RolledBack => "the transaction was rolled back",
CommitError::Unknown => "the transaction may or may not have been kept",
})
}
}
impl std::error::Error for CommitError {}
pub trait Catalog: Send + Sync {
fn immediate(&self) -> Result<Box<dyn CatalogTx + '_>, StoreError>;
fn snapshot(&self) -> Result<Box<dyn CatalogTx + '_>, StoreError>;
fn recover(&self) -> Result<(), StoreError>;
fn after_purge(&self) -> Result<(), StoreError>;
fn backup_into(&self, to: &Path) -> Result<Vec<(VersionId, Option<Packed>)>, StoreError>;
fn unpack_copy(&self, copy: &Path) -> Result<(), StoreError>;
fn export(&self, after: i64, out: &mut dyn Write) -> Result<(), AuditError>;
#[cfg(test)]
fn fail_commit(&self, nth: usize, how: CommitError);
#[cfg(test)]
fn on_checkpoint_wait(&self, busy: std::sync::mpsc::Sender<()>);
#[cfg(test)]
fn local_conn(&self) -> std::sync::MutexGuard<'_, rusqlite::Connection>;
}
pub trait CatalogTx {
fn tombstoned(&mut self, uuid: Uuid) -> Result<bool, StoreError>;
fn vcon_row(&mut self, uuid: Uuid) -> Result<Option<VconRow>, StoreError>;
fn version_row(&mut self, version: VersionId) -> Result<Option<VersionRow>, StoreError>;
fn has_version(&mut self, version: VersionId, uuid: Uuid) -> Result<bool, StoreError>;
fn insert_version(&mut self, row: &NewVersion) -> Result<(), StoreError>;
fn insert_vcon(
&mut self,
uuid: Uuid,
current: VersionId,
stored_at: i64,
) -> Result<(), StoreError>;
fn set_current(&mut self, uuid: Uuid, current: VersionId) -> Result<(), StoreError>;
fn hide_version(&mut self, version: VersionId, at: i64) -> Result<(), StoreError>;
fn mark_deleted(&mut self, uuid: Uuid, at: i64) -> Result<(), StoreError>;
fn wraps(&mut self, key: KeyId) -> Result<u64, StoreError>;
fn add_wrap(&mut self, key: KeyId) -> Result<(), StoreError>;
fn unpacked(&mut self) -> Result<Vec<UnpackedRow>, StoreError>;
fn is_unpacked(&mut self, version: VersionId) -> Result<bool, StoreError>;
fn insert_pack_file(&mut self, row: &NewPackFile) -> Result<(), StoreError>;
fn set_place(
&mut self,
version: VersionId,
file: FileId,
place: pack::Place,
) -> Result<(), StoreError>;
fn pack_files(&mut self) -> Result<Vec<FileId>, StoreError>;
fn pack_file(&mut self, file: FileId) -> Result<Option<PackFileRow>, StoreError>;
fn placed_in(&mut self, file: FileId) -> Result<Vec<(VersionId, i64, Packed)>, StoreError>;
fn add_purge_pending(&mut self, file: FileId) -> Result<(), StoreError>;
fn purge_pending(&mut self) -> Result<Vec<FileId>, StoreError>;
fn delete_purge_pending(&mut self, file: FileId) -> Result<(), StoreError>;
fn unname_pack_file(&mut self, file: FileId, at: i64) -> Result<(), StoreError>;
fn unnamed_files(&mut self) -> Result<Vec<FileId>, StoreError>;
fn delete_pack_file(&mut self, file: FileId) -> Result<(), StoreError>;
fn allocate_manifest(&mut self) -> Result<i64, StoreError>;
fn unpublished_manifests(&mut self) -> Result<Vec<i64>, StoreError>;
fn mark_published(&mut self, seq: i64, at: i64) -> Result<(), StoreError>;
fn published_manifests(&mut self) -> Result<Vec<(i64, i64)>, StoreError>;
fn delete_manifest(&mut self, seq: i64) -> Result<(), StoreError>;
fn named_files(&mut self) -> Result<Vec<(FileId, PackFileRow, i64)>, StoreError>;
fn live_versions(&mut self) -> Result<Vec<VersionId>, StoreError>;
#[expect(clippy::type_complexity, reason = "one result per version")]
fn places(
&mut self,
) -> Result<Vec<(VersionId, Result<Option<Packed>, StoreError>)>, StoreError>;
fn count_versions(&mut self) -> Result<u64, StoreError>;
fn count_packed(&mut self) -> Result<u64, StoreError>;
fn count_held(&mut self) -> Result<u64, StoreError>;
fn hidden_versions(&mut self, uuid: Option<Uuid>) -> Result<Vec<VersionId>, StoreError>;
fn hidden_row(&mut self, version: VersionId) -> Result<Option<HiddenRow>, StoreError>;
fn delete_version(&mut self, version: VersionId) -> Result<(), StoreError>;
fn versions_of(&mut self, uuid: Uuid) -> Result<u64, StoreError>;
fn tombstone(&mut self, uuid: Uuid, at: i64) -> Result<(), StoreError>;
fn latest(&mut self, uuid: Uuid) -> Result<Option<VersionId>, StoreError>;
fn kept(&mut self, uuid: Uuid) -> Result<Vec<KeptRow>, StoreError>;
fn unhide(&mut self, version: VersionId) -> Result<(), StoreError>;
fn undelete(&mut self, uuid: Uuid, version: VersionId) -> Result<(), StoreError>;
fn set_hold(&mut self, uuid: Uuid, on: bool) -> Result<(), StoreError>;
fn page(&mut self, query: &PageQuery) -> Result<Vec<ListedRow>, StoreError>;
fn wrapped_under(&mut self, key: KeyId, limit: usize) -> Result<Vec<WrappedRow>, StoreError>;
fn set_wrapped(
&mut self,
version: VersionId,
key: KeyId,
wrapped: &[u8],
) -> Result<(), StoreError>;
fn count_not_under(&mut self, key: KeyId) -> Result<u64, StoreError>;
fn examined(&mut self) -> Result<Vec<ExaminedRow>, StoreError>;
fn exported(
&mut self,
since: Option<i64>,
until: Option<i64>,
) -> Result<Vec<ExportedRow>, StoreError>;
fn purge_times(&mut self) -> Result<Vec<String>, StoreError>;
fn begin_part(&mut self) -> Result<(), StoreError>;
fn release_part(&mut self) -> Result<(), StoreError>;
fn rollback_part(&mut self) -> Result<(), StoreError>;
fn record(&mut self, entry: &Entry, now: OffsetDateTime) -> Result<(), AuditError>;
fn count(&mut self, entry: &Entry, now: OffsetDateTime) -> Result<(), AuditError>;
fn audit_lines(
&mut self,
after: i64,
limit: Option<u32>,
out: &mut dyn Write,
) -> Result<(), AuditError>;
fn commit(self: Box<Self>) -> Result<(), CommitError>;
}
#[derive(Debug)]
#[must_use]
pub enum ObjectError {
Missing,
Io(std::io::Error),
}
impl std::fmt::Display for ObjectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ObjectError::Missing => f.write_str("no object has the name"),
ObjectError::Io(e) => write!(f, "the object storage failed: {e}"),
}
}
}
impl std::error::Error for ObjectError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ObjectError::Missing => None,
ObjectError::Io(e) => Some(e),
}
}
}
pub trait Objects: Send + Sync {
fn put(&self, version: VersionId, blob: &[u8]) -> Result<(), ObjectError>;
fn get(&self, version: VersionId) -> Result<Vec<u8>, ObjectError>;
fn remove(&self, version: VersionId) -> Result<(), ObjectError>;
fn list(&self) -> Result<Vec<String>, ObjectError>;
fn remove_name(&self, name: &str) -> Result<(), ObjectError>;
fn sync(&self) -> Result<(), ObjectError>;
}
pub trait Packs: Send + Sync + std::fmt::Debug {
fn read(&self, name: &str, start: u64, len: u64) -> Result<Vec<u8>, ObjectError>;
fn size(&self, name: &str) -> Result<Option<u64>, ObjectError>;
fn put_new(&self, name: &str, bytes: &[u8]) -> Result<bool, ObjectError>;
fn remove(&self, name: &str) -> Result<(), ObjectError>;
fn list(&self) -> Result<Vec<String>, ObjectError>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TenantRow {
pub id: TenantId,
pub name: String,
pub retention: Option<String>,
pub metadata: Option<String>,
pub created_at: i64,
pub tenant_key_id: KeyId,
pub wrapped: Vec<u8>,
pub pending: Option<Vec<u8>>,
pub key_since: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyRow {
pub id: KeyId,
pub tenant: Option<TenantId>,
pub token_hash: Vec<u8>,
pub scopes: String,
pub label: String,
pub created_at: i64,
pub revoked_at: Option<i64>,
}
pub trait Registry: Send + Sync {
fn immediate(&self) -> Result<Box<dyn RegistryTx + '_>, TenantError>;
fn snapshot(&self) -> Result<Box<dyn RegistryTx + '_>, TenantError>;
fn export(&self, after: i64, out: &mut dyn Write) -> Result<(), AuditError>;
fn backup_into(&self, to: &Path) -> Result<Vec<TenantId>, TenantError>;
#[cfg(test)]
fn fail_commit(&self, nth: usize, how: CommitError);
#[cfg(test)]
fn local_conn(&self) -> std::sync::MutexGuard<'_, rusqlite::Connection>;
}
pub trait RegistryTx {
fn tenant(&mut self, id: TenantId) -> Result<Option<TenantRow>, TenantError>;
fn tenants(&mut self) -> Result<Vec<TenantRow>, TenantError>;
fn name_taken(&mut self, name: &str) -> Result<bool, TenantError>;
fn insert_tenant(&mut self, row: &TenantRow) -> Result<(), TenantError>;
fn set_retention(&mut self, id: TenantId, retention: Option<&str>)
-> Result<bool, TenantError>;
fn set_metadata(&mut self, id: TenantId, level: Option<&str>) -> Result<bool, TenantError>;
fn delete_tenant(&mut self, id: TenantId, at: i64) -> Result<(), TenantError>;
fn set_pending(&mut self, id: TenantId, pending: &[u8]) -> Result<(), TenantError>;
fn promote_pending(&mut self, id: TenantId, key: KeyId, at: i64) -> Result<(), TenantError>;
fn master_wraps(&mut self, key: KeyId) -> Result<u64, TenantError>;
fn add_master_wrap(&mut self, key: KeyId, at: i64) -> Result<(), TenantError>;
fn master_since(&mut self, key: KeyId) -> Result<Option<i64>, TenantError>;
fn key(&mut self, id: KeyId) -> Result<Option<KeyRow>, TenantError>;
fn keys(&mut self, tenant: Option<TenantId>) -> Result<Vec<KeyRow>, TenantError>;
fn insert_key(&mut self, row: &KeyRow) -> Result<(), TenantError>;
fn revoke_key(&mut self, id: KeyId, at: i64) -> Result<(), TenantError>;
fn record_times(&mut self, action: Action, status: u16) -> Result<Vec<String>, TenantError>;
fn record(&mut self, entry: &Entry, now: OffsetDateTime) -> Result<(), AuditError>;
fn count(&mut self, entry: &Entry, now: OffsetDateTime) -> Result<(), AuditError>;
fn audit_lines(
&mut self,
after: i64,
limit: Option<u32>,
out: &mut dyn Write,
) -> Result<(), AuditError>;
fn commit(self: Box<Self>) -> Result<(), CommitError>;
}
pub trait TenantSpace: Send + Sync {
fn make(&self, id: TenantId, key: Key, clock: Arc<dyn Clock>) -> Result<(), OpenError>;
fn open(
&self,
id: TenantId,
key: Key,
recover: bool,
clock: Arc<dyn Clock>,
) -> Result<Store, OpenError>;
fn present(&self) -> Result<Vec<TenantId>, TenantError>;
fn remove(&self, id: TenantId) -> Result<(), TenantError>;
fn catalog_under(&self, id: TenantId, old: &Key, new: &Key) -> Result<Under, TenantError>;
fn reseal(&self, id: TenantId, old: &Key, new: &Key) -> Result<(), TenantError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Under {
Old,
New,
}
#[derive(Debug, Clone, Default)]
pub(crate) enum ObjectPlace {
#[default]
Local,
#[cfg(feature = "s3")]
S3(Arc<s3::S3>),
}
pub(crate) fn objects_for(
storage: &crate::config::Storage,
#[cfg_attr(
not(feature = "s3"),
expect(
unused_variables,
reason = "only a bucket is bound to a data directory"
)
)]
data_dir: &Path,
) -> Result<ObjectPlace, crate::config::StorageError> {
match &storage.objects {
crate::config::ObjectStore::Local => Ok(ObjectPlace::Local),
#[cfg(feature = "s3")]
crate::config::ObjectStore::S3(settings) => {
s3::place(settings, storage.parquet.is_some(), data_dir)
}
#[cfg(not(feature = "s3"))]
crate::config::ObjectStore::S3(_) => Err(crate::config::StorageError(
"storage.objects is \"s3\", but this build has no S3 support (cargo feature s3)"
.to_owned(),
)),
}
}