1use serde::{Deserialize, Serialize};
14use sentinel_wal::{CollectionWalConfig, StoreWalConfig};
15
16use crate::META_SENTINEL_VERSION;
17
18pub type MetadataVersion = u32;
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct CollectionMetadata {
32 pub version: MetadataVersion,
34 pub name: String,
36 pub created_at: u64,
38 pub updated_at: u64,
40 pub document_count: u64,
42 pub total_size_bytes: u64,
44 pub wal_config: Option<CollectionWalConfig>,
46}
47
48impl CollectionMetadata {
49 pub fn new(name: String) -> Self {
51 let now = std::time::SystemTime::now()
52 .duration_since(std::time::UNIX_EPOCH)
53 .unwrap()
54 .as_secs();
55
56 Self {
57 version: META_SENTINEL_VERSION,
58 name,
59 created_at: now,
60 updated_at: now,
61 document_count: 0,
62 total_size_bytes: 0,
63 wal_config: None,
64 }
65 }
66
67 pub fn upgrade_to_current(&mut self) -> Result<(), String> {
72 let current_version = META_SENTINEL_VERSION;
73
74 while self.version < current_version {
75 match self.version {
76 1 => {
77 self.version = current_version;
83 },
84
85 _ => {
88 return Err(format!(
89 "Unsupported metadata version: {} (current: {})",
90 self.version, current_version
91 ));
92 },
93 }
94 }
95
96 Ok(())
97 }
98
99 pub const fn needs_upgrade(&self) -> bool { self.version < META_SENTINEL_VERSION }
101
102 pub fn touch(&mut self) {
103 self.updated_at = std::time::SystemTime::now()
104 .duration_since(std::time::UNIX_EPOCH)
105 .unwrap()
106 .as_secs();
107 }
108
109 pub fn add_document(&mut self, size_bytes: u64) {
111 self.document_count = self
112 .document_count
113 .checked_add(1)
114 .unwrap_or(self.document_count);
115 self.total_size_bytes = self
116 .total_size_bytes
117 .checked_add(size_bytes)
118 .unwrap_or(self.total_size_bytes);
119 self.touch();
120 }
121
122 pub fn remove_document(&mut self, size_bytes: u64) {
124 self.document_count = self.document_count.saturating_sub(1);
125 self.total_size_bytes = self.total_size_bytes.saturating_sub(size_bytes);
126 self.touch();
127 }
128
129 pub fn update_document_size(&mut self, old_size: u64, new_size: u64) {
131 self.total_size_bytes = self
132 .total_size_bytes
133 .saturating_sub(old_size)
134 .checked_add(new_size)
135 .unwrap_or(self.total_size_bytes);
136 self.touch();
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct StoreMetadata {
148 pub version: MetadataVersion,
150 pub created_at: u64,
152 pub updated_at: u64,
154 pub collection_count: u64,
156 pub total_documents: u64,
158 pub total_size_bytes: u64,
160 pub wal_config: StoreWalConfig,
162}
163
164#[allow(
165 clippy::arithmetic_side_effects,
166 reason = "counter increments in metadata"
167)]
168impl StoreMetadata {
169 pub fn new() -> Self {
171 let now = std::time::SystemTime::now()
172 .duration_since(std::time::UNIX_EPOCH)
173 .unwrap()
174 .as_secs();
175
176 Self {
177 version: META_SENTINEL_VERSION,
178 created_at: now,
179 updated_at: now,
180 collection_count: 0,
181 total_documents: 0,
182 total_size_bytes: 0,
183 wal_config: StoreWalConfig::default(),
184 }
185 }
186}
187
188impl Default for StoreMetadata {
189 fn default() -> Self { Self::new() }
190}
191
192#[allow(
193 clippy::arithmetic_side_effects,
194 reason = "counter increments in metadata"
195)]
196#[allow(
197 clippy::multiple_inherent_impl,
198 reason = "multiple impl blocks for StoreMetadata are intentional for organization"
199)]
200impl StoreMetadata {
201 pub fn upgrade_to_current(&mut self) -> Result<(), String> {
206 let current_version = META_SENTINEL_VERSION;
207
208 while self.version < current_version {
209 match self.version {
210 1 => {
211 self.version = current_version;
216 },
217 _ => {
220 return Err(format!(
221 "Unsupported metadata version: {} (current: {})",
222 self.version, current_version
223 ));
224 },
225 }
226 }
227
228 Ok(())
229 }
230
231 pub const fn needs_upgrade(&self) -> bool { self.version < META_SENTINEL_VERSION }
233
234 pub fn touch(&mut self) {
236 self.updated_at = std::time::SystemTime::now()
237 .duration_since(std::time::UNIX_EPOCH)
238 .unwrap()
239 .as_secs();
240 }
241
242 pub fn add_collection(&mut self) {
244 self.collection_count += 1;
245 self.touch();
246 }
247
248 pub fn remove_collection(&mut self) {
250 self.collection_count = self.collection_count.saturating_sub(1);
251 self.touch();
252 }
253
254 pub fn update_documents(&mut self, document_delta: i64, size_delta: i64) {
256 self.total_documents = (self.total_documents as i128 + document_delta as i128).max(0) as u64;
257 self.total_size_bytes = (self.total_size_bytes as i128 + size_delta as i128).max(0) as u64;
258 self.touch();
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_collection_metadata_new() {
268 let metadata = CollectionMetadata::new("test_collection".to_string());
269 assert_eq!(metadata.version, META_SENTINEL_VERSION);
270 assert_eq!(metadata.name, "test_collection");
271 assert_eq!(metadata.document_count, 0);
272 assert_eq!(metadata.total_size_bytes, 0);
273 assert!(
274 metadata.created_at <=
275 std::time::SystemTime::now()
276 .duration_since(std::time::UNIX_EPOCH)
277 .unwrap()
278 .as_secs()
279 );
280 assert_eq!(metadata.created_at, metadata.updated_at);
281 }
282
283 #[test]
284 fn test_collection_metadata_add_remove_document() {
285 let mut metadata = CollectionMetadata::new("test".to_string());
286
287 metadata.add_document(100);
289 assert_eq!(metadata.document_count, 1);
290 assert_eq!(metadata.total_size_bytes, 100);
291 assert!(metadata.updated_at >= metadata.created_at);
292
293 let updated_at = metadata.updated_at;
294
295 metadata.add_document(200);
297 assert_eq!(metadata.document_count, 2);
298 assert_eq!(metadata.total_size_bytes, 300);
299 assert!(metadata.updated_at >= updated_at);
300
301 metadata.remove_document(100);
303 assert_eq!(metadata.document_count, 1);
304 assert_eq!(metadata.total_size_bytes, 200);
305
306 metadata.remove_document(200);
308 assert_eq!(metadata.document_count, 0);
309 assert_eq!(metadata.total_size_bytes, 0);
310 }
311
312 #[test]
313 fn test_collection_metadata_update_document_size() {
314 let mut metadata = CollectionMetadata::new("test".to_string());
315 metadata.add_document(100);
316
317 metadata.update_document_size(100, 150);
318 assert_eq!(metadata.document_count, 1);
319 assert_eq!(metadata.total_size_bytes, 150);
320 }
321
322 #[test]
323 fn test_collection_metadata_upgrade() {
324 let mut metadata = CollectionMetadata::new("test".to_string());
325 metadata.version = 1;
326
327 assert!(metadata.needs_upgrade());
328 assert!(metadata.upgrade_to_current().is_ok());
329 }
330
331 #[test]
332 fn test_store_metadata_new() {
333 let metadata = StoreMetadata::new();
334 assert_eq!(metadata.version, META_SENTINEL_VERSION);
335 assert_eq!(metadata.collection_count, 0);
336 assert_eq!(metadata.total_documents, 0);
337 assert_eq!(metadata.total_size_bytes, 0);
338 assert!(
339 metadata.created_at <=
340 std::time::SystemTime::now()
341 .duration_since(std::time::UNIX_EPOCH)
342 .unwrap()
343 .as_secs()
344 );
345 }
346
347 #[test]
348 fn test_store_metadata_operations() {
349 let mut metadata = StoreMetadata::new();
350
351 metadata.add_collection();
353 assert_eq!(metadata.collection_count, 1);
354
355 metadata.update_documents(5, 1000);
357 assert_eq!(metadata.total_documents, 5);
358 assert_eq!(metadata.total_size_bytes, 1000);
359
360 metadata.update_documents(3, 500);
362 assert_eq!(metadata.total_documents, 8);
363 assert_eq!(metadata.total_size_bytes, 1500);
364
365 metadata.update_documents(-2, -200);
367 assert_eq!(metadata.total_documents, 6);
368 assert_eq!(metadata.total_size_bytes, 1300);
369
370 metadata.remove_collection();
372 assert_eq!(metadata.collection_count, 0);
373 }
374
375 #[test]
376 fn test_store_metadata_upgrade() {
377 let mut metadata = StoreMetadata::new();
378 metadata.version = 1;
379
380 assert!(metadata.needs_upgrade());
381 assert!(metadata.upgrade_to_current().is_ok());
382 }
383
384 #[test]
385 fn test_metadata_serialization() {
386 let collection_meta = CollectionMetadata::new("test".to_string());
387 let serialized = serde_json::to_string(&collection_meta).unwrap();
388 let deserialized: CollectionMetadata = serde_json::from_str(&serialized).unwrap();
389 assert_eq!(collection_meta.name, deserialized.name);
390 assert_eq!(collection_meta.version, deserialized.version);
391
392 let store_meta = StoreMetadata::new();
393 let serialized = serde_json::to_string(&store_meta).unwrap();
394 let deserialized: StoreMetadata = serde_json::from_str(&serialized).unwrap();
395 assert_eq!(store_meta.version, deserialized.version);
396 }
397}