packages feed

hedis-0.16.3: src/Database/Redis/ManualCommands/CF.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Database.Redis.ManualCommands.CF where

import Data.ByteString (ByteString)
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE

import Database.Redis.Core
import Database.Redis.Protocol
import Database.Redis.Types

data CFInfo = CFInfo
    { cfInfoSize :: Integer
      -- ^ Number of bytes allocated for the Cuckoo filter.
    , cfInfoBuckets :: Integer
      -- ^ Number of buckets in the filter.
    , cfInfoFilters :: Integer
      -- ^ Number of sub-filters.
    , cfInfoItemsInserted :: Integer
      -- ^ Number of inserted items tracked by the filter.
    , cfInfoItemsDeleted :: Integer
      -- ^ Number of deleted items tracked by the filter.
    , cfInfoBucketSize :: Integer
      -- ^ Number of entries per bucket.
    , cfInfoExpansion :: Integer
      -- ^ Expansion rate used when additional sub-filters are created.
    , cfInfoMaxIterations :: Integer
      -- ^ Maximum number of displacement attempts during insertion.
    } deriving (Show, Eq)

instance RedisResult CFInfo where
    decode r = do
        fields <- decode r :: Either Reply [(ByteString, Integer)]
        cfInfoSize <- decodeField "Size" fields
        cfInfoBuckets <- decodeField "Number of buckets" fields
        cfInfoFilters <- decodeField "Number of filters" fields
        cfInfoItemsInserted <- decodeField "Number of items inserted" fields
        cfInfoItemsDeleted <- decodeField "Number of items deleted" fields
        cfInfoBucketSize <- decodeField "Bucket size" fields
        cfInfoExpansion <- decodeField "Expansion rate" fields
        cfInfoMaxIterations <- decodeField "Max iterations" fields
        pure CFInfo{..}
      where
        decodeField key fields = maybe (Left r) Right (lookup key fields)

data CFReserveOpts = CFReserveOpts
    { cfReserveBucketSize :: Maybe Integer
      -- ^ Number of entries per bucket.
    , cfReserveMaxIterations :: Maybe Integer
      -- ^ Maximum number of displacement attempts during insertion.
    , cfReserveExpansion :: Maybe Integer
      -- ^ Expansion rate for newly created sub-filters.
    } deriving (Show, Eq)

defaultCFReserveOpts :: CFReserveOpts
defaultCFReserveOpts = CFReserveOpts
    { cfReserveBucketSize = Nothing
    , cfReserveMaxIterations = Nothing
    , cfReserveExpansion = Nothing
    }

data CFInsertOpts = CFInsertOpts
    { cfInsertCapacity :: Maybe Integer
      -- ^ Initial capacity to use if a new filter is created.
    , cfInsertNoCreate :: Bool
      -- ^ Return an error instead of creating the filter when the key does not exist.
    } deriving (Show, Eq)

defaultCFInsertOpts :: CFInsertOpts
defaultCFInsertOpts = CFInsertOpts
    { cfInsertCapacity = Nothing
    , cfInsertNoCreate = False
    }

data CFInsertResult
    = CFInsertAdded
    | CFInsertAlreadyExists
    | CFInsertFilterFull
    deriving (Show, Eq)

instance RedisResult CFInsertResult where
    decode r = do
        result <- decode r :: Either Reply Integer
        case result of
            1 -> Right CFInsertAdded
            0 -> Right CFInsertAlreadyExists
            -1 -> Right CFInsertFilterFull
            _ -> Left r

cfReserveOptsToArgs :: CFReserveOpts -> [ByteString]
cfReserveOptsToArgs CFReserveOpts{..} =
    bucketSizeArg ++ maxIterationsArg ++ expansionArg
  where
    bucketSizeArg = maybe [] (\bucketSize -> ["BUCKETSIZE", encode bucketSize]) cfReserveBucketSize
    maxIterationsArg = maybe [] (\iterations -> ["MAXITERATIONS", encode iterations]) cfReserveMaxIterations
    expansionArg = maybe [] (\expansion -> ["EXPANSION", encode expansion]) cfReserveExpansion

cfInsertOptsToArgs :: CFInsertOpts -> [ByteString]
cfInsertOptsToArgs CFInsertOpts{..} =
    capacityArg ++ noCreateArg
  where
    capacityArg = maybe [] (\capacity -> ["CAPACITY", encode capacity]) cfInsertCapacity
    noCreateArg = ["NOCREATE" | cfInsertNoCreate]

