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}