Skip to main content

kora_doc/
collection.rs

1//! Collection metadata and configuration.
2//!
3//! Every document belongs to exactly one collection. A [`Collection`] record
4//! stores the collection's name, its compact [`CollectionId`], the creation
5//! timestamp, the chosen [`CompressionProfile`], and a running document count
6//! that is maintained by the engine on each insert or delete.
7//!
8//! [`CollectionConfig`] is the user-facing input when creating a collection;
9//! it currently controls only the compression profile, but is designed to
10//! absorb future per-collection tuning knobs (e.g., index defaults, TTL
11//! policies) without breaking the public API.
12
13use std::time::{SystemTime, UNIX_EPOCH};
14
15use thiserror::Error;
16
17use crate::registry::CollectionId;
18
19/// Collection-level compression policy.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum CompressionProfile {
22    /// No additional compression.
23    None,
24    /// Dictionary compression for low-cardinality strings.
25    Dictionary,
26    /// Dictionary + lightweight entropy compression.
27    #[default]
28    Balanced,
29    /// Most aggressive profile with higher CPU cost.
30    Compact,
31}
32
33/// Parameters used when creating a collection.
34#[derive(Debug, Clone, PartialEq, Eq, Default)]
35pub struct CollectionConfig {
36    /// Compression policy for the collection.
37    pub compression: CompressionProfile,
38}
39
40/// Stored collection metadata.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Collection {
43    name: String,
44    id: CollectionId,
45    created_at: u64,
46    compression: CompressionProfile,
47    doc_count: u64,
48}
49
50impl Collection {
51    /// Create new metadata for a collection.
52    #[must_use]
53    pub fn new(name: String, id: CollectionId, config: CollectionConfig) -> Self {
54        Self {
55            name,
56            id,
57            created_at: current_unix_seconds(),
58            compression: config.compression,
59            doc_count: 0,
60        }
61    }
62
63    /// Collection name.
64    #[must_use]
65    pub fn name(&self) -> &str {
66        &self.name
67    }
68
69    /// Collection ID.
70    #[must_use]
71    pub fn id(&self) -> CollectionId {
72        self.id
73    }
74
75    /// Creation timestamp (seconds since UNIX epoch).
76    #[must_use]
77    pub fn created_at(&self) -> u64 {
78        self.created_at
79    }
80
81    /// Compression profile.
82    #[must_use]
83    pub fn compression(&self) -> CompressionProfile {
84        self.compression
85    }
86
87    /// Number of stored documents.
88    #[must_use]
89    pub fn doc_count(&self) -> u64 {
90        self.doc_count
91    }
92
93    /// Increase document count by one.
94    pub fn increment_doc_count(&mut self) {
95        self.doc_count = self.doc_count.saturating_add(1);
96    }
97
98    /// Decrease document count by one.
99    pub fn decrement_doc_count(&mut self) {
100        self.doc_count = self.doc_count.saturating_sub(1);
101    }
102}
103
104/// Errors for collection management operations.
105#[derive(Debug, Error, PartialEq, Eq)]
106pub enum CollectionError {
107    /// The named collection already exists.
108    #[error("collection '{0}' already exists")]
109    AlreadyExists(String),
110}
111
112fn current_unix_seconds() -> u64 {
113    SystemTime::now()
114        .duration_since(UNIX_EPOCH)
115        .map_or(0, |duration| duration.as_secs())
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn collection_doc_count_tracks_mutations() {
124        let mut collection = Collection::new(
125            "users".to_string(),
126            7,
127            CollectionConfig {
128                compression: CompressionProfile::Dictionary,
129            },
130        );
131
132        assert_eq!(collection.doc_count(), 0);
133        collection.increment_doc_count();
134        collection.increment_doc_count();
135        assert_eq!(collection.doc_count(), 2);
136        collection.decrement_doc_count();
137        assert_eq!(collection.doc_count(), 1);
138        collection.decrement_doc_count();
139        collection.decrement_doc_count();
140        assert_eq!(collection.doc_count(), 0);
141        assert_eq!(collection.name(), "users");
142        assert_eq!(collection.id(), 7);
143        assert_eq!(collection.compression(), CompressionProfile::Dictionary);
144    }
145}