redis_oxide/commands/
mod.rs

1//! Command builders for Redis operations
2//!
3//! This module provides type-safe command builders for Redis commands.
4
5pub mod hash;
6pub mod list;
7pub mod optimized;
8pub mod set;
9pub mod sorted_set;
10
11use crate::core::{error::RedisResult, value::RespValue};
12use crate::pipeline::PipelineCommand;
13use std::time::Duration;
14
15// Re-export hash commands
16pub use hash::{
17    HDelCommand, HExistsCommand, HGetAllCommand, HGetCommand, HLenCommand, HMGetCommand,
18    HMSetCommand, HSetCommand,
19};
20
21// Re-export list commands
22pub use list::{
23    LIndexCommand, LLenCommand, LPopCommand, LPushCommand, LRangeCommand, LSetCommand, RPopCommand,
24    RPushCommand,
25};
26
27// Re-export set commands
28pub use set::{
29    SAddCommand, SCardCommand, SIsMemberCommand, SMembersCommand, SPopCommand, SRandMemberCommand,
30    SRemCommand,
31};
32
33// Re-export sorted set commands
34pub use sorted_set::{
35    ZAddCommand, ZCardCommand, ZRangeCommand, ZRankCommand, ZRemCommand, ZRevRankCommand,
36    ZScoreCommand,
37};
38
39/// Trait for commands that can be executed
40pub trait Command {
41    /// The return type of the command
42    type Output;
43
44    /// Get the command name
45    fn command_name(&self) -> &str;
46
47    /// Get the command arguments
48    fn args(&self) -> Vec<RespValue>;
49
50    /// Parse the response into the output type
51    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output>;
52
53    /// Get the key(s) involved in this command (for cluster routing)
54    fn keys(&self) -> Vec<&[u8]>;
55}
56
57/// GET command builder
58pub struct GetCommand {
59    key: String,
60}
61
62impl GetCommand {
63    /// Create a new GET command
64    pub fn new(key: impl Into<String>) -> Self {
65        Self { key: key.into() }
66    }
67}
68
69impl Command for GetCommand {
70    type Output = Option<String>;
71
72    fn command_name(&self) -> &str {
73        "GET"
74    }
75
76    fn args(&self) -> Vec<RespValue> {
77        vec![RespValue::from(self.key.as_str())]
78    }
79
80    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
81        if response.is_null() {
82            Ok(None)
83        } else {
84            Ok(Some(response.as_string()?))
85        }
86    }
87
88    fn keys(&self) -> Vec<&[u8]> {
89        vec![self.key.as_bytes()]
90    }
91}
92
93/// SET command builder
94pub struct SetCommand {
95    key: String,
96    value: String,
97    expiration: Option<Duration>,
98    nx: bool,
99    xx: bool,
100}
101
102impl SetCommand {
103    /// Create a new SET command
104    pub fn new(key: impl Into<String>, value: impl Into<String>) -> Self {
105        Self {
106            key: key.into(),
107            value: value.into(),
108            expiration: None,
109            nx: false,
110            xx: false,
111        }
112    }
113
114    /// Set expiration time (EX seconds)
115    pub fn expire(mut self, duration: Duration) -> Self {
116        self.expiration = Some(duration);
117        self
118    }
119
120    /// Only set if key doesn't exist (NX)
121    pub fn only_if_not_exists(mut self) -> Self {
122        self.nx = true;
123        self
124    }
125
126    /// Only set if key exists (XX)
127    pub fn only_if_exists(mut self) -> Self {
128        self.xx = true;
129        self
130    }
131}
132
133impl Command for SetCommand {
134    type Output = bool;
135
136    fn command_name(&self) -> &str {
137        "SET"
138    }
139
140    fn args(&self) -> Vec<RespValue> {
141        let mut args = vec![
142            RespValue::from(self.key.as_str()),
143            RespValue::from(self.value.as_str()),
144        ];
145
146        if let Some(duration) = self.expiration {
147            args.push(RespValue::from("EX"));
148            args.push(RespValue::from(duration.as_secs().to_string()));
149        }
150
151        if self.nx {
152            args.push(RespValue::from("NX"));
153        }
154
155        if self.xx {
156            args.push(RespValue::from("XX"));
157        }
158
159        args
160    }
161
162    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
163        match response {
164            RespValue::SimpleString(ref s) if s == "OK" => Ok(true),
165            // NX or XX condition not met
166            _ => Ok(false),
167        }
168    }
169
170    fn keys(&self) -> Vec<&[u8]> {
171        vec![self.key.as_bytes()]
172    }
173}
174
175/// DEL command builder
176pub struct DelCommand {
177    keys: Vec<String>,
178}
179
180impl DelCommand {
181    /// Create a new DEL command
182    pub fn new(keys: Vec<String>) -> Self {
183        Self { keys }
184    }
185}
186
187impl Command for DelCommand {
188    type Output = i64;
189
190    fn command_name(&self) -> &str {
191        "DEL"
192    }
193
194    fn args(&self) -> Vec<RespValue> {
195        self.keys
196            .iter()
197            .map(|k| RespValue::from(k.as_str()))
198            .collect()
199    }
200
201    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
202        response.as_int()
203    }
204
205    fn keys(&self) -> Vec<&[u8]> {
206        self.keys.iter().map(String::as_bytes).collect()
207    }
208}
209
210/// EXISTS command builder
211pub struct ExistsCommand {
212    keys: Vec<String>,
213}
214
215impl ExistsCommand {
216    /// Create a new EXISTS command
217    pub fn new(keys: Vec<String>) -> Self {
218        Self { keys }
219    }
220}
221
222impl Command for ExistsCommand {
223    type Output = i64;
224
225    fn command_name(&self) -> &str {
226        "EXISTS"
227    }
228
229    fn args(&self) -> Vec<RespValue> {
230        self.keys
231            .iter()
232            .map(|k| RespValue::from(k.as_str()))
233            .collect()
234    }
235
236    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
237        response.as_int()
238    }
239
240    fn keys(&self) -> Vec<&[u8]> {
241        self.keys.iter().map(String::as_bytes).collect()
242    }
243}
244
245/// EXPIRE command builder
246pub struct ExpireCommand {
247    key: String,
248    seconds: i64,
249}
250
251impl ExpireCommand {
252    /// Create a new EXPIRE command
253    pub fn new(key: impl Into<String>, duration: Duration) -> Self {
254        #[allow(clippy::cast_possible_wrap)]
255        Self {
256            key: key.into(),
257            seconds: duration.as_secs() as i64,
258        }
259    }
260}
261
262impl Command for ExpireCommand {
263    type Output = bool;
264
265    fn command_name(&self) -> &str {
266        "EXPIRE"
267    }
268
269    fn args(&self) -> Vec<RespValue> {
270        vec![
271            RespValue::from(self.key.as_str()),
272            RespValue::from(self.seconds.to_string()),
273        ]
274    }
275
276    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
277        Ok(response.as_int()? == 1)
278    }
279
280    fn keys(&self) -> Vec<&[u8]> {
281        vec![self.key.as_bytes()]
282    }
283}
284
285/// TTL command builder
286pub struct TtlCommand {
287    key: String,
288}
289
290impl TtlCommand {
291    /// Create a new TTL command
292    pub fn new(key: impl Into<String>) -> Self {
293        Self { key: key.into() }
294    }
295}
296
297impl Command for TtlCommand {
298    type Output = Option<i64>;
299
300    fn command_name(&self) -> &str {
301        "TTL"
302    }
303
304    fn args(&self) -> Vec<RespValue> {
305        vec![RespValue::from(self.key.as_str())]
306    }
307
308    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
309        let ttl = response.as_int()?;
310        if ttl < 0 {
311            Ok(None) // -2 = key doesn't exist, -1 = no expiration
312        } else {
313            Ok(Some(ttl))
314        }
315    }
316
317    fn keys(&self) -> Vec<&[u8]> {
318        vec![self.key.as_bytes()]
319    }
320}
321
322/// INCR command builder
323pub struct IncrCommand {
324    key: String,
325}
326
327impl IncrCommand {
328    /// Create a new INCR command
329    pub fn new(key: impl Into<String>) -> Self {
330        Self { key: key.into() }
331    }
332}
333
334impl Command for IncrCommand {
335    type Output = i64;
336
337    fn command_name(&self) -> &str {
338        "INCR"
339    }
340
341    fn args(&self) -> Vec<RespValue> {
342        vec![RespValue::from(self.key.as_str())]
343    }
344
345    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
346        response.as_int()
347    }
348
349    fn keys(&self) -> Vec<&[u8]> {
350        vec![self.key.as_bytes()]
351    }
352}
353
354/// DECR command builder
355pub struct DecrCommand {
356    key: String,
357}
358
359impl DecrCommand {
360    /// Create a new DECR command
361    pub fn new(key: impl Into<String>) -> Self {
362        Self { key: key.into() }
363    }
364}
365
366impl Command for DecrCommand {
367    type Output = i64;
368
369    fn command_name(&self) -> &str {
370        "DECR"
371    }
372
373    fn args(&self) -> Vec<RespValue> {
374        vec![RespValue::from(self.key.as_str())]
375    }
376
377    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
378        response.as_int()
379    }
380
381    fn keys(&self) -> Vec<&[u8]> {
382        vec![self.key.as_bytes()]
383    }
384}
385
386/// INCRBY command builder
387pub struct IncrByCommand {
388    key: String,
389    increment: i64,
390}
391
392impl IncrByCommand {
393    /// Create a new INCRBY command
394    pub fn new(key: impl Into<String>, increment: i64) -> Self {
395        Self {
396            key: key.into(),
397            increment,
398        }
399    }
400}
401
402impl Command for IncrByCommand {
403    type Output = i64;
404
405    fn command_name(&self) -> &str {
406        "INCRBY"
407    }
408
409    fn args(&self) -> Vec<RespValue> {
410        vec![
411            RespValue::from(self.key.as_str()),
412            RespValue::from(self.increment.to_string()),
413        ]
414    }
415
416    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
417        response.as_int()
418    }
419
420    fn keys(&self) -> Vec<&[u8]> {
421        vec![self.key.as_bytes()]
422    }
423}
424
425/// DECRBY command builder
426pub struct DecrByCommand {
427    key: String,
428    decrement: i64,
429}
430
431impl DecrByCommand {
432    /// Create a new DECRBY command
433    pub fn new(key: impl Into<String>, decrement: i64) -> Self {
434        Self {
435            key: key.into(),
436            decrement,
437        }
438    }
439}
440
441impl Command for DecrByCommand {
442    type Output = i64;
443
444    fn command_name(&self) -> &str {
445        "DECRBY"
446    }
447
448    fn args(&self) -> Vec<RespValue> {
449        vec![
450            RespValue::from(self.key.as_str()),
451            RespValue::from(self.decrement.to_string()),
452        ]
453    }
454
455    fn parse_response(&self, response: RespValue) -> RedisResult<Self::Output> {
456        response.as_int()
457    }
458
459    fn keys(&self) -> Vec<&[u8]> {
460        vec![self.key.as_bytes()]
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467
468    #[test]
469    fn test_get_command() {
470        let cmd = GetCommand::new("mykey");
471        assert_eq!(cmd.command_name(), "GET");
472        assert_eq!(cmd.keys(), vec![b"mykey"]);
473    }
474
475    #[test]
476    fn test_set_command_basic() {
477        let cmd = SetCommand::new("key", "value");
478        assert_eq!(cmd.command_name(), "SET");
479        let args = <SetCommand as Command>::args(&cmd);
480        assert_eq!(args.len(), 2);
481    }
482
483    #[test]
484    fn test_set_command_with_expiration() {
485        let cmd = SetCommand::new("key", "value").expire(Duration::from_secs(60));
486        let args = <SetCommand as Command>::args(&cmd);
487        assert_eq!(args.len(), 4); // key, value, EX, 60
488    }
489
490    #[test]
491    fn test_set_command_nx() {
492        let cmd = SetCommand::new("key", "value").only_if_not_exists();
493        let args = <SetCommand as Command>::args(&cmd);
494        assert!(args.len() >= 3); // key, value, NX
495    }
496
497    #[test]
498    fn test_del_command() {
499        let cmd = DelCommand::new(vec!["key1".to_string(), "key2".to_string()]);
500        assert_eq!(cmd.command_name(), "DEL");
501        assert_eq!(cmd.keys().len(), 2);
502    }
503
504    #[test]
505    fn test_incr_command() {
506        let cmd = IncrCommand::new("counter");
507        assert_eq!(cmd.command_name(), "INCR");
508        assert_eq!(cmd.keys(), vec![b"counter"]);
509    }
510}
511
512// Implement PipelineCommand for all command types
513impl PipelineCommand for GetCommand {
514    fn name(&self) -> &str {
515        self.command_name()
516    }
517
518    fn args(&self) -> Vec<RespValue> {
519        <Self as Command>::args(self)
520    }
521
522    fn key(&self) -> Option<String> {
523        Some(self.key.clone())
524    }
525}
526
527impl PipelineCommand for SetCommand {
528    fn name(&self) -> &str {
529        self.command_name()
530    }
531
532    fn args(&self) -> Vec<RespValue> {
533        <Self as Command>::args(self)
534    }
535
536    fn key(&self) -> Option<String> {
537        Some(self.key.clone())
538    }
539}
540
541impl PipelineCommand for DelCommand {
542    fn name(&self) -> &str {
543        self.command_name()
544    }
545
546    fn args(&self) -> Vec<RespValue> {
547        <Self as Command>::args(self)
548    }
549
550    fn key(&self) -> Option<String> {
551        self.keys.first().cloned()
552    }
553}
554
555impl PipelineCommand for IncrCommand {
556    fn name(&self) -> &str {
557        self.command_name()
558    }
559
560    fn args(&self) -> Vec<RespValue> {
561        <Self as Command>::args(self)
562    }
563
564    fn key(&self) -> Option<String> {
565        Some(self.key.clone())
566    }
567}
568
569impl PipelineCommand for DecrCommand {
570    fn name(&self) -> &str {
571        self.command_name()
572    }
573
574    fn args(&self) -> Vec<RespValue> {
575        <Self as Command>::args(self)
576    }
577
578    fn key(&self) -> Option<String> {
579        Some(self.key.clone())
580    }
581}
582
583impl PipelineCommand for IncrByCommand {
584    fn name(&self) -> &str {
585        self.command_name()
586    }
587
588    fn args(&self) -> Vec<RespValue> {
589        <Self as Command>::args(self)
590    }
591
592    fn key(&self) -> Option<String> {
593        Some(self.key.clone())
594    }
595}
596
597impl PipelineCommand for DecrByCommand {
598    fn name(&self) -> &str {
599        self.command_name()
600    }
601
602    fn args(&self) -> Vec<RespValue> {
603        <Self as Command>::args(self)
604    }
605
606    fn key(&self) -> Option<String> {
607        Some(self.key.clone())
608    }
609}
610
611impl PipelineCommand for ExistsCommand {
612    fn name(&self) -> &str {
613        self.command_name()
614    }
615
616    fn args(&self) -> Vec<RespValue> {
617        <Self as Command>::args(self)
618    }
619
620    fn key(&self) -> Option<String> {
621        self.keys.first().cloned()
622    }
623}
624
625impl PipelineCommand for ExpireCommand {
626    fn name(&self) -> &str {
627        self.command_name()
628    }
629
630    fn args(&self) -> Vec<RespValue> {
631        <Self as Command>::args(self)
632    }
633
634    fn key(&self) -> Option<String> {
635        Some(self.key.clone())
636    }
637}
638
639impl PipelineCommand for TtlCommand {
640    fn name(&self) -> &str {
641        self.command_name()
642    }
643
644    fn args(&self) -> Vec<RespValue> {
645        <Self as Command>::args(self)
646    }
647
648    fn key(&self) -> Option<String> {
649        Some(self.key.clone())
650    }
651}