hedis (empty) → 0.1
raw patch · 12 files changed
+1687/−0 lines, 12 filesdep +attoparsecdep +basedep +bytestringsetup-changed
Dependencies added: attoparsec, base, bytestring, bytestring-lexing, hedis, mtl, network, time
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- benchmark/Benchmark.hs +67/−0
- hedis.cabal +80/−0
- src/Database/Redis.hs +38/−0
- src/Database/Redis/Commands.hs +779/−0
- src/Database/Redis/Internal.hs +99/−0
- src/Database/Redis/ManualCommands.hs +271/−0
- src/Database/Redis/PubSub.hs +134/−0
- src/Database/Redis/Reply.hs +66/−0
- src/Database/Redis/Request.hs +24/−0
- src/Database/Redis/Types.hs +97/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Falko Peters++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Falko Peters nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ benchmark/Benchmark.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Control.Concurrent+import Control.Monad+import Control.Monad.Trans+import Data.Time+import Database.Redis+import Text.Printf++nRequests, nClients :: Int+nRequests = 10000+nClients = 50+++main :: IO ()+main = do+ ----------------------------------------------------------------------+ -- Preparation+ --+ conn <- connect "localhost" (PortNumber 6379)+ runRedis conn $ do+ Right _ <- mset [ ("k1","v1"), ("k2","v2"), ("k3","v3")+ , ("k4","v4"), ("k5","v5") ]+ return ()+ + disconnect conn+ + ----------------------------------------------------------------------+ -- Spawn clients+ --+ start <- newEmptyMVar+ done <- newEmptyMVar+ replicateM_ nClients $ forkIO $ do+ c <- connect "localhost" (PortNumber 6379)+ runRedis c $ forever $ do+ action <- liftIO $ takeMVar start+ replicateM_ (nRequests `div` nClients) $ action+ liftIO $ putMVar done ()++ let timeAction name action = do+ startT <- getCurrentTime+ replicateM_ nClients $ putMVar start action+ replicateM_ nClients $ takeMVar done+ stopT <- getCurrentTime+ let deltaT = realToFrac $ diffUTCTime stopT startT+ rqsPerSec = fromIntegral nRequests / deltaT :: Double+ putStrLn $ printf "%-10s %.2f Req/s" (name :: String) rqsPerSec++ ----------------------------------------------------------------------+ -- Benchmarks+ --+ timeAction "ping" $ do+ Right Pong <- ping+ return ()+ + timeAction "get" $ do+ Right Nothing <- get "key"+ return ()+ + timeAction "mget" $ do+ Right vs <- mget ["k1","k2","k3","k4","k5"]+ let expected = map Just ["v1","v2","v3","v4","v5"]+ True <- return $ vs == expected+ return ()+
+ hedis.cabal view
@@ -0,0 +1,80 @@+name: hedis+version: 0.1+synopsis:+ Client library for the Redis datastore: supports full command set, + pipelining.+Description:+ Redis is an open source, advanced key-value store. It is often referred to+ as a data structure server since keys can contain strings, hashes, lists,+ sets and sorted sets. This library is a Haskell client for the Redis+ datastore. Compared to other Haskell client libraries it has some+ advantages:+ .+ [Complete Redis 2.4 command set:] All Redis commands + (<http://redis.io/commands>) are available as haskell functions. The + exceptions to the rule are a handfull of internal and debugging+ commands: MONITOR, DEBUG OBJECT, DEBUG SEGFAULT, SYNC. If needed, these+ commands can easily be implemented by the library user with the+ 'sendRequest' function.+ .+ [Pipelining \"Just Works\":] Commands are pipelined + (<http://redis.io/topics/pipelining>) as much as possible without any+ work by the user.+ .+ [Enforced Pub\/Sub semantics:] When subscribed to the Redis Pub\/Sub server+ (<http://redis.io/topics/pubsub>), clients are not allowed to issue+ commands other than subscribing to or unsubscribing from channels. This+ library uses the type system to enforce the correct behavior.+ .+ For detailed documentation, see the "Database.Redis" module.+license: BSD3+license-file: LICENSE+author: Falko Peters+maintainer: falko.peters@gmail.com+copyright: Copyright (c) 2011 Falko Peters+category: Database+build-type: Simple+cabal-version: >=1.8++bug-reports: https://github.com/informatikr/hedis/issues++flag benchmark+ description: Build the benchmark executable.+ default: False++library+ hs-source-dirs: src+ ghc-options: -Wall+ ghc-prof-options: -auto-all+ exposed-modules: Database.Redis+ build-depends: attoparsec == 0.10.*,+ base == 4.*,+ bytestring == 0.9.*,+ bytestring-lexing == 0.2.*,+ mtl == 2.*,+ network == 2.*++ other-modules: Database.Redis.Internal,+ Database.Redis.PubSub,+ Database.Redis.Reply,+ Database.Redis.Request,+ Database.Redis.Types+ Database.Redis.Commands,+ Database.Redis.ManualCommands++executable hedis-benchmark+ main-is: benchmark/Benchmark.hs+ ghc-options: -Wall -rtsopts+ ghc-prof-options: -auto-all+ if ! flag(benchmark)+ buildable: False+ else+ build-depends:+ base >= 4,+ mtl == 2.0.*,+ hedis,+ time >= 1.2++source-repository head+ type: git+ location: https://github.com/informatikr/hedis
+ src/Database/Redis.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE OverloadedStrings, CPP #-}++module Database.Redis (+ + -- * The Redis Monad+ Redis(), runRedis,+ + -- * Connection+ RedisConn, connect, disconnect,+ HostName,PortID(..),defaultPort,+ + -- * Commands+ module Database.Redis.Commands,++ -- * Pub\/Sub+ module Database.Redis.PubSub,++ -- * Low-Level Requests and Replies+ sendRequest,+ -- |'sendRequest' can be used to implement one of the unimplemented + -- commands, as shown below.+ --+ -- @+ -- -- |Redis DEBUG OBJECT command+ -- debugObject :: ByteString -> 'Redis' (Either 'Reply' ByteString)+ -- debugObject key = 'sendRequest' [\"DEBUG\", \"OBJECT\", 'encode' key]+ -- @+ --+ Reply(..),Status(..),RedisResult(..)+ +) where++import Database.Redis.Internal+import Database.Redis.PubSub+import Database.Redis.Reply+import Database.Redis.Types++import Database.Redis.Commands
+ src/Database/Redis/Commands.hs view
@@ -0,0 +1,779 @@+-- Generated by GenCmds.hs. DO NOT EDIT.++{-# LANGUAGE OverloadedStrings #-}++module Database.Redis.Commands (++-- ** Connection+auth, -- |Authenticate to the server (<http://redis.io/commands/auth>).+echo, -- |Echo the given string (<http://redis.io/commands/echo>).+ping, -- |Ping the server (<http://redis.io/commands/ping>).+quit, -- |Close the connection (<http://redis.io/commands/quit>).+select, -- |Change the selected database for the current connection (<http://redis.io/commands/select>).++-- ** Keys+del, -- |Delete a key (<http://redis.io/commands/del>).+exists, -- |Determine if a key exists (<http://redis.io/commands/exists>).+expire, -- |Set a key's time to live in seconds (<http://redis.io/commands/expire>).+expireat, -- |Set the expiration for a key as a UNIX timestamp (<http://redis.io/commands/expireat>).+keys, -- |Find all keys matching the given pattern (<http://redis.io/commands/keys>).+move, -- |Move a key to another database (<http://redis.io/commands/move>).+objectRefcount, -- |Inspect the internals of Redis objects (<http://redis.io/commands/object>). The Redis command @OBJECT@ is split up into 'objectRefcount', 'objectEncoding', 'objectIdletime'.+objectEncoding, -- |Inspect the internals of Redis objects (<http://redis.io/commands/object>). The Redis command @OBJECT@ is split up into 'objectRefcount', 'objectEncoding', 'objectIdletime'.+objectIdletime, -- |Inspect the internals of Redis objects (<http://redis.io/commands/object>). The Redis command @OBJECT@ is split up into 'objectRefcount', 'objectEncoding', 'objectIdletime'.+persist, -- |Remove the expiration from a key (<http://redis.io/commands/persist>).+randomkey, -- |Return a random key from the keyspace (<http://redis.io/commands/randomkey>).+rename, -- |Rename a key (<http://redis.io/commands/rename>).+renamenx, -- |Rename a key, only if the new key does not exist (<http://redis.io/commands/renamenx>).+SortOpts(..),+defaultSortOpts,+SortOrder(..),+sort, -- |Sort the elements in a list, set or sorted set (<http://redis.io/commands/sort>). The Redis command @SORT@ is split up into 'sort', 'sortStore'.+sortStore, -- |Sort the elements in a list, set or sorted set (<http://redis.io/commands/sort>). The Redis command @SORT@ is split up into 'sort', 'sortStore'.+ttl, -- |Get the time to live for a key (<http://redis.io/commands/ttl>).+getType, -- |Determine the type stored at key (<http://redis.io/commands/type>).++-- ** Hashes+hdel, -- |Delete one or more hash fields (<http://redis.io/commands/hdel>).+hexists, -- |Determine if a hash field exists (<http://redis.io/commands/hexists>).+hget, -- |Get the value of a hash field (<http://redis.io/commands/hget>).+hgetall, -- |Get all the fields and values in a hash (<http://redis.io/commands/hgetall>).+hincrby, -- |Increment the integer value of a hash field by the given number (<http://redis.io/commands/hincrby>).+hkeys, -- |Get all the fields in a hash (<http://redis.io/commands/hkeys>).+hlen, -- |Get the number of fields in a hash (<http://redis.io/commands/hlen>).+hmget, -- |Get the values of all the given hash fields (<http://redis.io/commands/hmget>).+hmset, -- |Set multiple hash fields to multiple values (<http://redis.io/commands/hmset>).+hset, -- |Set the string value of a hash field (<http://redis.io/commands/hset>).+hsetnx, -- |Set the value of a hash field, only if the field does not exist (<http://redis.io/commands/hsetnx>).+hvals, -- |Get all the values in a hash (<http://redis.io/commands/hvals>).++-- ** Lists+blpop, -- |Remove and get the first element in a list, or block until one is available (<http://redis.io/commands/blpop>).+brpop, -- |Remove and get the last element in a list, or block until one is available (<http://redis.io/commands/brpop>).+brpoplpush, -- |Pop a value from a list, push it to another list and return it; or block until one is available (<http://redis.io/commands/brpoplpush>).+lindex, -- |Get an element from a list by its index (<http://redis.io/commands/lindex>).+linsertBefore, -- |Insert an element before or after another element in a list (<http://redis.io/commands/linsert>). The Redis command @LINSERT@ is split up into 'linsertBefore', 'linsertAfter'.+linsertAfter, -- |Insert an element before or after another element in a list (<http://redis.io/commands/linsert>). The Redis command @LINSERT@ is split up into 'linsertBefore', 'linsertAfter'.+llen, -- |Get the length of a list (<http://redis.io/commands/llen>).+lpop, -- |Remove and get the first element in a list (<http://redis.io/commands/lpop>).+lpush, -- |Prepend one or multiple values to a list (<http://redis.io/commands/lpush>).+lpushx, -- |Prepend a value to a list, only if the list exists (<http://redis.io/commands/lpushx>).+lrange, -- |Get a range of elements from a list (<http://redis.io/commands/lrange>).+lrem, -- |Remove elements from a list (<http://redis.io/commands/lrem>).+lset, -- |Set the value of an element in a list by its index (<http://redis.io/commands/lset>).+ltrim, -- |Trim a list to the specified range (<http://redis.io/commands/ltrim>).+rpop, -- |Remove and get the last element in a list (<http://redis.io/commands/rpop>).+rpoplpush, -- |Remove the last element in a list, append it to another list and return it (<http://redis.io/commands/rpoplpush>).+rpush, -- |Append one or multiple values to a list (<http://redis.io/commands/rpush>).+rpushx, -- |Append a value to a list, only if the list exists (<http://redis.io/commands/rpushx>).++-- ** Server+bgrewriteaof, -- |Asynchronously rewrite the append-only file (<http://redis.io/commands/bgrewriteaof>).+bgsave, -- |Asynchronously save the dataset to disk (<http://redis.io/commands/bgsave>).+configGet, -- |Get the value of a configuration parameter (<http://redis.io/commands/config-get>).+configResetstat, -- |Reset the stats returned by INFO (<http://redis.io/commands/config-resetstat>).+configSet, -- |Set a configuration parameter to the given value (<http://redis.io/commands/config-set>).+dbsize, -- |Return the number of keys in the selected database (<http://redis.io/commands/dbsize>).+flushall, -- |Remove all keys from all databases (<http://redis.io/commands/flushall>).+flushdb, -- |Remove all keys from the current database (<http://redis.io/commands/flushdb>).+info, -- |Get information and statistics about the server (<http://redis.io/commands/info>).+lastsave, -- |Get the UNIX time stamp of the last successful save to disk (<http://redis.io/commands/lastsave>).+save, -- |Synchronously save the dataset to disk (<http://redis.io/commands/save>).+shutdown, -- |Synchronously save the dataset to disk and then shut down the server (<http://redis.io/commands/shutdown>).+slaveof, -- |Make the server a slave of another instance, or promote it as master (<http://redis.io/commands/slaveof>).+slowlogGet, -- |Manages the Redis slow queries log (<http://redis.io/commands/slowlog>). The Redis command @SLOWLOG@ is split up into 'slowlogGet', 'slowlogLen', 'slowlogReset'.+slowlogLen, -- |Manages the Redis slow queries log (<http://redis.io/commands/slowlog>). The Redis command @SLOWLOG@ is split up into 'slowlogGet', 'slowlogLen', 'slowlogReset'.+slowlogReset, -- |Manages the Redis slow queries log (<http://redis.io/commands/slowlog>). The Redis command @SLOWLOG@ is split up into 'slowlogGet', 'slowlogLen', 'slowlogReset'.++-- ** Sets+sadd, -- |Add one or more members to a set (<http://redis.io/commands/sadd>).+scard, -- |Get the number of members in a set (<http://redis.io/commands/scard>).+sdiff, -- |Subtract multiple sets (<http://redis.io/commands/sdiff>).+sdiffstore, -- |Subtract multiple sets and store the resulting set in a key (<http://redis.io/commands/sdiffstore>).+sinter, -- |Intersect multiple sets (<http://redis.io/commands/sinter>).+sinterstore, -- |Intersect multiple sets and store the resulting set in a key (<http://redis.io/commands/sinterstore>).+sismember, -- |Determine if a given value is a member of a set (<http://redis.io/commands/sismember>).+smembers, -- |Get all the members in a set (<http://redis.io/commands/smembers>).+smove, -- |Move a member from one set to another (<http://redis.io/commands/smove>).+spop, -- |Remove and return a random member from a set (<http://redis.io/commands/spop>).+srandmember, -- |Get a random member from a set (<http://redis.io/commands/srandmember>).+srem, -- |Remove one or more members from a set (<http://redis.io/commands/srem>).+sunion, -- |Add multiple sets (<http://redis.io/commands/sunion>).+sunionstore, -- |Add multiple sets and store the resulting set in a key (<http://redis.io/commands/sunionstore>).++-- ** Sorted Sets+zadd, -- |Add one or more members to a sorted set, or update its score if it already exists (<http://redis.io/commands/zadd>).+zcard, -- |Get the number of members in a sorted set (<http://redis.io/commands/zcard>).+zcount, -- |Count the members in a sorted set with scores within the given values (<http://redis.io/commands/zcount>).+zincrby, -- |Increment the score of a member in a sorted set (<http://redis.io/commands/zincrby>).+Aggregate(..),+zinterstore, -- |Intersect multiple sorted sets and store the resulting sorted set in a new key (<http://redis.io/commands/zinterstore>). The Redis command @ZINTERSTORE@ is split up into 'zinterstore', 'zinterstoreWeights'.+zinterstoreWeights, -- |Intersect multiple sorted sets and store the resulting sorted set in a new key (<http://redis.io/commands/zinterstore>). The Redis command @ZINTERSTORE@ is split up into 'zinterstore', 'zinterstoreWeights'.+zrange, -- |Return a range of members in a sorted set, by index (<http://redis.io/commands/zrange>). The Redis command @ZRANGE@ is split up into 'zrange', 'zrangeWithscores'.+zrangeWithscores, -- |Return a range of members in a sorted set, by index (<http://redis.io/commands/zrange>). The Redis command @ZRANGE@ is split up into 'zrange', 'zrangeWithscores'.+zrangebyscore, -- |Return a range of members in a sorted set, by score (<http://redis.io/commands/zrangebyscore>). The Redis command @ZRANGEBYSCORE@ is split up into 'zrangebyscore', 'zrangebyscoreWithscores', 'zrangebyscoreLimit', 'zrangebyscoreWithscoresLimit'.+zrangebyscoreWithscores, -- |Return a range of members in a sorted set, by score (<http://redis.io/commands/zrangebyscore>). The Redis command @ZRANGEBYSCORE@ is split up into 'zrangebyscore', 'zrangebyscoreWithscores', 'zrangebyscoreLimit', 'zrangebyscoreWithscoresLimit'.+zrangebyscoreLimit, -- |Return a range of members in a sorted set, by score (<http://redis.io/commands/zrangebyscore>). The Redis command @ZRANGEBYSCORE@ is split up into 'zrangebyscore', 'zrangebyscoreWithscores', 'zrangebyscoreLimit', 'zrangebyscoreWithscoresLimit'.+zrangebyscoreWithscoresLimit, -- |Return a range of members in a sorted set, by score (<http://redis.io/commands/zrangebyscore>). The Redis command @ZRANGEBYSCORE@ is split up into 'zrangebyscore', 'zrangebyscoreWithscores', 'zrangebyscoreLimit', 'zrangebyscoreWithscoresLimit'.+zrank, -- |Determine the index of a member in a sorted set (<http://redis.io/commands/zrank>).+zrem, -- |Remove one or more members from a sorted set (<http://redis.io/commands/zrem>).+zremrangebyrank, -- |Remove all members in a sorted set within the given indexes (<http://redis.io/commands/zremrangebyrank>).+zremrangebyscore, -- |Remove all members in a sorted set within the given scores (<http://redis.io/commands/zremrangebyscore>).+zrevrange, -- |Return a range of members in a sorted set, by index, with scores ordered from high to low (<http://redis.io/commands/zrevrange>). The Redis command @ZREVRANGE@ is split up into 'zrevrange', 'zrevrangeWithscores'.+zrevrangeWithscores, -- |Return a range of members in a sorted set, by index, with scores ordered from high to low (<http://redis.io/commands/zrevrange>). The Redis command @ZREVRANGE@ is split up into 'zrevrange', 'zrevrangeWithscores'.+zrevrangebyscore, -- |Return a range of members in a sorted set, by score, with scores ordered from high to low (<http://redis.io/commands/zrevrangebyscore>). The Redis command @ZREVRANGEBYSCORE@ is split up into 'zrevrangebyscore', 'zrevrangebyscoreWithscores', 'zrevrangebyscoreLimit', 'zrevrangebyscoreWithscoresLimit'.+zrevrangebyscoreWithscores, -- |Return a range of members in a sorted set, by score, with scores ordered from high to low (<http://redis.io/commands/zrevrangebyscore>). The Redis command @ZREVRANGEBYSCORE@ is split up into 'zrevrangebyscore', 'zrevrangebyscoreWithscores', 'zrevrangebyscoreLimit', 'zrevrangebyscoreWithscoresLimit'.+zrevrangebyscoreLimit, -- |Return a range of members in a sorted set, by score, with scores ordered from high to low (<http://redis.io/commands/zrevrangebyscore>). The Redis command @ZREVRANGEBYSCORE@ is split up into 'zrevrangebyscore', 'zrevrangebyscoreWithscores', 'zrevrangebyscoreLimit', 'zrevrangebyscoreWithscoresLimit'.+zrevrangebyscoreWithscoresLimit, -- |Return a range of members in a sorted set, by score, with scores ordered from high to low (<http://redis.io/commands/zrevrangebyscore>). The Redis command @ZREVRANGEBYSCORE@ is split up into 'zrevrangebyscore', 'zrevrangebyscoreWithscores', 'zrevrangebyscoreLimit', 'zrevrangebyscoreWithscoresLimit'.+zrevrank, -- |Determine the index of a member in a sorted set, with scores ordered from high to low (<http://redis.io/commands/zrevrank>).+zscore, -- |Get the score associated with the given member in a sorted set (<http://redis.io/commands/zscore>).+zunionstore, -- |Add multiple sorted sets and store the resulting sorted set in a new key (<http://redis.io/commands/zunionstore>). The Redis command @ZUNIONSTORE@ is split up into 'zunionstore', 'zunionstoreWeights'.+zunionstoreWeights, -- |Add multiple sorted sets and store the resulting sorted set in a new key (<http://redis.io/commands/zunionstore>). The Redis command @ZUNIONSTORE@ is split up into 'zunionstore', 'zunionstoreWeights'.++-- ** Strings+append, -- |Append a value to a key (<http://redis.io/commands/append>).+decr, -- |Decrement the integer value of a key by one (<http://redis.io/commands/decr>).+decrby, -- |Decrement the integer value of a key by the given number (<http://redis.io/commands/decrby>).+get, -- |Get the value of a key (<http://redis.io/commands/get>).+getbit, -- |Returns the bit value at offset in the string value stored at key (<http://redis.io/commands/getbit>).+getrange, -- |Get a substring of the string stored at a key (<http://redis.io/commands/getrange>).+getset, -- |Set the string value of a key and return its old value (<http://redis.io/commands/getset>).+incr, -- |Increment the integer value of a key by one (<http://redis.io/commands/incr>).+incrby, -- |Increment the integer value of a key by the given number (<http://redis.io/commands/incrby>).+mget, -- |Get the values of all the given keys (<http://redis.io/commands/mget>).+mset, -- |Set multiple keys to multiple values (<http://redis.io/commands/mset>).+msetnx, -- |Set multiple keys to multiple values, only if none of the keys exist (<http://redis.io/commands/msetnx>).+set, -- |Set the string value of a key (<http://redis.io/commands/set>).+setbit, -- |Sets or clears the bit at offset in the string value stored at key (<http://redis.io/commands/setbit>).+setex, -- |Set the value and expiration of a key (<http://redis.io/commands/setex>).+setnx, -- |Set the value of a key, only if the key does not exist (<http://redis.io/commands/setnx>).+setrange, -- |Overwrite part of a string at key starting at the specified offset (<http://redis.io/commands/setrange>).+strlen, -- |Get the length of the value stored in a key (<http://redis.io/commands/strlen>).++-- ** Transactions+discard, -- |Discard all commands issued after MULTI (<http://redis.io/commands/discard>).+exec, -- |Execute all commands issued after MULTI (<http://redis.io/commands/exec>).+multi, -- |Mark the start of a transaction block (<http://redis.io/commands/multi>).+unwatch, -- |Forget about all watched keys (<http://redis.io/commands/unwatch>).+watch, -- |Watch the given keys to determine execution of the MULTI/EXEC block (<http://redis.io/commands/watch>).++-- * Unimplemented Commands+-- |These commands are not implemented, as of now. Library users can implement them with the 'sendRequest' function.+--+-- * EVAL (<http://redis.io/commands/eval>)+--+--+-- * MONITOR (<http://redis.io/commands/monitor>)+--+--+-- * DEBUG OBJECT (<http://redis.io/commands/debug-object>)+--+--+-- * DEBUG SEGFAULT (<http://redis.io/commands/debug-segfault>)+--+--+-- * SYNC (<http://redis.io/commands/sync>)+--+) where++import Prelude hiding (min,max)+import Data.ByteString (ByteString)+import Database.Redis.ManualCommands+import Database.Redis.Types+import Database.Redis.Internal+import Database.Redis.Reply++flushall+ :: Redis (Either Reply Status)+flushall = sendRequest (["FLUSHALL"] )++hdel+ :: ByteString -- ^ key+ -> [ByteString] -- ^ field+ -> Redis (Either Reply Bool)+hdel key field = sendRequest (["HDEL"] ++ [encode key] ++ map encode field )++hincrby+ :: ByteString -- ^ key+ -> ByteString -- ^ field+ -> Integer -- ^ increment+ -> Redis (Either Reply Integer)+hincrby key field increment = sendRequest (["HINCRBY"] ++ [encode key] ++ [encode field] ++ [encode increment] )++configResetstat+ :: Redis (Either Reply Status)+configResetstat = sendRequest (["CONFIG","RESETSTAT"] )++del+ :: [ByteString] -- ^ key+ -> Redis (Either Reply Integer)+del key = sendRequest (["DEL"] ++ map encode key )++zrevrank+ :: ByteString -- ^ key+ -> ByteString -- ^ member+ -> Redis (Either Reply Integer)+zrevrank key member = sendRequest (["ZREVRANK"] ++ [encode key] ++ [encode member] )++brpoplpush+ :: ByteString -- ^ source+ -> ByteString -- ^ destination+ -> Integer -- ^ timeout+ -> Redis (Either Reply ByteString)+brpoplpush source destination timeout = sendRequest (["BRPOPLPUSH"] ++ [encode source] ++ [encode destination] ++ [encode timeout] )++incrby+ :: ByteString -- ^ key+ -> Integer -- ^ increment+ -> Redis (Either Reply Integer)+incrby key increment = sendRequest (["INCRBY"] ++ [encode key] ++ [encode increment] )++rpop+ :: ByteString -- ^ key+ -> Redis (Either Reply ByteString)+rpop key = sendRequest (["RPOP"] ++ [encode key] )++setrange+ :: ByteString -- ^ key+ -> Integer -- ^ offset+ -> ByteString -- ^ value+ -> Redis (Either Reply Integer)+setrange key offset value = sendRequest (["SETRANGE"] ++ [encode key] ++ [encode offset] ++ [encode value] )++setbit+ :: ByteString -- ^ key+ -> Integer -- ^ offset+ -> ByteString -- ^ value+ -> Redis (Either Reply Integer)+setbit key offset value = sendRequest (["SETBIT"] ++ [encode key] ++ [encode offset] ++ [encode value] )++save+ :: Redis (Either Reply Status)+save = sendRequest (["SAVE"] )++echo+ :: ByteString -- ^ message+ -> Redis (Either Reply ByteString)+echo message = sendRequest (["ECHO"] ++ [encode message] )++blpop+ :: [ByteString] -- ^ key+ -> Integer -- ^ timeout+ -> Redis (Either Reply (ByteString,ByteString))+blpop key timeout = sendRequest (["BLPOP"] ++ map encode key ++ [encode timeout] )++sdiffstore+ :: ByteString -- ^ destination+ -> [ByteString] -- ^ key+ -> Redis (Either Reply Integer)+sdiffstore destination key = sendRequest (["SDIFFSTORE"] ++ [encode destination] ++ map encode key )++move+ :: ByteString -- ^ key+ -> Integer -- ^ db+ -> Redis (Either Reply Bool)+move key db = sendRequest (["MOVE"] ++ [encode key] ++ [encode db] )++multi+ :: Redis (Either Reply Status)+multi = sendRequest (["MULTI"] )++getrange+ :: ByteString -- ^ key+ -> Integer -- ^ start+ -> Integer -- ^ end+ -> Redis (Either Reply ByteString)+getrange key start end = sendRequest (["GETRANGE"] ++ [encode key] ++ [encode start] ++ [encode end] )++srem+ :: ByteString -- ^ key+ -> [ByteString] -- ^ member+ -> Redis (Either Reply Integer)+srem key member = sendRequest (["SREM"] ++ [encode key] ++ map encode member )++watch+ :: [ByteString] -- ^ key+ -> Redis (Either Reply Status)+watch key = sendRequest (["WATCH"] ++ map encode key )++getbit+ :: ByteString -- ^ key+ -> Integer -- ^ offset+ -> Redis (Either Reply Integer)+getbit key offset = sendRequest (["GETBIT"] ++ [encode key] ++ [encode offset] )++zcount+ :: ByteString -- ^ key+ -> Double -- ^ min+ -> Double -- ^ max+ -> Redis (Either Reply Integer)+zcount key min max = sendRequest (["ZCOUNT"] ++ [encode key] ++ [encode min] ++ [encode max] )++quit+ :: Redis (Either Reply Status)+quit = sendRequest (["QUIT"] )++msetnx+ :: [(ByteString,ByteString)] -- ^ keyValue+ -> Redis (Either Reply Bool)+msetnx keyValue = sendRequest (["MSETNX"] ++ concatMap (\(x,y) -> [encode x,encode y])keyValue )++sismember+ :: ByteString -- ^ key+ -> ByteString -- ^ member+ -> Redis (Either Reply Bool)+sismember key member = sendRequest (["SISMEMBER"] ++ [encode key] ++ [encode member] )++bgrewriteaof+ :: Redis (Either Reply Status)+bgrewriteaof = sendRequest (["BGREWRITEAOF"] )++hmset+ :: ByteString -- ^ key+ -> [(ByteString,ByteString)] -- ^ fieldValue+ -> Redis (Either Reply Status)+hmset key fieldValue = sendRequest (["HMSET"] ++ [encode key] ++ concatMap (\(x,y) -> [encode x,encode y])fieldValue )++scard+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+scard key = sendRequest (["SCARD"] ++ [encode key] )++zincrby+ :: ByteString -- ^ key+ -> Integer -- ^ increment+ -> ByteString -- ^ member+ -> Redis (Either Reply Double)+zincrby key increment member = sendRequest (["ZINCRBY"] ++ [encode key] ++ [encode increment] ++ [encode member] )++sinter+ :: [ByteString] -- ^ key+ -> Redis (Either Reply [ByteString])+sinter key = sendRequest (["SINTER"] ++ map encode key )++mset+ :: [(ByteString,ByteString)] -- ^ keyValue+ -> Redis (Either Reply Status)+mset keyValue = sendRequest (["MSET"] ++ concatMap (\(x,y) -> [encode x,encode y])keyValue )++rpoplpush+ :: ByteString -- ^ source+ -> ByteString -- ^ destination+ -> Redis (Either Reply ByteString)+rpoplpush source destination = sendRequest (["RPOPLPUSH"] ++ [encode source] ++ [encode destination] )++hlen+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+hlen key = sendRequest (["HLEN"] ++ [encode key] )++setex+ :: ByteString -- ^ key+ -> Integer -- ^ seconds+ -> ByteString -- ^ value+ -> Redis (Either Reply Status)+setex key seconds value = sendRequest (["SETEX"] ++ [encode key] ++ [encode seconds] ++ [encode value] )++sunionstore+ :: ByteString -- ^ destination+ -> [ByteString] -- ^ key+ -> Redis (Either Reply Integer)+sunionstore destination key = sendRequest (["SUNIONSTORE"] ++ [encode destination] ++ map encode key )++brpop+ :: [ByteString] -- ^ key+ -> Integer -- ^ timeout+ -> Redis (Either Reply (ByteString,ByteString))+brpop key timeout = sendRequest (["BRPOP"] ++ map encode key ++ [encode timeout] )++hgetall+ :: ByteString -- ^ key+ -> Redis (Either Reply [(ByteString,ByteString)])+hgetall key = sendRequest (["HGETALL"] ++ [encode key] )++dbsize+ :: Redis (Either Reply Integer)+dbsize = sendRequest (["DBSIZE"] )++lpop+ :: ByteString -- ^ key+ -> Redis (Either Reply ByteString)+lpop key = sendRequest (["LPOP"] ++ [encode key] )++hmget+ :: ByteString -- ^ key+ -> [ByteString] -- ^ field+ -> Redis (Either Reply [Maybe ByteString])+hmget key field = sendRequest (["HMGET"] ++ [encode key] ++ map encode field )++lrange+ :: ByteString -- ^ key+ -> Integer -- ^ start+ -> Integer -- ^ stop+ -> Redis (Either Reply [ByteString])+lrange key start stop = sendRequest (["LRANGE"] ++ [encode key] ++ [encode start] ++ [encode stop] )++expire+ :: ByteString -- ^ key+ -> Integer -- ^ seconds+ -> Redis (Either Reply Bool)+expire key seconds = sendRequest (["EXPIRE"] ++ [encode key] ++ [encode seconds] )++lastsave+ :: Redis (Either Reply Integer)+lastsave = sendRequest (["LASTSAVE"] )++llen+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+llen key = sendRequest (["LLEN"] ++ [encode key] )++decrby+ :: ByteString -- ^ key+ -> Integer -- ^ decrement+ -> Redis (Either Reply Integer)+decrby key decrement = sendRequest (["DECRBY"] ++ [encode key] ++ [encode decrement] )++exec+ :: Redis (Either Reply Reply)+exec = sendRequest (["EXEC"] )++mget+ :: [ByteString] -- ^ key+ -> Redis (Either Reply [Maybe ByteString])+mget key = sendRequest (["MGET"] ++ map encode key )++zadd+ :: ByteString -- ^ key+ -> [(Double,ByteString)] -- ^ scoreMember+ -> Redis (Either Reply Integer)+zadd key scoreMember = sendRequest (["ZADD"] ++ [encode key] ++ concatMap (\(x,y) -> [encode x,encode y])scoreMember )++keys+ :: ByteString -- ^ pattern+ -> Redis (Either Reply [ByteString])+keys pattern = sendRequest (["KEYS"] ++ [encode pattern] )++bgsave+ :: Redis (Either Reply Status)+bgsave = sendRequest (["BGSAVE"] )++slaveof+ :: ByteString -- ^ host+ -> ByteString -- ^ port+ -> Redis (Either Reply Status)+slaveof host port = sendRequest (["SLAVEOF"] ++ [encode host] ++ [encode port] )++getset+ :: ByteString -- ^ key+ -> ByteString -- ^ value+ -> Redis (Either Reply (Maybe ByteString))+getset key value = sendRequest (["GETSET"] ++ [encode key] ++ [encode value] )++rpushx+ :: ByteString -- ^ key+ -> ByteString -- ^ value+ -> Redis (Either Reply Integer)+rpushx key value = sendRequest (["RPUSHX"] ++ [encode key] ++ [encode value] )++setnx+ :: ByteString -- ^ key+ -> ByteString -- ^ value+ -> Redis (Either Reply Bool)+setnx key value = sendRequest (["SETNX"] ++ [encode key] ++ [encode value] )++zrank+ :: ByteString -- ^ key+ -> ByteString -- ^ member+ -> Redis (Either Reply Integer)+zrank key member = sendRequest (["ZRANK"] ++ [encode key] ++ [encode member] )++zremrangebyscore+ :: ByteString -- ^ key+ -> Double -- ^ min+ -> Double -- ^ max+ -> Redis (Either Reply Integer)+zremrangebyscore key min max = sendRequest (["ZREMRANGEBYSCORE"] ++ [encode key] ++ [encode min] ++ [encode max] )++ttl+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+ttl key = sendRequest (["TTL"] ++ [encode key] )++hkeys+ :: ByteString -- ^ key+ -> Redis (Either Reply [ByteString])+hkeys key = sendRequest (["HKEYS"] ++ [encode key] )++rpush+ :: ByteString -- ^ key+ -> [ByteString] -- ^ value+ -> Redis (Either Reply Integer)+rpush key value = sendRequest (["RPUSH"] ++ [encode key] ++ map encode value )++randomkey+ :: Redis (Either Reply ByteString)+randomkey = sendRequest (["RANDOMKEY"] )++spop+ :: ByteString -- ^ key+ -> Redis (Either Reply ByteString)+spop key = sendRequest (["SPOP"] ++ [encode key] )++hsetnx+ :: ByteString -- ^ key+ -> ByteString -- ^ field+ -> ByteString -- ^ value+ -> Redis (Either Reply Bool)+hsetnx key field value = sendRequest (["HSETNX"] ++ [encode key] ++ [encode field] ++ [encode value] )++configGet+ :: ByteString -- ^ parameter+ -> Redis (Either Reply [(ByteString,ByteString)])+configGet parameter = sendRequest (["CONFIG","GET"] ++ [encode parameter] )++hvals+ :: ByteString -- ^ key+ -> Redis (Either Reply [ByteString])+hvals key = sendRequest (["HVALS"] ++ [encode key] )++exists+ :: ByteString -- ^ key+ -> Redis (Either Reply Bool)+exists key = sendRequest (["EXISTS"] ++ [encode key] )++sunion+ :: [ByteString] -- ^ key+ -> Redis (Either Reply [ByteString])+sunion key = sendRequest (["SUNION"] ++ map encode key )++zrem+ :: ByteString -- ^ key+ -> [ByteString] -- ^ member+ -> Redis (Either Reply Integer)+zrem key member = sendRequest (["ZREM"] ++ [encode key] ++ map encode member )++smembers+ :: ByteString -- ^ key+ -> Redis (Either Reply [ByteString])+smembers key = sendRequest (["SMEMBERS"] ++ [encode key] )++ping+ :: Redis (Either Reply Status)+ping = sendRequest (["PING"] )++rename+ :: ByteString -- ^ key+ -> ByteString -- ^ newkey+ -> Redis (Either Reply Status)+rename key newkey = sendRequest (["RENAME"] ++ [encode key] ++ [encode newkey] )++decr+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+decr key = sendRequest (["DECR"] ++ [encode key] )++select+ :: Integer -- ^ index+ -> Redis (Either Reply Status)+select index = sendRequest (["SELECT"] ++ [encode index] )++hexists+ :: ByteString -- ^ key+ -> ByteString -- ^ field+ -> Redis (Either Reply Bool)+hexists key field = sendRequest (["HEXISTS"] ++ [encode key] ++ [encode field] )++auth+ :: ByteString -- ^ password+ -> Redis (Either Reply Status)+auth password = sendRequest (["AUTH"] ++ [encode password] )++sinterstore+ :: ByteString -- ^ destination+ -> [ByteString] -- ^ key+ -> Redis (Either Reply Integer)+sinterstore destination key = sendRequest (["SINTERSTORE"] ++ [encode destination] ++ map encode key )++shutdown+ :: Redis (Either Reply Status)+shutdown = sendRequest (["SHUTDOWN"] )++configSet+ :: ByteString -- ^ parameter+ -> ByteString -- ^ value+ -> Redis (Either Reply Status)+configSet parameter value = sendRequest (["CONFIG","SET"] ++ [encode parameter] ++ [encode value] )++renamenx+ :: ByteString -- ^ key+ -> ByteString -- ^ newkey+ -> Redis (Either Reply Bool)+renamenx key newkey = sendRequest (["RENAMENX"] ++ [encode key] ++ [encode newkey] )++expireat+ :: ByteString -- ^ key+ -> Integer -- ^ timestamp+ -> Redis (Either Reply Bool)+expireat key timestamp = sendRequest (["EXPIREAT"] ++ [encode key] ++ [encode timestamp] )++get+ :: ByteString -- ^ key+ -> Redis (Either Reply (Maybe ByteString))+get key = sendRequest (["GET"] ++ [encode key] )++lrem+ :: ByteString -- ^ key+ -> Integer -- ^ count+ -> ByteString -- ^ value+ -> Redis (Either Reply Integer)+lrem key count value = sendRequest (["LREM"] ++ [encode key] ++ [encode count] ++ [encode value] )++incr+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+incr key = sendRequest (["INCR"] ++ [encode key] )++zcard+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+zcard key = sendRequest (["ZCARD"] ++ [encode key] )++ltrim+ :: ByteString -- ^ key+ -> Integer -- ^ start+ -> Integer -- ^ stop+ -> Redis (Either Reply Status)+ltrim key start stop = sendRequest (["LTRIM"] ++ [encode key] ++ [encode start] ++ [encode stop] )++append+ :: ByteString -- ^ key+ -> ByteString -- ^ value+ -> Redis (Either Reply Integer)+append key value = sendRequest (["APPEND"] ++ [encode key] ++ [encode value] )++lset+ :: ByteString -- ^ key+ -> Integer -- ^ index+ -> ByteString -- ^ value+ -> Redis (Either Reply Status)+lset key index value = sendRequest (["LSET"] ++ [encode key] ++ [encode index] ++ [encode value] )++info+ :: Redis (Either Reply ByteString)+info = sendRequest (["INFO"] )++hget+ :: ByteString -- ^ key+ -> ByteString -- ^ field+ -> Redis (Either Reply (Maybe ByteString))+hget key field = sendRequest (["HGET"] ++ [encode key] ++ [encode field] )++sdiff+ :: [ByteString] -- ^ key+ -> Redis (Either Reply [ByteString])+sdiff key = sendRequest (["SDIFF"] ++ map encode key )++smove+ :: ByteString -- ^ source+ -> ByteString -- ^ destination+ -> ByteString -- ^ member+ -> Redis (Either Reply Bool)+smove source destination member = sendRequest (["SMOVE"] ++ [encode source] ++ [encode destination] ++ [encode member] )++flushdb+ :: Redis (Either Reply Status)+flushdb = sendRequest (["FLUSHDB"] )++set+ :: ByteString -- ^ key+ -> ByteString -- ^ value+ -> Redis (Either Reply Status)+set key value = sendRequest (["SET"] ++ [encode key] ++ [encode value] )++zremrangebyrank+ :: ByteString -- ^ key+ -> Integer -- ^ start+ -> Integer -- ^ stop+ -> Redis (Either Reply Integer)+zremrangebyrank key start stop = sendRequest (["ZREMRANGEBYRANK"] ++ [encode key] ++ [encode start] ++ [encode stop] )++sadd+ :: ByteString -- ^ key+ -> [ByteString] -- ^ member+ -> Redis (Either Reply Integer)+sadd key member = sendRequest (["SADD"] ++ [encode key] ++ map encode member )++lpush+ :: ByteString -- ^ key+ -> [ByteString] -- ^ value+ -> Redis (Either Reply Integer)+lpush key value = sendRequest (["LPUSH"] ++ [encode key] ++ map encode value )++lindex+ :: ByteString -- ^ key+ -> Integer -- ^ index+ -> Redis (Either Reply ByteString)+lindex key index = sendRequest (["LINDEX"] ++ [encode key] ++ [encode index] )++zscore+ :: ByteString -- ^ key+ -> ByteString -- ^ member+ -> Redis (Either Reply Double)+zscore key member = sendRequest (["ZSCORE"] ++ [encode key] ++ [encode member] )++strlen+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+strlen key = sendRequest (["STRLEN"] ++ [encode key] )++unwatch+ :: Redis (Either Reply Status)+unwatch = sendRequest (["UNWATCH"] )++hset+ :: ByteString -- ^ key+ -> ByteString -- ^ field+ -> ByteString -- ^ value+ -> Redis (Either Reply Bool)+hset key field value = sendRequest (["HSET"] ++ [encode key] ++ [encode field] ++ [encode value] )++lpushx+ :: ByteString -- ^ key+ -> ByteString -- ^ value+ -> Redis (Either Reply Integer)+lpushx key value = sendRequest (["LPUSHX"] ++ [encode key] ++ [encode value] )++discard+ :: Redis (Either Reply Status)+discard = sendRequest (["DISCARD"] )++srandmember+ :: ByteString -- ^ key+ -> Redis (Either Reply ByteString)+srandmember key = sendRequest (["SRANDMEMBER"] ++ [encode key] )++persist+ :: ByteString -- ^ key+ -> Redis (Either Reply Bool)+persist key = sendRequest (["PERSIST"] ++ [encode key] )+++-- * Unimplemented Commands+-- |These commands are not implemented, as of now. Library users can implement them with the 'sendRequest' function.+--+-- * EVAL (<http://redis.io/commands/eval>)+--+--+-- * MONITOR (<http://redis.io/commands/monitor>)+--+--+-- * DEBUG OBJECT (<http://redis.io/commands/debug-object>)+--+--+-- * DEBUG SEGFAULT (<http://redis.io/commands/debug-segfault>)+--+--+-- * SYNC (<http://redis.io/commands/sync>)+--+
+ src/Database/Redis/Internal.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Database.Redis.Internal (+ HostName,PortID(..), defaultPort,+ RedisConn(), connect, disconnect,+ Redis(),runRedis,+ send,+ recv,+ sendRequest+) where++import Control.Applicative+import Control.Monad.RWS+import Control.Concurrent+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy.Char8 as LB+import Network (HostName, PortID(..), connectTo)+import System.IO (Handle, hFlush, hClose, hIsOpen)++import Database.Redis.Reply+import Database.Redis.Request+import Database.Redis.Types++------------------------------------------------------------------------------+-- Connection+--++-- |Connection to a Redis server. Use the 'connect' function to create one.+--+-- A 'RedisConn' can only be used by a single thread at a time. This means that+-- calls to 'runRedis' or 'disconnet' may block when the 'RedisConn' is shared+-- between multiple threads.+data RedisConn = Conn (MVar (Handle, [Reply]))++withConn :: RedisConn+ -> (Handle -> [Reply] -> IO ([Reply], a))+ -> IO a+withConn (Conn conn) f = do+ (h,rs) <- takeMVar conn+ (rs',a) <- f h rs+ putMVar conn (h,rs')+ return a++-- |Opens a connection to a Redis server at the given host and port.+connect :: HostName -> PortID -> IO RedisConn+connect host port = do+ h <- connectTo host port+ replies <- parseReply <$> LB.hGetContents h+ Conn <$> newMVar (h, replies)++-- |Close the given connection.+--+-- May block when the given 'RedisConn' is shared between multiple threads. The+-- 'RedisConn' can not be re-used.+disconnect :: RedisConn -> IO ()+disconnect conn = withConn conn $ \h rs -> do+ open <- hIsOpen h+ when open (hClose h)+ return (rs, ())++-- | The Redis default port 6379. Equivalent to @'PortNumber' 6379@.+defaultPort :: PortID+defaultPort = PortNumber 6379++------------------------------------------------------------------------------+-- The Redis Monad+--+newtype Redis a = Redis (RWST Handle () [Reply] IO a)+ deriving (Monad, MonadIO, Functor, Applicative)++-- |Interact with a Redis datastore specified by the given 'RedisConn'.+--+-- May block when the given 'RedisConn' is shared between multiple threads.+runRedis :: RedisConn -> Redis a -> IO a+runRedis conn (Redis redis) = withConn conn $ \h rs -> do+ open <- hIsOpen h+ if open+ then do+ (a,rs',_) <- runRWST redis h rs+ return (rs',a)+ else error "Redis: disconnected"++send :: [B.ByteString] -> Redis ()+send req = Redis $ do+ h <- ask+ liftIO $ do+ B.hPut h $ renderRequest req+ hFlush h++recv :: Redis Reply+recv = Redis $ do+ -- head/tail avoids forcing the ':' constructor, enabling automatic+ -- pipelining.+ rs <- get+ put (tail rs)+ return (head rs)++-- |Sends a request to the Redis server, returning the 'decode'd reply.+sendRequest :: (RedisResult a) => [B.ByteString] -> Redis (Either Reply a)+sendRequest req = decode <$> (send req >> recv)
+ src/Database/Redis/ManualCommands.hs view
@@ -0,0 +1,271 @@+{-# LANGUAGE OverloadedStrings, RecordWildCards #-}++module Database.Redis.ManualCommands where++import Prelude hiding (min,max)+import Data.ByteString (ByteString)+import Database.Redis.Internal+import Database.Redis.Reply+import Database.Redis.Types+++objectRefcount+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+objectRefcount key = sendRequest ["OBJECT", "refcount", encode key]++objectIdletime+ :: ByteString -- ^ key+ -> Redis (Either Reply Integer)+objectIdletime key = sendRequest ["OBJECT", "idletime", encode key]++objectEncoding+ :: ByteString -- ^ key+ -> Redis (Either Reply ByteString)+objectEncoding key = sendRequest ["OBJECT", "encoding", encode key]++linsertBefore+ :: ByteString -- ^ key+ -> ByteString -- ^ pivot+ -> ByteString -- ^ value+ -> Redis (Either Reply Integer)+linsertBefore key pivot value =+ sendRequest ["LINSERT", encode key, "BEFORE", encode pivot, encode value]++linsertAfter+ :: ByteString -- ^ key+ -> ByteString -- ^ pivot+ -> ByteString -- ^ value+ -> Redis (Either Reply Integer)+linsertAfter key pivot value =+ sendRequest ["LINSERT", encode key, "AFTER", encode pivot, encode value]++getType+ :: ByteString -- ^ key+ -> Redis (Either Reply Status)+getType key = sendRequest ["TYPE", encode key]++slowlogGet+ :: Integer -- ^ cnt+ -> Redis (Either Reply Reply)+slowlogGet n = sendRequest ["SLOWLOG", "GET", encode n]++slowlogLen :: Redis (Either Reply Integer)+slowlogLen = sendRequest ["SLOWLOG", "LEN"]++slowlogReset :: Redis (Either Reply Status)+slowlogReset = sendRequest ["SLOWLOG", "RESET"]++zrange+ :: ByteString -- ^ key+ -> Integer -- ^ start+ -> Integer -- ^ stop+ -> Redis (Either Reply [ByteString])+zrange key start stop =+ sendRequest ["ZRANGE", encode key, encode start, encode stop]++zrangeWithscores+ :: ByteString -- ^ key+ -> Integer -- ^ start+ -> Integer -- ^ stop+ -> Redis (Either Reply [(ByteString,Double)])+zrangeWithscores key start stop =+ sendRequest ["ZRANGE", encode key, encode start, encode stop, "WITHSCORES"]++zrevrange+ :: ByteString -- ^ key+ -> Integer -- ^ start+ -> Integer -- ^ stop+ -> Redis (Either Reply [ByteString])+zrevrange key start stop =+ sendRequest ["ZREVRANGE", encode key, encode start, encode stop]++zrevrangeWithscores+ :: ByteString -- ^ key+ -> Integer -- ^ start+ -> Integer -- ^ stop+ -> Redis (Either Reply [(ByteString,Double)])+zrevrangeWithscores key start stop =+ sendRequest ["ZREVRANGE", encode key, encode start, encode stop+ ,"WITHSCORES"]++zrangebyscore+ :: ByteString -- ^ key+ -> Double -- ^ min+ -> Double -- ^ max+ -> Redis (Either Reply [ByteString])+zrangebyscore key min max =+ sendRequest ["ZRANGEBYSCORE", encode key, encode min, encode max]++zrangebyscoreWithscores+ :: ByteString -- ^ key+ -> Double -- ^ min+ -> Double -- ^ max+ -> Redis (Either Reply [(ByteString,Double)])+zrangebyscoreWithscores key min max =+ sendRequest ["ZRANGEBYSCORE", encode key, encode min, encode max+ ,"WITHSCORES"]++zrangebyscoreLimit+ :: ByteString -- ^ key+ -> Double -- ^ min+ -> Double -- ^ max+ -> Integer -- ^ offset+ -> Integer -- ^ count+ -> Redis (Either Reply [ByteString])+zrangebyscoreLimit key min max offset count =+ sendRequest ["ZRANGEBYSCORE", encode key, encode min, encode max+ ,"LIMIT", encode offset, encode count]++zrangebyscoreWithscoresLimit+ :: ByteString -- ^ key+ -> Double -- ^ min+ -> Double -- ^ max+ -> Integer -- ^ offset+ -> Integer -- ^ count+ -> Redis (Either Reply [(ByteString,Double)])+zrangebyscoreWithscoresLimit key min max offset count =+ sendRequest ["ZRANGEBYSCORE", encode key, encode min, encode max+ ,"WITHSCORES","LIMIT", encode offset, encode count]++zrevrangebyscore+ :: ByteString -- ^ key+ -> Double -- ^ max+ -> Double -- ^ min+ -> Redis (Either Reply [ByteString])+zrevrangebyscore key min max =+ sendRequest ["ZREVRANGEBYSCORE", encode key, encode min, encode max]++zrevrangebyscoreWithscores+ :: ByteString -- ^ key+ -> Double -- ^ max+ -> Double -- ^ min+ -> Redis (Either Reply [(ByteString,Double)])+zrevrangebyscoreWithscores key min max =+ sendRequest ["ZREVRANGEBYSCORE", encode key, encode min, encode max+ ,"WITHSCORES"]++zrevrangebyscoreLimit+ :: ByteString -- ^ key+ -> Double -- ^ max+ -> Double -- ^ min+ -> Integer -- ^ offset+ -> Integer -- ^ count+ -> Redis (Either Reply [ByteString])+zrevrangebyscoreLimit key min max offset count =+ sendRequest ["ZREVRANGEBYSCORE", encode key, encode min, encode max+ ,"LIMIT", encode offset, encode count]++zrevrangebyscoreWithscoresLimit+ :: ByteString -- ^ key+ -> Double -- ^ max+ -> Double -- ^ min+ -> Integer -- ^ offset+ -> Integer -- ^ count+ -> Redis (Either Reply [(ByteString,Double)])+zrevrangebyscoreWithscoresLimit key min max offset count =+ sendRequest ["ZREVRANGEBYSCORE", encode key, encode min, encode max+ ,"WITHSCORES","LIMIT", encode offset, encode count]++data SortOpts = SortOpts+ { sortBy :: Maybe ByteString+ , sortLimit :: (Integer,Integer)+ , sortGet :: [ByteString]+ , sortOrder :: SortOrder+ , sortAlpha :: Bool+ } deriving (Show, Eq)++defaultSortOpts :: SortOpts+defaultSortOpts = SortOpts+ { sortBy = Nothing+ , sortLimit = (0,-1)+ , sortGet = []+ , sortOrder = Asc+ , sortAlpha = False+ }++data SortOrder = Asc | Desc deriving (Show, Eq)++sortStore+ :: ByteString -- ^ key+ -> ByteString -- ^ destination+ -> SortOpts+ -> Redis (Either Reply Integer)+sortStore key dest = sortInternal key (Just dest)++sort+ :: ByteString -- ^ key+ -> SortOpts+ -> Redis (Either Reply [ByteString])+sort key = sortInternal key Nothing++sortInternal+ :: (RedisResult a)+ => ByteString -- ^ key+ -> Maybe ByteString -- ^ destination+ -> SortOpts+ -> Redis (Either Reply a)+sortInternal key destination SortOpts{..} = sendRequest $+ concat [["SORT", encode key], by, limit, get, order, alpha, store]+ where+ by = maybe [] (\pattern -> ["BY", pattern]) sortBy+ limit = let (off,cnt) = sortLimit in ["LIMIT", encode off, encode cnt]+ get = concatMap (\pattern -> ["GET", pattern]) sortGet+ order = case sortOrder of Desc -> ["DESC"]; Asc -> ["ASC"]+ alpha = ["ALPHA" | sortAlpha]+ store = maybe [] (\dest -> ["STORE", dest]) destination+++data Aggregate = Sum | Min | Max deriving (Show,Eq)++zunionstore+ :: ByteString -- ^ destination+ -> [ByteString] -- ^ keys+ -> Aggregate+ -> Redis (Either Reply Integer)+zunionstore dest keys =+ zstoreInternal "ZUNIONSTORE" dest keys []++zunionstoreWeights+ :: ByteString -- ^ destination+ -> [(ByteString,Double)] -- ^ weighted keys+ -> Aggregate+ -> Redis (Either Reply Integer)+zunionstoreWeights dest kws =+ let (keys,weights) = unzip kws+ in zstoreInternal "ZUNIONSTORE" dest keys weights++zinterstore+ :: ByteString -- ^ destination+ -> [ByteString] -- ^ keys+ -> Aggregate+ -> Redis (Either Reply Integer)+zinterstore dest keys =+ zstoreInternal "ZINTERSTORE" dest keys []++zinterstoreWeights+ :: ByteString -- ^ destination+ -> [(ByteString,Double)] -- ^ weighted keys+ -> Aggregate+ -> Redis (Either Reply Integer)+zinterstoreWeights dest kws =+ let (keys,weights) = unzip kws+ in zstoreInternal "ZINTERSTORE" dest keys weights++zstoreInternal+ :: ByteString -- ^ cmd+ -> ByteString -- ^ destination+ -> [ByteString] -- ^ keys+ -> [Double] -- ^ weights+ -> Aggregate+ -> Redis (Either Reply Integer)+zstoreInternal cmd dest keys weights aggregate = sendRequest $+ concat [ [cmd, dest, encode . toInteger $ length keys], keys+ , if null weights then [] else "WEIGHTS" : map encode weights+ , ["AGGREGATE", aggregate']+ ]+ where+ aggregate' = case aggregate of+ Sum -> "SUM"+ Min -> "MIN"+ Max -> "MAX"
+ src/Database/Redis/PubSub.hs view
@@ -0,0 +1,134 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Database.Redis.PubSub (+ publish,+ pubSub,+ Message(..),+ PubSub(),+ subscribe, unsubscribe,+ psubscribe, punsubscribe,+) where++import Control.Applicative+import Control.Monad.Writer+import Data.ByteString.Char8 (ByteString)+import Data.Maybe+import Database.Redis.Internal (Redis)+import qualified Database.Redis.Internal as Internal+import Database.Redis.Reply+import Database.Redis.Types+++newtype PubSub = PubSub [[ByteString]]++instance Monoid PubSub where+ mempty = PubSub []+ mappend (PubSub p) (PubSub p') = PubSub (p ++ p')++data Message = Message { msgChannel, msgMessage :: ByteString}+ | PMessage { msgPattern, msgChannel, msgMessage :: ByteString}+ deriving (Show)++------------------------------------------------------------------------------+-- Public Interface+--++-- |Post a message to a channel (<http://redis.io/commands/publish>).+publish+ :: ByteString -- ^ channel+ -> ByteString -- ^ message+ -> Redis (Either Reply Integer)+publish channel message =+ Internal.sendRequest ["PUBLISH", channel, message]++-- |Listen for messages published to the given channels+-- (<http://redis.io/commands/subscribe>).+subscribe+ :: [ByteString] -- ^ channel+ -> PubSub+subscribe = pubSubAction "SUBSCRIBE"++-- |Stop listening for messages posted to the given channels +-- (<http://redis.io/commands/unsubscribe>).+unsubscribe+ :: [ByteString] -- ^ channel+ -> PubSub+unsubscribe = pubSubAction "UNSUBSCRIBE"++-- |Listen for messages published to channels matching the given patterns +-- (<http://redis.io/commands/psubscribe>).+psubscribe+ :: [ByteString] -- ^ pattern+ -> PubSub+psubscribe = pubSubAction "PSUBSCRIBE"++-- |Stop listening for messages posted to channels matching the given patterns +-- (<http://redis.io/commands/punsubscribe>).+punsubscribe+ :: [ByteString] -- ^ pattern+ -> PubSub+punsubscribe = pubSubAction "PUNSUBSCRIBE"++-- |Listens to published messages on subscribed channels.+-- +-- The given callback function is called for each received message. +-- Subscription changes are triggered by the returned 'PubSub'. To keep+-- subscriptions unchanged, the callback can return 'mempty'.+-- +-- Example: Subscribe to the \"news\" channel indefinitely.+--+-- @+-- pubSub (subscribe [\"news\"]) $ \\msg -> do+-- putStrLn $ \"Message from \" ++ show (msgChannel msg)+-- return mempty+-- @+--+-- Example: Receive a single message from the \"chat\" channel.+--+-- @+-- pubSub (subscribe [\"chat\"]) $ \\msg -> do+-- putStrLn $ \"Message from \" ++ show (msgChannel msg)+-- return $ unsubscribe [\"chat\"]+-- @+-- +pubSub+ :: PubSub -- ^ Initial subscriptions.+ -> (Message -> IO PubSub) -- ^ Callback function.+ -> Redis ()+pubSub p callback = send p 0+ where+ send (PubSub cmds) pending = do+ mapM_ Internal.send cmds+ recv (pending + length cmds)++ recv pending = do+ reply <- Internal.recv + case decodeMsg reply of+ Left cnt+ | cnt == 0 && pending == 0+ -> return ()+ | otherwise -> send mempty (pending - 1)+ Right msg -> do act <- liftIO $ callback msg+ send act pending++------------------------------------------------------------------------------+-- Helpers+--++pubSubAction :: ByteString -> [ByteString] -> PubSub+pubSubAction cmd chans = PubSub [cmd : chans]++decodeMsg :: Reply -> Either Integer Message+decodeMsg (MultiBulk (Just (r0:r1:r2:rs))) = either (error "decodeMsg") id $ do+ kind <- decode r0+ case kind :: ByteString of+ "message" -> Right <$> decodeMessage+ "pmessage" -> Right <$> decodePMessage+ -- kind `elem` ["subscribe","unsubscribe","psubscribe","punsubscribe"]+ _ -> Left <$> decode r2+ where+ decodeMessage = Message <$> decode r1 <*> decode r2+ decodePMessage = PMessage <$> decode r1 <*> decode r2+ <*> decode (head rs)+ +decodeMsg r = error $ "not a message: " ++ show r
+ src/Database/Redis/Reply.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE OverloadedStrings #-}+module Database.Redis.Reply (Reply(..), parseReply) where++import Prelude hiding (error, take)+import Control.Applicative+import Data.Attoparsec.Char8+import qualified Data.Attoparsec.Lazy as P+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L++-- |Low-level representation of replies from the Redis server.+data Reply = SingleLine S.ByteString+ | Error S.ByteString+ | Integer Integer+ | Bulk (Maybe S.ByteString)+ | MultiBulk (Maybe [Reply])+ deriving (Eq, Show)++-- |Parse a lazy 'L.ByteString' into a (possibly infinite) list of 'Reply's.+parseReply :: L.ByteString -> [Reply]+parseReply input =+ case P.parse reply input of+ P.Fail _ _ _ -> []+ P.Done rest r -> r : parseReply rest++------------------------------------------------------------------------------+-- Reply parsers+--+reply :: Parser Reply+reply = choice [singleLine, integer, bulk, multiBulk, error]++singleLine :: Parser Reply+singleLine = SingleLine <$> '+' `prefixing` line++error :: Parser Reply+error = Error <$> '-' `prefixing` line++integer :: Parser Reply+integer = Integer <$> ':' `prefixing` signed decimal++bulk :: Parser Reply+bulk = Bulk <$> do + len <- '$' `prefixing` signed decimal+ if len < 0+ then return Nothing+ else Just <$> P.take len <* crlf++multiBulk :: Parser Reply+multiBulk = MultiBulk <$> do+ len <- '*' `prefixing` signed decimal+ if len < 0+ then return Nothing+ else Just <$> count len reply+++------------------------------------------------------------------------------+-- Helpers & Combinators+--+prefixing :: Char -> Parser a -> Parser a+c `prefixing` a = char c *> a <* crlf++crlf :: Parser S.ByteString+crlf = string "\r\n"++line :: Parser S.ByteString+line = takeTill (=='\r')
+ src/Database/Redis/Request.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+module Database.Redis.Request (renderRequest) where++import qualified Data.ByteString.Char8 as S+++renderRequest :: [S.ByteString] -> S.ByteString+renderRequest req = S.concat (argCnt:args)+ where+ argCnt = S.concat ["*", showBS (length req), crlf]+ args = map renderArg req+++renderArg :: S.ByteString -> S.ByteString+renderArg arg = S.concat ["$", argLen arg, crlf, arg, crlf]+ where+ argLen = showBS . S.length+++showBS :: (Show a) => a -> S.ByteString+showBS = S.pack . show++crlf :: S.ByteString+crlf = "\r\n"
+ src/Database/Redis/Types.hs view
@@ -0,0 +1,97 @@+{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances,+ TypeSynonymInstances, OverloadedStrings #-}++module Database.Redis.Types where++import Control.Applicative+import Control.Monad+import Data.ByteString.Char8 (ByteString, pack)+import Data.ByteString.Lex.Double (readDouble)+import Data.Maybe+import Database.Redis.Reply++------------------------------------------------------------------------------+-- Classes of types Redis understands+--+class RedisArg a where+ encode :: a -> ByteString++class RedisResult a where+ decode :: Reply -> Either Reply a++------------------------------------------------------------------------------+-- RedisArg instances+--+instance RedisArg ByteString where+ encode = id++instance RedisArg Integer where+ encode = pack . show++instance RedisArg Double where+ encode = pack . show++------------------------------------------------------------------------------+-- RedisResult instances+--+data Status = Ok | Pong | None | String | Hash | List | Set | ZSet | Queued+ deriving (Show, Eq)++instance RedisResult Reply where+ decode = Right++instance RedisResult ByteString where+ decode (Bulk (Just s)) = Right s+ decode r = Left r++instance RedisResult Integer where+ decode (Integer n) = Right n+ decode r = Left r++instance RedisResult Double where+ decode r = maybe (Left r) (Right . fst) . readDouble =<< decode r++instance RedisResult Status where+ decode (SingleLine s) = Right $ case s of+ "OK" -> Ok+ "PONG" -> Pong+ "none" -> None+ "string" -> String+ "hash" -> Hash+ "list" -> List+ "set" -> Set+ "zset" -> ZSet+ "QUEUED" -> Queued+ _ -> error $ "unhandled status-code: " ++ show s+ decode r = Left r++instance RedisResult Bool where+ decode (Integer 1) = Right True+ decode (Integer 0) = Right False+ decode r = Left r++instance (RedisResult a) => RedisResult (Maybe a) where+ decode (Bulk Nothing) = Right Nothing+ decode (MultiBulk Nothing) = Right Nothing+ decode r = Just <$> decode r++instance (RedisResult a) => RedisResult [a] where+ decode (MultiBulk (Just rs)) = mapM decode rs+ decode r = Left r+ +instance (RedisResult a, RedisResult b) => RedisResult (a,b) where+ decode (MultiBulk (Just [x, y])) = (,) <$> decode x <*> decode y+ decode r = Left r++instance (RedisResult k, RedisResult v) => RedisResult [(k,v)] where+ decode r = case r of+ (MultiBulk (Just rs)) -> pairs rs+ _ -> Left r+ where+ pairs [] = Right []+ pairs (_:[]) = Left r+ pairs (r1:r2:rs) = do+ k <- decode r1+ v <- decode r2+ kvs <- pairs rs+ return $ (k,v) : kvs