pgm_extra/index/
key.rs

1use alloc::string::String;
2use alloc::vec::Vec;
3use core::ops::{Add, Sub};
4use num_traits::{Bounded, NumCast, ToPrimitive, Zero};
5
6use crate::util::bytes::Prefix;
7
8pub trait Key:
9    Copy
10    + Clone
11    + Ord
12    + Default
13    + Send
14    + Sync
15    + ToPrimitive
16    + Bounded
17    + Zero
18    + Add<Output = Self>
19    + Sub<Output = Self>
20{
21    type Unsigned: Copy + Ord + ToPrimitive + NumCast;
22
23    fn to_unsigned(self) -> Self::Unsigned;
24
25    fn to_f64_fast(self) -> f64;
26
27    fn to_i64_fast(self) -> i64;
28}
29
30macro_rules! impl_key_unsigned {
31    ($($t:ty),*) => {
32        $(
33            impl Key for $t {
34                type Unsigned = $t;
35
36                #[inline]
37                fn to_unsigned(self) -> Self::Unsigned {
38                    self
39                }
40
41                #[inline]
42                fn to_f64_fast(self) -> f64 {
43                    self as f64
44                }
45
46                #[inline]
47                fn to_i64_fast(self) -> i64 {
48                    self as i64
49                }
50            }
51        )*
52    };
53}
54
55macro_rules! impl_key_signed {
56    ($(($signed:ty, $unsigned:ty)),*) => {
57        $(
58            impl Key for $signed {
59                type Unsigned = $unsigned;
60
61                #[inline]
62                fn to_unsigned(self) -> Self::Unsigned {
63                    const OFFSET: $unsigned = <$signed>::MIN as $unsigned;
64                    (self as $unsigned).wrapping_sub(OFFSET)
65                }
66
67                #[inline]
68                fn to_f64_fast(self) -> f64 {
69                    self as f64
70                }
71
72                #[inline]
73                fn to_i64_fast(self) -> i64 {
74                    self as i64
75                }
76            }
77        )*
78    };
79}
80
81impl_key_unsigned!(u8, u16, u32, u64, u128, usize);
82impl_key_signed!(
83    (i8, u8),
84    (i16, u16),
85    (i32, u32),
86    (i64, u64),
87    (i128, u128),
88    (isize, usize)
89);
90
91/// Trait for types that can be indexed by a PGM-Index.
92///
93/// The PGM-Index requires numeric keys for its piecewise linear models.
94/// This trait defines how a value is converted to such a key.
95///
96/// - For numeric types (u64, i32, etc.), the value IS the key.
97/// - For string/bytes types, an 8-byte prefix is extracted as the key.
98///
99/// # Implementing for custom types
100///
101/// ```
102/// use pgm_extra::index::key::Indexable;
103///
104/// struct UserId(u64);
105///
106/// impl Indexable for UserId {
107///     type Key = u64;
108///     fn index_key(&self) -> u64 { self.0 }
109/// }
110/// ```
111pub trait Indexable {
112    /// The numeric key type used for indexing.
113    type Key: Key;
114
115    /// Extract the index key from this value.
116    fn index_key(&self) -> Self::Key;
117}
118
119macro_rules! impl_indexable_numeric {
120    ($($t:ty),*) => {
121        $(
122            impl Indexable for $t {
123                type Key = $t;
124
125                #[inline]
126                fn index_key(&self) -> Self::Key {
127                    *self
128                }
129            }
130        )*
131    };
132}
133
134impl_indexable_numeric!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
135
136impl Indexable for String {
137    type Key = Prefix;
138
139    #[inline]
140    fn index_key(&self) -> Prefix {
141        Prefix::from_bytes(self.as_bytes())
142    }
143}
144
145impl Indexable for str {
146    type Key = Prefix;
147
148    #[inline]
149    fn index_key(&self) -> Prefix {
150        Prefix::from_bytes(self.as_bytes())
151    }
152}
153
154impl Indexable for [u8] {
155    type Key = Prefix;
156
157    #[inline]
158    fn index_key(&self) -> Prefix {
159        Prefix::from_bytes(self)
160    }
161}
162
163impl Indexable for Vec<u8> {
164    type Key = Prefix;
165
166    #[inline]
167    fn index_key(&self) -> Prefix {
168        Prefix::from_bytes(self)
169    }
170}
171
172impl<const N: usize> Indexable for [u8; N] {
173    type Key = Prefix;
174
175    #[inline]
176    fn index_key(&self) -> Prefix {
177        Prefix::from_bytes(self)
178    }
179}
180
181// References delegate to the underlying type
182impl<T: Indexable + ?Sized> Indexable for &T {
183    type Key = T::Key;
184
185    #[inline]
186    fn index_key(&self) -> Self::Key {
187        (*self).index_key()
188    }
189}
190
191impl<T: Indexable + ?Sized> Indexable for &mut T {
192    type Key = T::Key;
193
194    #[inline]
195    fn index_key(&self) -> Self::Key {
196        (**self).index_key()
197    }
198}
199
200impl<T: Indexable + ?Sized> Indexable for alloc::boxed::Box<T> {
201    type Key = T::Key;
202
203    #[inline]
204    fn index_key(&self) -> Self::Key {
205        (**self).index_key()
206    }
207}
208
209impl<T: Indexable + ?Sized> Indexable for alloc::rc::Rc<T> {
210    type Key = T::Key;
211
212    #[inline]
213    fn index_key(&self) -> Self::Key {
214        (**self).index_key()
215    }
216}
217
218impl<T: Indexable + ?Sized> Indexable for alloc::sync::Arc<T> {
219    type Key = T::Key;
220
221    #[inline]
222    fn index_key(&self) -> Self::Key {
223        (**self).index_key()
224    }
225}
226
227// BytesPrefix indexes itself
228impl Indexable for Prefix {
229    type Key = Prefix;
230
231    #[inline]
232    fn index_key(&self) -> Prefix {
233        *self
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use alloc::vec;
241    use alloc::vec::Vec;
242
243    #[test]
244    fn test_unsigned_monotonic() {
245        let vals: Vec<u64> = vec![0, 1, 100, 1000, u64::MAX];
246        for w in vals.windows(2) {
247            assert!(w[0].to_unsigned() < w[1].to_unsigned());
248        }
249    }
250
251    #[test]
252    fn test_signed_monotonic() {
253        let vals: Vec<i64> = vec![i64::MIN, -1000, -1, 0, 1, 1000, i64::MAX];
254        for w in vals.windows(2) {
255            assert!(
256                w[0].to_unsigned() < w[1].to_unsigned(),
257                "{} -> {} should be < {} -> {}",
258                w[0],
259                w[0].to_unsigned(),
260                w[1],
261                w[1].to_unsigned()
262            );
263        }
264    }
265
266    #[test]
267    fn test_numeric_indexable() {
268        assert_eq!(42u64.index_key(), 42u64);
269        assert_eq!((-10i32).index_key(), -10i32);
270    }
271
272    #[test]
273    fn test_string_indexable() {
274        let s = String::from("hello");
275        let key = s.index_key();
276        assert_eq!(key, Prefix::from_bytes(b"hello"));
277    }
278
279    #[test]
280    fn test_str_indexable() {
281        let s = "world";
282        let key = s.index_key();
283        assert_eq!(key, Prefix::from_bytes(b"world"));
284    }
285
286    #[test]
287    fn test_bytes_indexable() {
288        let bytes: Vec<u8> = vec![1, 2, 3, 4, 5];
289        let key = bytes.index_key();
290        assert_eq!(key, Prefix::from_bytes(&[1, 2, 3, 4, 5]));
291    }
292
293    #[test]
294    fn test_reference_indexable() {
295        let val = 100u64;
296        let ref_val = &val;
297        assert_eq!(ref_val.index_key(), 100u64);
298
299        let s = "test";
300        assert_eq!(s.index_key(), Prefix::from_bytes(b"test"));
301    }
302}