rskit_database/
database.rs1use std::collections::VecDeque;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::time::Instant;
6
7use async_trait::async_trait;
8use parking_lot::Mutex;
9use rskit_bootstrap::{Component, Health};
10use rskit_errors::{AppError, AppResult, ErrorCode};
11use serde::{Deserialize, Serialize};
12
13use crate::config::{DatabaseConfig, MemoryDatabaseConfig};
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct DatabaseQuery {
18 pub statement: String,
20 #[serde(default)]
22 pub parameters: Vec<serde_json::Value>,
23}
24
25impl DatabaseQuery {
26 #[must_use]
28 pub fn new(statement: impl Into<String>) -> Self {
29 Self {
30 statement: statement.into(),
31 parameters: Vec::new(),
32 }
33 }
34
35 #[must_use]
37 pub fn with_parameter(mut self, value: impl Into<serde_json::Value>) -> Self {
38 self.parameters.push(value.into());
39 self
40 }
41}
42
43#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
45pub struct DatabaseResult {
46 pub rows_affected: u64,
48}
49
50#[async_trait]
52pub trait DatabaseTransaction: Send + Sync {
53 async fn execute(&self, query: DatabaseQuery) -> AppResult<DatabaseResult>;
55 async fn commit(self: Box<Self>) -> AppResult<()>;
57 async fn rollback(self: Box<Self>) -> AppResult<()>;
59}
60
61#[async_trait]
63pub trait DatabaseClient: Send + Sync {
64 async fn execute(&self, query: DatabaseQuery) -> AppResult<DatabaseResult>;
66 async fn begin(&self) -> AppResult<Box<dyn DatabaseTransaction>>;
68 async fn ping(&self) -> AppResult<()>;
70}
71
72#[derive(Debug)]
74pub struct InMemoryDatabase {
75 config: MemoryDatabaseConfig,
76 connected: AtomicBool,
77 history: Mutex<VecDeque<DatabaseQuery>>,
78}
79
80impl InMemoryDatabase {
81 #[must_use]
83 pub fn new(config: MemoryDatabaseConfig) -> Self {
84 Self {
85 config,
86 connected: AtomicBool::new(true),
87 history: Mutex::new(VecDeque::new()),
88 }
89 }
90
91 #[must_use]
93 pub fn recorded_queries(&self) -> Vec<DatabaseQuery> {
94 self.history.lock().iter().cloned().collect()
95 }
96
97 fn record(&self, query: DatabaseQuery) {
98 if self.config.statement_history == 0 {
99 return;
100 }
101 let mut history = self.history.lock();
102 if history.len() == self.config.statement_history {
103 history.pop_front();
104 }
105 history.push_back(query);
106 }
107}
108
109impl Default for InMemoryDatabase {
110 fn default() -> Self {
111 Self::new(MemoryDatabaseConfig::default())
112 }
113}
114
115#[async_trait]
116impl DatabaseClient for InMemoryDatabase {
117 async fn execute(&self, query: DatabaseQuery) -> AppResult<DatabaseResult> {
118 if !self.connected.load(Ordering::SeqCst) {
119 return Err(AppError::new(
120 ErrorCode::ConnectionFailed,
121 "in-memory database is stopped",
122 ));
123 }
124 if query.statement.trim().is_empty() {
125 return Err(AppError::new(
126 ErrorCode::InvalidInput,
127 "database query statement is required",
128 ));
129 }
130 self.record(query);
131 Ok(DatabaseResult { rows_affected: 1 })
132 }
133
134 async fn begin(&self) -> AppResult<Box<dyn DatabaseTransaction>> {
135 self.ping().await?;
136 Ok(Box::new(InMemoryTransaction {
137 started_at: Instant::now(),
138 queries: Mutex::new(Vec::new()),
139 committed: AtomicBool::new(false),
140 rolled_back: AtomicBool::new(false),
141 }))
142 }
143
144 async fn ping(&self) -> AppResult<()> {
145 if self.connected.load(Ordering::SeqCst) {
146 Ok(())
147 } else {
148 Err(AppError::new(
149 ErrorCode::ConnectionFailed,
150 "in-memory database is stopped",
151 ))
152 }
153 }
154}
155
156#[async_trait]
157impl Component for InMemoryDatabase {
158 fn name(&self) -> &str {
159 "database"
160 }
161
162 async fn start(&self) -> AppResult<()> {
163 self.connected.store(true, Ordering::SeqCst);
164 Ok(())
165 }
166
167 async fn stop(&self) -> AppResult<()> {
168 self.connected.store(false, Ordering::SeqCst);
169 Ok(())
170 }
171
172 fn health(&self) -> Health {
173 if self.connected.load(Ordering::SeqCst) {
174 Health::healthy("database")
175 } else {
176 Health::unhealthy("database", "backend is stopped")
177 }
178 }
179}
180
181#[derive(Debug)]
182struct InMemoryTransaction {
183 started_at: Instant,
184 queries: Mutex<Vec<DatabaseQuery>>,
185 committed: AtomicBool,
186 rolled_back: AtomicBool,
187}
188
189#[async_trait]
190impl DatabaseTransaction for InMemoryTransaction {
191 async fn execute(&self, query: DatabaseQuery) -> AppResult<DatabaseResult> {
192 if query.statement.trim().is_empty() {
193 return Err(AppError::new(
194 ErrorCode::InvalidInput,
195 "database query statement is required",
196 ));
197 }
198 self.queries.lock().push(query);
199 Ok(DatabaseResult { rows_affected: 1 })
200 }
201
202 async fn commit(self: Box<Self>) -> AppResult<()> {
203 if self.rolled_back.load(Ordering::SeqCst) {
204 return Err(AppError::new(
205 ErrorCode::InvalidInput,
206 "database transaction was already rolled back",
207 ));
208 }
209 self.committed.store(true, Ordering::SeqCst);
210 let _elapsed = self.started_at.elapsed();
211 Ok(())
212 }
213
214 async fn rollback(self: Box<Self>) -> AppResult<()> {
215 if self.committed.load(Ordering::SeqCst) {
216 return Err(AppError::new(
217 ErrorCode::InvalidInput,
218 "database transaction was already committed",
219 ));
220 }
221 self.rolled_back.store(true, Ordering::SeqCst);
222 Ok(())
223 }
224}
225
226pub(crate) fn memory_from_config(config: &DatabaseConfig) -> InMemoryDatabase {
227 InMemoryDatabase::new(config.memory.clone())
228}