1#![allow(clippy::type_complexity)]
4
5use crate::ValkeyResult;
6use crate::cmd::Cmd;
7use crate::commands::options::Limit;
8use crate::executor::CommandExecutor;
9use crate::value::FromValkeyValue;
10use crate::value::ValkeyValue;
11use crate::value::to_glide_error;
12use crate::write::ToValkeyArgs;
13use crate::write::ValkeyWrite;
14use async_trait::async_trait;
15use bytes::Bytes;
16
17#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum ScoreBound {
22 NegativeInfinity,
24 PositiveInfinity,
26 Inclusive(f64),
28 Exclusive(f64),
30}
31
32impl ToValkeyArgs for ScoreBound {
33 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
34 match self {
35 ScoreBound::NegativeInfinity => out.write_arg(b"-inf"),
36 ScoreBound::PositiveInfinity => out.write_arg(b"+inf"),
37 ScoreBound::Inclusive(v) => out.write_arg_fmt(v),
38 ScoreBound::Exclusive(v) => out.write_arg_fmt(format_args!("({v}")),
39 }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum LexBound {
48 NegativeInfinity,
50 PositiveInfinity,
52 Inclusive(Vec<u8>),
54 Exclusive(Vec<u8>),
56}
57
58impl ToValkeyArgs for LexBound {
59 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
60 let prefixed = |prefix: u8, value: &[u8]| {
62 let mut arg = Vec::with_capacity(1 + value.len());
63 arg.push(prefix);
64 arg.extend_from_slice(value);
65 arg
66 };
67 match self {
68 LexBound::NegativeInfinity => out.write_arg(b"-"),
69 LexBound::PositiveInfinity => out.write_arg(b"+"),
70 LexBound::Inclusive(v) => out.write_arg(&prefixed(b'[', v)),
71 LexBound::Exclusive(v) => out.write_arg(&prefixed(b'(', v)),
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum AggregationType {
81 Sum,
83 Min,
85 Max,
87}
88
89impl AggregationType {
90 fn as_arg(&self) -> &'static str {
91 match self {
92 AggregationType::Sum => "SUM",
93 AggregationType::Min => "MIN",
94 AggregationType::Max => "MAX",
95 }
96 }
97}
98
99#[async_trait]
101pub trait SortedSetCommands: CommandExecutor {
102 async fn zadd_incr<K: ToValkeyArgs + Send, M: ToValkeyArgs + Send>(
110 &self,
111 key: K,
112 member: M,
113 increment: f64,
114 ) -> ValkeyResult<Option<f64>> {
115 let mut cmd = Cmd::new();
116 cmd.arg("ZADD")
117 .arg(key)
118 .arg("INCR")
119 .arg(increment)
120 .arg(member);
121 Option::<f64>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
122 }
123
124 async fn zrank_withscore<K: ToValkeyArgs + Send, M: ToValkeyArgs + Send>(
126 &self,
127 key: K,
128 member: M,
129 ) -> ValkeyResult<Option<(i64, f64)>> {
130 let mut cmd = Cmd::new();
131 cmd.arg("ZRANK").arg(key).arg(member).arg("WITHSCORE");
132 parse_rank_withscore(self.execute_command(cmd, None).await?)
133 }
134
135 async fn zrevrank_withscore<K: ToValkeyArgs + Send, M: ToValkeyArgs + Send>(
138 &self,
139 key: K,
140 member: M,
141 ) -> ValkeyResult<Option<(i64, f64)>> {
142 let mut cmd = Cmd::new();
143 cmd.arg("ZREVRANK").arg(key).arg(member).arg("WITHSCORE");
144 parse_rank_withscore(self.execute_command(cmd, None).await?)
145 }
146
147 #[doc(hidden)]
148 async fn bzpop<K: ToValkeyArgs + Send + Sync>(
149 &self,
150 op: &'static str,
151 keys: &[K],
152 timeout: f64,
153 ) -> ValkeyResult<Option<(Bytes, Bytes, f64)>> {
154 let mut cmd = Cmd::new();
155 cmd.arg(op);
156 for k in keys {
157 cmd.arg(k);
158 }
159 cmd.arg(timeout);
160 match self.execute_command(cmd, None).await? {
161 ValkeyValue::Nil => Ok(None),
162 ValkeyValue::Array(mut items) if items.len() == 3 => {
163 let score = f64::from_owned_valkey_value(items.pop().unwrap())?;
164 let member = Bytes::from_owned_valkey_value(items.pop().unwrap())?;
165 let key = Bytes::from_owned_valkey_value(items.pop().unwrap())?;
166 Ok(Some((key, member, score)))
167 }
168 other => Err(to_glide_error(other, "Unexpected blocking zpop reply.")),
169 }
170 }
171
172 async fn zrangestore_by_index<D: ToValkeyArgs + Send, S: ToValkeyArgs + Send>(
175 &self,
176 destination: D,
177 source: S,
178 start: i64,
179 stop: i64,
180 rev: bool,
181 ) -> ValkeyResult<i64> {
182 let mut cmd = Cmd::new();
183 cmd.arg("ZRANGESTORE")
184 .arg(destination)
185 .arg(source)
186 .arg(start)
187 .arg(stop);
188 if rev {
189 cmd.arg("REV");
190 }
191 i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
192 }
193
194 async fn zrangestore_by_score<D: ToValkeyArgs + Send, S: ToValkeyArgs + Send>(
202 &self,
203 destination: D,
204 source: S,
205 min: ScoreBound,
206 max: ScoreBound,
207 rev: bool,
208 limit: Option<Limit>,
209 ) -> ValkeyResult<i64> {
210 let (first, second) = if rev { (max, min) } else { (min, max) };
212 let mut cmd = Cmd::new();
213 cmd.arg("ZRANGESTORE")
214 .arg(destination)
215 .arg(source)
216 .arg(first)
217 .arg(second)
218 .arg("BYSCORE");
219 if rev {
220 cmd.arg("REV");
221 }
222 if let Some(limit) = limit {
223 cmd.arg("LIMIT").arg(limit.offset).arg(limit.count);
224 }
225 i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
226 }
227
228 async fn zrangestore_by_lex<D: ToValkeyArgs + Send, S: ToValkeyArgs + Send>(
236 &self,
237 destination: D,
238 source: S,
239 min: &LexBound,
240 max: &LexBound,
241 rev: bool,
242 limit: Option<Limit>,
243 ) -> ValkeyResult<i64> {
244 let (first, second) = if rev { (max, min) } else { (min, max) };
245 let mut cmd = Cmd::new();
246 cmd.arg("ZRANGESTORE")
247 .arg(destination)
248 .arg(source)
249 .arg(first)
250 .arg(second)
251 .arg("BYLEX");
252 if rev {
253 cmd.arg("REV");
254 }
255 if let Some(limit) = limit {
256 cmd.arg("LIMIT").arg(limit.offset).arg(limit.count);
257 }
258 i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
259 }
260
261 async fn zdiff<K: ToValkeyArgs + Send + Sync>(&self, keys: &[K]) -> ValkeyResult<Vec<Bytes>> {
263 let mut cmd = Cmd::new();
264 cmd.arg("ZDIFF").arg(keys.len());
265 for k in keys {
266 cmd.arg(k);
267 }
268 collect_bytes(self.execute_command(cmd, None).await?)
269 }
270
271 async fn zdiff_withscores<K: ToValkeyArgs + Send + Sync>(
274 &self,
275 keys: &[K],
276 ) -> ValkeyResult<Vec<(Bytes, f64)>> {
277 let mut cmd = Cmd::new();
278 cmd.arg("ZDIFF").arg(keys.len());
279 for k in keys {
280 cmd.arg(k);
281 }
282 cmd.arg("WITHSCORES");
283 collect_member_scores(self.execute_command(cmd, None).await?)
284 }
285
286 async fn zdiffstore<D: ToValkeyArgs + Send, K: ToValkeyArgs + Send + Sync>(
289 &self,
290 destination: D,
291 keys: &[K],
292 ) -> ValkeyResult<i64> {
293 let mut cmd = Cmd::new();
294 cmd.arg("ZDIFFSTORE").arg(destination).arg(keys.len());
295 for k in keys {
296 cmd.arg(k);
297 }
298 i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
299 }
300
301 async fn zunion<K: ToValkeyArgs + Send + Sync>(
303 &self,
304 keys: &[K],
305 aggregate: Option<AggregationType>,
306 ) -> ValkeyResult<Vec<Bytes>> {
307 let mut cmd = Cmd::new();
308 cmd.arg("ZUNION").arg(keys.len());
309 for k in keys {
310 cmd.arg(k);
311 }
312 if let Some(agg) = aggregate {
313 cmd.arg("AGGREGATE").arg(agg.as_arg());
314 }
315 collect_bytes(self.execute_command(cmd, None).await?)
316 }
317
318 async fn zunion_withscores<K: ToValkeyArgs + Send + Sync>(
321 &self,
322 keys: &[K],
323 aggregate: Option<AggregationType>,
324 ) -> ValkeyResult<Vec<(Bytes, f64)>> {
325 let mut cmd = Cmd::new();
326 cmd.arg("ZUNION").arg(keys.len());
327 for k in keys {
328 cmd.arg(k);
329 }
330 if let Some(agg) = aggregate {
331 cmd.arg("AGGREGATE").arg(agg.as_arg());
332 }
333 cmd.arg("WITHSCORES");
334 collect_member_scores(self.execute_command(cmd, None).await?)
335 }
336
337 async fn zinter<K: ToValkeyArgs + Send + Sync>(
339 &self,
340 keys: &[K],
341 aggregate: Option<AggregationType>,
342 ) -> ValkeyResult<Vec<Bytes>> {
343 let mut cmd = Cmd::new();
344 cmd.arg("ZINTER").arg(keys.len());
345 for k in keys {
346 cmd.arg(k);
347 }
348 if let Some(agg) = aggregate {
349 cmd.arg("AGGREGATE").arg(agg.as_arg());
350 }
351 collect_bytes(self.execute_command(cmd, None).await?)
352 }
353
354 async fn zinter_withscores<K: ToValkeyArgs + Send + Sync>(
357 &self,
358 keys: &[K],
359 aggregate: Option<AggregationType>,
360 ) -> ValkeyResult<Vec<(Bytes, f64)>> {
361 let mut cmd = Cmd::new();
362 cmd.arg("ZINTER").arg(keys.len());
363 for k in keys {
364 cmd.arg(k);
365 }
366 if let Some(agg) = aggregate {
367 cmd.arg("AGGREGATE").arg(agg.as_arg());
368 }
369 cmd.arg("WITHSCORES");
370 collect_member_scores(self.execute_command(cmd, None).await?)
371 }
372
373 async fn zintercard<K: ToValkeyArgs + Send + Sync>(
376 &self,
377 keys: &[K],
378 limit: Option<i64>,
379 ) -> ValkeyResult<i64> {
380 let mut cmd = Cmd::new();
381 cmd.arg("ZINTERCARD").arg(keys.len());
382 for k in keys {
383 cmd.arg(k);
384 }
385 if let Some(l) = limit {
386 cmd.arg("LIMIT").arg(l);
387 }
388 i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
389 }
390
391 #[doc(hidden)]
392 async fn zsetop_store<D: ToValkeyArgs + Send, K: ToValkeyArgs + Send + Sync>(
393 &self,
394 op: &'static str,
395 destination: D,
396 keys: &[K],
397 aggregate: Option<AggregationType>,
398 ) -> ValkeyResult<i64> {
399 let mut cmd = Cmd::new();
400 cmd.arg(op).arg(destination).arg(keys.len());
401 for k in keys {
402 cmd.arg(k);
403 }
404 if let Some(agg) = aggregate {
405 cmd.arg("AGGREGATE").arg(agg.as_arg());
406 }
407 i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
408 }
409}
410
411fn collect_bytes(v: ValkeyValue) -> ValkeyResult<Vec<Bytes>> {
412 match v {
413 ValkeyValue::Array(items) => items
414 .into_iter()
415 .map(Bytes::from_owned_valkey_value)
416 .collect(),
417 ValkeyValue::Nil => Ok(Vec::new()),
418 other => Ok(vec![Bytes::from_owned_valkey_value(other)?]),
419 }
420}
421
422fn collect_member_scores(v: ValkeyValue) -> ValkeyResult<Vec<(Bytes, f64)>> {
425 match v {
426 ValkeyValue::Nil => Ok(Vec::new()),
427 ValkeyValue::Map(pairs) => pairs
429 .into_iter()
430 .map(|(m, s)| {
431 Ok((
432 Bytes::from_owned_valkey_value(m)?,
433 f64::from_owned_valkey_value(s)?,
434 ))
435 })
436 .collect(),
437 ValkeyValue::Array(items) => {
438 if items
440 .iter()
441 .all(|it| matches!(it, ValkeyValue::Array(inner) if inner.len() == 2))
442 {
443 let mut out = Vec::with_capacity(items.len());
444 for it in items {
445 if let ValkeyValue::Array(mut pair) = it {
446 let score = f64::from_owned_valkey_value(pair.pop().unwrap())?;
447 let member = Bytes::from_owned_valkey_value(pair.pop().unwrap())?;
448 out.push((member, score));
449 }
450 }
451 Ok(out)
452 } else {
453 let mut out = Vec::with_capacity(items.len() / 2);
455 let mut iter = items.into_iter();
456 while let (Some(m), Some(s)) = (iter.next(), iter.next()) {
457 out.push((
458 Bytes::from_owned_valkey_value(m)?,
459 f64::from_owned_valkey_value(s)?,
460 ));
461 }
462 Ok(out)
463 }
464 }
465 other => Err(to_glide_error(other, "Unexpected sorted-set reply.")),
466 }
467}
468
469impl<T: CommandExecutor + ?Sized> SortedSetCommands for T {}
470
471fn parse_rank_withscore(v: ValkeyValue) -> ValkeyResult<Option<(i64, f64)>> {
473 match v {
474 ValkeyValue::Nil => Ok(None),
475 ValkeyValue::Array(mut items) if items.len() == 2 => {
476 let score = f64::from_owned_valkey_value(items.pop().unwrap())?;
477 let rank = i64::from_owned_valkey_value(items.pop().unwrap())?;
478 Ok(Some((rank, score)))
479 }
480 other => Err(to_glide_error(other, "Unexpected ZRANK WITHSCORE reply.")),
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487 use crate::test_utils::assert_args;
488
489 #[test]
490 fn score_bound_args() {
491 assert_args(ScoreBound::NegativeInfinity, &["-inf"]);
492 assert_args(ScoreBound::PositiveInfinity, &["+inf"]);
493 assert_args(ScoreBound::Inclusive(1.5), &["1.5"]);
494 assert_args(ScoreBound::Exclusive(1.0), &["(1"]);
495 }
496
497 #[test]
498 fn lex_bound_args() {
499 assert_args(LexBound::NegativeInfinity, &["-"]);
500 assert_args(LexBound::PositiveInfinity, &["+"]);
501 assert_args(LexBound::Inclusive(b"a".to_vec()), &["[a"]);
502 assert_args(LexBound::Exclusive(b"b".to_vec()), &["(b"]);
503 assert_args(
505 LexBound::Inclusive(vec![0xFF, 0x00]),
506 &[[b'[', 0xFF, 0x00].as_slice()],
507 );
508 }
509}