#[cfg_attr(
not(any(
feature = "storage-fjall",
feature = "storage-redb",
feature = "storage-rocksdb",
feature = "storage-sqlite",
feature = "storage-lmdb"
)),
allow(
dead_code,
reason = "backend marker helpers are dormant when every durable backend feature is disabled"
)
)]
mod backend_marker;
mod durable;
mod facade;
#[cfg(feature = "storage-fjall")]
mod fjall;
#[cfg_attr(
not(any(
feature = "storage-fjall",
feature = "storage-redb",
feature = "storage-rocksdb",
feature = "storage-sqlite",
feature = "storage-lmdb"
)),
allow(
dead_code,
reason = "format helpers are dormant when every durable backend feature is disabled"
)
)]
mod format_v1;
#[cfg(feature = "storage-lmdb")]
mod lmdb;
#[cfg(feature = "storage-redb")]
mod redb;
#[cfg(feature = "storage-rocksdb")]
mod rocksdb;
#[cfg(feature = "storage-sqlite")]
mod sqlite;
pub(crate) use durable::{DurableStore, DurableStoreOps};
pub use facade::StorageFacade;
pub(crate) use format_v1::stored_matching_quad;
use std::path::{Path, PathBuf};
use crate::{Error, Result};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum StorageBackend {
Memory,
Fjall,
Redb,
RocksDb,
Sqlite,
Lmdb,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LayoutReaderPolicy {
None,
FormatV1,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct StorageBackendDescriptor {
pub backend: StorageBackend,
pub name: &'static str,
pub cargo_feature: Option<&'static str>,
pub compiled: bool,
pub durable: bool,
pub layout_reader: LayoutReaderPolicy,
}
const SUPPORTED_BACKENDS: [StorageBackend; 6] = [
StorageBackend::Memory,
StorageBackend::Fjall,
StorageBackend::Redb,
StorageBackend::RocksDb,
StorageBackend::Sqlite,
StorageBackend::Lmdb,
];
const KNOWN_OPTIONAL_BACKENDS: &[&str] = &[
"redb",
"rocksdb",
"sqlite",
"lmdb",
"sled",
"leveldb",
"mdbx",
"surrealkv",
];
const LEGACY_REDLAND_BACKENDS: &[&str] = &[
"hashes",
"file",
"mysql",
"postgresql",
"postgres",
"tstore",
"uri",
"virtuoso",
];
impl StorageBackend {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Memory => "memory",
Self::Fjall => "fjall",
Self::Redb => "redb",
Self::RocksDb => "rocksdb",
Self::Sqlite => "sqlite",
Self::Lmdb => "lmdb",
}
}
#[must_use]
pub const fn is_compiled(self) -> bool {
match self {
Self::Memory => true,
Self::Fjall => cfg!(feature = "storage-fjall"),
Self::Redb => cfg!(feature = "storage-redb"),
Self::RocksDb => cfg!(feature = "storage-rocksdb"),
Self::Sqlite => cfg!(feature = "storage-sqlite"),
Self::Lmdb => cfg!(feature = "storage-lmdb"),
}
}
#[must_use]
pub const fn descriptor(self) -> StorageBackendDescriptor {
StorageBackendDescriptor {
backend: self,
name: self.name(),
cargo_feature: match self {
Self::Memory => None,
Self::Fjall => Some("storage-fjall"),
Self::Redb => Some("storage-redb"),
Self::RocksDb => Some("storage-rocksdb"),
Self::Sqlite => Some("storage-sqlite"),
Self::Lmdb => Some("storage-lmdb"),
},
compiled: self.is_compiled(),
durable: !matches!(self, Self::Memory),
layout_reader: if matches!(self, Self::Memory) {
LayoutReaderPolicy::None
} else {
LayoutReaderPolicy::FormatV1
},
}
}
pub fn from_name(name: &str) -> Result<Self> {
let normalized = name.trim().to_ascii_lowercase();
let backend = match normalized.as_str() {
"memory" | "mem" => Self::Memory,
"fjall" => Self::Fjall,
"redb" => Self::Redb,
"rocksdb" => Self::RocksDb,
"sqlite" => Self::Sqlite,
"lmdb" => Self::Lmdb,
other if KNOWN_OPTIONAL_BACKENDS.contains(&other) => {
return Err(Error::Unsupported(format!(
"storage backend '{other}' is known but not compiled into this build"
)));
}
other if LEGACY_REDLAND_BACKENDS.contains(&other) => {
return Err(Error::Unsupported(format!(
"legacy Redland storage backend '{name}' is unsupported; export to N-Quads and use a supported Oxiland backend (see docs/design/0.4-legacy-storage.md)"
)));
}
other => {
return Err(Error::Unsupported(format!(
"storage backend '{other}' is not recognized"
)));
}
};
if backend != Self::Memory && !backend.is_compiled() {
return Err(Error::Unsupported(format!(
"storage backend '{}' is known but not compiled into this build",
backend.name()
)));
}
Ok(backend)
}
}
#[must_use]
pub fn supported_backends() -> impl ExactSizeIterator<Item = StorageBackendDescriptor> {
SUPPORTED_BACKENDS
.iter()
.copied()
.map(StorageBackend::descriptor)
}
#[must_use]
pub fn compiled_backends() -> &'static [StorageBackend] {
static COMPILED: std::sync::LazyLock<Vec<StorageBackend>> = std::sync::LazyLock::new(|| {
let mut backends = vec![StorageBackend::Memory];
if cfg!(feature = "storage-fjall") {
backends.push(StorageBackend::Fjall);
}
if cfg!(feature = "storage-redb") {
backends.push(StorageBackend::Redb);
}
if cfg!(feature = "storage-rocksdb") {
backends.push(StorageBackend::RocksDb);
}
if cfg!(feature = "storage-sqlite") {
backends.push(StorageBackend::Sqlite);
}
if cfg!(feature = "storage-lmdb") {
backends.push(StorageBackend::Lmdb);
}
backends
});
COMPILED.as_slice()
}
#[must_use]
pub fn is_known_backend_name(name: &str) -> bool {
let normalized = name.trim().to_ascii_lowercase();
matches!(
normalized.as_str(),
"memory" | "mem" | "fjall" | "redb" | "rocksdb" | "sqlite" | "lmdb"
) || KNOWN_OPTIONAL_BACKENDS.contains(&normalized.as_str())
|| LEGACY_REDLAND_BACKENDS.contains(&normalized.as_str())
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OpenOptions {
backend: StorageBackend,
path: PathBuf,
read_only: bool,
create: bool,
}
impl OpenOptions {
#[must_use]
pub fn new(backend: StorageBackend, path: impl AsRef<Path>) -> Self {
Self {
backend,
path: path.as_ref().to_owned(),
read_only: false,
create: true,
}
}
#[must_use]
pub fn fjall(path: impl AsRef<Path>) -> Self {
Self::new(StorageBackend::Fjall, path)
}
#[must_use]
pub fn redb(path: impl AsRef<Path>) -> Self {
Self::new(StorageBackend::Redb, path)
}
#[must_use]
pub fn rocksdb(path: impl AsRef<Path>) -> Self {
Self::new(StorageBackend::RocksDb, path)
}
#[must_use]
pub fn sqlite(path: impl AsRef<Path>) -> Self {
Self::new(StorageBackend::Sqlite, path)
}
#[must_use]
pub fn lmdb(path: impl AsRef<Path>) -> Self {
Self::new(StorageBackend::Lmdb, path)
}
#[must_use]
pub fn backend(&self) -> StorageBackend {
self.backend
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub fn read_only(mut self, read_only: bool) -> Self {
self.read_only = read_only;
self
}
#[must_use]
pub fn create(mut self, create: bool) -> Self {
self.create = create;
self
}
#[must_use]
pub fn is_read_only(&self) -> bool {
self.read_only
}
#[must_use]
pub fn can_create(&self) -> bool {
self.create
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct StorageCapabilities {
pub backend: StorageBackend,
pub durable: bool,
pub transactions: bool,
pub sync: bool,
pub clear: bool,
pub read_only: bool,
pub bulk_load: bool,
}
impl StorageCapabilities {
#[must_use]
pub const fn for_backend(backend: StorageBackend, read_only: bool) -> Self {
match backend {
StorageBackend::Memory => Self {
backend: StorageBackend::Memory,
durable: false,
transactions: !read_only,
sync: true,
clear: !read_only,
read_only,
bulk_load: !read_only,
},
StorageBackend::Fjall => Self::fjall(read_only),
StorageBackend::Redb => Self::redb(read_only),
StorageBackend::RocksDb => Self::rocksdb(read_only),
StorageBackend::Sqlite => Self::sqlite(read_only),
StorageBackend::Lmdb => Self::lmdb(read_only),
}
}
#[must_use]
pub const fn memory() -> Self {
Self::for_backend(StorageBackend::Memory, false)
}
#[must_use]
pub const fn fjall(read_only: bool) -> Self {
Self {
backend: StorageBackend::Fjall,
durable: true,
transactions: !read_only,
sync: true,
clear: !read_only,
read_only,
bulk_load: !read_only,
}
}
#[must_use]
pub const fn redb(read_only: bool) -> Self {
Self {
backend: StorageBackend::Redb,
durable: true,
transactions: !read_only,
sync: true,
clear: !read_only,
read_only,
bulk_load: !read_only,
}
}
#[must_use]
pub const fn rocksdb(read_only: bool) -> Self {
Self {
backend: StorageBackend::RocksDb,
durable: true,
transactions: !read_only,
sync: true,
clear: !read_only,
read_only,
bulk_load: !read_only,
}
}
#[must_use]
pub const fn sqlite(read_only: bool) -> Self {
Self {
backend: StorageBackend::Sqlite,
durable: true,
transactions: !read_only,
sync: true,
clear: !read_only,
read_only,
bulk_load: !read_only,
}
}
#[must_use]
pub const fn lmdb(read_only: bool) -> Self {
Self {
backend: StorageBackend::Lmdb,
durable: true,
transactions: !read_only,
sync: true,
clear: !read_only,
read_only,
bulk_load: !read_only,
}
}
}