-- |Adds an item to a Cuckoo filter (<https://redis.io/commands/cf.add>).
--
-- A filter is created automatically if the key does not exist.
--
-- /O(k + i)/, where /k/ is the number of sub-filters and /i/ is maxIterations.
--
-- Since RedisBloom 1.0.0
cfadd
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> ByteString -- ^ Item to add.
    -> m (f Bool)
cfadd key item = sendRequest ["CF.ADD", key, item]

-- |Adds an item to a Cuckoo filter only if it did not already exist (<https://redis.io/commands/cf.addnx>).
--
-- A filter is created automatically if the key does not exist.
--
-- /O(k + i)/, where /k/ is the number of sub-filters and /i/ is maxIterations.
--
-- Since RedisBloom 1.0.0
cfaddnx
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> ByteString -- ^ Item to add.
    -> m (f Bool)
cfaddnx key item = sendRequest ["CF.ADDNX", key, item]

-- |Returns the number of times an item might appear in a Cuckoo filter (<https://redis.io/commands/cf.count>).
--
-- Returns @0@ when the key does not exist or the item was not found.
--
-- /O(k)/, where /k/ is the number of sub-filters.
--
-- Since RedisBloom 1.0.0
cfcount
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> ByteString -- ^ Item to count.
    -> m (f Integer)
cfcount key item = sendRequest ["CF.COUNT", key, item]

-- |Deletes an item from a Cuckoo filter (<https://redis.io/commands/cf.del>).
--
-- Returns 'False' when the key does not exist or the item was not found.
--
-- /O(k)/, where /k/ is the number of sub-filters.
--
-- Since RedisBloom 1.0.0
cfdel
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> ByteString -- ^ Item to delete.
    -> m (f Bool)
cfdel key item = sendRequest ["CF.DEL", key, item]

-- |Checks whether an item may exist in a Cuckoo filter (<https://redis.io/commands/cf.exists>).
--
-- Returns 'False' when the key does not exist or the item is definitely absent.
--
-- /O(k)/, where /k/ is the number of sub-filters.
--
-- Since RedisBloom 1.0.0
cfexists
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> ByteString -- ^ Item to check.
    -> m (f Bool)
cfexists key item = sendRequest ["CF.EXISTS", key, item]

-- |Returns information about a Cuckoo filter (<https://redis.io/commands/cf.info>).
--
-- /O(1)/
--
-- Since RedisBloom 1.0.0
cfinfo
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> m (f CFInfo)
cfinfo key = sendRequest ["CF.INFO", key]

-- |Adds one or more items to a Cuckoo filter, creating it when needed (<https://redis.io/commands/cf.insert>).
--
-- This is equivalent to inserting with default options and automatic creation enabled.
--
-- /O(n * (k + i))/, where /n/ is the number of items, /k/ is the number of sub-filters, and /i/ is maxIterations.
--
-- Since RedisBloom 1.0.0
cfinsert
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> NonEmpty ByteString -- ^ Items to add.
    -> m (f [CFInsertResult])
cfinsert key items = cfinsertOpts key items defaultCFInsertOpts

-- |Adds one or more items to a Cuckoo filter, creating it when needed (<https://redis.io/commands/cf.insert>).
--
-- /O(n * (k + i))/, where /n/ is the number of items, /k/ is the number of sub-filters, and /i/ is maxIterations.
--
-- Since RedisBloom 1.0.0
cfinsertOpts
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> NonEmpty ByteString -- ^ Items to add.
    -> CFInsertOpts -- ^ Optional creation parameters.
    -> m (f [CFInsertResult])
cfinsertOpts key items opts =
    sendRequest $ ["CF.INSERT", key] ++ cfInsertOptsToArgs opts ++ ["ITEMS"] ++ NE.toList items

-- |Adds one or more items to a Cuckoo filter only if they did not already exist (<https://redis.io/commands/cf.insertnx>).
--
-- This is equivalent to inserting with default options and automatic creation enabled.
--
-- /O(n * (k + i))/, where /n/ is the number of items, /k/ is the number of sub-filters, and /i/ is maxIterations.
--
-- Since RedisBloom 1.0.0
cfinsertnx
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> NonEmpty ByteString -- ^ Items to add.
    -> m (f [CFInsertResult])
cfinsertnx key items = cfinsertnxOpts key items defaultCFInsertOpts

-- |Adds one or more items to a Cuckoo filter only if they did not already exist (<https://redis.io/commands/cf.insertnx>).
--
-- /O(n * (k + i))/, where /n/ is the number of items, /k/ is the number of sub-filters, and /i/ is maxIterations.
--
-- Since RedisBloom 1.0.0
cfinsertnxOpts
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> NonEmpty ByteString -- ^ Items to add.
    -> CFInsertOpts -- ^ Optional creation parameters.
    -> m (f [CFInsertResult])
cfinsertnxOpts key items opts =
    sendRequest $ ["CF.INSERTNX", key] ++ cfInsertOptsToArgs opts ++ ["ITEMS"] ++ NE.toList items

-- |Checks whether one or more items may exist in a Cuckoo filter (<https://redis.io/commands/cf.mexists>).
--
-- A 'False' result means the item is definitely absent, or the key does not exist.
--
-- /O(k * n)/, where /k/ is the number of sub-filters and /n/ is the number of items.
--
-- Since RedisBloom 1.0.0
cfmexists
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter.
    -> NonEmpty ByteString -- ^ Items to check.
    -> m (f [Bool])
cfmexists key items = sendRequest $ ["CF.MEXISTS", key] ++ NE.toList items

-- |Creates an empty Cuckoo filter (<https://redis.io/commands/cf.reserve>).
--
-- The filter will fail if the key already exists.
--
-- /O(1)/
--
-- Since RedisBloom 1.0.0
cfreserve
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter to create.
    -> Integer -- ^ Initial capacity.
    -> m (f Status)
cfreserve key capacity = cfreserveOpts key capacity defaultCFReserveOpts

-- |Creates an empty Cuckoo filter (<https://redis.io/commands/cf.reserve>).
--
-- /O(1)/
--
-- Since RedisBloom 1.0.0
cfreserveOpts
    :: (RedisCtx m f)
    => ByteString -- ^ Key of the Cuckoo filter to create.
    -> Integer -- ^ Initial capacity.
    -> CFReserveOpts -- ^ Bucket and scaling options.
    -> m (f Status)
cfreserveOpts key capacity opts =
    sendRequest $ ["CF.RESERVE", key, encode capacity] ++ cfReserveOptsToArgs opts