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