Skip to main content

reddb_server/
engine.rs

1//! Engine layer facade.
2//!
3//! This module keeps the physical storage concerns separated from unified domain APIs.
4
5#![allow(deprecated)]
6
7use std::path::{Path, PathBuf};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10use crate::api::{
11    CatalogService, CatalogSnapshot, CollectionStats, RedDBError, RedDBOptions, RedDBResult,
12    StorageMode, StorageMode::Persistent,
13};
14use crate::health::{storage_file_health, HealthProvider, HealthReport};
15use crate::index::IndexCatalog;
16use crate::physical::PhysicalLayout;
17use crate::storage;
18use crate::storage::wal::CheckpointResult;
19
20#[derive(Debug, Clone, Copy)]
21pub struct EngineStats {
22    pub page_count: u32,
23    pub cache_hits: u64,
24    pub cache_misses: u64,
25    pub file_size_bytes: Option<u64>,
26    pub path_exists: bool,
27}
28
29#[derive(Debug, Clone)]
30pub struct EngineInfo {
31    pub path: Option<PathBuf>,
32    pub started_at_unix_ms: u128,
33    pub read_only: bool,
34    pub mode: StorageMode,
35    pub layout: PhysicalLayout,
36    pub options: RedDBOptions,
37}
38
39#[deprecated(
40    since = "1.23.2",
41    note = "RedDBEngine::execute_query is a SILENT NO-OP — it returns Ok(()) without executing any query. Use RedDBRuntime (in-process) or RedDBServer (serving) instead. Removed at the next major."
42)]
43pub struct RedDBEngine {
44    options: RedDBOptions,
45    layout: PhysicalLayout,
46    db: Option<storage::engine::Database>,
47    indices: IndexCatalog,
48    started_at_unix_ms: u128,
49}
50
51impl RedDBEngine {
52    pub fn open<P: AsRef<Path>>(path: P) -> RedDBResult<Self> {
53        Self::with_options(RedDBOptions::persistent(path.as_ref()))
54    }
55
56    pub fn with_options(options: RedDBOptions) -> RedDBResult<Self> {
57        let started_at_unix_ms = SystemTime::now()
58            .duration_since(UNIX_EPOCH)
59            .unwrap_or_default()
60            .as_millis();
61        let layout = PhysicalLayout::from_options(&options);
62
63        let indices = IndexCatalog::register_default_vector_graph(
64            options.has_capability(crate::api::Capability::Table),
65            options.has_capability(crate::api::Capability::Graph),
66        );
67
68        let db = {
69            let path = options.resolved_path(reddb_file::default_database_path());
70            let config = storage::engine::DatabaseConfig {
71                read_only: options.read_only,
72                create: options.create_if_missing,
73                verify_checksums: options.verify_checksums,
74                auto_checkpoint_threshold: options.auto_checkpoint_pages,
75                ..Default::default()
76            };
77            Some(storage::engine::Database::open_with_config(path, config)?)
78        };
79
80        Ok(Self {
81            options,
82            layout,
83            db,
84            indices,
85            started_at_unix_ms,
86        })
87    }
88
89    pub fn options(&self) -> &RedDBOptions {
90        &self.options
91    }
92
93    pub fn layout(&self) -> &PhysicalLayout {
94        &self.layout
95    }
96
97    pub fn mode(&self) -> StorageMode {
98        self.options.mode
99    }
100
101    pub fn path(&self) -> Option<&Path> {
102        self.db.as_ref().map(|db| db.path())
103    }
104
105    pub fn begin_transaction(&self) -> RedDBResult<storage::wal::Transaction> {
106        let db = self
107            .db
108            .as_ref()
109            .ok_or_else(|| RedDBError::InvalidConfig("in-memory mode".to_string()))?;
110        Ok(db.begin()?)
111    }
112
113    pub fn sync(&self) -> RedDBResult<()> {
114        if let Some(db) = &self.db {
115            db.sync()?;
116        }
117        Ok(())
118    }
119
120    pub fn checkpoint(&self) -> RedDBResult<Option<CheckpointResult>> {
121        match &self.db {
122            Some(db) => db.checkpoint().map(Some).map_err(RedDBError::from),
123            None => Ok(None),
124        }
125    }
126
127    pub fn checkpoint_if_needed(&self) -> RedDBResult<Option<CheckpointResult>> {
128        match &self.db {
129            Some(db) => db.maybe_auto_checkpoint().map_err(RedDBError::from),
130            None => Ok(None),
131        }
132    }
133
134    pub fn stats(&self) -> EngineStats {
135        let mut stats = EngineStats {
136            page_count: 0,
137            cache_hits: 0,
138            cache_misses: 0,
139            file_size_bytes: None,
140            path_exists: false,
141        };
142
143        if let Some(db) = &self.db {
144            stats.page_count = db.page_count();
145            if let Ok(file_size) = db.file_size() {
146                stats.file_size_bytes = Some(file_size);
147            }
148            let cache = db.cache_stats();
149            stats.cache_hits = cache.hits;
150            stats.cache_misses = cache.misses;
151            stats.path_exists = db.path().exists();
152        }
153
154        stats
155    }
156
157    pub fn indices(&self) -> &IndexCatalog {
158        &self.indices
159    }
160
161    pub fn close(self) -> RedDBResult<()> {
162        if let Some(db) = self.db {
163            db.close().map_err(RedDBError::from)?;
164        }
165        Ok(())
166    }
167
168    pub fn info(&self) -> EngineInfo {
169        EngineInfo {
170            path: self.path().map(Path::to_path_buf),
171            started_at_unix_ms: self.started_at_unix_ms,
172            read_only: self.options.read_only,
173            mode: self.options.mode,
174            layout: self.layout.clone(),
175            options: self.options.clone(),
176        }
177    }
178
179    fn fallback_snapshot(&self) -> CatalogSnapshot {
180        CatalogSnapshot {
181            name: "reddb_engine".into(),
182            total_entities: 0,
183            total_collections: 0,
184            stats_by_collection: std::collections::BTreeMap::new(),
185            updated_at: SystemTime::UNIX_EPOCH,
186        }
187    }
188}
189
190impl CatalogService for RedDBEngine {
191    fn list_collections(&self) -> Vec<String> {
192        Vec::new()
193    }
194
195    fn collection_stats(&self, _: &str) -> Option<CollectionStats> {
196        None
197    }
198
199    fn catalog_snapshot(&self) -> CatalogSnapshot {
200        self.fallback_snapshot()
201    }
202}
203
204impl HealthProvider for RedDBEngine {
205    fn health(&self) -> HealthReport {
206        let path = self.path();
207        let mut report = match path {
208            Some(path) => storage_file_health(path),
209            None => HealthReport::healthy().with_diagnostic("mode", "in-memory"),
210        };
211        report = report.with_diagnostic("engine-started-at", self.started_at_unix_ms.to_string());
212        report = report.with_diagnostic("read-only", self.options.read_only.to_string());
213        report.with_diagnostic("persistent", self.layout.is_persistent().to_string())
214    }
215}
216
217impl crate::api::DataOps for RedDBEngine {
218    fn execute_query(&self, _query: &str) -> RedDBResult<()> {
219        Ok(())
220    }
221}
222
223impl crate::api::QueryPlanner for RedDBEngine {
224    fn plan_cost(&self, query: &str) -> Option<f64> {
225        if query.trim().is_empty() {
226            None
227        } else {
228            Some(query.len() as f64 * 0.25)
229        }
230    }
231}