1#![expect(
15 clippy::let_underscore_must_use,
16 reason = "the discarded value is a count, not an error report"
17)]
18
19#[cfg(not(feature = "std"))]
20use crate::nostd_prelude::*;
21use crate::{Store, StoreError};
22
23#[cfg(feature = "std")]
27type ScratchMap<K, V> = std::collections::HashMap<K, V>;
28#[cfg(feature = "std")]
29type ScratchSet<T> = std::collections::HashSet<T>;
30#[cfg(not(feature = "std"))]
31type ScratchMap<K, V> = alloc::collections::BTreeMap<K, V>;
32#[cfg(not(feature = "std"))]
33type ScratchSet<T> = alloc::collections::BTreeSet<T>;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ZAggregate {
38 Sum,
40 Min,
42 Max,
44}
45
46fn agg(a: f64, b: f64, mode: ZAggregate) -> f64 {
47 match mode {
48 ZAggregate::Sum => a + b,
49 ZAggregate::Min => a.min(b),
50 ZAggregate::Max => a.max(b),
51 }
52}
53
54fn weight_of(weights: Option<&[f64]>, i: usize) -> f64 {
55 weights.map_or(1.0, |w| w.get(i).copied().unwrap_or(1.0))
56}
57
58pub fn zunion(
61 inputs: &[Vec<(Vec<u8>, f64)>],
62 weights: Option<&[f64]>,
63 mode: ZAggregate,
64) -> Vec<(Vec<u8>, f64)> {
65 let mut acc: Vec<(Vec<u8>, f64)> = Vec::new();
66 let mut idx: ScratchMap<Vec<u8>, usize> = ScratchMap::new();
67 for (i, input) in inputs.iter().enumerate() {
68 let w = weight_of(weights, i);
69 for (m, s) in input {
70 let ws = s * w;
71 match idx.get(m) {
72 Some(&slot) => acc[slot].1 = agg(acc[slot].1, ws, mode),
73 None => {
74 idx.insert(m.clone(), acc.len());
75 acc.push((m.clone(), ws));
76 }
77 }
78 }
79 }
80 acc
81}
82
83pub fn zinter(
85 inputs: &[Vec<(Vec<u8>, f64)>],
86 weights: Option<&[f64]>,
87 mode: ZAggregate,
88) -> Vec<(Vec<u8>, f64)> {
89 let Some((first, rest)) = inputs.split_first() else {
90 return Vec::new();
91 };
92 let maps: Vec<ScratchMap<&[u8], f64>> =
94 rest.iter().map(|inp| inp.iter().map(|(m, s)| (m.as_slice(), *s)).collect()).collect();
95 let w0 = weight_of(weights, 0);
96 let mut out = Vec::new();
97 'member: for (m, s) in first {
98 let mut score = s * w0;
99 for (j, map) in maps.iter().enumerate() {
100 match map.get(m.as_slice()) {
101 Some(&sj) => score = agg(score, sj * weight_of(weights, j + 1), mode),
102 None => continue 'member,
103 }
104 }
105 out.push((m.clone(), score));
106 }
107 out
108}
109
110pub fn zdiff(inputs: &[Vec<(Vec<u8>, f64)>]) -> Vec<(Vec<u8>, f64)> {
114 let Some((first, rest)) = inputs.split_first() else {
115 return Vec::new();
116 };
117 let excluded: ScratchSet<&[u8]> =
118 rest.iter().flat_map(|inp| inp.iter().map(|(m, _)| m.as_slice())).collect();
119 first.iter().filter(|(m, _)| !excluded.contains(m.as_slice())).cloned().collect()
120}
121
122pub fn zintercard(inputs: &[Vec<(Vec<u8>, f64)>], limit: usize) -> usize {
125 let Some((first, rest)) = inputs.split_first() else {
126 return 0;
127 };
128 let maps: Vec<ScratchSet<&[u8]>> =
129 rest.iter().map(|inp| inp.iter().map(|(m, _)| m.as_slice()).collect()).collect();
130 let mut n = 0;
131 'member: for (m, _) in first {
132 for map in &maps {
133 if !map.contains(m.as_slice()) {
134 continue 'member;
135 }
136 }
137 n += 1;
138 if limit != 0 && n >= limit {
139 return n;
140 }
141 }
142 n
143}
144
145impl Store {
146 pub fn zset_or_set_members(&mut self, key: &[u8]) -> Result<Vec<(Vec<u8>, f64)>, StoreError> {
150 use crate::Value;
151 match self.live_entry(key) {
152 None => Ok(Vec::new()),
153 Some(e) => match &e.value {
154 Value::ZSet(z) => Ok(z.by_member.iter().map(|(m, s)| (m.to_vec(), *s)).collect()),
155 Value::SegZSet(z) => Ok(z.ordered().map(|(m, s)| (m.to_vec(), s)).collect()),
156 Value::SmallZSetInline(z) => Ok(z.iter().map(|(m, s)| (m.to_vec(), s)).collect()),
157 Value::Set(s) => Ok(s.iter().map(|m| (m.to_vec(), 1.0)).collect()),
158 Value::SegSet(s) => Ok(s.keys().map(|m| (m.to_vec(), 1.0)).collect()),
159 Value::SmallSetInline(s) => Ok(s.iter().map(|m| (m.to_vec(), 1.0)).collect()),
160 _ => Err(StoreError::WrongType),
161 },
162 }
163 }
164
165 pub fn zstore_result(&mut self, dst: &[u8], pairs: &[(Vec<u8>, f64)]) -> usize {
171 self.del(&[dst]);
172 if pairs.is_empty() {
173 return 0;
174 }
175 let scored: Vec<(f64, &[u8])> = pairs.iter().map(|(m, s)| (*s, m.as_slice())).collect();
176 let _ = self.zadd(dst, &scored);
177 pairs.len()
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 fn zs(pairs: &[(&str, f64)]) -> Vec<(Vec<u8>, f64)> {
186 pairs.iter().map(|(m, s)| (m.as_bytes().to_vec(), *s)).collect()
187 }
188
189 #[test]
190 fn union_weights_and_aggregates() {
191 let a = zs(&[("x", 1.0), ("y", 2.0)]);
192 let b = zs(&[("y", 3.0), ("z", 4.0)]);
193 let mut u = zunion(&[a.clone(), b.clone()], None, ZAggregate::Sum);
194 u.sort_by(|x, y| x.0.cmp(&y.0));
195 assert_eq!(u, zs(&[("x", 1.0), ("y", 5.0), ("z", 4.0)]));
196 let mut u = zunion(&[a, b], Some(&[2.0, 3.0]), ZAggregate::Min);
198 u.sort_by(|x, y| x.0.cmp(&y.0));
199 assert_eq!(u, zs(&[("x", 2.0), ("y", 4.0), ("z", 12.0)]));
200 }
201
202 #[test]
203 fn inter_semantics() {
204 let a = zs(&[("x", 1.0), ("y", 2.0)]);
205 let b = zs(&[("y", 3.0), ("z", 4.0)]);
206 assert_eq!(zinter(&[a.clone(), b.clone()], None, ZAggregate::Sum), zs(&[("y", 5.0)]));
207 assert_eq!(zinter(&[a, b], Some(&[10.0, 1.0]), ZAggregate::Max), zs(&[("y", 20.0)]));
208 assert!(zinter(&[], None, ZAggregate::Sum).is_empty());
209 }
210
211 #[test]
212 fn diff_and_intercard() {
213 let a = zs(&[("x", 1.0), ("y", 2.0), ("z", 3.0)]);
214 let b = zs(&[("y", 9.0)]);
215 let mut d = zdiff(&[a.clone(), b.clone()]);
216 d.sort_by(|x, y| x.0.cmp(&y.0));
217 assert_eq!(d, zs(&[("x", 1.0), ("z", 3.0)]));
218 assert_eq!(zintercard(&[a.clone(), b.clone()], 0), 1);
219 let c = zs(&[("x", 0.0), ("y", 0.0), ("z", 0.0)]);
220 assert_eq!(zintercard(&[a.clone(), c.clone()], 0), 3);
221 assert_eq!(zintercard(&[a, c], 2), 2); }
223
224 #[test]
225 fn store_materialization_and_source_extraction() {
226 let mut s = Store::new();
227 s.zadd(b"z", &[(1.0, b"a".as_slice()), (2.0, b"b".as_slice())]).unwrap();
228 s.sadd(b"s", &[b"a".as_slice(), b"c".as_slice()]).unwrap();
229 let mut zm = s.zset_or_set_members(b"z").unwrap();
230 zm.sort_by(|x, y| x.0.cmp(&y.0));
231 assert_eq!(zm, zs(&[("a", 1.0), ("b", 2.0)]));
232 let mut sm = s.zset_or_set_members(b"s").unwrap();
233 sm.sort_by(|x, y| x.0.cmp(&y.0));
234 assert_eq!(sm, zs(&[("a", 1.0), ("c", 1.0)]));
235 assert!(s.zset_or_set_members(b"missing").unwrap().is_empty());
236 s.set(b"str", b"v".to_vec(), None, false, false);
237 assert!(s.zset_or_set_members(b"str").is_err());
238
239 s.set(b"dst", b"old".to_vec(), None, false, false);
241 assert_eq!(s.zstore_result(b"dst", &zs(&[("m", 7.0)])), 1);
242 assert_eq!(s.zscore(b"dst", b"m").unwrap(), Some(7.0));
243 assert_eq!(s.zstore_result(b"dst", &[]), 0);
244 assert_eq!(s.zcard(b"dst").unwrap(), 0);
245 assert_eq!(s.exists(&[b"dst".as_slice()]), 0);
246 }
247}