-- | Basic storage is simply a key-value lookup in Redis. module HLRDB.Structures.Basic where import Database.Redis as Redis import HLRDB.Primitives.Aggregate import HLRDB.Primitives.Redis import HLRDB.Internal import Data.ByteString (ByteString) import Data.ByteString.Char8 (pack) import qualified Data.HashMap.Strict as HM -- | Simple get command. Works on @RedisBasic a b@ and @RedisIntegral a b@. get :: MonadRedis m => RedisStructure (BASIC w) a b -> a -> m b get (RKeyValue (E k _ d)) a = liftRedis $ Redis.get (k a) >>= \case Left e -> fail (show e) Right r -> pure (d r) get (RKeyValueInteger k _ d) a = liftRedis $ Redis.get (k a) >>= \case Left e -> fail (show e) Right r -> pure $ d . fromIntegral $ decodeMInteger r get (RKeyValueByteString k) a = liftRedis $ Redis.get (k a) >>= \case Left e -> fail (show e) Right r -> pure (maybe mempty id r) -- | Construct a query to be used with @mget@. You may combine many of these together to create complex queries. Use @mget@ to execute the query back in the Redis monad. Works on @RedisBasic a b@ and @RedisIntegral a b@. liftq :: RedisStructure (BASIC w) a b -> a ⟿ b liftq (RKeyValue (E k _ d)) = T $ \f -> fmap d . f . k liftq (RKeyValueInteger k _ d) = T $ \f -> fmap (d . fromIntegral . decodeMInteger) . f . k liftq (RKeyValueByteString k) = T $ \f -> fmap (maybe mempty id) . f . k -- | Reify a (⟿) query into the Redis monad via a single mget command. mget :: MonadRedis m => a ⟿ b -> a -> m b mget = runT (liftRedis . mget') where mget' [] = pure [] mget' xs = Redis.mget xs >>= \case Left e -> fail (show e) Right vs -> pure vs -- | Set a value for a given key. Works on @RedisBasic a b@ and @RedisIntegral a b@. set :: MonadRedis m => RedisStructure (BASIC w) a b -> a -> b -> m () set (RKeyValue (E k e _)) a b = liftRedis $ case e b of Just bs -> ignore $ Redis.set (k a) bs Nothing -> ignore $ del [ k a ] set (RKeyValueInteger k e _) a i = liftRedis $ ignore $ Redis.set (k a) (pack $ show (e i)) set (RKeyValueByteString k) a b = liftRedis $ if b == mempty then ignore $ Redis.del [ k a ] else ignore $ Redis.set (k a) b -- | Convenient alias for setting a value for an optional path set' :: MonadRedis m => RedisBasic a (Maybe b) -> a -> b -> m () set' (RKeyValue (E k e _)) a b = liftRedis $ case e (Just b) of Just bs -> ignore $ Redis.set (k a) bs Nothing -> ignore $ del [ k a ] -- | Construct a query to be used with @mset@. The @MSET@ type is a @Monoid@, so you may combine many of these together before executing the batch with the @mset@ command. liftqs :: RedisStructure (BASIC w) a b -> (a , b) -> MSET liftqs (RKeyValue (E k e _)) (a , b) = MSET $ (<>) [ (k a , e b) ] liftqs (RKeyValueInteger k e _) (a , b) = MSET $ (<>) [ (k a , Just $ pack (show (e b))) ] liftqs (RKeyValueByteString k) (a , b) = MSET $ (<>) [ (k a , Just b) ] -- | Execute a @MSET@ query. mset :: MonadRedis m => MSET -> m () mset = go . flip runMSET [] where -- need this hashmap to/from in order to make sure deleting a value after setting it -- performs correctly. go xs = case (splitWith f . HM.toList . HM.fromList) xs of (as , bs) -> mdel' as >> mset' bs >> pure () where f (x , Nothing) = Left x f (x , Just y) = Right (x , y) mdel' [] = pure 0 mdel' xs = unwrap $ liftRedis $ Redis.del xs mset' [] = pure undefined mset' xs = unwrap $ liftRedis $ Redis.mset xs -- | Set a value together with a given expiration timeout (in seconds). setex :: MonadRedis m => RedisStructure (BASIC w) a b -> a -> Integer -> b -> m () setex (RKeyValue (E k e _)) a t b = liftRedis $ case e b of Just bs -> ignore $ Redis.setex (k a) t bs Nothing -> ignore $ del [ k a ] setex (RKeyValueInteger k e _) a t i = liftRedis $ ignore $ Redis.setex (k a) t (pack $ show (e i)) setex (RKeyValueByteString k) a t b = liftRedis $ if b == mempty then ignore $ Redis.del [ k a ] else ignore $ Redis.setex (k a) t b -- | Increment an Integer in Redis. Empty values are treated as 0. incr :: MonadRedis m => RedisIntegral a b -> a -> m b incr (RKeyValueInteger p _ d) = fmap d . unwrap . Redis.incr . p -- | Increment an Integer in Redis by a specific amount. Empty values are treated as 0. incrby :: MonadRedis m => RedisIntegral a b -> a -> b -> m b incrby (RKeyValueInteger p e d) k = fmap d . unwrap . Redis.incrby (p k) . e -- | Decrement an Integer in Redis. Empty values are treated as 0. decr :: MonadRedis m => RedisIntegral a b -> a -> m b decr (RKeyValueInteger p _ d) = fmap d . unwrap . Redis.decr . p -- | Decrement an Integer in Redis by a specific amount. Empty values are treated as 0. decrby :: MonadRedis m => RedisIntegral a b -> a -> b -> m b decrby (RKeyValueInteger p e d) k = fmap d . unwrap . Redis.decrby (p k) . e -- | Start and end indices are inclusive. Unlike @get@, the empty bytestring is returned if the key does not exist in Redis or if the specified range is out of range. getrange :: MonadRedis m => RedisByteString a ByteString -> a -> Integer -> Integer -> m ByteString getrange (RKeyValueByteString p) k start = unwrap . Redis.getrange (p k) start -- | The @Integer@ paramter is the offset. Returns the length of the string after the command has been executed. setrange :: MonadRedis m => RedisByteString a ByteString -> a -> Integer -> ByteString -> m Integer setrange (RKeyValueByteString p) k start = unwrap . Redis.setrange (p k) start -- | Get the bit stored at the specified offset. Note that if no value exists in Redis or if the specified range is outside the defined range, @False@ will be returned by default. getbit :: MonadRedis m => RedisByteString a ByteString -> a -> Integer -> m Bool getbit (RKeyValueByteString p) k = fmap (1==) . unwrap . Redis.getbit (p k) -- | Set the bit at the specified offset. If the offset is outside the existing defined range of the value, 0s are implicitly inserted to fill the intermediate space. Returns the existing value of this bit, as defined by the @getbit@ semantics above. setbit :: MonadRedis m => RedisByteString a ByteString -> a -> Integer -> Bool -> m Bool setbit (RKeyValueByteString p) k i = fmap (1==) . unwrap . Redis.setbit (p k) i . \case True -> "1" False -> "0"