Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
Redis
Description
A simple Redis library providing high level access to Redis features we use here at NoRedInk
As with our Ruby Redis access, we enforce working within a "namespace".
Synopsis
- handler :: Text -> Settings -> Acquire Handler
- handlerAutoExtendExpire :: Text -> Settings -> Acquire HandlerAutoExtendExpire
- type Handler = Handler' 'NoAutoExtendExpire
- type HandlerAutoExtendExpire = Handler' 'AutoExtendExpire
- data Handler' (x :: HasAutoExtendExpire)
- data HasAutoExtendExpire
- data Settings = Settings {
- connectionInfo :: ConnectInfo
- clusterMode :: ClusterMode
- defaultExpiry :: DefaultExpiry
- queryTimeout :: QueryTimeout
- maxKeySize :: MaxKeySize
- decoder :: Decoder Settings
- decoderWithEnvVarPrefix :: Text -> Decoder Settings
- decoderWithCustomConnectionString :: Text -> Decoder Settings
- withQueryTimeoutMilliseconds :: Int -> Handler' x -> Task () (Handler' x)
- withoutQueryTimeout :: Handler' x -> Task () (Handler' x)
- jsonApi :: forall a key. (ToJSON a, FromJSON a) => (key -> Text) -> Api key a
- textApi :: (key -> Text) -> Api key Text
- byteStringApi :: (key -> Text) -> Api key ByteString
- data Api key a
- del :: Api key a -> NonEmpty key -> Query Int
- exists :: Api key a -> key -> Query Bool
- expire :: Api key a -> key -> Int -> Query ()
- get :: Api key a -> key -> Query (Maybe a)
- getset :: Api key a -> key -> a -> Query (Maybe a)
- mget :: Api key a -> Ord key => NonEmpty key -> Query (Dict key a)
- mset :: Api key a -> NonEmptyDict key a -> Query ()
- ping :: Api key a -> Query ()
- set :: Api key a -> key -> a -> Query ()
- setex :: Api key a -> key -> Int -> a -> Query ()
- setnx :: Api key a -> key -> a -> Query Bool
- query :: HasCallStack => Handler' x -> Query a -> Task Error a
- transaction :: HasCallStack => Handler' x -> Query a -> Task Error a
- data Query a
- data Error
- = RedisError Text
- | ConnectionLost
- | DecodingError Text
- | DecodingFieldError Text
- | LibraryError Text
- | TransactionAborted
- | TimeoutError
- | KeyExceedsMaxSize Text Int
- map :: (a -> b) -> Query a -> Query b
- map2 :: (a -> b -> c) -> Query a -> Query b -> Query c
- map3 :: (a -> b -> c -> d) -> Query a -> Query b -> Query c -> Query d
- sequence :: List (Query a) -> Query (List a)
- foldWithScan :: Handler' x -> Maybe Text -> Maybe Int -> ([Text] -> a -> Task Error a) -> a -> Task Error a
- script :: QuasiQuoter
- data ScriptParam
- eval :: (HasCallStack, RedisResult a) => Handler' x -> Script a -> Task Error a
Creating a redis handler
handler :: Text -> Settings -> Acquire Handler Source #
Produce a namespaced handler for Redis access.
handlerAutoExtendExpire :: Text -> Settings -> Acquire HandlerAutoExtendExpire Source #
Produce a namespaced handler for Redis access. This will ensure that we extend all keys accessed by a query by a configured default time (see Settings.defaultExpiry)
type Handler = Handler' 'NoAutoExtendExpire Source #
This is a type alias of a handler parametrized by a value that indicates that the auto extend feature is disabled. Note: The tick in front of NoAutoExtendExpire is not necessary, but good practice to indicate that we are lifting a value to the type level.
type HandlerAutoExtendExpire = Handler' 'AutoExtendExpire Source #
This is a type alias of a handler parametrized by a value that indicates that the auto extend feature is enabled. Note: The tick in front of AutoExtendExpire is not necessary, but good practice to indicate that we are lifting a value to the type level.
data Handler' (x :: HasAutoExtendExpire) Source #
The redis handler allows applications to run scoped IO A handler that can only be parametrized by a value of this kind. Meaning that we use the values of the type parameter at a type level.
data HasAutoExtendExpire Source #
We use this to parametrize the handler. It specifies if the handler has the auto extend expire feater enabled or not.
Constructors
NoAutoExtendExpire | |
AutoExtendExpire |
Settings required to initiate a redis connection.
Constructors
Settings | |
Fields
|
decoderWithEnvVarPrefix :: Text -> Decoder Settings Source #
decodes Settings from environmental variables prefixed with a Text >>> decoderWithEnvVarPrefix WORKER_
decoderWithCustomConnectionString :: Text -> Decoder Settings Source #
decodes Settings from environmental variables with custom connection string
withQueryTimeoutMilliseconds :: Int -> Handler' x -> Task () (Handler' x) Source #
Sets a timeout for the query in milliseconds.
withoutQueryTimeout :: Handler' x -> Task () (Handler' x) Source #
Disables timeout for query in milliseconds
Creating a redis API
jsonApi :: forall a key. (ToJSON a, FromJSON a) => (key -> Text) -> Api key a Source #
Creates a json API mapping a key
to a json-encodable-decodable type
data Key = Key { fieldA: Text, fieldB: Text } data Val = Val { ... } myJsonApi :: Redis.Api Key Val myJsonApi = Redis.jsonApi (\Key {fieldA, fieldB}-> Text.join "-" [fieldA, fieldB, "v1"])
byteStringApi :: (key -> Text) -> Api key ByteString Source #
Creates a Redis API mapping a key
to a ByteString
a API type can be used to enforce a mapping of keys to values. without an API type, it can be easy to naiively serialize the wrong type into a redis key.
Out of the box, we have helpers to support
- jsonApi
for json-encodable and decodable values
- textApi
for Text
values
- byteStringApi
for ByteString
values
Creating redis queries
del :: Api key a -> NonEmpty key -> Query Int Source #
Removes the specified keys. A key is ignored if it does not exist.
expire :: Api key a -> key -> Int -> Query () Source #
Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is often said to be volatile in Redis terminology.
get :: Api key a -> key -> Query (Maybe a) Source #
Get the value of key. If the key does not exist the special value Nothing is returned. An error is returned if the value stored at key is not a string, because GET only handles string values.
getset :: Api key a -> key -> a -> Query (Maybe a) Source #
Atomically sets key to value and returns the old value stored at key. Returns an error when key exists but does not hold a string value.
mget :: Api key a -> Ord key => NonEmpty key -> Query (Dict key a) Source #
Returns the values of all specified keys. For every key that does not hold a string value or does not exist, no value is returned. Because of this, the operation never fails.
mset :: Api key a -> NonEmptyDict key a -> Query () Source #
Sets the given keys to their respective values. MSET replaces existing values with new values, just as regular SET. See MSETNX if you don't want to overwrite existing values.
MSET is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged.
ping :: Api key a -> Query () Source #
Returns PONG if no argument is provided, otherwise return a copy of the argument as a bulk. This command is often used to test if a connection is still alive, or to measure latency.
set :: Api key a -> key -> a -> Query () Source #
Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. Any previous time to live associated with the key is discarded on successful SET operation.
setex :: Api key a -> key -> Int -> a -> Query () Source #
Set key to hold the string value and set key to timeout after a given number of seconds.
setnx :: Api key a -> key -> a -> Query Bool Source #
Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for "SET if Not eXists".
Running Redis queries
query :: HasCallStack => Handler' x -> Query a -> Task Error a Source #
Run a Query
.
Note: A Query
in this library can consist of one or more queries in sequence.
if a Query
contains multiple queries, it may make more sense, if possible
to run them using transaction
transaction :: HasCallStack => Handler' x -> Query a -> Task Error a Source #
Run a redis Query in a transaction. If the query contains several Redis commands they're all executed together, and Redis will guarantee other requests won't be able change values in between.
In redis terms, this is wrappping the Query
in MULTI
and `EXEC
see redis transaction semantics here: https://redis.io/topics/transactions
A Redis query
Redis Errors, scoped by where they originate.
Constructors
RedisError Text | |
ConnectionLost | |
DecodingError Text | |
DecodingFieldError Text | |
LibraryError Text | |
TransactionAborted | |
TimeoutError | |
KeyExceedsMaxSize Text Int |
map :: (a -> b) -> Query a -> Query b Source #
Used to map the type of a query to another type
useful in combination with transaction
map2 :: (a -> b -> c) -> Query a -> Query b -> Query c Source #
Used to combine two queries
Useful to combine two queries.
Redis.map2
(Maybe.map2 (,))
(Redis.get api1 key)
(Redis.get api2 key)
|> Redis.query redis
map3 :: (a -> b -> c -> d) -> Query a -> Query b -> Query c -> Query d Source #
Used to combine three queries Useful to combine three queries.
sequence :: List (Query a) -> Query (List a) Source #
Used to run a series of queries in sequence.
Useful to run a list of queries in sequence.
queries
|> Redis.sequence
|> Redis.query redis
foldWithScan :: Handler' x -> Maybe Text -> Maybe Int -> ([Text] -> a -> Task Error a) -> a -> Task Error a Source #
Use SCAN command to find keys matching a pattern, and "fold" over them in batches, producing a result value. keyMatchPattern A glob-like pattern to match keys, see https://redis.io/commands/keys/ approxCountPerBatch A hint for the batch size you want to process at once. Only approximate. processKeyBatch Function to process one batch of keys (provided as plain Text without namespace prefix) initAccumulator Initial value for the fold accumulator
Lua Scripting
script :: QuasiQuoter Source #
Quasi-quoter for creating a Redis Lua script with placeholders for Redis keys and arguments.
[script|SET ${Key "a-redis-key"} ${Literal 123}|]
- *IMPORTANT**: It is NOT SAFE to return Redis keys using this. Our Redis APIs inject "namespaces" (prefixes) on keys, and any keys returned by Lua will have their namespaces applied. If you try to reuse those keys in follow-up queries, namespaces will be doubly-applied.
data ScriptParam Source #
A type for enforcing parameters used in [script|${ ... }|] are either tagged as Key or Literal.
We need keys to be tagged, otherwise we can't implement mapKeys
and enforce namespacing
in Redis APIs.
We make this extra generic to allow us to provide nice error messages using TypeError in a type class below.
eval :: (HasCallStack, RedisResult a) => Handler' x -> Script a -> Task Error a Source #