Skip to main content

redis_oxide/commands/
mod.rs

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