use serde::{Deserialize, Serialize};
use sentinel_wal::{CollectionWalConfig, StoreWalConfig};
use crate::META_SENTINEL_VERSION;
pub type MetadataVersion = u32;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollectionMetadata {
pub version: MetadataVersion,
pub name: String,
pub created_at: u64,
pub updated_at: u64,
pub document_count: u64,
pub total_size_bytes: u64,
pub wal_config: Option<CollectionWalConfig>,
}
impl CollectionMetadata {
pub fn new(name: String) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
Self {
version: META_SENTINEL_VERSION,
name,
created_at: now,
updated_at: now,
document_count: 0,
total_size_bytes: 0,
wal_config: None,
}
}
pub fn upgrade_to_current(&mut self) -> Result<(), String> {
let current_version = META_SENTINEL_VERSION;
while self.version < current_version {
match self.version {
1 => {
self.version = current_version;
},
_ => {
return Err(format!(
"Unsupported metadata version: {} (current: {})",
self.version, current_version
));
},
}
}
Ok(())
}
pub const fn needs_upgrade(&self) -> bool { self.version < META_SENTINEL_VERSION }
pub fn touch(&mut self) {
self.updated_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
}
pub fn add_document(&mut self, size_bytes: u64) {
self.document_count = self
.document_count
.checked_add(1)
.unwrap_or(self.document_count);
self.total_size_bytes = self
.total_size_bytes
.checked_add(size_bytes)
.unwrap_or(self.total_size_bytes);
self.touch();
}
pub fn remove_document(&mut self, size_bytes: u64) {
self.document_count = self.document_count.saturating_sub(1);
self.total_size_bytes = self.total_size_bytes.saturating_sub(size_bytes);
self.touch();
}
pub fn update_document_size(&mut self, old_size: u64, new_size: u64) {
self.total_size_bytes = self
.total_size_bytes
.saturating_sub(old_size)
.checked_add(new_size)
.unwrap_or(self.total_size_bytes);
self.touch();
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreMetadata {
pub version: MetadataVersion,
pub created_at: u64,
pub updated_at: u64,
pub collection_count: u64,
pub total_documents: u64,
pub total_size_bytes: u64,
pub wal_config: StoreWalConfig,
}
#[allow(
clippy::arithmetic_side_effects,
reason = "counter increments in metadata"
)]
impl StoreMetadata {
pub fn new() -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
Self {
version: META_SENTINEL_VERSION,
created_at: now,
updated_at: now,
collection_count: 0,
total_documents: 0,
total_size_bytes: 0,
wal_config: StoreWalConfig::default(),
}
}
}
impl Default for StoreMetadata {
fn default() -> Self { Self::new() }
}
#[allow(
clippy::arithmetic_side_effects,
reason = "counter increments in metadata"
)]
#[allow(
clippy::multiple_inherent_impl,
reason = "multiple impl blocks for StoreMetadata are intentional for organization"
)]
impl StoreMetadata {
pub fn upgrade_to_current(&mut self) -> Result<(), String> {
let current_version = META_SENTINEL_VERSION;
while self.version < current_version {
match self.version {
1 => {
self.version = current_version;
},
_ => {
return Err(format!(
"Unsupported metadata version: {} (current: {})",
self.version, current_version
));
},
}
}
Ok(())
}
pub const fn needs_upgrade(&self) -> bool { self.version < META_SENTINEL_VERSION }
pub fn touch(&mut self) {
self.updated_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
}
pub fn add_collection(&mut self) {
self.collection_count += 1;
self.touch();
}
pub fn remove_collection(&mut self) {
self.collection_count = self.collection_count.saturating_sub(1);
self.touch();
}
pub fn update_documents(&mut self, document_delta: i64, size_delta: i64) {
self.total_documents = (self.total_documents as i128 + document_delta as i128).max(0) as u64;
self.total_size_bytes = (self.total_size_bytes as i128 + size_delta as i128).max(0) as u64;
self.touch();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collection_metadata_new() {
let metadata = CollectionMetadata::new("test_collection".to_string());
assert_eq!(metadata.version, META_SENTINEL_VERSION);
assert_eq!(metadata.name, "test_collection");
assert_eq!(metadata.document_count, 0);
assert_eq!(metadata.total_size_bytes, 0);
assert!(
metadata.created_at <=
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
);
assert_eq!(metadata.created_at, metadata.updated_at);
}
#[test]
fn test_collection_metadata_add_remove_document() {
let mut metadata = CollectionMetadata::new("test".to_string());
metadata.add_document(100);
assert_eq!(metadata.document_count, 1);
assert_eq!(metadata.total_size_bytes, 100);
assert!(metadata.updated_at >= metadata.created_at);
let updated_at = metadata.updated_at;
metadata.add_document(200);
assert_eq!(metadata.document_count, 2);
assert_eq!(metadata.total_size_bytes, 300);
assert!(metadata.updated_at >= updated_at);
metadata.remove_document(100);
assert_eq!(metadata.document_count, 1);
assert_eq!(metadata.total_size_bytes, 200);
metadata.remove_document(200);
assert_eq!(metadata.document_count, 0);
assert_eq!(metadata.total_size_bytes, 0);
}
#[test]
fn test_collection_metadata_update_document_size() {
let mut metadata = CollectionMetadata::new("test".to_string());
metadata.add_document(100);
metadata.update_document_size(100, 150);
assert_eq!(metadata.document_count, 1);
assert_eq!(metadata.total_size_bytes, 150);
}
#[test]
fn test_collection_metadata_upgrade() {
let mut metadata = CollectionMetadata::new("test".to_string());
metadata.version = 1;
assert!(metadata.needs_upgrade());
assert!(metadata.upgrade_to_current().is_ok());
}
#[test]
fn test_store_metadata_new() {
let metadata = StoreMetadata::new();
assert_eq!(metadata.version, META_SENTINEL_VERSION);
assert_eq!(metadata.collection_count, 0);
assert_eq!(metadata.total_documents, 0);
assert_eq!(metadata.total_size_bytes, 0);
assert!(
metadata.created_at <=
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
);
}
#[test]
fn test_store_metadata_operations() {
let mut metadata = StoreMetadata::new();
metadata.add_collection();
assert_eq!(metadata.collection_count, 1);
metadata.update_documents(5, 1000);
assert_eq!(metadata.total_documents, 5);
assert_eq!(metadata.total_size_bytes, 1000);
metadata.update_documents(3, 500);
assert_eq!(metadata.total_documents, 8);
assert_eq!(metadata.total_size_bytes, 1500);
metadata.update_documents(-2, -200);
assert_eq!(metadata.total_documents, 6);
assert_eq!(metadata.total_size_bytes, 1300);
metadata.remove_collection();
assert_eq!(metadata.collection_count, 0);
}
#[test]
fn test_store_metadata_upgrade() {
let mut metadata = StoreMetadata::new();
metadata.version = 1;
assert!(metadata.needs_upgrade());
assert!(metadata.upgrade_to_current().is_ok());
}
#[test]
fn test_metadata_serialization() {
let collection_meta = CollectionMetadata::new("test".to_string());
let serialized = serde_json::to_string(&collection_meta).unwrap();
let deserialized: CollectionMetadata = serde_json::from_str(&serialized).unwrap();
assert_eq!(collection_meta.name, deserialized.name);
assert_eq!(collection_meta.version, deserialized.version);
let store_meta = StoreMetadata::new();
let serialized = serde_json::to_string(&store_meta).unwrap();
let deserialized: StoreMetadata = serde_json::from_str(&serialized).unwrap();
assert_eq!(store_meta.version, deserialized.version);
}
}