1use crate::{KevyError, KevyResult};
9
10use kevy_embedded::ZAggregate;
11use kevy_resp::Reply;
12use kevy_resp_client::RespClient;
13
14use crate::{Connection, string, unexpected};
15
16impl Connection {
17 pub fn zinterstore(&mut self, dest: &[u8], keys: &[&[u8]]) -> KevyResult<usize> {
21 self.zinterstore_with(dest, keys, None, ZAggregate::Sum)
22 }
23
24 pub fn zinterstore_with(
28 &mut self,
29 dest: &[u8],
30 keys: &[&[u8]],
31 weights: Option<&[f64]>,
32 aggregate: ZAggregate,
33 ) -> KevyResult<usize> {
34 check_keys(keys)?;
35 match self {
36 Self::Embedded(s) => s.zinterstore(dest, keys, weights, aggregate),
37 Self::Remote(c) => zstore_request(c, b"ZINTERSTORE", dest, keys, weights, aggregate),
38 }
39 }
40
41 pub fn zunionstore(&mut self, dest: &[u8], keys: &[&[u8]]) -> KevyResult<usize> {
45 self.zunionstore_with(dest, keys, None, ZAggregate::Sum)
46 }
47
48 pub fn zunionstore_with(
51 &mut self,
52 dest: &[u8],
53 keys: &[&[u8]],
54 weights: Option<&[f64]>,
55 aggregate: ZAggregate,
56 ) -> KevyResult<usize> {
57 check_keys(keys)?;
58 match self {
59 Self::Embedded(s) => s.zunionstore(dest, keys, weights, aggregate),
60 Self::Remote(c) => zstore_request(c, b"ZUNIONSTORE", dest, keys, weights, aggregate),
61 }
62 }
63
64 pub fn zintercard(&mut self, keys: &[&[u8]], limit: Option<usize>) -> KevyResult<usize> {
68 check_keys(keys)?;
69 match self {
70 Self::Embedded(s) => s.zintercard(keys, limit.unwrap_or(0)),
71 Self::Remote(c) => {
72 let mut args = Vec::with_capacity(keys.len() + 4);
73 args.push(b"ZINTERCARD".to_vec());
74 args.push(keys.len().to_string().into_bytes());
75 args.extend(keys.iter().map(|k| k.to_vec()));
76 if let Some(n) = limit {
77 args.push(b"LIMIT".to_vec());
78 args.push(n.to_string().into_bytes());
79 }
80 int_reply(c.request(&args)?)
81 }
82 }
83 }
84}
85
86fn check_keys(keys: &[&[u8]]) -> KevyResult<()> {
88 if keys.is_empty() {
89 return Err(KevyError::InvalidInput("zset algebra needs at least one source key".into()));
90 }
91 Ok(())
92}
93
94fn aggregate_tag(aggregate: ZAggregate) -> &'static [u8] {
95 match aggregate {
96 ZAggregate::Sum => b"SUM",
97 ZAggregate::Min => b"MIN",
98 ZAggregate::Max => b"MAX",
99 }
100}
101
102fn zstore_request(
107 c: &mut RespClient,
108 verb: &[u8],
109 dest: &[u8],
110 keys: &[&[u8]],
111 weights: Option<&[f64]>,
112 aggregate: ZAggregate,
113) -> KevyResult<usize> {
114 let mut args = Vec::with_capacity(keys.len() * 2 + 6);
115 args.push(verb.to_vec());
116 args.push(dest.to_vec());
117 args.push(keys.len().to_string().into_bytes());
118 args.extend(keys.iter().map(|k| k.to_vec()));
119 if let Some(ws) = weights {
120 args.push(b"WEIGHTS".to_vec());
121 args.extend(ws.iter().map(|w| format!("{w}").into_bytes()));
122 }
123 if aggregate != ZAggregate::Sum {
124 args.push(b"AGGREGATE".to_vec());
125 args.push(aggregate_tag(aggregate).to_vec());
126 }
127 int_reply(c.request(&args)?)
128}
129
130fn int_reply(reply: Reply) -> KevyResult<usize> {
131 match reply {
132 Reply::Int(n) if n >= 0 => Ok(n as usize),
133 Reply::Error(e) => Err(KevyError::Protocol(string(e))),
134 other => Err(unexpected(other)),
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 fn seed(c: &mut Connection) {
143 c.zadd(b"za", &[(1.0, b"x".as_ref()), (2.0, b"y".as_ref())])
144 .unwrap();
145 c.zadd(b"zb", &[(10.0, b"y".as_ref()), (20.0, b"z".as_ref())])
146 .unwrap();
147 }
148
149 #[test]
150 fn embedded_zinterstore_and_zunionstore() {
151 let mut c = Connection::connect("mem://").unwrap();
152 seed(&mut c);
153 assert_eq!(c.zinterstore(b"zi", &[&b"za"[..], &b"zb"[..]]).unwrap(), 1);
154 assert_eq!(c.zscore(b"zi", b"y").unwrap(), Some(12.0)); assert_eq!(c.zunionstore(b"zu", &[&b"za"[..], &b"zb"[..]]).unwrap(), 3);
156 }
157
158 #[test]
159 fn embedded_zstore_with_weights_and_aggregate() {
160 let mut c = Connection::connect("mem://").unwrap();
161 seed(&mut c);
162 let n = c
163 .zunionstore_with(
164 b"zw",
165 &[&b"za"[..], &b"zb"[..]],
166 Some(&[2.0, 1.0]),
167 ZAggregate::Max,
168 )
169 .unwrap();
170 assert_eq!(n, 3);
171 assert_eq!(c.zscore(b"zw", b"y").unwrap(), Some(10.0));
173 }
174
175 #[test]
176 fn embedded_zintercard_with_and_without_limit() {
177 let mut c = Connection::connect("mem://").unwrap();
178 c.zadd(b"za", &[(1.0, b"a".as_ref()), (2.0, b"b".as_ref()), (3.0, b"c".as_ref())])
179 .unwrap();
180 c.zadd(b"zb", &[(1.0, b"a".as_ref()), (2.0, b"b".as_ref()), (9.0, b"q".as_ref())])
181 .unwrap();
182 assert_eq!(c.zintercard(&[&b"za"[..], &b"zb"[..]], None).unwrap(), 2);
183 assert_eq!(c.zintercard(&[&b"za"[..], &b"zb"[..]], Some(1)).unwrap(), 1);
184 }
185
186 #[test]
187 fn empty_keys_rejected() {
188 let mut c = Connection::connect("mem://").unwrap();
189 let err = c.zintercard(&[], None).unwrap_err();
190 assert!(matches!(err, KevyError::InvalidInput(_)));
191 }
192}