1use std::fmt::Debug;
8
9use async_trait::async_trait;
10
11use crate::error::BackendError;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum BackendKind {
19 Sqlite,
21 Postgres,
23 Cassandra,
25 MongoDB,
27 Neo4j,
29 Elasticsearch,
31 S3,
33 Custom(&'static str),
35}
36
37impl std::fmt::Display for BackendKind {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self {
40 BackendKind::Sqlite => write!(f, "sqlite"),
41 BackendKind::Postgres => write!(f, "postgres"),
42 BackendKind::Cassandra => write!(f, "cassandra"),
43 BackendKind::MongoDB => write!(f, "mongodb"),
44 BackendKind::Neo4j => write!(f, "neo4j"),
45 BackendKind::Elasticsearch => write!(f, "elasticsearch"),
46 BackendKind::S3 => write!(f, "s3"),
47 BackendKind::Custom(name) => write!(f, "{}", name),
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
56pub enum BackendCapability {
57 Crud,
59 Versioning,
61 InstanceHistory,
63 TypeHistory,
65 SystemHistory,
67 BasicSearch,
69 DateSearch,
71 QuantitySearch,
73 ReferenceSearch,
75 ChainedSearch,
77 ReverseChaining,
79 Include,
81 Revinclude,
83 FullTextSearch,
85 TerminologySearch,
87 Transactions,
89 OptimisticLocking,
91 PessimisticLocking,
93 CursorPagination,
95 OffsetPagination,
97 Sorting,
99 BulkExport,
101 BulkImport,
103 SharedSchema,
105 SchemaPerTenant,
107 DatabasePerTenant,
109}
110
111impl std::fmt::Display for BackendCapability {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 let name = match self {
114 BackendCapability::Crud => "crud",
115 BackendCapability::Versioning => "versioning",
116 BackendCapability::InstanceHistory => "instance-history",
117 BackendCapability::TypeHistory => "type-history",
118 BackendCapability::SystemHistory => "system-history",
119 BackendCapability::BasicSearch => "basic-search",
120 BackendCapability::DateSearch => "date-search",
121 BackendCapability::QuantitySearch => "quantity-search",
122 BackendCapability::ReferenceSearch => "reference-search",
123 BackendCapability::ChainedSearch => "chained-search",
124 BackendCapability::ReverseChaining => "reverse-chaining",
125 BackendCapability::Include => "include",
126 BackendCapability::Revinclude => "revinclude",
127 BackendCapability::FullTextSearch => "full-text-search",
128 BackendCapability::TerminologySearch => "terminology-search",
129 BackendCapability::Transactions => "transactions",
130 BackendCapability::OptimisticLocking => "optimistic-locking",
131 BackendCapability::PessimisticLocking => "pessimistic-locking",
132 BackendCapability::CursorPagination => "cursor-pagination",
133 BackendCapability::OffsetPagination => "offset-pagination",
134 BackendCapability::Sorting => "sorting",
135 BackendCapability::BulkExport => "bulk-export",
136 BackendCapability::BulkImport => "bulk-import",
137 BackendCapability::SharedSchema => "shared-schema",
138 BackendCapability::SchemaPerTenant => "schema-per-tenant",
139 BackendCapability::DatabasePerTenant => "database-per-tenant",
140 };
141 write!(f, "{}", name)
142 }
143}
144
145#[derive(Debug, Clone)]
147pub struct BackendConfig {
148 pub connection_string: String,
150 pub max_connections: u32,
152 pub min_connections: u32,
154 pub connect_timeout_ms: u64,
156 pub idle_timeout_ms: Option<u64>,
158 pub max_lifetime_ms: Option<u64>,
160}
161
162impl Default for BackendConfig {
163 fn default() -> Self {
164 Self {
165 connection_string: String::new(),
166 max_connections: 10,
167 min_connections: 1,
168 connect_timeout_ms: 5000,
169 idle_timeout_ms: Some(600_000), max_lifetime_ms: Some(1_800_000), }
172 }
173}
174
175impl BackendConfig {
176 pub fn new(connection_string: impl Into<String>) -> Self {
178 Self {
179 connection_string: connection_string.into(),
180 ..Default::default()
181 }
182 }
183
184 pub fn with_max_connections(mut self, max: u32) -> Self {
186 self.max_connections = max;
187 self
188 }
189
190 pub fn with_connect_timeout_ms(mut self, timeout: u64) -> Self {
192 self.connect_timeout_ms = timeout;
193 self
194 }
195}
196
197#[async_trait]
222pub trait Backend: Send + Sync + Debug {
223 type Connection: Send;
225
226 fn kind(&self) -> BackendKind;
228
229 fn name(&self) -> &'static str;
231
232 fn supports(&self, capability: BackendCapability) -> bool;
234
235 fn capabilities(&self) -> Vec<BackendCapability>;
237
238 async fn acquire(&self) -> Result<Self::Connection, BackendError>;
240
241 async fn release(&self, conn: Self::Connection);
243
244 async fn health_check(&self) -> Result<(), BackendError>;
246
247 async fn initialize(&self) -> Result<(), BackendError>;
249
250 async fn migrate(&self) -> Result<(), BackendError>;
252}
253
254pub trait BackendPoolStats {
256 fn active_connections(&self) -> u32;
258
259 fn idle_connections(&self) -> u32;
261
262 fn max_connections(&self) -> u32;
264
265 fn pending_connections(&self) -> u32;
267}
268
269pub trait TransactionalBackend: Backend {}
271
272pub trait FullTextBackend: Backend {}
274
275pub trait GraphBackend: Backend {}
277
278pub trait TimeSeriesBackend: Backend {}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_backend_kind_display() {
287 assert_eq!(BackendKind::Sqlite.to_string(), "sqlite");
288 assert_eq!(BackendKind::Postgres.to_string(), "postgres");
289 assert_eq!(BackendKind::Custom("custom-db").to_string(), "custom-db");
290 }
291
292 #[test]
293 fn test_backend_capability_display() {
294 assert_eq!(BackendCapability::Crud.to_string(), "crud");
295 assert_eq!(
296 BackendCapability::ChainedSearch.to_string(),
297 "chained-search"
298 );
299 assert_eq!(
300 BackendCapability::FullTextSearch.to_string(),
301 "full-text-search"
302 );
303 }
304
305 #[test]
306 fn test_backend_config_default() {
307 let config = BackendConfig::default();
308 assert_eq!(config.max_connections, 10);
309 assert_eq!(config.min_connections, 1);
310 assert_eq!(config.connect_timeout_ms, 5000);
311 }
312
313 #[test]
314 fn test_backend_config_builder() {
315 let config = BackendConfig::new("postgres://localhost/db")
316 .with_max_connections(20)
317 .with_connect_timeout_ms(10000);
318
319 assert_eq!(config.connection_string, "postgres://localhost/db");
320 assert_eq!(config.max_connections, 20);
321 assert_eq!(config.connect_timeout_ms, 10000);
322 }
323}