Skip to main content

fast_cache/
commands.rs

1pub mod del;
2pub mod exists;
3pub mod expire;
4pub mod get;
5pub mod getex;
6pub(crate) mod parsing;
7pub mod persist;
8pub mod pexpire;
9pub mod psetex;
10pub mod pttl;
11pub mod set;
12pub mod setex;
13pub mod ttl;
14
15use bytes::Bytes as SharedBytes;
16
17use crate::protocol::{CommandSpanFrame, FastCommand, FastRequest, FastResponse, Frame};
18use crate::storage::{
19    EngineCommandContext, EngineFastFuture, EngineFrameFuture, EngineRespSpanFuture,
20};
21use crate::{FastCacheError, Result};
22
23pub(crate) trait CommandSpec {
24    const NAME: &'static str;
25    const MUTATES_VALUE: bool;
26
27    #[inline(always)]
28    fn matches(name: &[u8]) -> bool {
29        name.eq_ignore_ascii_case(Self::NAME.as_bytes())
30    }
31}
32
33pub(crate) trait OwnedCommandParse: CommandSpec {
34    fn parse_owned(parts: &[Vec<u8>]) -> Result<crate::storage::Command>;
35}
36
37pub(crate) type OwnedCommandBox = Box<dyn OwnedCommandObject>;
38
39/// Command-owned data that was parsed from an owned RESP frame.
40///
41/// Implement this for the concrete owned command payload. The blanket
42/// `OwnedCommandObject` impl supplies the command name and mutation metadata
43/// from the command spec so each command file does not repeat that boilerplate.
44pub(crate) trait OwnedCommandData: std::fmt::Debug + Send + Sync {
45    type Spec: CommandSpec;
46
47    fn route_key(&self) -> Option<&[u8]>;
48    fn to_borrowed_command(&self) -> BorrowedCommandBox<'_>;
49}
50
51/// Parsed owned command data owned by a concrete command module.
52pub(crate) trait OwnedCommandObject: std::fmt::Debug + Send + Sync {
53    fn name(&self) -> &'static str;
54    fn mutates_value(&self) -> bool;
55    fn route_key(&self) -> Option<&[u8]>;
56    fn to_borrowed_command(&self) -> BorrowedCommandBox<'_>;
57}
58
59impl<T> OwnedCommandObject for T
60where
61    T: OwnedCommandData,
62{
63    fn name(&self) -> &'static str {
64        <T::Spec as CommandSpec>::NAME
65    }
66
67    fn mutates_value(&self) -> bool {
68        <T::Spec as CommandSpec>::MUTATES_VALUE
69    }
70
71    fn route_key(&self) -> Option<&[u8]> {
72        <T as OwnedCommandData>::route_key(self)
73    }
74
75    fn to_borrowed_command(&self) -> BorrowedCommandBox<'_> {
76        <T as OwnedCommandData>::to_borrowed_command(self)
77    }
78}
79
80pub(crate) type BorrowedCommandBox<'a> = Box<dyn BorrowedCommandObject<'a> + 'a>;
81
82/// Command-owned data that borrows directly from a decoded request buffer.
83///
84/// The blanket `BorrowedCommandObject` impl keeps name/mutation metadata
85/// centralized while allowing each command file to own execution details.
86pub(crate) trait BorrowedCommandData<'a>: std::fmt::Debug + Send + Sync {
87    type Spec: CommandSpec;
88
89    fn route_key(&self) -> Option<&'a [u8]>;
90    fn supports_spanned_resp(&self) -> bool {
91        false
92    }
93    fn to_owned_command(&self) -> crate::storage::Command;
94    fn execute_engine<'b>(&'b self, ctx: EngineCommandContext<'b>) -> EngineFrameFuture<'b>
95    where
96        'a: 'b;
97
98    #[cfg(feature = "server")]
99    fn execute_borrowed_frame(&self, store: &crate::storage::EmbeddedStore, now_ms: u64) -> Frame;
100
101    #[cfg(feature = "server")]
102    fn execute_borrowed(&self, ctx: crate::server::commands::BorrowedCommandContext<'_, '_, '_>);
103
104    #[cfg(feature = "server")]
105    fn execute_direct_borrowed(&self, ctx: crate::server::commands::DirectCommandContext) -> Frame;
106}
107
108/// Parsed borrowed command data owned by a concrete command module.
109pub(crate) trait BorrowedCommandObject<'a>: std::fmt::Debug + Send + Sync {
110    fn name(&self) -> &'static str;
111    fn mutates_value(&self) -> bool;
112    fn route_key(&self) -> Option<&'a [u8]>;
113    fn supports_spanned_resp(&self) -> bool;
114    fn to_owned_command(&self) -> crate::storage::Command;
115    fn execute_engine<'b>(&'b self, ctx: EngineCommandContext<'b>) -> EngineFrameFuture<'b>
116    where
117        'a: 'b;
118
119    #[cfg(feature = "server")]
120    fn execute_borrowed_frame(&self, store: &crate::storage::EmbeddedStore, now_ms: u64) -> Frame;
121
122    #[cfg(feature = "server")]
123    fn execute_borrowed(&self, ctx: crate::server::commands::BorrowedCommandContext<'_, '_, '_>);
124
125    #[cfg(feature = "server")]
126    fn execute_direct_borrowed(&self, ctx: crate::server::commands::DirectCommandContext) -> Frame;
127}
128
129impl<'a, T> BorrowedCommandObject<'a> for T
130where
131    T: BorrowedCommandData<'a>,
132{
133    fn name(&self) -> &'static str {
134        <T::Spec as CommandSpec>::NAME
135    }
136
137    fn mutates_value(&self) -> bool {
138        <T::Spec as CommandSpec>::MUTATES_VALUE
139    }
140
141    fn route_key(&self) -> Option<&'a [u8]> {
142        <T as BorrowedCommandData<'a>>::route_key(self)
143    }
144
145    fn supports_spanned_resp(&self) -> bool {
146        <T as BorrowedCommandData<'a>>::supports_spanned_resp(self)
147    }
148
149    fn to_owned_command(&self) -> crate::storage::Command {
150        <T as BorrowedCommandData<'a>>::to_owned_command(self)
151    }
152
153    fn execute_engine<'b>(&'b self, ctx: EngineCommandContext<'b>) -> EngineFrameFuture<'b>
154    where
155        'a: 'b,
156    {
157        <T as BorrowedCommandData<'a>>::execute_engine(self, ctx)
158    }
159
160    #[cfg(feature = "server")]
161    fn execute_borrowed_frame(&self, store: &crate::storage::EmbeddedStore, now_ms: u64) -> Frame {
162        <T as BorrowedCommandData<'a>>::execute_borrowed_frame(self, store, now_ms)
163    }
164
165    #[cfg(feature = "server")]
166    fn execute_borrowed(&self, ctx: crate::server::commands::BorrowedCommandContext<'_, '_, '_>) {
167        <T as BorrowedCommandData<'a>>::execute_borrowed(self, ctx);
168    }
169
170    #[cfg(feature = "server")]
171    fn execute_direct_borrowed(&self, ctx: crate::server::commands::DirectCommandContext) -> Frame {
172        <T as BorrowedCommandData<'a>>::execute_direct_borrowed(self, ctx)
173    }
174}
175
176pub(crate) trait BorrowedCommandParse<'a>: CommandSpec {
177    fn parse_borrowed(parts: &[&'a [u8]]) -> Result<BorrowedCommandBox<'a>>;
178}
179
180/// Object-safe metadata shared by command implementations.
181pub(crate) trait CommandMetadata: Sync {
182    fn mutates_value(&self) -> bool;
183    fn matches(&self, name: &[u8]) -> bool;
184}
185
186impl<T> CommandMetadata for T
187where
188    T: CommandSpec + Sync,
189{
190    fn mutates_value(&self) -> bool {
191        T::MUTATES_VALUE
192    }
193
194    #[inline(always)]
195    fn matches(&self, name: &[u8]) -> bool {
196        <T as CommandSpec>::matches(name)
197    }
198}
199
200/// Object-safe parser entry owned by a command object.
201pub(crate) trait CommandDefinition: CommandMetadata {
202    fn parse_owned(&self, parts: &[Vec<u8>]) -> Result<crate::storage::Command>;
203    fn parse_borrowed<'a>(&self, parts: &[&'a [u8]]) -> Result<BorrowedCommandBox<'a>>;
204}
205
206impl<T> CommandDefinition for T
207where
208    T: CommandSpec + OwnedCommandParse + Sync,
209    for<'a> T: BorrowedCommandParse<'a>,
210{
211    fn parse_owned(&self, parts: &[Vec<u8>]) -> Result<crate::storage::Command> {
212        <T as OwnedCommandParse>::parse_owned(parts)
213    }
214
215    fn parse_borrowed<'a>(&self, parts: &[&'a [u8]]) -> Result<BorrowedCommandBox<'a>> {
216        <T as BorrowedCommandParse<'a>>::parse_borrowed(parts)
217    }
218}
219
220pub(crate) trait DecodedFastCommand: CommandMetadata {
221    fn matches_decoded_fast(&self, command: &FastCommand<'_>) -> bool;
222}
223
224pub(crate) trait EngineCommandDispatch: DecodedFastCommand {
225    fn execute_engine_fast<'a>(
226        &'static self,
227        ctx: EngineCommandContext<'a>,
228        request: FastRequest<'a>,
229    ) -> EngineFastFuture<'a>;
230}
231
232pub(crate) trait EngineRespSpanCommandDispatch: CommandMetadata {
233    fn execute_engine_resp_spanned<'a>(
234        &'static self,
235        ctx: EngineCommandContext<'a>,
236        frame: CommandSpanFrame,
237        owner: SharedBytes,
238        out: &'a mut Vec<u8>,
239    ) -> EngineRespSpanFuture<'a>;
240}
241
242pub(crate) static CATALOG: &[&dyn CommandDefinition] = &[
243    &get::COMMAND,
244    &set::COMMAND,
245    &del::COMMAND,
246    &exists::COMMAND,
247    &ttl::COMMAND,
248    &pttl::COMMAND,
249    &expire::COMMAND,
250    &pexpire::COMMAND,
251    &persist::COMMAND,
252    &getex::COMMAND,
253    &setex::COMMAND,
254    &psetex::COMMAND,
255];
256
257pub(crate) struct CommandCatalog;
258
259impl CommandCatalog {
260    pub(crate) fn find(name: &[u8]) -> Option<&'static dyn CommandDefinition> {
261        CATALOG
262            .iter()
263            .copied()
264            .find(|command| command.matches(name))
265    }
266
267    pub(crate) fn parse_owned(parts: &[Vec<u8>]) -> Result<crate::storage::Command> {
268        let command = Self::find_required(parts.first().map(Vec::as_slice))?;
269        command.parse_owned(parts)
270    }
271
272    pub(crate) fn parse_borrowed<'a>(parts: &[&'a [u8]]) -> Result<BorrowedCommandBox<'a>> {
273        let command = Self::find_required(parts.first().copied())?;
274        command.parse_borrowed(parts)
275    }
276
277    fn find_required(name: Option<&[u8]>) -> Result<&'static dyn CommandDefinition> {
278        match name {
279            Some(name) => Self::find(name).ok_or_else(|| {
280                FastCacheError::Command(format!(
281                    "unsupported command: {}",
282                    String::from_utf8_lossy(name)
283                ))
284            }),
285            None => Err(FastCacheError::Command("empty command".into())),
286        }
287    }
288}
289
290pub(crate) struct EngineCommandCatalog;
291
292impl EngineCommandCatalog {
293    fn find_fast(command: &FastCommand<'_>) -> Option<&'static dyn EngineCommandDispatch> {
294        [
295            &get::COMMAND as &dyn EngineCommandDispatch,
296            &set::COMMAND,
297            &del::COMMAND,
298            &exists::COMMAND,
299            &ttl::COMMAND,
300            &expire::COMMAND,
301            &getex::COMMAND,
302            &setex::COMMAND,
303        ]
304        .into_iter()
305        .find(|candidate| candidate.matches_decoded_fast(command))
306    }
307
308    fn find_resp_span(name: &[u8]) -> Option<&'static dyn EngineRespSpanCommandDispatch> {
309        [&set::COMMAND as &dyn EngineRespSpanCommandDispatch]
310            .into_iter()
311            .find(|candidate| candidate.matches(name))
312    }
313
314    pub(crate) async fn execute_fast<'a>(
315        ctx: EngineCommandContext<'a>,
316        request: FastRequest<'a>,
317    ) -> Option<Result<FastResponse>> {
318        let handler = Self::find_fast(&request.command)?;
319        Some(handler.execute_engine_fast(ctx, request).await)
320    }
321
322    pub(crate) async fn execute_resp_spanned<'a>(
323        ctx: EngineCommandContext<'a>,
324        frame: CommandSpanFrame,
325        owner: SharedBytes,
326        out: &'a mut Vec<u8>,
327    ) -> Option<Result<()>> {
328        let name = &owner[frame.parts.first()?.clone()];
329        let handler = Self::find_resp_span(name)?;
330        Some(
331            handler
332                .execute_engine_resp_spanned(ctx, frame, owner, out)
333                .await,
334        )
335    }
336}