Skip to main content

fast_cache/commands/
set.rs

1//! SET command parsing and execution.
2//!
3//! SET-specific behavior starts here. Transport-specific implementations,
4//! option parsing, and write-path selection live in `set/*.rs` submodules.
5
6mod engine;
7#[cfg(feature = "server")]
8mod fcnp;
9mod options;
10#[cfg(feature = "server")]
11mod server;
12#[cfg(feature = "server")]
13mod storage;
14
15use crate::Result;
16use crate::protocol::{FastCommand, Frame};
17#[cfg(feature = "server")]
18use crate::server::commands::{BorrowedCommandContext, DirectCommandContext};
19#[cfg(feature = "server")]
20use crate::storage::EmbeddedStore;
21use crate::storage::{Command, EngineCommandContext, EngineFrameFuture, RESP_SPANNED_VALUE_MIN};
22
23use super::DecodedFastCommand;
24use super::parsing::CommandArity;
25use options::StorageSetOptions;
26
27/// Command spec registered in the command catalogs.
28pub(crate) struct Set;
29pub(crate) static COMMAND: Set = Set;
30
31/// Owned SET payload used when a RESP frame has already been copied out.
32#[derive(Debug, Clone)]
33pub(crate) struct OwnedSet {
34    key: Vec<u8>,
35    value: Vec<u8>,
36    ttl_ms: Option<u64>,
37}
38
39impl OwnedSet {
40    fn new(key: Vec<u8>, value: Vec<u8>, ttl_ms: Option<u64>) -> Self {
41        Self { key, value, ttl_ms }
42    }
43}
44
45impl super::OwnedCommandData for OwnedSet {
46    type Spec = Set;
47
48    fn route_key(&self) -> Option<&[u8]> {
49        Some(&self.key)
50    }
51
52    fn to_borrowed_command(&self) -> super::BorrowedCommandBox<'_> {
53        Box::new(BorrowedSet::new(&self.key, &self.value, self.ttl_ms))
54    }
55}
56
57/// Borrowed SET payload used on zero-copy request paths.
58#[derive(Debug, Clone, Copy)]
59pub(crate) struct BorrowedSet<'a> {
60    key: &'a [u8],
61    value: &'a [u8],
62    ttl_ms: Option<u64>,
63}
64
65impl<'a> BorrowedSet<'a> {
66    fn new(key: &'a [u8], value: &'a [u8], ttl_ms: Option<u64>) -> Self {
67        Self { key, value, ttl_ms }
68    }
69}
70
71impl<'a> super::BorrowedCommandData<'a> for BorrowedSet<'a> {
72    type Spec = Set;
73
74    fn route_key(&self) -> Option<&'a [u8]> {
75        Some(self.key)
76    }
77
78    fn supports_spanned_resp(&self) -> bool {
79        self.value.len() >= RESP_SPANNED_VALUE_MIN
80    }
81
82    fn to_owned_command(&self) -> Command {
83        Command::new(Box::new(OwnedSet::new(
84            self.key.to_vec(),
85            self.value.to_vec(),
86            self.ttl_ms,
87        )))
88    }
89
90    fn execute_engine<'b>(&'b self, ctx: EngineCommandContext<'b>) -> EngineFrameFuture<'b>
91    where
92        'a: 'b,
93    {
94        let key = self.key;
95        let value = self.value;
96        let ttl_ms = self.ttl_ms;
97        Box::pin(async move { Set::execute_engine_frame(ctx, key, value, ttl_ms).await })
98    }
99
100    #[cfg(feature = "server")]
101    fn execute_borrowed_frame(&self, store: &EmbeddedStore, _now_ms: u64) -> Frame {
102        store.set(self.key.to_vec(), self.value.to_vec(), self.ttl_ms);
103        Frame::SimpleString("OK".into())
104    }
105
106    #[cfg(feature = "server")]
107    fn execute_borrowed(&self, ctx: BorrowedCommandContext<'_, '_, '_>) {
108        match ctx.single_threaded && !ctx.store.has_redis_objects() {
109            true => {
110                // SAFETY: forwarded from caller's single-worker contract.
111                unsafe {
112                    ctx.store
113                        .set_single_threaded(self.key, self.value, self.ttl_ms)
114                };
115            }
116            false => {
117                ctx.store
118                    .set(self.key.to_vec(), self.value.to_vec(), self.ttl_ms);
119            }
120        }
121        ctx.out.extend_from_slice(b"+OK\r\n");
122    }
123
124    #[cfg(feature = "server")]
125    fn execute_direct_borrowed(&self, ctx: DirectCommandContext) -> Frame {
126        ctx.set_owned(self.key.to_vec(), self.value.to_vec(), self.ttl_ms);
127        Frame::SimpleString("OK".into())
128    }
129}
130
131impl super::CommandSpec for Set {
132    const NAME: &'static str = "SET";
133    const MUTATES_VALUE: bool = true;
134}
135
136impl super::OwnedCommandParse for Set {
137    fn parse_owned(parts: &[Vec<u8>]) -> Result<Command> {
138        CommandArity::<Self>::at_least(parts.len(), 3, "key and value")?;
139        let options = StorageSetOptions::parse(parts[3..].iter().map(Vec::as_slice))?;
140        Ok(Command::new(Box::new(OwnedSet::new(
141            parts[1].clone(),
142            parts[2].clone(),
143            options.ttl_ms,
144        ))))
145    }
146}
147
148impl<'a> super::BorrowedCommandParse<'a> for Set {
149    fn parse_borrowed(parts: &[&'a [u8]]) -> Result<super::BorrowedCommandBox<'a>> {
150        CommandArity::<Self>::at_least(parts.len(), 3, "key and value")?;
151        let options = StorageSetOptions::parse(parts[3..].iter().copied())?;
152        Ok(Box::new(BorrowedSet::new(
153            parts[1],
154            parts[2],
155            options.ttl_ms,
156        )))
157    }
158}
159
160impl DecodedFastCommand for Set {
161    fn matches_decoded_fast(&self, command: &FastCommand<'_>) -> bool {
162        matches!(command, FastCommand::Set { .. })
163    }
164}