use std::fmt::Debug;
use async_trait::async_trait;
use crate::error::BackendError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BackendKind {
Sqlite,
Postgres,
Cassandra,
MongoDB,
Neo4j,
Elasticsearch,
S3,
Custom(&'static str),
}
impl std::fmt::Display for BackendKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BackendKind::Sqlite => write!(f, "sqlite"),
BackendKind::Postgres => write!(f, "postgres"),
BackendKind::Cassandra => write!(f, "cassandra"),
BackendKind::MongoDB => write!(f, "mongodb"),
BackendKind::Neo4j => write!(f, "neo4j"),
BackendKind::Elasticsearch => write!(f, "elasticsearch"),
BackendKind::S3 => write!(f, "s3"),
BackendKind::Custom(name) => write!(f, "{}", name),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BackendCapability {
Crud,
Versioning,
InstanceHistory,
TypeHistory,
SystemHistory,
BasicSearch,
DateSearch,
QuantitySearch,
ReferenceSearch,
ChainedSearch,
ReverseChaining,
Include,
Revinclude,
FullTextSearch,
TerminologySearch,
Transactions,
OptimisticLocking,
PessimisticLocking,
CursorPagination,
OffsetPagination,
Sorting,
BulkExport,
BulkImport,
SharedSchema,
SchemaPerTenant,
DatabasePerTenant,
}
impl std::fmt::Display for BackendCapability {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
BackendCapability::Crud => "crud",
BackendCapability::Versioning => "versioning",
BackendCapability::InstanceHistory => "instance-history",
BackendCapability::TypeHistory => "type-history",
BackendCapability::SystemHistory => "system-history",
BackendCapability::BasicSearch => "basic-search",
BackendCapability::DateSearch => "date-search",
BackendCapability::QuantitySearch => "quantity-search",
BackendCapability::ReferenceSearch => "reference-search",
BackendCapability::ChainedSearch => "chained-search",
BackendCapability::ReverseChaining => "reverse-chaining",
BackendCapability::Include => "include",
BackendCapability::Revinclude => "revinclude",
BackendCapability::FullTextSearch => "full-text-search",
BackendCapability::TerminologySearch => "terminology-search",
BackendCapability::Transactions => "transactions",
BackendCapability::OptimisticLocking => "optimistic-locking",
BackendCapability::PessimisticLocking => "pessimistic-locking",
BackendCapability::CursorPagination => "cursor-pagination",
BackendCapability::OffsetPagination => "offset-pagination",
BackendCapability::Sorting => "sorting",
BackendCapability::BulkExport => "bulk-export",
BackendCapability::BulkImport => "bulk-import",
BackendCapability::SharedSchema => "shared-schema",
BackendCapability::SchemaPerTenant => "schema-per-tenant",
BackendCapability::DatabasePerTenant => "database-per-tenant",
};
write!(f, "{}", name)
}
}
#[derive(Debug, Clone)]
pub struct BackendConfig {
pub connection_string: String,
pub max_connections: u32,
pub min_connections: u32,
pub connect_timeout_ms: u64,
pub idle_timeout_ms: Option<u64>,
pub max_lifetime_ms: Option<u64>,
}
impl Default for BackendConfig {
fn default() -> Self {
Self {
connection_string: String::new(),
max_connections: 10,
min_connections: 1,
connect_timeout_ms: 5000,
idle_timeout_ms: Some(600_000), max_lifetime_ms: Some(1_800_000), }
}
}
impl BackendConfig {
pub fn new(connection_string: impl Into<String>) -> Self {
Self {
connection_string: connection_string.into(),
..Default::default()
}
}
pub fn with_max_connections(mut self, max: u32) -> Self {
self.max_connections = max;
self
}
pub fn with_connect_timeout_ms(mut self, timeout: u64) -> Self {
self.connect_timeout_ms = timeout;
self
}
}
#[async_trait]
pub trait Backend: Send + Sync + Debug {
type Connection: Send;
fn kind(&self) -> BackendKind;
fn name(&self) -> &'static str;
fn supports(&self, capability: BackendCapability) -> bool;
fn capabilities(&self) -> Vec<BackendCapability>;
async fn acquire(&self) -> Result<Self::Connection, BackendError>;
async fn release(&self, conn: Self::Connection);
async fn health_check(&self) -> Result<(), BackendError>;
async fn initialize(&self) -> Result<(), BackendError>;
async fn migrate(&self) -> Result<(), BackendError>;
}
pub trait BackendPoolStats {
fn active_connections(&self) -> u32;
fn idle_connections(&self) -> u32;
fn max_connections(&self) -> u32;
fn pending_connections(&self) -> u32;
}
pub trait TransactionalBackend: Backend {}
pub trait FullTextBackend: Backend {}
pub trait GraphBackend: Backend {}
pub trait TimeSeriesBackend: Backend {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backend_kind_display() {
assert_eq!(BackendKind::Sqlite.to_string(), "sqlite");
assert_eq!(BackendKind::Postgres.to_string(), "postgres");
assert_eq!(BackendKind::Custom("custom-db").to_string(), "custom-db");
}
#[test]
fn test_backend_capability_display() {
assert_eq!(BackendCapability::Crud.to_string(), "crud");
assert_eq!(
BackendCapability::ChainedSearch.to_string(),
"chained-search"
);
assert_eq!(
BackendCapability::FullTextSearch.to_string(),
"full-text-search"
);
}
#[test]
fn test_backend_config_default() {
let config = BackendConfig::default();
assert_eq!(config.max_connections, 10);
assert_eq!(config.min_connections, 1);
assert_eq!(config.connect_timeout_ms, 5000);
}
#[test]
fn test_backend_config_builder() {
let config = BackendConfig::new("postgres://localhost/db")
.with_max_connections(20)
.with_connect_timeout_ms(10000);
assert_eq!(config.connection_string, "postgres://localhost/db");
assert_eq!(config.max_connections, 20);
assert_eq!(config.connect_timeout_ms, 10000);
}
}