use crate::{ client::{prepare_command, PreparedCommand}, resp::{ cmd, deserialize_byte_buf, CollectionResponse, CommandArgs, SingleArg, SingleArgCollection, ToArgs, }, }; use serde::Deserialize; /// A group of Redis commands related to [`Bloom filters`](https://redis.io/docs/stack/bloom/) /// /// # See Also /// [Bloom Filter Commands](https://redis.io/commands/?group=bf) pub trait BloomCommands<'a> { /// Adds an item to a bloom filter /// /// # Arguments /// * `key` - The name of the filter /// * `item` - The item to add /// /// # Return /// * `true` - if the item did not exist in the filter, /// * `false` - otherwise. /// /// # See Also /// * [](https://redis.io/commands/bf.add/) #[must_use] fn bf_add(self, key: impl SingleArg, item: impl SingleArg) -> PreparedCommand<'a, Self, bool> where Self: Sized, { prepare_command(self, cmd("BF.ADD").arg(key).arg(item)) } /// Determines whether an item may exist in the Bloom Filter or not. /// /// # Arguments /// * `key` - The name of the filter /// * `item` - The item to check for /// /// # Return /// * `true` - means the item may exist in the filter, /// * `false` - means it does not exist in the filter.. /// /// # See Also /// * [](https://redis.io/commands/bf.exists/) #[must_use] fn bf_exists(self, key: impl SingleArg, item: impl SingleArg) -> PreparedCommand<'a, Self, bool> where Self: Sized, { prepare_command(self, cmd("BF.EXISTS").arg(key).arg(item)) } /// Return information about key filter. /// /// # Arguments /// * `key` - Name of the key to return information about /// /// # Return /// an instance of [`BfInfoResult`](BfInfoResult) /// /// # See Also /// [](https://redis.io/commands/bf.info/) #[must_use] fn bf_info_all(self, key: impl SingleArg) -> PreparedCommand<'a, Self, BfInfoResult> where Self: Sized, { prepare_command(self, cmd("BF.INFO").arg(key)) } /// Return information about key filter for a specific information parameter /// /// # Arguments /// * `key` - Name of the key to return information about /// * `param` - specific information parameter to query /// /// # Return /// The value of the requested parameter /// /// # See Also /// [](https://redis.io/commands/bf.info/) #[must_use] fn bf_info( self, key: impl SingleArg, param: BfInfoParameter, ) -> PreparedCommand<'a, Self, usize> where Self: Sized, { prepare_command(self, cmd("BF.INFO").arg(key).arg(param)) } /// `bf_insert` is a sugarcoated combination of [`bf_reserve`](BloomCommands::bf_reserve) and [`bf_add`](BloomCommands::bf_add). /// /// It creates a new filter if the key does not exist using the relevant arguments (see [`bf_reserve`](BloomCommands::bf_reserve)). /// Next, all ITEMS are inserted. /// /// # Arguments /// * `key` - The name of the filter /// * `items` - One or more items to add /// * `options` - See [`BfInsertOptions`](BfInsertOptions) /// /// # Return /// A collection of booleans (integers). /// /// 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. /// /// # See Also /// [](https://redis.io/commands/bf.insert/) #[must_use] fn bf_insert>( self, key: impl SingleArg, items: impl SingleArgCollection, options: BfInsertOptions, ) -> PreparedCommand<'a, Self, R> where Self: Sized, { prepare_command( self, cmd("BF.INSERT") .arg(key) .arg(options) .arg("ITEMS") .arg(items), ) } /// Restores a filter previously saved using [`bf_scandump`](BloomCommands::bf_scandump). /// /// See the [`bf_scandump`](BloomCommands::bf_scandump) command for example usage. /// /// This command overwrites any bloom filter stored under key. /// Make sure that the bloom filter is not be changed between invocations. /// /// # Arguments /// * `key` - Name of the key to restore /// * `iterator` - Iterator value associated with `data` (returned by [`bf_scandump`](BloomCommands::bf_scandump)) /// * `data` - Current data chunk (returned by [`bf_scandump`](BloomCommands::bf_scandump)) /// /// # See Also /// [](https://redis.io/commands/bf.loadchunk/) #[must_use] fn bf_loadchunk( self, key: impl SingleArg, iterator: i64, data: impl SingleArg, ) -> PreparedCommand<'a, Self, ()> where Self: Sized, { prepare_command(self, cmd("BF.LOADCHUNK").arg(key).arg(iterator).arg(data)) } /// Adds one or more items to the Bloom Filter and creates the filter if it does not exist yet. /// /// This command operates identically to [`bf_add`](BloomCommands::bf_add) except that it allows multiple inputs and returns multiple values. /// /// # Arguments /// * `key` - The name of the filter /// * `items` - One or more items to add /// /// # Return /// Collection reply of boolean - for each item which is either `true` or `false` depending /// on whether the corresponding input element was newly added to the filter or may have previously existed. /// /// # See Also /// [](https://redis.io/commands/bf.madd/) #[must_use] fn bf_madd>( self, key: impl SingleArg, items: impl SingleArgCollection, ) -> PreparedCommand<'a, Self, R> where Self: Sized, { prepare_command(self, cmd("BF.MADD").arg(key).arg(items)) } /// Determines if one or more items may exist in the filter or not. /// /// # Arguments /// * `key` - The name of the filter /// * `items` - One or more items to check /// /// # Return /// Collection reply of boolean - for each item where `true` value means the corresponding item /// may exist in the filter, and a `false` value means it does not exist in the filter. /// /// # See Also /// [](https://redis.io/commands/bf.mexists/) #[must_use] fn bf_mexists>( self, key: impl SingleArg, items: impl SingleArgCollection, ) -> PreparedCommand<'a, Self, R> where Self: Sized, { prepare_command(self, cmd("BF.MEXISTS").arg(key).arg(items)) } /// Creates an empty Bloom Filter with a single sub-filter /// for the initial capacity requested and with an upper bound error_rate. /// /// By default, the filter auto-scales by creating additional sub-filters when capacity is reached. /// The new sub-filter is created with size of the previous sub-filter multiplied by expansion. /// /// Though the filter can scale up by creating sub-filters, /// it is recommended to reserve the estimated required capacity since maintaining and querying sub-filters requires additional memory /// (each sub-filter uses an extra bits and hash function) and consume further CPU time /// than an equivalent filter that had the right capacity at creation time. /// /// The number of hash functions is `log(error)/ln(2)^2`. The number of bits per item is `log(error)/ln(2)` ≈ 1.44. /// * 1% error rate requires 7 hash functions and 10.08 bits per item. /// * 0.1% error rate requires 10 hash functions and 14.4 bits per item. /// * 0.01% error rate requires 14 hash functions and 20.16 bits per item. /// /// # Arguments /// * `key` - The key under which the filter is found /// * `error_rate` - The desired probability for false positives. /// The rate is a decimal value between 0 and 1. /// For example, for a desired false positive rate of 0.1% (1 in 1000), /// error_rate should be set to 0.001. /// * `capacity` - The number of entries intended to be added to the filter. /// If your filter allows scaling, performance will begin to degrade after adding more items than this number. /// The actual degradation depends on how far the limit has been exceeded. /// Performance degrades linearly with the number of `sub-filters`. /// * `options` - See [`BfReserveOptions`](BfReserveOptions) /// /// # See Also /// [](https://redis.io/commands/bf.reserve/) #[must_use] fn bf_reserve( self, key: impl SingleArg, error_rate: f64, capacity: usize, options: BfReserveOptions, ) -> PreparedCommand<'a, Self, ()> where Self: Sized, { prepare_command( self, cmd("BF.RESERVE") .arg(key) .arg(error_rate) .arg(capacity) .arg(options), ) } /// Begins an incremental save of the bloom filter. /// This is useful for large bloom filters which cannot fit into the normal [`dump`](crate::commands::GenericCommands::dump) /// and [`restore`](crate::commands::GenericCommands::restore) model. /// /// # Arguments /// * `key` - Name of the filter /// * `iterator` - Iterator value; either 0 or the iterator from a previous invocation of this command.\ /// The first time this command is called, the value of `iterator` should be 0. /// /// # Return /// This command returns successive `(iterator, data)` pairs until `(0, vec![])` to indicate completion. /// /// # See Also /// [](https://redis.io/commands/bf.scandump/) #[must_use] fn bf_scandump( self, key: impl SingleArg, iterator: i64, ) -> PreparedCommand<'a, Self, BfScanDumpResult> where Self: Sized, { prepare_command(self, cmd("BF.SCANDUMP").arg(key).arg(iterator)) } } /// Optional parameter for the [`bf_info`](BloomCommands::bf_info) command. /// /// Used to query a specific parameter. pub enum BfInfoParameter { Capacity, Size, NumFilters, NumItemsInserted, ExpansionRate, } impl ToArgs for BfInfoParameter { fn write_args(&self, args: &mut CommandArgs) { match self { BfInfoParameter::Capacity => args.arg("CAPACITY"), BfInfoParameter::Size => args.arg("SIZE"), BfInfoParameter::NumFilters => args.arg("FILTERS"), BfInfoParameter::NumItemsInserted => args.arg("ITEMS"), BfInfoParameter::ExpansionRate => args.arg("EXPANSION"), }; } } /// Result for the [`bf_info`](BloomCommands::bf_info) command. #[derive(Debug, Deserialize)] pub struct BfInfoResult { #[serde(rename = "Capacity")] pub capacity: usize, #[serde(rename = "Size")] pub size: usize, #[serde(rename = "Number of filters")] pub num_filters: usize, #[serde(rename = "Number of items inserted")] pub num_items_inserted: usize, #[serde(rename = "Expansion rate")] pub expansion_rate: usize, } /// Options for the [`bf_insert`](BloomCommands::bf_insert) command. #[derive(Default)] pub struct BfInsertOptions { command_args: CommandArgs, } impl BfInsertOptions { /// Specifies the desired capacity for the filter to be created. /// /// This parameter is ignored if the filter already exists. /// If the filter is automatically created and this parameter is absent, /// then the module-level capacity is used. /// See [`bf_reserve`](BloomCommands::bf_reserve) for more information about the impact of this value. #[must_use] pub fn capacity(mut self, capacity: usize) -> Self { Self { command_args: self.command_args.arg("CAPACITY").arg(capacity).build(), } } /// Specifies the error ratio of the newly created filter if it does not yet exist. /// /// If the filter is automatically created and error is not specified then the module-level error rate is used. /// See [`bf_reserve`](BloomCommands::bf_reserve) for more information about the format of this value. #[must_use] pub fn error(mut self, error_rate: f64) -> Self { Self { command_args: self.command_args.arg("ERROR").arg(error_rate).build(), } } /// When `capacity` is reached, an additional sub-filter is created. /// The size of the new sub-filter is the size of the last sub-filter multiplied by `expansion`. /// If the number of elements to be stored in the filter is unknown, /// we recommend that you use an `expansion` of 2 or more to reduce the number of sub-filters. /// Otherwise, we recommend that you use an `expansion` of 1 to reduce memory consumption. /// The default expansion value is 2. #[must_use] pub fn expansion(mut self, expansion: usize) -> Self { Self { command_args: self.command_args.arg("EXPANSION").arg(expansion).build(), } } /// Indicates that the filter should not be created if it does not already exist. /// /// If the filter does not yet exist, an error is returned rather than creating it automatically. /// This may be used where a strict separation between filter creation and filter addition is desired. /// It is an error to specify `nocreate` together with either [`capacity`](BfInsertOptions::capacity) or [`error`](BfInsertOptions::error). #[must_use] pub fn nocreate(mut self) -> Self { Self { command_args: self.command_args.arg("NOCREATE").build(), } } /// Prevents the filter from creating additional sub-filters if initial capacity is reached. /// /// Non-scaling filters require slightly less memory than their scaling counterparts. /// The filter returns an error when `capacity` is reached. #[must_use] pub fn nonscaling(mut self) -> Self { Self { command_args: self.command_args.arg("NONSCALING").build(), } } } impl ToArgs for BfInsertOptions { fn write_args(&self, args: &mut CommandArgs) { args.arg(&self.command_args); } } /// Options for the [`bf_reserve`](BloomCommands::bf_reserve) command. #[derive(Default)] pub struct BfReserveOptions { command_args: CommandArgs, } impl BfReserveOptions { /// When `capacity` is reached, an additional sub-filter is created. /// The size of the new sub-filter is the size of the last sub-filter multiplied by `expansion`. /// If the number of elements to be stored in the filter is unknown, /// we recommend that you use an `expansion` of 2 or more to reduce the number of sub-filters. /// Otherwise, we recommend that you use an `expansion` of 1 to reduce memory consumption. /// The default expansion value is 2. #[must_use] pub fn expansion(mut self, expansion: usize) -> Self { Self { command_args: self.command_args.arg("EXPANSION").arg(expansion).build(), } } /// Prevents the filter from creating additional sub-filters if initial capacity is reached. /// /// Non-scaling filters require slightly less memory than their scaling counterparts. /// The filter returns an error when `capacity` is reached. #[must_use] pub fn nonscaling(mut self) -> Self { Self { command_args: self.command_args.arg("NONSCALING").build(), } } } impl ToArgs for BfReserveOptions { fn write_args(&self, args: &mut CommandArgs) { args.arg(&self.command_args); } } /// Result for the [`bf_scandump`](BloomCommands::bf_scandump) command. #[derive(Debug, Deserialize)] pub struct BfScanDumpResult { pub iterator: i64, #[serde(deserialize_with = "deserialize_byte_buf")] pub data: Vec, }