rustis/commands/
cuckoo_commands.rs

1use crate::{
2    client::{PreparedCommand, prepare_command},
3    resp::{BulkString, Response, Value, cmd, serialize_flag},
4};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// A group of Redis commands related to [`Cuckoo filters`](https://redis.io/docs/stack/bloom/)
9///
10/// # See Also
11/// [Cuckoo Filter Commands](https://redis.io/commands/?group=cf)
12pub trait CuckooCommands<'a>: Sized {
13    /// Adds an item to the cuckoo filter, creating the filter if it does not exist.
14    ///
15    /// Cuckoo filters can contain the same item multiple times, and consider each insert as separate.
16    /// You can use [`cf_addnx`](CuckooCommands::cf_addnx) to only add the item if it does not exist yet.
17    /// Keep in mind that deleting an element inserted using [`cf_addnx`](CuckooCommands::cf_addnx) may cause false-negative errors.
18    ///
19    /// # Arguments
20    /// * `key` - The name of the filter
21    /// * `item` - The item to add
22    ///
23    /// # See Also
24    /// * [<https://redis.io/commands/cf.add/>](https://redis.io/commands/cf.add/)
25    #[must_use]
26    fn cf_add(self, key: impl Serialize, item: impl Serialize) -> PreparedCommand<'a, Self, ()> {
27        prepare_command(self, cmd("CF.ADD").arg(key).arg(item))
28    }
29
30    /// Adds an item to a cuckoo filter if the item did not exist previously.
31    ///
32    /// See documentation on [`cf_add`](CuckooCommands::cf_add) for more information on this command.
33    ///
34    /// This command is equivalent to a [`cf_exists`](CuckooCommands::cf_exists) + [`cf_add`](CuckooCommands::cf_add) command.
35    /// It does not insert an element into the filter if its fingerprint already exists in order to use the available capacity more efficiently.
36    /// However, deleting elements can introduce `false negative` error rate!
37    ///
38    /// Note that this command is slower than [`cf_add`](CuckooCommands::cf_add) because it first checks whether the item exists.
39    ///
40    /// # Arguments
41    /// * `key` - The name of the filter
42    /// * `item` - The item to add
43    ///
44    /// # Return
45    /// * `true` - if the item did not exist in the filter.
46    /// * `false` - if the item already existed.
47    ///
48    /// # See Also
49    /// * [<https://redis.io/commands/cf.addnx/>](https://redis.io/commands/cf.addnx/)
50    #[must_use]
51    fn cf_addnx(
52        self,
53        key: impl Serialize,
54        item: impl Serialize,
55    ) -> PreparedCommand<'a, Self, bool> {
56        prepare_command(self, cmd("CF.ADDNX").arg(key).arg(item))
57    }
58
59    /// Returns the number of times an item may be in the filter.
60    ///
61    /// Because this is a probabilistic data structure, this may not necessarily be accurate.
62    ///
63    /// If you just want to know if an item exists in the filter,
64    /// use [`cf_exists`](CuckooCommands::cf_exists) because it is more efficient for that purpose.
65    ///
66    /// # Arguments
67    /// * `key` - The name of the filter
68    /// * `item` - The item to count
69    ///
70    /// # Return
71    /// the count of possible matching copies of the item in the filter.
72    ///
73    /// # See Also
74    /// * [<https://redis.io/commands/cf.count/>](https://redis.io/commands/cf.count/)
75    #[must_use]
76    fn cf_count(
77        self,
78        key: impl Serialize,
79        item: impl Serialize,
80    ) -> PreparedCommand<'a, Self, usize> {
81        prepare_command(self, cmd("CF.COUNT").arg(key).arg(item))
82    }
83
84    /// Deletes an item once from the filter.
85    ///
86    /// If the item exists only once, it will be removed from the filter.
87    /// If the item was added multiple times, it will still be present.
88    ///
89    /// # Danger !
90    /// Deleting elements that are not in the filter may delete a different item, resulting in false negatives!
91    ///
92    /// # Arguments
93    /// * `key` - The name of the filter
94    /// * `item` - The item to delete from the filter
95    ///
96    /// # Complexity
97    /// O(n), where n is the number of `sub-filters`. Both alternative locations are checked on all `sub-filters`.
98    ///
99    /// # Return
100    /// * `true` - the item has been deleted from the filter.
101    /// * `false` - if the item was not found.
102    ///
103    /// # See Also
104    /// * [<https://redis.io/commands/cf.del/>](https://redis.io/commands/cf.del/)
105    #[must_use]
106    fn cf_del(self, key: impl Serialize, item: impl Serialize) -> PreparedCommand<'a, Self, bool> {
107        prepare_command(self, cmd("CF.DEL").arg(key).arg(item))
108    }
109
110    /// Check if an `item` exists in a Cuckoo Filter `key`
111    ///
112    /// # Arguments
113    /// * `key` - The name of the filter
114    /// * `item` - The item to check for
115    ///
116    /// # Return
117    /// * `true` - the item may exist in the filter
118    /// * `false` - if the item does not exist in the filter.
119    ///
120    /// # See Also
121    /// * [<https://redis.io/commands/cf.exists/>](https://redis.io/commands/cf.exists/)
122    #[must_use]
123    fn cf_exists(
124        self,
125        key: impl Serialize,
126        item: impl Serialize,
127    ) -> PreparedCommand<'a, Self, bool> {
128        prepare_command(self, cmd("CF.EXISTS").arg(key).arg(item))
129    }
130
131    /// Return information about `key`
132    ///
133    /// # Arguments
134    /// * `key` - Name of the key to get info about
135    ///
136    /// # Return
137    /// An instance of [`CfInfoResult`](CfInfoResult)
138    ///
139    /// # See Also
140    /// * [<https://redis.io/commands/cf.info/>](https://redis.io/commands/cf.info/)
141    #[must_use]
142    fn cf_info(self, key: impl Serialize) -> PreparedCommand<'a, Self, CfInfoResult> {
143        prepare_command(self, cmd("CF.INFO").arg(key))
144    }
145
146    /// Adds one or more items to a cuckoo filter, allowing the filter to be created with a custom capacity if it does not exist yet.
147    ///
148    /// These commands offers more flexibility over the [`cf_add`](CuckooCommands::cf_add) command, at the cost of more verbosity.
149    ///
150    /// # Arguments
151    /// * `key` - The name of the filter
152    /// * `options` - see [`CfInsertOptions`](CfInsertOptions)
153    /// * `items` - One or more items to add.
154    ///
155    /// # See Also
156    /// * [<https://redis.io/commands/cf.insert/>](https://redis.io/commands/cf.insert/)
157    #[must_use]
158    fn cf_insert(
159        self,
160        key: impl Serialize,
161        options: CfInsertOptions,
162        item: impl Serialize,
163    ) -> PreparedCommand<'a, Self, Vec<bool>> {
164        prepare_command(
165            self,
166            cmd("CF.INSERT")
167                .arg(key)
168                .arg(options)
169                .arg("ITEMS")
170                .arg(item),
171        )
172    }
173
174    /// Adds one or more items to a cuckoo filter, allowing the filter to be created with a custom capacity if it does not exist yet.
175    ///
176    /// This command is equivalent to a [`cf_exists`](CuckooCommands::cf_exists) + [`cf_add`](CuckooCommands::cf_add) command.
177    /// It does not insert an element into the filter if its fingerprint already exists and therefore better utilizes the available capacity.
178    /// However, if you delete elements it might introduce `false negative` error rate!
179    ///
180    /// These commands offers more flexibility over the [`cf_add`](CuckooCommands::cf_add) and [`cf_addnx`](CuckooCommands::cf_addnx) commands,
181    /// at the cost of more verbosity.
182    ///
183    /// # Complexity
184    /// `O(n + i)`, where n is the number of `sub-filters` and `i` is `maxIterations`.
185    /// Adding items requires up to 2 memory accesses per `sub-filter`.
186    /// But as the filter fills up, both locations for an item might be full.
187    /// The filter attempts to `Cuckoo` swap items up to maxIterations times.
188    ///
189    /// # Arguments
190    /// * `key` - The name of the filter
191    /// * `options` - see [`CfInsertOptions`](CfInsertOptions)
192    /// * `items` - One or more items to add.
193    ///
194    /// # Return
195    /// A collection of integers corresponding to the items specified. Possible values for each element are:
196    /// * `>0` - if the item was successfully inserted
197    /// * `0` - if the item already existed and [`cf_insertnx`](CuckooCommands::cf_insertnx) is used.
198    /// * `<0` - if an error occurred
199    ///
200    /// # See Also
201    /// * [<https://redis.io/commands/cf.insert/>](https://redis.io/commands/cf.insert/)
202    #[must_use]
203    fn cf_insertnx<R: Response>(
204        self,
205        key: impl Serialize,
206        options: CfInsertOptions,
207        item: impl Serialize,
208    ) -> PreparedCommand<'a, Self, R> {
209        prepare_command(
210            self,
211            cmd("CF.INSERTNX")
212                .arg(key)
213                .arg(options)
214                .arg("ITEMS")
215                .arg(item),
216        )
217    }
218
219    /// Restores a filter previously saved using [`cf_scandump`](CuckooCommands::cf_scandump).
220    ///
221    /// See the [`cf_scandump`](CuckooCommands::cf_scandump) command for example usage.
222    ///
223    /// This command overwrites any bloom filter stored under `key`.
224    /// Make sure that the bloom filter is not be changed between invocations.
225    ///
226    /// # Arguments
227    /// * `key` - Name of the key to restore
228    /// * `iterator` - Iterator value associated with `data` (returned by [`cf_scandump`](CuckooCommands::cf_scandump))
229    /// * `data` - Current data chunk (returned by [`cf_scandump`](CuckooCommands::cf_scandump))
230    ///
231    /// # See Also
232    /// [<https://redis.io/commands/cf.loadchunk/>](https://redis.io/commands/cf.loadchunk/)
233    #[must_use]
234    fn cf_loadchunk(
235        self,
236        key: impl Serialize,
237        iterator: i64,
238        data: impl Serialize,
239    ) -> PreparedCommand<'a, Self, ()> {
240        prepare_command(self, cmd("CF.LOADCHUNK").arg(key).arg(iterator).arg(data))
241    }
242
243    /// Check if one or more `items` exists in a Cuckoo Filter `key`
244    ///
245    /// # Arguments
246    /// * `key` - The name of the filter
247    /// * `items` - One or more items to check for
248    ///
249    /// # Return
250    /// Collection reply of boolean - for each item where `true` value means the corresponding item
251    /// may exist in the filter, and a `false` value means it does not exist in the filter.
252    ///
253    /// # See Also
254    /// [<https://redis.io/commands/cf.mexists/>](https://redis.io/commands/cf.mexists/)
255    #[must_use]
256    fn cf_mexists<R: Response>(
257        self,
258        key: impl Serialize,
259        items: impl Serialize,
260    ) -> PreparedCommand<'a, Self, R> {
261        prepare_command(self, cmd("CF.MEXISTS").arg(key).arg(items))
262    }
263
264    /// Create a Cuckoo Filter as `key` with a single sub-filter for the initial amount of `capacity` for items.
265    /// Because of how Cuckoo Filters work, the filter is likely to declare itself full before `capacity` is reached
266    /// and therefore fill rate will likely never reach 100%.
267    /// The fill rate can be improved by using a larger `bucketsize` at the cost of a higher error rate.
268    /// When the filter self-declare itself `full`, it will auto-expand by generating additional sub-filters at the cost of reduced performance and increased error rate.
269    /// The new sub-filter is created with size of the previous sub-filter multiplied by `expansion`.
270    /// Like bucket size, additional sub-filters grow the error rate linearly.
271    /// The size of the new sub-filter is the size of the last sub-filter multiplied by `expansion`.
272    ///
273    /// The minimal false positive error rate is 2/255 ≈ 0.78% when bucket size of 1 is used.
274    /// Larger buckets increase the error rate linearly (for example, a bucket size of 3 yields a 2.35% error rate) but improve the fill rate of the filter.
275    ///
276    /// `maxiterations` dictates the number of attempts to find a slot for the incoming fingerprint.
277    /// Once the filter gets full, high `maxIterations` value will slow down insertions.
278    ///
279    /// Unused capacity in prior sub-filters is automatically used when possible. The filter can grow up to 32 times.
280    ///
281    /// # Arguments
282    /// * `key` - The key under which the filter is found
283    /// * `capacity` - Estimated capacity for the filter.
284    ///   Capacity is rounded to the next 2^n number.
285    ///   The filter will likely not fill up to 100% of it's capacity.
286    ///   Make sure to reserve extra capacity if you want to avoid expansions.
287    /// * `options` - See [`CfReserveOptions`](CfReserveOptions)
288    ///
289    /// # See Also
290    /// [<https://redis.io/commands/cf.reserve/>](https://redis.io/commands/cf.reserve/)
291    #[must_use]
292    fn cf_reserve(
293        self,
294        key: impl Serialize,
295        capacity: usize,
296        options: CfReserveOptions,
297    ) -> PreparedCommand<'a, Self, ()> {
298        prepare_command(self, cmd("CF.RESERVE").arg(key).arg(capacity).arg(options))
299    }
300
301    /// Begins an incremental save of the cuckoo filter.
302    /// This is useful for large cuckoo filters which cannot fit into the normal [`dump`](crate::commands::GenericCommands::dump)
303    /// and [`restore`](crate::commands::GenericCommands::restore) model.
304    ///
305    /// # Arguments
306    /// * `key` - Name of the filter
307    /// * `iterator` - Iterator value; either 0 or the iterator from a previous invocation of this command.\
308    ///   The first time this command is called, the value of `iterator` should be 0.
309    ///
310    /// # Return
311    /// This command returns successive `(iterator, data)` pairs until `(0, vec![])` to indicate completion.
312    ///
313    /// # See Also
314    /// [<https://redis.io/commands/cf.scandump/>](https://redis.io/commands/cf.scandump/)
315    #[must_use]
316    fn cf_scandump(
317        self,
318        key: impl Serialize,
319        iterator: i64,
320    ) -> PreparedCommand<'a, Self, CfScanDumpResult> {
321        prepare_command(self, cmd("CF.SCANDUMP").arg(key).arg(iterator))
322    }
323}
324
325/// Result for the [`cf_info`](CuckooCommands::cf_info) command.
326#[derive(Debug, Deserialize)]
327pub struct CfInfoResult {
328    /// Size
329    #[serde(rename = "Size")]
330    pub size: usize,
331    /// Number of buckets
332    #[serde(rename = "Number of buckets")]
333    pub num_buckets: usize,
334    /// Number of filters
335    #[serde(rename = "Number of filters")]
336    pub num_filters: usize,
337    /// Number of items inserted
338    #[serde(rename = "Number of items inserted")]
339    pub num_items_inserted: usize,
340    /// Number of items deleted
341    #[serde(rename = "Number of items deleted")]
342    pub num_items_deleted: usize,
343    /// Bucket size
344    #[serde(rename = "Bucket size")]
345    pub bucket_size: usize,
346    /// Expansion rate
347    #[serde(rename = "Expansion rate")]
348    pub expansion_rate: usize,
349    /// Max iteration
350    #[serde(rename = "Max iterations")]
351    pub max_iteration: usize,
352    /// Additional information
353    #[serde(flatten)]
354    pub additional_info: HashMap<String, Value>,
355}
356
357/// Options for the [`cf_insert`](CuckooCommands::cf_insert) command.
358#[derive(Default, Serialize)]
359#[serde(rename_all(serialize = "UPPERCASE"))]
360pub struct CfInsertOptions {
361    #[serde(skip_serializing_if = "Option::is_none")]
362    capacity: Option<u32>,
363    #[serde(
364        skip_serializing_if = "std::ops::Not::not",
365        serialize_with = "serialize_flag"
366    )]
367    nocreate: bool,
368}
369
370impl CfInsertOptions {
371    /// Specifies the desired capacity of the new filter, if this filter does not exist yet.
372    ///
373    /// If the filter already exists, then this parameter is ignored.
374    /// If the filter does not exist yet and this parameter is not specified,
375    /// then the filter is created with the module-level default capacity which is `1024`.
376    /// See [`cf_reserve`](CuckooCommands::cf_reserve) for more information on cuckoo filter capacities.
377    #[must_use]
378    pub fn capacity(mut self, capacity: u32) -> Self {
379        self.capacity = Some(capacity);
380        self
381    }
382
383    /// If specified, prevents automatic filter creation if the filter does not exist.
384    ///
385    /// Instead, an error is returned if the filter does not already exist.
386    /// This option is mutually exclusive with [`capacity`](CfInsertOptions::capacity).
387    #[must_use]
388    pub fn nocreate(mut self) -> Self {
389        self.nocreate = true;
390        self
391    }
392}
393
394/// Options for the [`cf_reserve`](CuckooCommands::cf_reserve) command.
395#[derive(Default, Serialize)]
396#[serde(rename_all(serialize = "UPPERCASE"))]
397pub struct CfReserveOptions {
398    #[serde(skip_serializing_if = "Option::is_none")]
399    bucketsize: Option<u32>,
400    #[serde(skip_serializing_if = "Option::is_none")]
401    maxiterations: Option<u32>,
402    #[serde(skip_serializing_if = "Option::is_none")]
403    expansion: Option<u32>,
404}
405
406impl CfReserveOptions {
407    /// Number of items in each bucket.
408    ///
409    /// A higher bucket size value improves the fill rate but also causes a higher error rate and slightly slower performance.
410    /// The default value is 2.
411    #[must_use]
412    pub fn bucketsize(mut self, bucket_size: u32) -> Self {
413        self.bucketsize = Some(bucket_size);
414        self
415    }
416
417    /// Number of attempts to swap items between buckets before declaring filter as full and creating an additional filter.
418    ///
419    ///  A low value is better for performance and a higher number is better for filter fill rate.
420    /// The default value is 20.
421    pub fn maxiterations(mut self, max_iterations: u32) -> Self {
422        self.maxiterations = Some(max_iterations);
423        self
424    }
425
426    /// When a new filter is created, its size is the size of the current filter multiplied by `expansion`.
427    /// Expansion is rounded to the next `2^n` number.
428    /// The default value is 1.
429    #[must_use]
430    pub fn expansion(mut self, expansion: u32) -> Self {
431        self.expansion = Some(expansion);
432        self
433    }
434}
435
436/// Result for the [`cf_scandump`](CuckooCommands::cf_scandump) command.
437#[derive(Debug, Deserialize)]
438pub struct CfScanDumpResult {
439    pub iterator: i64,
440    pub data: BulkString,
441}