rustis/commands/
vector_set.rs

1use serde::{Deserialize, de::DeserializeOwned};
2
3use crate::{
4    client::{PreparedCommand, prepare_command},
5    resp::{
6        BulkString, CollectionResponse, CommandArgs, KeyValueCollectionResponse, PrimitiveResponse,
7        Response, SingleArg, ToArgs, cmd,
8    },
9};
10
11/// A group of Redis commands related to [`Vector Sets`](https://redis.io/docs/data-types/vector-sets/)
12///
13/// # See Also
14/// [Redis Sorted Set Commands](https://redis.io/docs/latest/commands/?group=vector_set)
15pub trait VectorSetCommands<'a> {
16    /// Add a new element into the vector set specified by key.
17    ///
18    /// The vector can be provided as 32-bit floating point (FP32) blob of values,
19    /// or as floating point numbers as strings
20    ///
21    /// # Arguments
22    /// * `key` - is the name of the key that will hold the vector set data.
23    /// * `reduce_dim` - implements random projection to reduce the dimensionality of the vector.
24    ///   The projection matrix is saved and reloaded along with the vector set.
25    /// * `values` - vector values.
26    /// * `element` - is the name of the element that is being added to the vector set.
27    ///
28    /// # Return
29    /// * `true` - if key was added.
30    /// * `false` - if key was not added.
31    ///
32    /// # See Also
33    /// [<https://redis.io/commands/vadd/>](https://redis.io/commands/vadd/)
34    #[must_use]
35    fn vadd<K, E>(
36        self,
37        key: K,
38        reduce_dim: Option<usize>,
39        values: &[f32],
40        element: E,
41        options: VAddOptions,
42    ) -> PreparedCommand<'a, Self, bool>
43    where
44        Self: Sized,
45        K: SingleArg,
46        E: SingleArg,
47    {
48        prepare_command(
49            self,
50            cmd("VADD")
51                .arg(key)
52                .arg(reduce_dim)
53                .arg("FP32")
54                .arg(to_fp32(values))
55                .arg(element)
56                .arg(options),
57        )
58    }
59
60    /// Return the number of elements in the specified vector set.
61    ///
62    /// # See Also
63    /// [<https://redis.io/commands/vcard/>](https://redis.io/commands/vcard/)
64    #[must_use]
65    fn vcard<K>(self, key: K) -> PreparedCommand<'a, Self, usize>
66    where
67        Self: Sized,
68        K: SingleArg,
69    {
70        prepare_command(self, cmd("VCARD").arg(key))
71    }
72
73    /// Return the number of dimensions of the vectors in the specified vector set.
74    ///
75    /// # See Also
76    /// [<https://redis.io/commands/vdim/>](https://redis.io/commands/vdim/)
77    #[must_use]
78    fn vdim<K>(self, key: K) -> PreparedCommand<'a, Self, usize>
79    where
80        Self: Sized,
81        K: SingleArg,
82    {
83        prepare_command(self, cmd("VDIM").arg(key))
84    }
85
86    /// Return the approximate vector associated with a given element in the vector set.
87    ///
88    /// # See Also
89    /// [<https://redis.io/commands/vemb/>](https://redis.io/commands/vemb/)
90    #[must_use]
91    fn vemb<K, E, R>(self, key: K, element: E) -> PreparedCommand<'a, Self, R>
92    where
93        Self: Sized,
94        K: SingleArg,
95        E: SingleArg,
96        R: CollectionResponse<f32>,
97    {
98        prepare_command(self, cmd("VEMB").arg(key).arg(element))
99    }
100
101    /// Return the JSON attributes associated with an element in a vector set.
102    ///
103    /// # See Also
104    /// [<https://redis.io/commands/vemb/>](https://redis.io/commands/vemb/)
105    #[must_use]
106    fn vgetattr<K, E, R>(self, key: K, element: E) -> PreparedCommand<'a, Self, R>
107    where
108        Self: Sized,
109        K: SingleArg,
110        E: SingleArg,
111        R: Response,
112    {
113        prepare_command(self, cmd("VGETATTR").arg(key).arg(element))
114    }
115
116    /// Return metadata and internal details about a vector set,
117    /// including size, dimensions, quantization type, and graph structure.
118    ///
119    /// # See Also
120    /// [<https://redis.io/commands/vinfo/>](https://redis.io/commands/vinfo/)
121    #[must_use]
122    fn vinfo<K>(self, key: K) -> PreparedCommand<'a, Self, VInfoResult>
123    where
124        Self: Sized,
125        K: SingleArg,
126    {
127        prepare_command(self, cmd("VINFO").arg(key))
128    }
129
130    /// Return the neighbors of a specified element in a vector set.
131    /// The command shows the connections for each layer of the HNSW graph.
132    ///
133    /// # Return
134    /// a collection containing the names of adjacent elements
135    ///
136    /// # See Also
137    /// [<https://redis.io/commands/vlinks/>](https://redis.io/commands/vlinks/)
138    #[must_use]
139    fn vlinks<K, E, R, RR>(self, key: K, element: E) -> PreparedCommand<'a, Self, RR>
140    where
141        Self: Sized,
142        K: SingleArg,
143        E: SingleArg,
144        R: Response + DeserializeOwned,
145        RR: CollectionResponse<R> + DeserializeOwned,
146    {
147        prepare_command(self, cmd("VLINKS").arg(key).arg(element))
148    }
149
150    /// Return the neighbors of a specified element in a vector set.
151    /// The command shows the connections for each layer of the HNSW graph.
152    ///
153    /// # Return
154    /// a collection containing the names of adjacent elements
155    /// together with their scores as doubles
156    ///
157    /// # See Also
158    /// [<https://redis.io/commands/vlinks/>](https://redis.io/commands/vlinks/)
159    #[must_use]
160    fn vlinks_with_score<K, E, R, RR>(self, key: K, element: E) -> PreparedCommand<'a, Self, RR>
161    where
162        Self: Sized,
163        K: SingleArg,
164        E: SingleArg,
165        R: PrimitiveResponse + DeserializeOwned,
166        RR: KeyValueCollectionResponse<R, f64> + DeserializeOwned,
167    {
168        prepare_command(self, cmd("VLINKS").arg(key).arg(element))
169    }
170
171    /// Return one or more random elements from a vector set.
172    ///
173    /// The behavior is similar to the SRANDMEMBER command:
174    /// * When called without a count, returns a single element as a bulk string.
175    /// * When called with a positive count,
176    ///   returns up to that many distinct elements (no duplicates).
177    /// * When called with a negative count, returns that many elements,
178    ///   possibly with duplicates.
179    /// * If the count exceeds the number of elements, the entire set is returned.
180    /// * If the key does not exist, the command returns null if no count is given,
181    ///   or an empty array if a count is provided.
182    ///
183    /// # Return
184    /// a collecton containing the names of count random elements as strings.
185    ///
186    /// # See Also
187    /// [<https://redis.io/commands/vrandmember/>](https://redis.io/commands/vrandmember/)
188    #[must_use]
189    fn vrandmember<K, R, RR>(self, key: K, count: isize) -> PreparedCommand<'a, Self, RR>
190    where
191        Self: Sized,
192        K: SingleArg,
193        R: PrimitiveResponse + DeserializeOwned,
194        RR: CollectionResponse<R> + DeserializeOwned,
195    {
196        prepare_command(self, cmd("VRANDMEMBER").arg(key).arg(count))
197    }
198
199    /// Remove an element from a vector set.
200    ///
201    /// # Return
202    /// * `true` - if the element was removed.
203    /// * `false` - if either element or key do not exist.
204    ///
205    /// VREM reclaims memory immediately.
206    /// It does not use tombstones or logical deletions,
207    /// making it safe to use in long-running applications
208    /// that frequently update the same vector set.
209    ///
210    /// # See Also
211    /// [<https://redis.io/commands/vrem/>](https://redis.io/commands/vrem/)
212    #[must_use]
213    fn vrem<K, E>(self, key: K, element: E) -> PreparedCommand<'a, Self, bool>
214    where
215        Self: Sized,
216        K: SingleArg,
217        E: SingleArg,
218    {
219        prepare_command(self, cmd("VREM").arg(key).arg(element))
220    }
221
222    /// Associate a JSON object with an element in a vector set.
223    ///
224    /// Use this command to store attributes that can be used in filtered similarity searches with VSIM.
225    ///
226    /// You can also update existing attributes or delete them by setting an empty string.
227    ///
228    /// # See Also
229    /// [<https://redis.io/commands/vemb/>](https://redis.io/commands/vemb/)
230    #[must_use]
231    fn vsetattr<K, E, J>(self, key: K, element: E, json: J) -> PreparedCommand<'a, Self, bool>
232    where
233        Self: Sized,
234        K: SingleArg,
235        E: SingleArg,
236        J: SingleArg,
237    {
238        prepare_command(self, cmd("VSETATTR").arg(key).arg(element).arg(json))
239    }
240
241    /// Return elements similar to a given vector or element.
242    /// Use this command to perform approximate or exact similarity searches within a vector set.
243    ///
244    /// # See Also
245    /// [<https://redis.io/commands/vsim/>](https://redis.io/commands/vsim/)
246    #[must_use]
247    fn vsim<K, R, RR>(
248        self,
249        key: K,
250        vector_or_element: VectorOrElement,
251        options: VSimOptions,
252    ) -> PreparedCommand<'a, Self, RR>
253    where
254        Self: Sized,
255        K: SingleArg,
256        R: PrimitiveResponse + DeserializeOwned,
257        RR: CollectionResponse<R> + DeserializeOwned,
258    {
259        prepare_command(
260            self,
261            cmd("VSIM").arg(key).arg(vector_or_element).arg(options),
262        )
263    }
264
265    /// Return elements similar to a given vector or element.
266    /// Use this command to perform approximate or exact similarity searches within a vector set.
267    ///
268    /// # See Also
269    /// [<https://redis.io/commands/vsim/>](https://redis.io/commands/vsim/)
270    #[must_use]
271    fn vsim_with_scores<K, RF, R>(
272        self,
273        key: K,
274        vector_or_element: VectorOrElement,
275        options: VSimOptions,
276    ) -> PreparedCommand<'a, Self, R>
277    where
278        Self: Sized,
279        K: SingleArg,
280        RF: PrimitiveResponse + DeserializeOwned,
281        R: KeyValueCollectionResponse<RF, f64> + DeserializeOwned,
282    {
283        prepare_command(
284            self,
285            cmd("VSIM")
286                .arg(key)
287                .arg(vector_or_element)
288                .arg("WITHSCORES")
289                .arg(options),
290        )
291    }
292}
293
294fn to_fp32(values: &[f32]) -> BulkString {
295    let mut buf = Vec::with_capacity(values.len() * 4);
296    for f in values {
297        buf.extend_from_slice(&f.to_le_bytes()); // little endian
298    }
299    buf.into()
300}
301
302/// Options for the [`vadd`](VectorSetCommands::vadd) command.
303#[derive(Default)]
304pub struct VAddOptions {
305    command_args: CommandArgs,
306}
307
308impl VAddOptions {
309    /// performs the operation partially using threads, in a check-and-set style.
310    /// The neighbor candidates collection, which is slow, is performed in the background,
311    ///  while the command is executed in the main thread.
312    #[must_use]
313    pub fn cas(mut self) -> Self {
314        Self {
315            command_args: self.command_args.arg("CAS").build(),
316        }
317    }
318
319    /// in the first VADD call for a given key,
320    /// NOQUANT forces the vector to be created without int8 quantization,
321    /// which is otherwise the default.
322    #[must_use]
323    pub fn noquant(mut self) -> Self {
324        Self {
325            command_args: self.command_args.arg("NOQUANT").build(),
326        }
327    }
328
329    /// forces the vector to use binary quantization instead of int8.
330    /// This is much faster and uses less memory, but impacts the recall quality.
331    #[must_use]
332    pub fn bin(mut self) -> Self {
333        Self {
334            command_args: self.command_args.arg("BIN").build(),
335        }
336    }
337
338    /// forces the vector to use signed 8-bit quantization.
339    /// This is the default, and the option only exists to make sure to check at insertion time
340    /// that the vector set is of the same format.
341    #[must_use]
342    pub fn q8(mut self) -> Self {
343        Self {
344            command_args: self.command_args.arg("Q8").build(),
345        }
346    }
347
348    /// plays a role in the effort made to find good candidates when connecting the new node
349    /// to the existing Hierarchical Navigable Small World (HNSW) graph. The default is 200.
350    /// Using a larger value may help in achieving a better recall.
351    /// To improve the recall it is also possible to increase EF during VSIM searches.
352    #[must_use]
353    pub fn ef(mut self, build_exploration_factor: u32) -> Self {
354        Self {
355            command_args: self
356                .command_args
357                .arg("EF")
358                .arg(build_exploration_factor)
359                .build(),
360        }
361    }
362
363    /// associates attributes in the form of a JavaScript object to the newly created entry
364    /// or updates the attributes (if they already exist).
365    /// It is the same as calling the VSETATTR command separately.
366    #[must_use]
367    pub fn setattr(mut self, attributes: &str) -> Self {
368        Self {
369            command_args: self.command_args.arg("SETATTR").arg(attributes).build(),
370        }
371    }
372
373    /// is the maximum number of connections that each node of the graph
374    /// will have with other nodes.
375    /// The default is 16. More connections means more memory, but provides for more efficient
376    /// graph exploration.
377    /// Nodes at layer zero (every node exists at least at layer zero) have M * 2 connections,
378    /// while the other layers only have M connections.
379    /// For example, setting M to 64 will use at least 1024 bytes of memory for layer zero.
380    ///  That's M * 2 connections times 8 bytes (pointers), or 128 * 8 = 1024. For higher layers,
381    /// consider the following:
382    ///
383    /// * Each node appears in ~1.33 layers on average (empirical observation from HNSW papers),
384    ///   which works out to be 0.33 higher layers per node.
385    /// * Each of those higher layers has M = 64 connections.
386    ///
387    /// So, the additional amount of memory is approximately 0.33 × 64 × 8 ≈ 169.6 bytes per node,
388    /// bringing the total memory to ~1193 bytes.
389    ///
390    /// If you don't have a recall quality problem, the default is acceptable,
391    /// and uses a minimal amount of memory.
392    #[must_use]
393    pub fn m(mut self, numlinks: usize) -> Self {
394        Self {
395            command_args: self.command_args.arg("SMETATTR").arg(numlinks).build(),
396        }
397    }
398}
399
400impl ToArgs for VAddOptions {
401    fn write_args(&self, args: &mut CommandArgs) {
402        self.command_args.write_args(args);
403    }
404}
405
406/// Result for the [`vinfo`](VectorSetCommands::vinfo) command.
407#[derive(Debug, Deserialize)]
408pub struct VInfoResult {
409    #[serde(rename = "quant-type")]
410    pub quant_type: String,
411    #[serde(rename = "vector-dim")]
412    pub vector_dim: usize,
413    pub size: usize,
414    #[serde(rename = "max-level")]
415    pub max_level: usize,
416    #[serde(rename = "vset-uid")]
417    pub vset_uid: u32,
418    #[serde(rename = "hnsw-max-node-uid")]
419    pub hnsw_max_node_uid: u32,
420}
421
422/// Argument of the [`vsim`](VectorSetCommands::vsim) command
423pub enum VectorOrElement<'a> {
424    Vector(&'a [f32]),
425    Element(&'a str),
426}
427
428impl<'a> ToArgs for VectorOrElement<'a> {
429    fn write_args(&self, args: &mut CommandArgs) {
430        match self {
431            VectorOrElement::Vector(vector) => {
432                args.arg("FP32").arg(to_fp32(vector));
433            }
434            VectorOrElement::Element(element) => {
435                args.arg("ELE").arg(*element);
436            }
437        }
438    }
439}
440
441/// Options for the [`vsim`](VectorSetCommands::vsim) command.
442#[derive(Default)]
443pub struct VSimOptions {
444    command_args: CommandArgs,
445}
446
447impl VSimOptions {
448    /// Limits the number of returned results to num.
449    #[must_use]
450    pub fn count(mut self, num: usize) -> Self {
451        Self {
452            command_args: self.command_args.arg("COUNT").arg(num).build(),
453        }
454    }
455
456    /// Controls the search effort.
457    ///
458    /// Higher values explore more nodes, improving recall at the cost of speed.
459    /// Typical values range from 50 to 1000.
460    #[must_use]
461    pub fn ef(mut self, search_exploration_factor: u32) -> Self {
462        Self {
463            command_args: self
464                .command_args
465                .arg("EF")
466                .arg(search_exploration_factor)
467                .build(),
468        }
469    }
470
471    /// Applies a filter expression to restrict matching elements.
472    /// See the filtered search section for syntax details.
473    #[must_use]
474    pub fn filter(mut self, expression: &str) -> Self {
475        Self {
476            command_args: self.command_args.arg("FILTER").arg(expression).build(),
477        }
478    }
479
480    /// Limits the number of filtering attempts for the FILTER expression.
481    ///
482    /// See the [filtered search](https://redis.io/docs/data-types/vector-sets/filtered-search/) section for more.
483    #[must_use]
484    pub fn filter_ef(mut self, max_filtering_effort: usize) -> Self {
485        Self {
486            command_args: self
487                .command_args
488                .arg("FILTER-EF")
489                .arg(max_filtering_effort)
490                .build(),
491        }
492    }
493
494    /// Forces an exact linear scan of all elements, bypassing the HNSW graph.
495    ///
496    /// Use for benchmarking or to calculate recall.
497    /// This is significantly slower (O(N)).
498    #[must_use]
499    pub fn truth(mut self) -> Self {
500        Self {
501            command_args: self.command_args.arg("TRUTH").build(),
502        }
503    }
504
505    /// Executes the search in the main thread instead of a background thread.
506    ///
507    /// Useful for small vector sets or benchmarks.
508    /// This may block the server during execution.
509    #[must_use]
510    pub fn nothread(mut self) -> Self {
511        Self {
512            command_args: self.command_args.arg("NOTHREAD").build(),
513        }
514    }
515}
516
517impl ToArgs for VSimOptions {
518    fn write_args(&self, args: &mut CommandArgs) {
519        self.command_args.write_args(args);
520    }
521}