1#![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}