grafeo_engine/database/
admin.rs1use std::path::Path;
4
5use grafeo_common::utils::error::Result;
6
7impl super::GrafeoDB {
8 #[must_use]
14 pub fn node_count(&self) -> usize {
15 self.store.node_count()
16 }
17
18 #[must_use]
20 pub fn edge_count(&self) -> usize {
21 self.store.edge_count()
22 }
23
24 #[must_use]
26 pub fn label_count(&self) -> usize {
27 self.store.label_count()
28 }
29
30 #[must_use]
32 pub fn property_key_count(&self) -> usize {
33 self.store.property_key_count()
34 }
35
36 #[must_use]
38 pub fn edge_type_count(&self) -> usize {
39 self.store.edge_type_count()
40 }
41
42 #[must_use]
50 pub fn is_persistent(&self) -> bool {
51 self.config.path.is_some()
52 }
53
54 #[must_use]
58 pub fn path(&self) -> Option<&Path> {
59 self.config.path.as_deref()
60 }
61
62 #[must_use]
66 pub fn info(&self) -> crate::admin::DatabaseInfo {
67 crate::admin::DatabaseInfo {
68 mode: crate::admin::DatabaseMode::Lpg,
69 node_count: self.store.node_count(),
70 edge_count: self.store.edge_count(),
71 is_persistent: self.is_persistent(),
72 path: self.config.path.clone(),
73 wal_enabled: self.config.wal_enabled,
74 version: env!("CARGO_PKG_VERSION").to_string(),
75 }
76 }
77
78 #[must_use]
82 pub fn detailed_stats(&self) -> crate::admin::DatabaseStats {
83 #[cfg(feature = "wal")]
84 let disk_bytes = self.config.path.as_ref().and_then(|p| {
85 if p.exists() {
86 Self::calculate_disk_usage(p).ok()
87 } else {
88 None
89 }
90 });
91 #[cfg(not(feature = "wal"))]
92 let disk_bytes: Option<usize> = None;
93
94 crate::admin::DatabaseStats {
95 node_count: self.store.node_count(),
96 edge_count: self.store.edge_count(),
97 label_count: self.store.label_count(),
98 edge_type_count: self.store.edge_type_count(),
99 property_key_count: self.store.property_key_count(),
100 index_count: 0, memory_bytes: self.buffer_manager.allocated(),
102 disk_bytes,
103 }
104 }
105
106 #[cfg(feature = "wal")]
108 fn calculate_disk_usage(path: &Path) -> Result<usize> {
109 let mut total = 0usize;
110 if path.is_dir() {
111 for entry in std::fs::read_dir(path)? {
112 let entry = entry?;
113 let metadata = entry.metadata()?;
114 if metadata.is_file() {
115 total += metadata.len() as usize;
116 } else if metadata.is_dir() {
117 total += Self::calculate_disk_usage(&entry.path())?;
118 }
119 }
120 }
121 Ok(total)
122 }
123
124 #[must_use]
129 pub fn schema(&self) -> crate::admin::SchemaInfo {
130 let labels = self
131 .store
132 .all_labels()
133 .into_iter()
134 .map(|name| crate::admin::LabelInfo {
135 name: name.clone(),
136 count: self.store.nodes_with_label(&name).count(),
137 })
138 .collect();
139
140 let edge_types = self
141 .store
142 .all_edge_types()
143 .into_iter()
144 .map(|name| crate::admin::EdgeTypeInfo {
145 name: name.clone(),
146 count: self.store.edges_with_type(&name).count(),
147 })
148 .collect();
149
150 let property_keys = self.store.all_property_keys();
151
152 crate::admin::SchemaInfo::Lpg(crate::admin::LpgSchemaInfo {
153 labels,
154 edge_types,
155 property_keys,
156 })
157 }
158
159 #[cfg(feature = "rdf")]
163 #[must_use]
164 pub fn rdf_schema(&self) -> crate::admin::SchemaInfo {
165 let stats = self.rdf_store.stats();
166
167 let predicates = self
168 .rdf_store
169 .predicates()
170 .into_iter()
171 .map(|predicate| {
172 let count = self.rdf_store.triples_with_predicate(&predicate).len();
173 crate::admin::PredicateInfo {
174 iri: predicate.to_string(),
175 count,
176 }
177 })
178 .collect();
179
180 crate::admin::SchemaInfo::Rdf(crate::admin::RdfSchemaInfo {
181 predicates,
182 named_graphs: Vec::new(), subject_count: stats.subject_count,
184 object_count: stats.object_count,
185 })
186 }
187
188 #[must_use]
196 pub fn validate(&self) -> crate::admin::ValidationResult {
197 let mut result = crate::admin::ValidationResult::default();
198
199 for edge in self.store.all_edges() {
201 if self.store.get_node(edge.src).is_none() {
202 result.errors.push(crate::admin::ValidationError {
203 code: "DANGLING_SRC".to_string(),
204 message: format!(
205 "Edge {} references non-existent source node {}",
206 edge.id.0, edge.src.0
207 ),
208 context: Some(format!("edge:{}", edge.id.0)),
209 });
210 }
211 if self.store.get_node(edge.dst).is_none() {
212 result.errors.push(crate::admin::ValidationError {
213 code: "DANGLING_DST".to_string(),
214 message: format!(
215 "Edge {} references non-existent destination node {}",
216 edge.id.0, edge.dst.0
217 ),
218 context: Some(format!("edge:{}", edge.id.0)),
219 });
220 }
221 }
222
223 if self.store.node_count() > 0 && self.store.edge_count() == 0 {
225 result.warnings.push(crate::admin::ValidationWarning {
226 code: "NO_EDGES".to_string(),
227 message: "Database has nodes but no edges".to_string(),
228 context: None,
229 });
230 }
231
232 result
233 }
234
235 #[must_use]
239 pub fn wal_status(&self) -> crate::admin::WalStatus {
240 #[cfg(feature = "wal")]
241 if let Some(ref wal) = self.wal {
242 return crate::admin::WalStatus {
243 enabled: true,
244 path: self.config.path.as_ref().map(|p| p.join("wal")),
245 size_bytes: wal.size_bytes(),
246 record_count: wal.record_count() as usize,
247 last_checkpoint: wal.last_checkpoint_timestamp(),
248 current_epoch: self.store.current_epoch().as_u64(),
249 };
250 }
251
252 crate::admin::WalStatus {
253 enabled: false,
254 path: None,
255 size_bytes: 0,
256 record_count: 0,
257 last_checkpoint: None,
258 current_epoch: self.store.current_epoch().as_u64(),
259 }
260 }
261
262 pub fn wal_checkpoint(&self) -> Result<()> {
270 #[cfg(feature = "wal")]
271 if let Some(ref wal) = self.wal {
272 let epoch = self.store.current_epoch();
273 let tx_id = self
274 .tx_manager
275 .last_assigned_tx_id()
276 .unwrap_or_else(|| self.tx_manager.begin());
277 wal.checkpoint(tx_id, epoch)?;
278 wal.sync()?;
279 }
280 Ok(())
281 }
282
283 #[cfg(feature = "cdc")]
295 pub fn history(
296 &self,
297 entity_id: impl Into<crate::cdc::EntityId>,
298 ) -> Result<Vec<crate::cdc::ChangeEvent>> {
299 Ok(self.cdc_log.history(entity_id.into()))
300 }
301
302 #[cfg(feature = "cdc")]
304 pub fn history_since(
305 &self,
306 entity_id: impl Into<crate::cdc::EntityId>,
307 since_epoch: grafeo_common::types::EpochId,
308 ) -> Result<Vec<crate::cdc::ChangeEvent>> {
309 Ok(self.cdc_log.history_since(entity_id.into(), since_epoch))
310 }
311
312 #[cfg(feature = "cdc")]
314 pub fn changes_between(
315 &self,
316 start_epoch: grafeo_common::types::EpochId,
317 end_epoch: grafeo_common::types::EpochId,
318 ) -> Result<Vec<crate::cdc::ChangeEvent>> {
319 Ok(self.cdc_log.changes_between(start_epoch, end_epoch))
320 }
321}