Skip to main content

rustis/commands/
bloom_commands.rs

1use crate::{
2    client::{PreparedCommand, prepare_command},
3    resp::{BulkString, Response, cmd, serialize_flag},
4};
5use serde::{Deserialize, Serialize};
6
7/// A group of Redis commands related to [`Bloom filters`](https://redis.io/docs/stack/bloom/)
8///
9/// # See Also
10/// [Bloom Filter Commands](https://redis.io/commands/?group=bf)
11pub trait BloomCommands<'a>: Sized {
12    /// Adds an item to a bloom filter
13    ///
14    /// # Arguments
15    /// * `key` - The name of the filter
16    /// * `item` - The item to add
17    ///
18    /// # Return
19    /// * `true` - if the item did not exist in the filter,
20    /// * `false` - otherwise.
21    ///
22    /// # See Also
23    /// * [<https://redis.io/commands/bf.add/>](https://redis.io/commands/bf.add/)
24    #[must_use]
25    fn bf_add(self, key: impl Serialize, item: impl Serialize) -> PreparedCommand<'a, Self, bool> {
26        prepare_command(self, cmd("BF.ADD").key(key).arg(item))
27    }
28
29    /// Determines whether an item may exist in the Bloom Filter or not.
30    ///
31    /// # Arguments
32    /// * `key` - The name of the filter
33    /// * `item` - The item to check for
34    ///
35    /// # Return
36    /// * `true` - means the item may exist in the filter,
37    /// * `false` - means it does not exist in the filter..
38    ///
39    /// # See Also
40    /// * [<https://redis.io/commands/bf.exists/>](https://redis.io/commands/bf.exists/)
41    #[must_use]
42    fn bf_exists(
43        self,
44        key: impl Serialize,
45        item: impl Serialize,
46    ) -> PreparedCommand<'a, Self, bool> {
47        prepare_command(self, cmd("BF.EXISTS").key(key).arg(item))
48    }
49
50    /// Return information about key filter.
51    ///
52    /// # Arguments
53    /// * `key` - Name of the key to return information about
54    ///
55    /// # Return
56    /// an instance of [`BfInfoResult`](BfInfoResult)
57    ///
58    /// # See Also
59    /// [<https://redis.io/commands/bf.info/>](https://redis.io/commands/bf.info/)
60    #[must_use]
61    fn bf_info_all(self, key: impl Serialize) -> PreparedCommand<'a, Self, BfInfoResult> {
62        prepare_command(self, cmd("BF.INFO").key(key))
63    }
64
65    /// Return information about key filter for a specific information parameter
66    ///
67    /// # Arguments
68    /// * `key` - Name of the key to return information about
69    /// * `param` - specific information parameter to query
70    ///
71    /// # Return
72    /// The value of the requested parameter
73    ///
74    /// # See Also
75    /// [<https://redis.io/commands/bf.info/>](https://redis.io/commands/bf.info/)
76    #[must_use]
77    fn bf_info<R: Response>(
78        self,
79        key: impl Serialize,
80        param: BfInfoParameter,
81    ) -> PreparedCommand<'a, Self, R> {
82        prepare_command(self, cmd("BF.INFO").key(key).arg(param))
83    }
84
85    /// `bf_insert` is a sugarcoated combination of [`bf_reserve`](BloomCommands::bf_reserve) and [`bf_add`](BloomCommands::bf_add).
86    ///
87    /// It creates a new filter if the key does not exist using the relevant arguments (see [`bf_reserve`](BloomCommands::bf_reserve)).
88    /// Next, all ITEMS are inserted.
89    ///
90    /// # Arguments
91    /// * `key` - The name of the filter
92    /// * `items` - One or more items to add
93    /// * `options` - See [`BfInsertOptions`](BfInsertOptions)
94    ///
95    /// # Return
96    /// A collection of booleans (integers).
97    ///
98    /// Each element is either true or false depending on whether the corresponding input element was newly added to the filter or may have previously existed.
99    ///
100    /// # See Also
101    /// [<https://redis.io/commands/bf.insert/>](https://redis.io/commands/bf.insert/)
102    #[must_use]
103    fn bf_insert<R: Response>(
104        self,
105        key: impl Serialize,
106        items: impl Serialize,
107        options: BfInsertOptions,
108    ) -> PreparedCommand<'a, Self, R> {
109        prepare_command(
110            self,
111            cmd("BF.INSERT")
112                .key(key)
113                .arg(options)
114                .arg("ITEMS")
115                .arg(items),
116        )
117    }
118
119    /// Restores a filter previously saved using [`bf_scandump`](BloomCommands::bf_scandump).
120    ///
121    /// See the [`bf_scandump`](BloomCommands::bf_scandump) command for example usage.
122    ///
123    /// This command overwrites any bloom filter stored under key.
124    /// Make sure that the bloom filter is not be changed between invocations.
125    ///
126    /// # Arguments
127    /// * `key` - Name of the key to restore
128    /// * `iterator` - Iterator value associated with `data` (returned by [`bf_scandump`](BloomCommands::bf_scandump))
129    /// * `data` - Current data chunk (returned by [`bf_scandump`](BloomCommands::bf_scandump))
130    ///
131    /// # See Also
132    /// [<https://redis.io/commands/bf.loadchunk/>](https://redis.io/commands/bf.loadchunk/)
133    #[must_use]
134    fn bf_loadchunk(
135        self,
136        key: impl Serialize,
137        iterator: i64,
138        data: impl Serialize,
139    ) -> PreparedCommand<'a, Self, ()> {
140        prepare_command(self, cmd("BF.LOADCHUNK").key(key).arg(iterator).arg(data))
141    }
142
143    /// Adds one or more items to the Bloom Filter and creates the filter if it does not exist yet.
144    ///
145    /// This command operates identically to [`bf_add`](BloomCommands::bf_add) except that it allows multiple inputs and returns multiple values.
146    ///
147    /// # Arguments
148    /// * `key` - The name of the filter
149    /// * `items` - One or more items to add
150    ///
151    /// # Return
152    /// Collection reply of boolean - for each item which is either `true` or `false` depending
153    /// on whether the corresponding input element was newly added to the filter or may have previously existed.
154    ///
155    /// # See Also
156    /// [<https://redis.io/commands/bf.madd/>](https://redis.io/commands/bf.madd/)
157    #[must_use]
158    fn bf_madd<R: Response>(
159        self,
160        key: impl Serialize,
161        items: impl Serialize,
162    ) -> PreparedCommand<'a, Self, R> {
163        prepare_command(self, cmd("BF.MADD").key(key).arg(items))
164    }
165
166    /// Determines if one or more items may exist in the filter or not.
167    ///
168    /// # Arguments
169    /// * `key` - The name of the filter
170    /// * `items` - One or more items to check
171    ///
172    /// # Return
173    /// Collection reply of boolean - for each item where `true` value means the corresponding item
174    /// may exist in the filter, and a `false` value means it does not exist in the filter.
175    ///
176    /// # See Also
177    /// [<https://redis.io/commands/bf.mexists/>](https://redis.io/commands/bf.mexists/)
178    #[must_use]
179    fn bf_mexists<R: Response>(
180        self,
181        key: impl Serialize,
182        items: impl Serialize,
183    ) -> PreparedCommand<'a, Self, R> {
184        prepare_command(self, cmd("BF.MEXISTS").key(key).arg(items))
185    }
186
187    /// Creates an empty Bloom Filter with a single sub-filter
188    /// for the initial capacity requested and with an upper bound error_rate.
189    ///
190    /// By default, the filter auto-scales by creating additional sub-filters when capacity is reached.
191    /// The new sub-filter is created with size of the previous sub-filter multiplied by expansion.
192    ///
193    /// Though the filter can scale up by creating sub-filters,
194    /// it is recommended to reserve the estimated required capacity since maintaining and querying sub-filters requires additional memory
195    /// (each sub-filter uses an extra bits and hash function) and consume further CPU time
196    /// than an equivalent filter that had the right capacity at creation time.
197    ///
198    /// The number of hash functions is `log(error)/ln(2)^2`. The number of bits per item is `log(error)/ln(2)` ≈ 1.44.
199    /// * 1% error rate requires 7 hash functions and 10.08 bits per item.
200    /// * 0.1% error rate requires 10 hash functions and 14.4 bits per item.
201    /// * 0.01% error rate requires 14 hash functions and 20.16 bits per item.
202    ///
203    /// # Arguments
204    /// * `key` - The key under which the filter is found
205    /// * `error_rate` - The desired probability for false positives.
206    ///   The rate is a decimal value between 0 and 1.
207    ///   For example, for a desired false positive rate of 0.1% (1 in 1000),
208    ///   error_rate should be set to 0.001.
209    /// * `capacity` - The number of entries intended to be added to the filter.
210    ///   If your filter allows scaling, performance will begin to degrade after adding more items than this number.
211    ///   The actual degradation depends on how far the limit has been exceeded.
212    ///   Performance degrades linearly with the number of `sub-filters`.
213    /// * `options` - See [`BfReserveOptions`](BfReserveOptions)
214    ///
215    /// # See Also
216    /// [<https://redis.io/commands/bf.reserve/>](https://redis.io/commands/bf.reserve/)
217    #[must_use]
218    fn bf_reserve(
219        self,
220        key: impl Serialize,
221        error_rate: f64,
222        capacity: usize,
223        options: BfReserveOptions,
224    ) -> PreparedCommand<'a, Self, ()> {
225        prepare_command(
226            self,
227            cmd("BF.RESERVE")
228                .key(key)
229                .arg(error_rate)
230                .arg(capacity)
231                .arg(options),
232        )
233    }
234
235    /// Begins an incremental save of the bloom filter.
236    /// This is useful for large bloom filters which cannot fit into the normal [`dump`](crate::commands::GenericCommands::dump)
237    /// and [`restore`](crate::commands::GenericCommands::restore) model.
238    ///
239    /// # Arguments
240    /// * `key` - Name of the filter
241    /// * `iterator` - Iterator value; either 0 or the iterator from a previous invocation of this command.\
242    ///   The first time this command is called, the value of `iterator` should be 0.
243    ///
244    /// # Return
245    /// This command returns successive `(iterator, data)` pairs until `(0, vec![])` to indicate completion.
246    ///
247    /// # See Also
248    /// [<https://redis.io/commands/bf.scandump/>](https://redis.io/commands/bf.scandump/)
249    #[must_use]
250    fn bf_scandump(
251        self,
252        key: impl Serialize,
253        iterator: i64,
254    ) -> PreparedCommand<'a, Self, BfScanDumpResult> {
255        prepare_command(self, cmd("BF.SCANDUMP").key(key).arg(iterator))
256    }
257}
258
259/// Optional parameter for the [`bf_info`](BloomCommands::bf_info) command.
260///
261/// Used to query a specific parameter.
262#[derive(Serialize)]
263pub enum BfInfoParameter {
264    #[serde(rename = "CAPACITY")]
265    Capacity,
266    #[serde(rename = "SIZE")]
267    Size,
268    #[serde(rename = "FILTERS")]
269    NumFilters,
270    #[serde(rename = "ITEMS")]
271    NumItemsInserted,
272    #[serde(rename = "EXPANSION")]
273    ExpansionRate,
274}
275
276/// Result for the [`bf_info`](BloomCommands::bf_info) command.
277#[derive(Debug, Deserialize)]
278pub struct BfInfoResult {
279    #[serde(rename = "Capacity")]
280    pub capacity: usize,
281    #[serde(rename = "Size")]
282    pub size: usize,
283    #[serde(rename = "Number of filters")]
284    pub num_filters: usize,
285    #[serde(rename = "Number of items inserted")]
286    pub num_items_inserted: usize,
287    #[serde(rename = "Expansion rate")]
288    pub expansion_rate: usize,
289}
290
291/// Options for the [`bf_insert`](BloomCommands::bf_insert) command.
292#[derive(Default, Serialize)]
293#[serde(rename_all(serialize = "UPPERCASE"))]
294pub struct BfInsertOptions {
295    #[serde(skip_serializing_if = "Option::is_none")]
296    capacity: Option<u32>,
297    #[serde(skip_serializing_if = "Option::is_none")]
298    error: Option<f64>,
299    #[serde(skip_serializing_if = "Option::is_none")]
300    expansion: Option<u32>,
301    #[serde(
302        skip_serializing_if = "std::ops::Not::not",
303        serialize_with = "serialize_flag"
304    )]
305    nocreate: bool,
306    #[serde(
307        skip_serializing_if = "std::ops::Not::not",
308        serialize_with = "serialize_flag"
309    )]
310    nonscaling: bool,
311}
312
313impl BfInsertOptions {
314    /// Specifies the desired capacity for the filter to be created.
315    ///
316    /// This parameter is ignored if the filter already exists.
317    /// If the filter is automatically created and this parameter is absent,
318    /// then the module-level capacity is used.
319    /// See [`bf_reserve`](BloomCommands::bf_reserve) for more information about the impact of this value.
320    #[must_use]
321    pub fn capacity(mut self, capacity: u32) -> Self {
322        self.capacity = Some(capacity);
323        self
324    }
325
326    /// Specifies the error ratio of the newly created filter if it does not yet exist.
327    ///
328    /// If the filter is automatically created and error is not specified then the module-level error rate is used.
329    /// See [`bf_reserve`](BloomCommands::bf_reserve) for more information about the format of this value.
330    #[must_use]
331    pub fn error(mut self, error_rate: f64) -> Self {
332        self.error = Some(error_rate);
333        self
334    }
335
336    /// When `capacity` is reached, an additional sub-filter is created.
337    /// The size of the new sub-filter is the size of the last sub-filter multiplied by `expansion`.
338    /// If the number of elements to be stored in the filter is unknown,
339    /// we recommend that you use an `expansion` of 2 or more to reduce the number of sub-filters.
340    /// Otherwise, we recommend that you use an `expansion` of 1 to reduce memory consumption.
341    /// The default expansion value is 2.
342    #[must_use]
343    pub fn expansion(mut self, expansion: u32) -> Self {
344        self.expansion = Some(expansion);
345        self
346    }
347
348    /// Indicates that the filter should not be created if it does not already exist.
349    ///
350    /// If the filter does not yet exist, an error is returned rather than creating it automatically.
351    /// This may be used where a strict separation between filter creation and filter addition is desired.
352    /// It is an error to specify `nocreate` together with either [`capacity`](BfInsertOptions::capacity) or [`error`](BfInsertOptions::error).
353    #[must_use]
354    pub fn nocreate(mut self) -> Self {
355        self.nocreate = true;
356        self
357    }
358
359    /// Prevents the filter from creating additional sub-filters if initial capacity is reached.
360    ///
361    /// Non-scaling filters require slightly less memory than their scaling counterparts.
362    /// The filter returns an error when `capacity` is reached.
363    #[must_use]
364    pub fn nonscaling(mut self) -> Self {
365        self.nonscaling = true;
366        self
367    }
368}
369
370/// Options for the [`bf_reserve`](BloomCommands::bf_reserve) command.
371#[derive(Default, Serialize)]
372#[serde(rename_all(serialize = "UPPERCASE"))]
373pub struct BfReserveOptions {
374    #[serde(skip_serializing_if = "Option::is_none")]
375    expansion: Option<u32>,
376    #[serde(
377        skip_serializing_if = "std::ops::Not::not",
378        serialize_with = "serialize_flag"
379    )]
380    nonscaling: bool,
381}
382
383impl BfReserveOptions {
384    /// When `capacity` is reached, an additional sub-filter is created.
385    /// The size of the new sub-filter is the size of the last sub-filter multiplied by `expansion`.
386    /// If the number of elements to be stored in the filter is unknown,
387    /// we recommend that you use an `expansion` of 2 or more to reduce the number of sub-filters.
388    /// Otherwise, we recommend that you use an `expansion` of 1 to reduce memory consumption.
389    /// The default expansion value is 2.
390    #[must_use]
391    pub fn expansion(mut self, expansion: u32) -> Self {
392        self.expansion = Some(expansion);
393        self
394    }
395
396    /// Prevents the filter from creating additional sub-filters if initial capacity is reached.
397    ///
398    /// Non-scaling filters require slightly less memory than their scaling counterparts.
399    /// The filter returns an error when `capacity` is reached.
400    #[must_use]
401    pub fn nonscaling(mut self) -> Self {
402        self.nonscaling = true;
403        self
404    }
405}
406
407/// Result for the [`bf_scandump`](BloomCommands::bf_scandump) command.
408#[derive(Debug, Deserialize)]
409pub struct BfScanDumpResult {
410    pub iterator: i64,
411    pub data: BulkString,
412}