Skip to main content

glide/commands/
scripting.rs

1// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2//! Scripting & function commands. Mirrors Python's scripting surface.
3
4use crate::ValkeyResult;
5use crate::cmd::Cmd;
6use crate::commands::options::{FunctionFlushOptions, FunctionRestorePolicy};
7use crate::executor::CommandExecutor;
8use crate::routes::Route;
9use crate::value::FromValkeyValue;
10use crate::value::ValkeyValue;
11use crate::write::ToValkeyArgs;
12use async_trait::async_trait;
13use bytes::Bytes;
14
15/// Scripting and function commands (`EVAL`, `EVALSHA`, `SCRIPT ...`, `FCALL`, ...).
16#[async_trait]
17pub trait ScriptingCommands: CommandExecutor {
18    /// Evaluate a Lua `script` (`EVAL`). Returns the raw reply.
19    async fn eval<K: ToValkeyArgs + Send + Sync, A: ToValkeyArgs + Send + Sync>(
20        &self,
21        script: &str,
22        keys: &[K],
23        args: &[A],
24    ) -> ValkeyResult<ValkeyValue> {
25        let mut cmd = Cmd::new();
26        cmd.arg("EVAL").arg(script).arg(keys.len());
27        for k in keys {
28            cmd.arg(k);
29        }
30        for a in args {
31            cmd.arg(a);
32        }
33        self.execute_command(cmd, None).await
34    }
35
36    /// Evaluate a cached script by its SHA1 hash (`EVALSHA`).
37    async fn evalsha<K: ToValkeyArgs + Send + Sync, A: ToValkeyArgs + Send + Sync>(
38        &self,
39        sha1: &str,
40        keys: &[K],
41        args: &[A],
42    ) -> ValkeyResult<ValkeyValue> {
43        let mut cmd = Cmd::new();
44        cmd.arg("EVALSHA").arg(sha1).arg(keys.len());
45        for k in keys {
46            cmd.arg(k);
47        }
48        for a in args {
49            cmd.arg(a);
50        }
51        self.execute_command(cmd, None).await
52    }
53
54    /// Load a script into the script cache, returning its SHA1 (`SCRIPT LOAD`).
55    async fn script_load(&self, script: &str) -> ValkeyResult<String> {
56        let mut cmd = Cmd::new();
57        cmd.arg("SCRIPT").arg("LOAD").arg(script);
58        String::from_owned_valkey_value(self.execute_command(cmd, None).await?)
59    }
60
61    /// Check whether scripts exist in the cache by SHA1 (`SCRIPT EXISTS`).
62    async fn script_exists(&self, sha1s: &[&str]) -> ValkeyResult<Vec<bool>> {
63        let mut cmd = Cmd::new();
64        cmd.arg("SCRIPT").arg("EXISTS");
65        for s in sha1s {
66            cmd.arg(*s);
67        }
68        match self.execute_command(cmd, None).await? {
69            ValkeyValue::Array(items) => items
70                .into_iter()
71                .map(bool::from_owned_valkey_value)
72                .collect(),
73            other => Ok(vec![bool::from_owned_valkey_value(other)?]),
74        }
75    }
76
77    /// Flush the script cache (`SCRIPT FLUSH`).
78    async fn script_flush(&self) -> ValkeyResult<()> {
79        let mut cmd = Cmd::new();
80        cmd.arg("SCRIPT").arg("FLUSH");
81        <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
82    }
83
84    /// Invoke a function registered with `FUNCTION LOAD` (`FCALL`).
85    async fn fcall<K: ToValkeyArgs + Send + Sync, A: ToValkeyArgs + Send + Sync>(
86        &self,
87        function: &str,
88        keys: &[K],
89        args: &[A],
90    ) -> ValkeyResult<ValkeyValue> {
91        let mut cmd = Cmd::new();
92        cmd.arg("FCALL").arg(function).arg(keys.len());
93        for k in keys {
94            cmd.arg(k);
95        }
96        for a in args {
97            cmd.arg(a);
98        }
99        self.execute_command(cmd, None).await
100    }
101
102    /// Read-only variant of [`ScriptingCommands::fcall`] (`FCALL_RO`).
103    async fn fcall_ro<K: ToValkeyArgs + Send + Sync, A: ToValkeyArgs + Send + Sync>(
104        &self,
105        function: &str,
106        keys: &[K],
107        args: &[A],
108    ) -> ValkeyResult<ValkeyValue> {
109        let mut cmd = Cmd::new();
110        cmd.arg("FCALL_RO").arg(function).arg(keys.len());
111        for k in keys {
112            cmd.arg(k);
113        }
114        for a in args {
115            cmd.arg(a);
116        }
117        self.execute_command(cmd, None).await
118    }
119
120    /// `FCALL` routed to specific cluster node(s) (`route`). Keyless function
121    /// calls are commonly invoked with an explicit route (e.g. `AllPrimaries`);
122    /// on a standalone client the route is ignored.
123    async fn fcall_route<K: ToValkeyArgs + Send + Sync, A: ToValkeyArgs + Send + Sync>(
124        &self,
125        function: &str,
126        keys: &[K],
127        args: &[A],
128        route: Route,
129    ) -> ValkeyResult<ValkeyValue> {
130        let mut cmd = Cmd::new();
131        cmd.arg("FCALL").arg(function).arg(keys.len());
132        for k in keys {
133            cmd.arg(k);
134        }
135        for a in args {
136            cmd.arg(a);
137        }
138        self.execute_command(cmd, Some(route)).await
139    }
140
141    /// Read-only `FCALL_RO` routed to specific cluster node(s) (`route`). On a
142    /// standalone client the route is ignored.
143    async fn fcall_ro_route<K: ToValkeyArgs + Send + Sync, A: ToValkeyArgs + Send + Sync>(
144        &self,
145        function: &str,
146        keys: &[K],
147        args: &[A],
148        route: Route,
149    ) -> ValkeyResult<ValkeyValue> {
150        let mut cmd = Cmd::new();
151        cmd.arg("FCALL_RO").arg(function).arg(keys.len());
152        for k in keys {
153            cmd.arg(k);
154        }
155        for a in args {
156            cmd.arg(a);
157        }
158        self.execute_command(cmd, Some(route)).await
159    }
160
161    /// Load a function library (`FUNCTION LOAD`); returns the library name.
162    async fn function_load(&self, code: &str, replace: bool) -> ValkeyResult<String> {
163        let mut cmd = Cmd::new();
164        cmd.arg("FUNCTION").arg("LOAD");
165        if replace {
166            cmd.arg("REPLACE");
167        }
168        cmd.arg(code);
169        String::from_owned_valkey_value(self.execute_command(cmd, None).await?)
170    }
171
172    /// Delete a function library (`FUNCTION DELETE`).
173    async fn function_delete(&self, library_name: &str) -> ValkeyResult<()> {
174        let mut cmd = Cmd::new();
175        cmd.arg("FUNCTION").arg("DELETE").arg(library_name);
176        <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
177    }
178
179    /// Flush all function libraries (`FUNCTION FLUSH`).
180    async fn function_flush(&self) -> ValkeyResult<()> {
181        let mut cmd = Cmd::new();
182        cmd.arg("FUNCTION").arg("FLUSH");
183        <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
184    }
185
186    /// Flush all function libraries (`FUNCTION FLUSH SYNC|ASYNC`).
187    async fn function_flush_options(&self, options: &FunctionFlushOptions) -> ValkeyResult<()> {
188        let mut cmd = Cmd::new();
189        cmd.arg("FUNCTION").arg("FLUSH").arg(options);
190        <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
191    }
192
193    /// List registered function libraries (`FUNCTION LIST`). Set `with_code` to
194    /// include the library source. Returns the raw structured reply.
195    async fn function_list(
196        &self,
197        library_name: Option<&str>,
198        with_code: bool,
199    ) -> ValkeyResult<ValkeyValue> {
200        let mut cmd = Cmd::new();
201        cmd.arg("FUNCTION").arg("LIST");
202        if let Some(n) = library_name {
203            cmd.arg("LIBRARYNAME").arg(n);
204        }
205        if with_code {
206            cmd.arg("WITHCODE");
207        }
208        self.execute_command(cmd, None).await
209    }
210
211    /// Dump the serialized payload of all function libraries (`FUNCTION DUMP`).
212    async fn function_dump(&self) -> ValkeyResult<Bytes> {
213        let mut cmd = Cmd::new();
214        cmd.arg("FUNCTION").arg("DUMP");
215        Bytes::from_owned_valkey_value(self.execute_command(cmd, None).await?)
216    }
217
218    /// Restore function libraries from a `FUNCTION DUMP` payload
219    /// (`FUNCTION RESTORE`).
220    async fn function_restore<P: ToValkeyArgs + Send>(
221        &self,
222        payload: P,
223        policy: FunctionRestorePolicy,
224    ) -> ValkeyResult<()> {
225        let mut cmd = Cmd::new();
226        cmd.arg("FUNCTION")
227            .arg("RESTORE")
228            .arg(payload)
229            .arg(policy.as_arg());
230        <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
231    }
232
233    /// Get information about the function engine and running function
234    /// (`FUNCTION STATS`). Returns the raw structured reply.
235    async fn function_stats(&self) -> ValkeyResult<ValkeyValue> {
236        let mut cmd = Cmd::new();
237        cmd.arg("FUNCTION").arg("STATS");
238        self.execute_command(cmd, None).await
239    }
240
241    /// Kill a running function that made no write commands (`FUNCTION KILL`).
242    async fn function_kill(&self) -> ValkeyResult<()> {
243        let mut cmd = Cmd::new();
244        cmd.arg("FUNCTION").arg("KILL");
245        <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
246    }
247
248    /// Kill a running script that made no write commands (`SCRIPT KILL`).
249    async fn script_kill(&self) -> ValkeyResult<()> {
250        let mut cmd = Cmd::new();
251        cmd.arg("SCRIPT").arg("KILL");
252        <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
253    }
254
255    /// Show the source of a cached script by its SHA1 (`SCRIPT SHOW`, Valkey 8+).
256    async fn script_show(&self, sha1: &str) -> ValkeyResult<Bytes> {
257        let mut cmd = Cmd::new();
258        cmd.arg("SCRIPT").arg("SHOW").arg(sha1);
259        Bytes::from_owned_valkey_value(self.execute_command(cmd, None).await?)
260    }
261}
262
263impl<T: CommandExecutor + ?Sized> ScriptingCommands for T {}