Skip to main content

glide/commands/
hash.rs

1// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2//! Hash commands. Mirrors Python's hash command surface.
3
4use crate::ValkeyResult;
5use crate::cmd::Cmd;
6use crate::executor::CommandExecutor;
7use crate::value::FromValkeyValue;
8use crate::value::ValkeyValue;
9use crate::write::ToValkeyArgs;
10use async_trait::async_trait;
11use bytes::Bytes;
12
13/// Hash commands (`HSET`, `HGET`, `HGETALL`, `HDEL`, ...).
14#[async_trait]
15pub trait HashCommands: CommandExecutor {
16    // TODO #7082: add a multi-field `hset` that returns the count of newly-added
17    // fields, to match the other GLIDE clients.
18
19    /// Get the string length of a field's value (`HSTRLEN`).
20    async fn hstrlen<K: ToValkeyArgs + Send, F: ToValkeyArgs + Send>(
21        &self,
22        key: K,
23        field: F,
24    ) -> ValkeyResult<i64> {
25        let mut cmd = Cmd::new();
26        cmd.arg("HSTRLEN").arg(key).arg(field);
27        i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
28    }
29
30    /// Get a random field from the hash (`HRANDFIELD`).
31    async fn hrandfield<K: ToValkeyArgs + Send>(&self, key: K) -> ValkeyResult<Option<Bytes>> {
32        let mut cmd = Cmd::new();
33        cmd.arg("HRANDFIELD").arg(key);
34        Option::<Bytes>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
35    }
36
37    /// Get `count` random fields from the hash (`HRANDFIELD key count`).
38    async fn hrandfield_count<K: ToValkeyArgs + Send>(
39        &self,
40        key: K,
41        count: i64,
42    ) -> ValkeyResult<Vec<Bytes>> {
43        let mut cmd = Cmd::new();
44        cmd.arg("HRANDFIELD").arg(key).arg(count);
45        collect_bytes(self.execute_command(cmd, None).await?)
46    }
47
48    /// Get `count` random fields with their values
49    /// (`HRANDFIELD key count WITHVALUES`).
50    async fn hrandfield_withvalues<K: ToValkeyArgs + Send>(
51        &self,
52        key: K,
53        count: i64,
54    ) -> ValkeyResult<Vec<(Bytes, Bytes)>> {
55        let mut cmd = Cmd::new();
56        cmd.arg("HRANDFIELD").arg(key).arg(count).arg("WITHVALUES");
57        collect_pairs(self.execute_command(cmd, None).await?)
58    }
59
60    /// Incrementally iterate a hash returning only field names
61    /// (`HSCAN ... NOVALUES`). Returns `(cursor, fields)`.
62    async fn hscan_novalues<K: ToValkeyArgs + Send>(
63        &self,
64        key: K,
65        cursor: &str,
66        pattern: Option<&[u8]>,
67        count: Option<i64>,
68    ) -> ValkeyResult<(String, Vec<Bytes>)> {
69        let mut cmd = Cmd::new();
70        cmd.arg("HSCAN").arg(key).arg(cursor);
71        if let Some(p) = pattern {
72            cmd.arg("MATCH").arg(p);
73        }
74        if let Some(c) = count {
75            cmd.arg("COUNT").arg(c);
76        }
77        cmd.arg("NOVALUES");
78        crate::commands::generic::parse_scan_reply(self.execute_command(cmd, None).await?)
79    }
80}
81
82/// Parse a flat `[a, b, a, b, ...]` reply into `(a, b)` pairs.
83fn collect_pairs(v: ValkeyValue) -> ValkeyResult<Vec<(Bytes, Bytes)>> {
84    match v {
85        ValkeyValue::Nil => Ok(Vec::new()),
86        ValkeyValue::Map(pairs) => pairs
87            .into_iter()
88            .map(|(a, b)| {
89                Ok((
90                    Bytes::from_owned_valkey_value(a)?,
91                    Bytes::from_owned_valkey_value(b)?,
92                ))
93            })
94            .collect(),
95        ValkeyValue::Array(items) => {
96            if items
97                .iter()
98                .all(|it| matches!(it, ValkeyValue::Array(inner) if inner.len() == 2))
99            {
100                let mut out = Vec::with_capacity(items.len());
101                for it in items {
102                    if let ValkeyValue::Array(mut pair) = it {
103                        let b = Bytes::from_owned_valkey_value(pair.pop().unwrap())?;
104                        let a = Bytes::from_owned_valkey_value(pair.pop().unwrap())?;
105                        out.push((a, b));
106                    }
107                }
108                Ok(out)
109            } else {
110                let mut out = Vec::with_capacity(items.len() / 2);
111                let mut iter = items.into_iter();
112                while let (Some(a), Some(b)) = (iter.next(), iter.next()) {
113                    out.push((
114                        Bytes::from_owned_valkey_value(a)?,
115                        Bytes::from_owned_valkey_value(b)?,
116                    ));
117                }
118                Ok(out)
119            }
120        }
121        other => Ok(vec![(Bytes::from_owned_valkey_value(other)?, Bytes::new())]),
122    }
123}
124
125fn collect_bytes(v: ValkeyValue) -> ValkeyResult<Vec<Bytes>> {
126    match v {
127        ValkeyValue::Array(items) => items
128            .into_iter()
129            .map(Bytes::from_owned_valkey_value)
130            .collect(),
131        ValkeyValue::Nil => Ok(Vec::new()),
132        other => Ok(vec![Bytes::from_owned_valkey_value(other)?]),
133    }
134}
135
136impl<T: CommandExecutor + ?Sized> HashCommands for T {}