1#[derive(Clone)]
36pub struct SmallZSetData {
37 count: u8,
38 used: u8,
39 buf: [u8; SMALL_ZSET_BUF_CAP],
40}
41
42pub(crate) const SMALL_ZSET_BUF_CAP: usize = 22;
43pub(crate) const SMALL_ZSET_MEMBER_MAX: usize = SMALL_ZSET_BUF_CAP - 9;
46pub(crate) const SMALL_ZSET_COUNT_MAX: usize = 2;
47
48pub(crate) enum AddResult {
50 Added,
52 Updated,
54 NoRoom,
56}
57
58impl SmallZSetData {
59 pub(crate) fn new() -> Self {
60 Self { count: 0, used: 0, buf: [0; SMALL_ZSET_BUF_CAP] }
61 }
62
63 pub(crate) fn with_one(member: &[u8], score: f64) -> Option<Self> {
64 if member.len() > SMALL_ZSET_MEMBER_MAX {
65 return None;
66 }
67 let mut s = Self::new();
68 s.write_pair_at(0, member, score);
69 s.count = 1;
70 s.used = (9 + member.len()) as u8;
71 Some(s)
72 }
73
74 pub fn len(&self) -> usize {
75 self.count as usize
76 }
77
78 pub fn is_empty(&self) -> bool {
79 self.count == 0
80 }
81
82 pub fn score(&self, member: &[u8]) -> Option<f64> {
84 for (m, s) in self.iter() {
85 if m == member {
86 return Some(s);
87 }
88 }
89 None
90 }
91
92 pub fn contains(&self, member: &[u8]) -> bool {
94 self.score(member).is_some()
95 }
96
97 pub fn iter(&self) -> SmallZSetIter<'_> {
99 SmallZSetIter { buf: &self.buf[..self.used as usize], cursor: 0 }
100 }
101
102 pub(crate) fn try_set(&mut self, member: &[u8], score: f64) -> AddResult {
104 if member.len() > SMALL_ZSET_MEMBER_MAX {
105 return AddResult::NoRoom;
106 }
107 if let Some(off) = self.locate(member) {
108 self.buf[off..off + 8].copy_from_slice(&score.to_bits().to_le_bytes());
110 return AddResult::Updated;
111 }
112 if self.count as usize >= SMALL_ZSET_COUNT_MAX {
113 return AddResult::NoRoom;
114 }
115 let need = 9 + member.len();
116 let new_used = self.used as usize + need;
117 if new_used > SMALL_ZSET_BUF_CAP {
118 return AddResult::NoRoom;
119 }
120 let off = self.used as usize;
121 self.write_pair_at(off, member, score);
122 self.used = new_used as u8;
123 self.count += 1;
124 AddResult::Added
125 }
126
127 pub(crate) fn try_remove(&mut self, member: &[u8]) -> bool {
129 let Some(off) = self.locate(member) else {
130 return false;
131 };
132 let used = self.used as usize;
133 let len = self.buf[off + 8] as usize;
134 let entry_end = off + 9 + len;
135 self.buf.copy_within(entry_end..used, off);
136 let shifted = used - entry_end;
137 let new_used = off + shifted;
138 self.buf[new_used..used].fill(0);
139 self.used = new_used as u8;
140 self.count -= 1;
141 true
142 }
143
144 fn write_pair_at(&mut self, off: usize, member: &[u8], score: f64) {
145 self.buf[off..off + 8].copy_from_slice(&score.to_bits().to_le_bytes());
146 self.buf[off + 8] = member.len() as u8;
147 let mstart = off + 9;
148 let mend = mstart + member.len();
149 self.buf[mstart..mend].copy_from_slice(member);
150 }
151
152 fn locate(&self, member: &[u8]) -> Option<usize> {
154 let mut cursor = 0usize;
155 let used = self.used as usize;
156 while cursor < used {
157 let len = self.buf[cursor + 8] as usize;
158 let mstart = cursor + 9;
159 let mend = mstart + len;
160 if &self.buf[mstart..mend] == member {
161 return Some(cursor);
162 }
163 cursor = mend;
164 }
165 None
166 }
167}
168
169pub struct SmallZSetIter<'a> {
171 buf: &'a [u8],
172 cursor: usize,
173}
174
175impl<'a> Iterator for SmallZSetIter<'a> {
176 type Item = (&'a [u8], f64);
177 fn next(&mut self) -> Option<Self::Item> {
178 if self.cursor >= self.buf.len() {
179 return None;
180 }
181 let mut score_bytes = [0u8; 8];
182 score_bytes.copy_from_slice(&self.buf[self.cursor..self.cursor + 8]);
183 let score = f64::from_bits(u64::from_le_bytes(score_bytes));
184 let len = self.buf[self.cursor + 8] as usize;
185 let mstart = self.cursor + 9;
186 let mend = mstart + len;
187 self.cursor = mend;
188 Some((&self.buf[mstart..mend], score))
189 }
190}
191
192pub(crate) fn promote(inline: &SmallZSetData) -> crate::value::ZSetData {
194 let mut z = crate::value::ZSetData::default();
195 for (m, sc) in inline.iter() {
196 z.insert(m, sc);
197 }
198 z
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn size_is_24_bytes() {
207 assert_eq!(std::mem::size_of::<SmallZSetData>(), 24);
208 }
209
210 #[test]
211 fn add_and_score() {
212 let mut z = SmallZSetData::new();
213 assert!(matches!(z.try_set(b"a", 1.0), AddResult::Added));
214 assert!(matches!(z.try_set(b"b", 2.5), AddResult::Added));
215 assert_eq!(z.score(b"a"), Some(1.0));
216 assert_eq!(z.score(b"b"), Some(2.5));
217 assert!(z.contains(b"a"));
218 assert!(!z.contains(b"c"));
219 }
220
221 #[test]
222 fn update_in_place() {
223 let mut z = SmallZSetData::new();
224 z.try_set(b"a", 1.0);
225 assert!(matches!(z.try_set(b"a", 9.5), AddResult::Updated));
226 assert_eq!(z.score(b"a"), Some(9.5));
227 assert_eq!(z.len(), 1);
228 }
229
230 #[test]
231 fn count_cap() {
232 let mut z = SmallZSetData::new();
233 z.try_set(b"a", 1.0);
234 z.try_set(b"b", 2.0);
235 assert!(matches!(z.try_set(b"c", 3.0), AddResult::NoRoom));
237 }
238
239 #[test]
240 fn member_too_long() {
241 let mut z = SmallZSetData::new();
242 let big = vec![b'x'; SMALL_ZSET_MEMBER_MAX + 1];
243 assert!(matches!(z.try_set(&big, 1.0), AddResult::NoRoom));
244 }
245
246 #[test]
247 fn budget_overflow() {
248 let mut z = SmallZSetData::new();
249 let m1 = vec![b'a'; SMALL_ZSET_MEMBER_MAX];
251 assert!(matches!(z.try_set(&m1, 1.0), AddResult::Added));
252 assert!(matches!(z.try_set(b"b", 2.0), AddResult::NoRoom));
254 }
255
256 #[test]
257 fn remove_works() {
258 let mut z = SmallZSetData::new();
259 z.try_set(b"a", 1.0);
260 z.try_set(b"b", 2.0);
261 assert!(z.try_remove(b"a"));
262 assert!(!z.contains(b"a"));
263 assert_eq!(z.score(b"b"), Some(2.0));
264 assert_eq!(z.len(), 1);
265 }
266
267 #[test]
268 fn iter_order() {
269 let mut z = SmallZSetData::new();
270 z.try_set(b"b", 2.0);
271 z.try_set(b"a", 1.0);
272 let v: Vec<(&[u8], f64)> = z.iter().collect();
273 assert_eq!(v[0], (b"b".as_slice(), 2.0));
274 assert_eq!(v[1], (b"a".as_slice(), 1.0));
275 }
276
277 #[test]
278 fn promote_preserves_pairs() {
279 let mut z = SmallZSetData::new();
280 z.try_set(b"a", 1.0);
281 z.try_set(b"b", 2.0);
282 let zd = promote(&z);
283 assert_eq!(zd.len(), 2);
284 }
285}