mongreldb_server/
storage_runtime.rs1use std::path::{Path, PathBuf};
14use std::sync::Arc;
15
16use mongreldb_core::Database;
17use mongreldb_query::ai_retrieval::RemoteAiEndpoint;
18use mongreldb_query::distributed::RemoteFragmentEndpoint;
19
20use crate::cluster_runtime::ClusterRuntimeHandle;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct StorageRuntimeError {
26 message: String,
27}
28
29impl StorageRuntimeError {
30 pub fn new(message: impl Into<String>) -> Self {
32 Self {
33 message: message.into(),
34 }
35 }
36
37 pub fn cluster_refuses_standalone_bypass() -> Self {
39 Self::new(
40 "cluster mode refuses standalone AppState.db data-plane access; \
41 public data operations are owned by consensus/tablet state",
42 )
43 }
44}
45
46impl std::fmt::Display for StorageRuntimeError {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 f.write_str(&self.message)
49 }
50}
51
52impl std::error::Error for StorageRuntimeError {}
53
54impl From<StorageRuntimeError> for mongreldb_core::MongrelError {
55 fn from(error: StorageRuntimeError) -> Self {
56 mongreldb_core::MongrelError::Other(error.message)
57 }
58}
59
60#[derive(Clone)]
62pub struct StandaloneRuntime {
63 db: Arc<Database>,
64}
65
66impl StandaloneRuntime {
67 pub fn new(db: Arc<Database>) -> Self {
69 Self { db }
70 }
71
72 pub fn db(&self) -> &Arc<Database> {
74 &self.db
75 }
76
77 pub fn root(&self) -> &Path {
79 self.db.root()
80 }
81}
82
83#[derive(Clone)]
89pub struct ClusterGatewayRuntime {
90 handle: ClusterRuntimeHandle,
91 fragment_endpoint: Option<Arc<RemoteFragmentEndpoint>>,
92 ai_endpoint: Option<Arc<RemoteAiEndpoint>>,
93}
94
95impl ClusterGatewayRuntime {
96 pub fn new(handle: ClusterRuntimeHandle) -> Self {
98 Self {
99 handle,
100 fragment_endpoint: None,
101 ai_endpoint: None,
102 }
103 }
104
105 pub fn with_workers(
107 handle: ClusterRuntimeHandle,
108 fragment_endpoint: Arc<RemoteFragmentEndpoint>,
109 ai_endpoint: Arc<RemoteAiEndpoint>,
110 ) -> Self {
111 Self {
112 handle,
113 fragment_endpoint: Some(fragment_endpoint),
114 ai_endpoint: Some(ai_endpoint),
115 }
116 }
117
118 pub fn handle(&self) -> &ClusterRuntimeHandle {
120 &self.handle
121 }
122
123 pub fn node_data(&self) -> &Path {
125 self.handle.node_data()
126 }
127
128 pub fn fragment_endpoint(&self) -> Option<&Arc<RemoteFragmentEndpoint>> {
130 self.fragment_endpoint.as_ref()
131 }
132
133 pub fn ai_endpoint(&self) -> Option<&Arc<RemoteAiEndpoint>> {
135 self.ai_endpoint.as_ref()
136 }
137
138 pub fn set_workers(
140 &mut self,
141 fragment_endpoint: Arc<RemoteFragmentEndpoint>,
142 ai_endpoint: Arc<RemoteAiEndpoint>,
143 ) {
144 self.fragment_endpoint = Some(fragment_endpoint);
145 self.ai_endpoint = Some(ai_endpoint);
146 }
147
148 pub fn workers_installed(&self) -> bool {
150 self.fragment_endpoint.is_some() && self.ai_endpoint.is_some()
151 }
152}
153
154#[derive(Clone)]
156pub enum ServerStorageRuntime {
157 Standalone(StandaloneRuntime),
159 Cluster(ClusterGatewayRuntime),
161}
162
163impl ServerStorageRuntime {
164 pub fn standalone(db: Arc<Database>) -> Self {
166 Self::Standalone(StandaloneRuntime::new(db))
167 }
168
169 pub fn cluster(handle: ClusterRuntimeHandle) -> Self {
171 Self::Cluster(ClusterGatewayRuntime::new(handle))
172 }
173
174 pub fn cluster_with_workers(
176 handle: ClusterRuntimeHandle,
177 fragment_endpoint: Arc<RemoteFragmentEndpoint>,
178 ai_endpoint: Arc<RemoteAiEndpoint>,
179 ) -> Self {
180 Self::Cluster(ClusterGatewayRuntime::with_workers(
181 handle,
182 fragment_endpoint,
183 ai_endpoint,
184 ))
185 }
186
187 pub fn is_cluster(&self) -> bool {
189 matches!(self, Self::Cluster(_))
190 }
191
192 pub fn is_standalone(&self) -> bool {
194 matches!(self, Self::Standalone(_))
195 }
196
197 pub fn standalone_db(&self) -> Option<&Arc<Database>> {
199 match self {
200 Self::Standalone(rt) => Some(rt.db()),
201 Self::Cluster(_) => None,
202 }
203 }
204
205 pub fn require_standalone_db(&self) -> Result<&Arc<Database>, StorageRuntimeError> {
208 self.standalone_db()
209 .ok_or_else(StorageRuntimeError::cluster_refuses_standalone_bypass)
210 }
211
212 pub fn cluster_handle(&self) -> Option<&ClusterRuntimeHandle> {
214 match self {
215 Self::Cluster(rt) => Some(rt.handle()),
216 Self::Standalone(_) => None,
217 }
218 }
219
220 pub fn cluster_gateway(&self) -> Option<&ClusterGatewayRuntime> {
222 match self {
223 Self::Cluster(rt) => Some(rt),
224 Self::Standalone(_) => None,
225 }
226 }
227
228 pub fn cluster_gateway_mut(&mut self) -> Option<&mut ClusterGatewayRuntime> {
230 match self {
231 Self::Cluster(rt) => Some(rt),
232 Self::Standalone(_) => None,
233 }
234 }
235
236 pub fn durable_path(&self) -> PathBuf {
239 match self {
240 Self::Standalone(rt) => rt.root().to_path_buf(),
241 Self::Cluster(rt) => rt.node_data().to_path_buf(),
242 }
243 }
244
245 pub fn mode_name(&self) -> &'static str {
247 match self {
248 Self::Standalone(_) => "standalone",
249 Self::Cluster(_) => "cluster",
250 }
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use tempfile::tempdir;
258
259 #[test]
260 fn standalone_exposes_db_cluster_refuses() {
261 let dir = tempdir().unwrap();
262 let db = Arc::new(Database::create(dir.path()).unwrap());
263 let standalone = ServerStorageRuntime::standalone(Arc::clone(&db));
264 assert!(standalone.is_standalone());
265 assert!(!standalone.is_cluster());
266 assert!(Arc::ptr_eq(
267 standalone.require_standalone_db().unwrap(),
268 &db
269 ));
270 assert_eq!(standalone.mode_name(), "standalone");
271 }
272
273 #[test]
274 fn cluster_runtime_enum_exists_and_has_no_standalone_db() {
275 let dir = tempdir().unwrap();
279 let _ = dir;
280 let err = StorageRuntimeError::cluster_refuses_standalone_bypass();
281 assert!(err.to_string().contains("cluster mode refuses"));
282 let converted: mongreldb_core::MongrelError = err.into();
283 assert!(converted.to_string().contains("cluster mode refuses"));
284 }
285}