mongreldb_server/
storage_runtime.rs1use std::path::{Path, PathBuf};
19use std::sync::Arc;
20
21use mongreldb_core::Database;
22#[cfg(feature = "cluster")]
23use mongreldb_query::ai_retrieval::RemoteAiEndpoint;
24#[cfg(feature = "cluster")]
25use mongreldb_query::distributed::RemoteFragmentEndpoint;
26
27#[cfg(feature = "cluster")]
28use crate::cluster_runtime::ClusterRuntimeHandle;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct StorageRuntimeError {
34 message: String,
35}
36
37impl StorageRuntimeError {
38 pub fn new(message: impl Into<String>) -> Self {
40 Self {
41 message: message.into(),
42 }
43 }
44
45 pub fn cluster_refuses_standalone_bypass() -> Self {
47 Self::new(
48 "cluster mode refuses standalone AppState.db data-plane access; \
49 public data operations are owned by consensus/tablet state",
50 )
51 }
52}
53
54impl std::fmt::Display for StorageRuntimeError {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 f.write_str(&self.message)
57 }
58}
59
60impl std::error::Error for StorageRuntimeError {}
61
62impl From<StorageRuntimeError> for mongreldb_core::MongrelError {
63 fn from(error: StorageRuntimeError) -> Self {
64 mongreldb_core::MongrelError::Other(error.message)
65 }
66}
67
68#[derive(Clone)]
70pub struct StandaloneRuntime {
71 db: Arc<Database>,
72}
73
74impl StandaloneRuntime {
75 pub fn new(db: Arc<Database>) -> Self {
77 Self { db }
78 }
79
80 pub fn db(&self) -> &Arc<Database> {
82 &self.db
83 }
84
85 pub fn root(&self) -> &Path {
87 self.db.root()
88 }
89}
90
91#[cfg(feature = "cluster")]
97#[derive(Clone)]
98pub struct ClusterGatewayRuntime {
99 handle: ClusterRuntimeHandle,
100 fragment_endpoint: Option<Arc<RemoteFragmentEndpoint>>,
101 ai_endpoint: Option<Arc<RemoteAiEndpoint>>,
102}
103
104#[cfg(feature = "cluster")]
105impl ClusterGatewayRuntime {
106 pub fn new(handle: ClusterRuntimeHandle) -> Self {
108 Self {
109 handle,
110 fragment_endpoint: None,
111 ai_endpoint: None,
112 }
113 }
114
115 pub fn with_workers(
117 handle: ClusterRuntimeHandle,
118 fragment_endpoint: Arc<RemoteFragmentEndpoint>,
119 ai_endpoint: Arc<RemoteAiEndpoint>,
120 ) -> Self {
121 Self {
122 handle,
123 fragment_endpoint: Some(fragment_endpoint),
124 ai_endpoint: Some(ai_endpoint),
125 }
126 }
127
128 pub fn handle(&self) -> &ClusterRuntimeHandle {
130 &self.handle
131 }
132
133 pub fn node_data(&self) -> &Path {
135 self.handle.node_data()
136 }
137
138 pub fn fragment_endpoint(&self) -> Option<&Arc<RemoteFragmentEndpoint>> {
140 self.fragment_endpoint.as_ref()
141 }
142
143 pub fn ai_endpoint(&self) -> Option<&Arc<RemoteAiEndpoint>> {
145 self.ai_endpoint.as_ref()
146 }
147
148 pub fn set_workers(
150 &mut self,
151 fragment_endpoint: Arc<RemoteFragmentEndpoint>,
152 ai_endpoint: Arc<RemoteAiEndpoint>,
153 ) {
154 self.fragment_endpoint = Some(fragment_endpoint);
155 self.ai_endpoint = Some(ai_endpoint);
156 }
157
158 pub fn workers_installed(&self) -> bool {
160 self.fragment_endpoint.is_some() && self.ai_endpoint.is_some()
161 }
162}
163
164#[derive(Clone)]
166pub enum ServerStorageRuntime {
167 Standalone(StandaloneRuntime),
169 #[cfg(feature = "cluster")]
171 Cluster(ClusterGatewayRuntime),
172}
173
174impl ServerStorageRuntime {
175 pub fn standalone(db: Arc<Database>) -> Self {
177 Self::Standalone(StandaloneRuntime::new(db))
178 }
179
180 #[cfg(feature = "cluster")]
182 pub fn cluster(handle: ClusterRuntimeHandle) -> Self {
183 Self::Cluster(ClusterGatewayRuntime::new(handle))
184 }
185
186 #[cfg(feature = "cluster")]
188 pub fn cluster_with_workers(
189 handle: ClusterRuntimeHandle,
190 fragment_endpoint: Arc<RemoteFragmentEndpoint>,
191 ai_endpoint: Arc<RemoteAiEndpoint>,
192 ) -> Self {
193 Self::Cluster(ClusterGatewayRuntime::with_workers(
194 handle,
195 fragment_endpoint,
196 ai_endpoint,
197 ))
198 }
199
200 pub fn is_cluster(&self) -> bool {
203 #[cfg(feature = "cluster")]
204 {
205 matches!(self, Self::Cluster(_))
206 }
207 #[cfg(not(feature = "cluster"))]
208 {
209 false
210 }
211 }
212
213 pub fn is_standalone(&self) -> bool {
215 matches!(self, Self::Standalone(_))
216 }
217
218 pub fn standalone_db(&self) -> Option<&Arc<Database>> {
220 match self {
221 Self::Standalone(rt) => Some(rt.db()),
222 #[cfg(feature = "cluster")]
223 Self::Cluster(_) => None,
224 }
225 }
226
227 pub fn require_standalone_db(&self) -> Result<&Arc<Database>, StorageRuntimeError> {
230 self.standalone_db()
231 .ok_or_else(StorageRuntimeError::cluster_refuses_standalone_bypass)
232 }
233
234 #[cfg(feature = "cluster")]
236 pub fn cluster_handle(&self) -> Option<&ClusterRuntimeHandle> {
237 match self {
238 Self::Cluster(rt) => Some(rt.handle()),
239 Self::Standalone(_) => None,
240 }
241 }
242
243 #[cfg(feature = "cluster")]
245 pub fn cluster_gateway(&self) -> Option<&ClusterGatewayRuntime> {
246 match self {
247 Self::Cluster(rt) => Some(rt),
248 Self::Standalone(_) => None,
249 }
250 }
251
252 #[cfg(feature = "cluster")]
254 pub fn cluster_gateway_mut(&mut self) -> Option<&mut ClusterGatewayRuntime> {
255 match self {
256 Self::Cluster(rt) => Some(rt),
257 Self::Standalone(_) => None,
258 }
259 }
260
261 pub fn durable_path(&self) -> PathBuf {
264 match self {
265 Self::Standalone(rt) => rt.root().to_path_buf(),
266 #[cfg(feature = "cluster")]
267 Self::Cluster(rt) => rt.node_data().to_path_buf(),
268 }
269 }
270
271 pub fn mode_name(&self) -> &'static str {
273 match self {
274 Self::Standalone(_) => "standalone",
275 #[cfg(feature = "cluster")]
276 Self::Cluster(_) => "cluster",
277 }
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284 use tempfile::tempdir;
285
286 #[test]
287 fn standalone_exposes_db_cluster_refuses() {
288 let dir = tempdir().unwrap();
289 let db = Arc::new(Database::create(dir.path()).unwrap());
290 let standalone = ServerStorageRuntime::standalone(Arc::clone(&db));
291 assert!(standalone.is_standalone());
292 assert!(!standalone.is_cluster());
293 assert!(Arc::ptr_eq(
294 standalone.require_standalone_db().unwrap(),
295 &db
296 ));
297 assert_eq!(standalone.mode_name(), "standalone");
298 }
299
300 #[test]
301 fn cluster_runtime_enum_exists_and_has_no_standalone_db() {
302 let dir = tempdir().unwrap();
306 let _ = dir;
307 let err = StorageRuntimeError::cluster_refuses_standalone_bypass();
308 assert!(err.to_string().contains("cluster mode refuses"));
309 let converted: mongreldb_core::MongrelError = err.into();
310 assert!(converted.to_string().contains("cluster mode refuses"));
311 }
312}