redb_extras/key_buckets/
key.rs

1//! Bucketed key implementations.
2//!
3//! Provides KeyBuilder for configuration and BucketedKey for storage.
4
5use crate::key_buckets::BucketError;
6use redb::{Key, Value};
7use std::cmp::Ordering;
8use std::fmt::Debug;
9
10/// Builder for creating bucketed keys with consistent configuration.
11///
12/// KeyBuilder holds the bucket configuration and can be reused to create
13/// bucketed keys for any base key type and sequence.
14#[derive(Debug, Clone)]
15pub struct KeyBuilder {
16    bucket_size: u64,
17}
18
19impl KeyBuilder {
20    /// Create a new KeyBuilder with the specified bucket size.
21    ///
22    /// # Arguments
23    /// * `bucket_size` - Size of each bucket for integer division (must be > 0)
24    ///
25    /// # Returns
26    /// Configured KeyBuilder or error if bucket_size is invalid
27    pub fn new(bucket_size: u64) -> Result<Self, BucketError> {
28        if bucket_size == 0 {
29            return Err(BucketError::InvalidBucketSize(bucket_size));
30        }
31        Ok(Self { bucket_size })
32    }
33
34    /// Create a bucketed key from the given base key and sequence.
35    ///
36    /// The bucket is calculated as `sequence / bucket_size` using integer division.
37    ///
38    /// # Arguments
39    /// * `base_key` - The base key (any type implementing redb::Key)
40    /// * `sequence` - The sequence value to bucket
41    ///
42    /// # Returns
43    /// BucketedKey with bucket as prefix and base_key as secondary component
44    pub fn bucketed_key<K: Key>(&self, base_key: K, sequence: u64) -> BucketedKey<K> {
45        let bucket = sequence / self.bucket_size;
46        BucketedKey { base_key, bucket }
47    }
48
49    /// Get the configured bucket size.
50    pub fn bucket_size(&self) -> u64 {
51        self.bucket_size
52    }
53}
54
55/// A bucketed key that implements redb::Key for storage.
56///
57/// BucketedKey stores a base key along with its computed bucket.
58/// The bucket serves as the primary sort key (prefix) while the base key
59/// provides secondary sorting within each bucket.
60#[derive(Debug, Clone)]
61pub struct BucketedKey<K: Key> {
62    pub base_key: K,
63    pub bucket: u64,
64}
65
66impl<K: Key> BucketedKey<K> {
67    /// Create a new BucketedKey directly.
68    ///
69    /// Note: Typically you should use KeyBuilder::bucketed_key() instead
70    /// to ensure consistent bucket calculation.
71    pub fn new(base_key: K, bucket: u64) -> Self {
72        Self { base_key, bucket }
73    }
74
75    /// Get reference to the base key.
76    pub fn base_key(&self) -> &K {
77        &self.base_key
78    }
79
80    /// Get the bucket number.
81    pub fn bucket(&self) -> u64 {
82        self.bucket
83    }
84}
85
86// For now, we'll implement a simple version that works with u64 base keys
87impl Value for BucketedKey<u64> {
88    type SelfType<'a>
89        = BucketedKey<u64>
90    where
91        Self: 'a;
92
93    type AsBytes<'a>
94        = Vec<u8>
95    where
96        Self: 'a;
97
98    fn fixed_width() -> Option<usize> {
99        Some(16) // 8 bytes bucket + 8 bytes u64 base key
100    }
101
102    fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
103    where
104        Self: 'a,
105    {
106        if data.len() < 16 {
107            panic!(
108                "BucketedKey data too short: expected at least 16 bytes, got {}",
109                data.len()
110            );
111        }
112
113        // Read bucket (first 8 bytes, little-endian)
114        let bucket = u64::from_le_bytes([
115            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
116        ]);
117
118        // Read base key (next 8 bytes, little-endian)
119        let base_key = u64::from_le_bytes([
120            data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
121        ]);
122
123        BucketedKey { base_key, bucket }
124    }
125
126    fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
127    where
128        Self: 'a,
129        Self: 'b,
130    {
131        // Serialize bucket as 8-byte little-endian
132        let bucket_bytes = value.bucket.to_le_bytes();
133
134        // Serialize base key as 8-byte little-endian
135        let base_key_bytes = value.base_key.to_le_bytes();
136
137        // Concatenate bucket + base key
138        let mut result = Vec::with_capacity(16);
139        result.extend_from_slice(&bucket_bytes);
140        result.extend_from_slice(&base_key_bytes);
141
142        result
143    }
144
145    fn type_name() -> redb::TypeName {
146        redb::TypeName::new("redb_extras::key_buckets::BucketedKey<u64>")
147    }
148}
149
150impl Key for BucketedKey<u64> {
151    fn compare(data1: &[u8], data2: &[u8]) -> Ordering {
152        // Extract bucket from both keys (first 8 bytes)
153        if data1.len() < 16 || data2.len() < 16 {
154            panic!("BucketedKey data too short for comparison");
155        }
156
157        let bucket1 = u64::from_le_bytes([
158            data1[0], data1[1], data1[2], data1[3], data1[4], data1[5], data1[6], data1[7],
159        ]);
160        let bucket2 = u64::from_le_bytes([
161            data2[0], data2[1], data2[2], data2[3], data2[4], data2[5], data2[6], data2[7],
162        ]);
163
164        // First compare bucket
165        match bucket1.cmp(&bucket2) {
166            Ordering::Equal => {
167                // If buckets equal, compare base keys
168                let base1 = u64::from_le_bytes([
169                    data1[8], data1[9], data1[10], data1[11], data1[12], data1[13], data1[14],
170                    data1[15],
171                ]);
172                let base2 = u64::from_le_bytes([
173                    data2[8], data2[9], data2[10], data2[11], data2[12], data2[13], data2[14],
174                    data2[15],
175                ]);
176                base1.cmp(&base2)
177            }
178            other => other,
179        }
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn test_key_builder_creation() {
189        // Valid bucket size
190        let builder = KeyBuilder::new(1000);
191        assert!(builder.is_ok());
192        assert_eq!(builder.unwrap().bucket_size(), 1000);
193
194        // Invalid bucket size
195        let builder = KeyBuilder::new(0);
196        assert!(builder.is_err());
197    }
198
199    #[test]
200    fn test_bucketed_key_creation() {
201        let builder = KeyBuilder::new(1000).unwrap();
202
203        // Test bucket calculation
204        let key1 = builder.bucketed_key(123u64, 500);
205        assert_eq!(key1.bucket(), 0);
206        assert_eq!(key1.base_key(), &123u64);
207
208        let key2 = builder.bucketed_key(123u64, 1500);
209        assert_eq!(key2.bucket(), 1);
210        assert_eq!(key2.base_key(), &123u64);
211
212        let key3 = builder.bucketed_key(123u64, 2500);
213        assert_eq!(key3.bucket(), 2);
214    }
215
216    #[test]
217    fn test_bucketed_key_serialization() {
218        let builder = KeyBuilder::new(1000).unwrap();
219        let key = builder.bucketed_key(123u64, 1500); // bucket 1
220
221        // Serialize to bytes
222        let bytes: Vec<u8> = BucketedKey::as_bytes(&key);
223        assert_eq!(bytes.len(), 16);
224
225        // Deserialize back
226        let deserialized: BucketedKey<u64> = BucketedKey::from_bytes(&bytes);
227        assert_eq!(deserialized.bucket(), 1);
228        assert_eq!(deserialized.base_key(), &123u64);
229    }
230
231    #[test]
232    fn test_bucketed_key_ordering() {
233        let builder = KeyBuilder::new(1000).unwrap();
234
235        // Create keys with different buckets
236        let key1 = builder.bucketed_key(123u64, 500); // bucket 0
237        let key2 = builder.bucketed_key(123u64, 1500); // bucket 1
238        let key3 = builder.bucketed_key(456u64, 500); // bucket 0, different base
239
240        // Serialize for comparison
241        let bytes1: Vec<u8> = BucketedKey::as_bytes(&key1);
242        let bytes2: Vec<u8> = BucketedKey::as_bytes(&key2);
243        let bytes3: Vec<u8> = BucketedKey::as_bytes(&key3);
244
245        // Bucket should be primary sort key
246        assert_eq!(
247            BucketedKey::<u64>::compare(&bytes1, &bytes2),
248            Ordering::Less
249        );
250        assert_eq!(
251            BucketedKey::<u64>::compare(&bytes2, &bytes1),
252            Ordering::Greater
253        );
254
255        // Same bucket, base key should determine order
256        assert_eq!(
257            BucketedKey::<u64>::compare(&bytes1, &bytes3),
258            Ordering::Less
259        );
260        assert_eq!(
261            BucketedKey::<u64>::compare(&bytes3, &bytes1),
262            Ordering::Greater
263        );
264    }
265}