use crate::{
api::bloom::r#const::{
DEFAULT_BF_CAPACITY, DEFAULT_BF_ERROR_RATE, DEFAULT_BF_EXPANSION, DEFAULT_CF_BUCKET_SIZE,
DEFAULT_CF_CAPACITY, DEFAULT_CF_EXPANSION, DEFAULT_CF_MAX_ITERATIONS, DEFAULT_CF_PAGE_SIZE,
MAX_CF_EXPANSION,
},
error::{Error, Result},
hll::rapid_hash,
};
#[derive(Debug, Clone, Copy, PartialEq, bitcode::Encode, bitcode::Decode)]
pub enum BfInsert {
Capacity(u32),
ErrorRate(f64),
Expansion(u16),
NoCreate,
NonScaling,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, bitcode::Encode, bitcode::Decode)]
pub enum BfReserve {
Expansion(u16),
NonScaling,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, bitcode::Encode, bitcode::Decode)]
pub enum BloomFilterAddResult {
Ok,
Exist,
Full,
}
#[derive(Debug, Clone, PartialEq, bitcode::Encode, bitcode::Decode)]
pub struct BloomFilterInsert {
pub capacity: u32,
pub error_rate: f64,
pub expansion: u16,
pub auto_create: bool,
}
impl FromIterator<BfInsert> for BloomFilterInsert {
fn from_iter<I: IntoIterator<Item = BfInsert>>(iter: I) -> Self {
let mut opt = Self::default();
for o in iter {
match o {
BfInsert::Capacity(c) => opt.capacity = c,
BfInsert::ErrorRate(e) => opt.error_rate = e,
BfInsert::Expansion(exp) => opt.expansion = exp,
BfInsert::NoCreate => opt.auto_create = false,
BfInsert::NonScaling => opt.expansion = 0,
}
}
opt
}
}
impl BloomFilterInsert {
#[inline]
pub fn from_options(options: impl IntoIterator<Item = BfInsert>) -> Self {
options.into_iter().collect()
}
}
impl Default for BloomFilterInsert {
#[inline]
fn default() -> Self {
Self {
capacity: DEFAULT_BF_CAPACITY,
error_rate: DEFAULT_BF_ERROR_RATE,
expansion: DEFAULT_BF_EXPANSION,
auto_create: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, bitcode::Encode, bitcode::Decode)]
pub struct BloomFilterInfo {
pub capacity: u32,
pub bloom_bytes: u32,
pub n_filters: u16,
pub size: u64,
pub expansion: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, bitcode::Encode, bitcode::Decode)]
pub enum CfInsert {
Capacity(u64),
BucketSize(u8),
MaxIterations(u16),
Expansion(u16),
PageSize(u32),
NoCreate,
Nx,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, bitcode::Encode, bitcode::Decode)]
pub enum CfReserve {
BucketSize(u8),
MaxIterations(u16),
Expansion(u16),
PageSize(u32),
}
#[derive(Debug, Clone, PartialEq, Eq, bitcode::Encode, bitcode::Decode)]
pub struct CuckooFilterInsert {
pub capacity: u64,
pub bucket_size: u8,
pub max_iterations: u16,
pub expansion: u16,
pub page_size: u32,
pub auto_create: bool,
pub nx: bool,
}
impl FromIterator<CfInsert> for CuckooFilterInsert {
fn from_iter<I: IntoIterator<Item = CfInsert>>(iter: I) -> Self {
let mut opt = Self::default();
for o in iter {
match o {
CfInsert::Capacity(c) => opt.capacity = c,
CfInsert::BucketSize(bs) => opt.bucket_size = bs,
CfInsert::MaxIterations(mi) => opt.max_iterations = mi,
CfInsert::Expansion(exp) => opt.expansion = exp,
CfInsert::PageSize(ps) => opt.page_size = ps,
CfInsert::NoCreate => opt.auto_create = false,
CfInsert::Nx => opt.nx = true,
}
}
opt
}
}
impl CuckooFilterInsert {
#[inline]
pub fn from_options(options: impl IntoIterator<Item = CfInsert>) -> Self {
options.into_iter().collect()
}
}
impl Default for CuckooFilterInsert {
#[inline]
fn default() -> Self {
Self {
capacity: DEFAULT_CF_CAPACITY,
bucket_size: DEFAULT_CF_BUCKET_SIZE,
max_iterations: DEFAULT_CF_MAX_ITERATIONS,
expansion: DEFAULT_CF_EXPANSION,
page_size: DEFAULT_CF_PAGE_SIZE,
auto_create: true,
nx: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, bitcode::Encode, bitcode::Decode)]
pub struct CuckooFilterInfo {
pub size: u64,
pub num_buckets: u64,
pub num_filters: u16,
pub num_items_inserted: u64,
pub num_items_deleted: u64,
pub bucket_size: u8,
pub expansion: u16,
pub max_iterations: u16,
}
pub struct CuckooFilterHelper;
impl CuckooFilterHelper {
pub const LOAD_FACTOR: f64 = 0.955;
pub const ALT_HASH_MULTIPLIER: u64 = 0x5bd1_e995;
pub const FINGERPRINT_MODULUS: u64 = 255;
pub const DEFAULT_PAGE_SIZE: u32 = DEFAULT_CF_PAGE_SIZE;
pub const DEFAULT_CAPACITY: u64 = DEFAULT_CF_CAPACITY;
pub const DEFAULT_BUCKET_SIZE: u8 = DEFAULT_CF_BUCKET_SIZE;
pub const DEFAULT_MAX_ITERATIONS: u16 = DEFAULT_CF_MAX_ITERATIONS;
pub const DEFAULT_EXPANSION: u16 = DEFAULT_CF_EXPANSION;
pub const MAX_EXPANSION: u16 = MAX_CF_EXPANSION;
#[inline]
pub fn hash(data: &[u8]) -> u64 {
rapid_hash(data)
}
#[inline]
pub fn generate_fingerprint(hash: u64) -> u8 {
((hash % Self::FINGERPRINT_MODULUS) + 1) as u8
}
#[inline]
pub fn get_alt_hash(fingerprint: u8, hash: u64) -> u64 {
hash ^ ((fingerprint as u64).wrapping_mul(Self::ALT_HASH_MULTIPLIER))
}
#[inline]
pub fn get_alt_bucket_index(bucket_idx: u32, fingerprint: u8, num_buckets: u32) -> u32 {
let alt_hash = Self::get_alt_hash(fingerprint, bucket_idx as u64);
(alt_hash % (num_buckets as u64)) as u32
}
#[inline]
pub fn normalize_expansion(expansion: u16) -> u16 {
if expansion <= 1 {
expansion
} else {
(expansion as u32).next_power_of_two().min(32768) as u16
}
}
pub fn calculate_required_buckets(capacity: u64, bucket_size: u8) -> Result<u32> {
if bucket_size == 0 {
return Err(Error::invalid_data("bucket_size must be larger than 0"));
}
let max_supported_capacity =
((1u64 << 31) as f64 * (bucket_size as f64) * Self::LOAD_FACTOR) as u64;
if capacity > max_supported_capacity {
return Err(Error::invalid_data("capacity is too large"));
}
let exact_buckets = (capacity as f64) / (bucket_size as f64) / Self::LOAD_FACTOR;
let mut req_buckets = exact_buckets.ceil() as u64;
if req_buckets == 0 {
req_buckets = 1;
}
let num_buckets = req_buckets.next_power_of_two();
if num_buckets > (1u64 << 31) {
return Err(Error::invalid_data("capacity is too large"));
}
Ok(num_buckets as u32)
}
}