mirror of
https://github.com/fluencelabs/redis
synced 2025-06-18 19:51:22 +00:00
Scala client added thanks to Alejanro Crosa
This commit is contained in:
@ -0,0 +1,8 @@
|
||||
package com.redis
|
||||
|
||||
/**
|
||||
* Redis client Connection
|
||||
*
|
||||
*/
|
||||
|
||||
case class Connection(val host: String, val port: Int) extends SocketOperations
|
@ -0,0 +1,66 @@
|
||||
package com.redis
|
||||
|
||||
/**
|
||||
* Hash Ring
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.zip.CRC32
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.collection.mutable.Map
|
||||
|
||||
trait HashRing {
|
||||
|
||||
val replicas: Int
|
||||
|
||||
var sortedKeys: List[Long] = List()
|
||||
var cluster = new ArrayBuffer[Redis]
|
||||
val ring = Map[Long, Redis]()
|
||||
|
||||
// Adds the node to the hashRing
|
||||
// including a number of replicas.
|
||||
def addNode(node: Redis) = {
|
||||
cluster += node
|
||||
(1 to replicas).foreach{ replica =>
|
||||
val key = calculateChecksum(node+":"+replica)
|
||||
ring += (key -> node)
|
||||
sortedKeys = sortedKeys ::: List(key)
|
||||
}
|
||||
sortedKeys = sortedKeys.sort(_ < _)
|
||||
}
|
||||
|
||||
// get the node in the hash ring for this key
|
||||
def getNode(key: String) = getNodePos(key)._1
|
||||
|
||||
def getNodePos(key: String): (Redis, Int) = {
|
||||
val crc = calculateChecksum(key)
|
||||
val idx = binarySearch(crc)
|
||||
(ring(sortedKeys(idx)), idx)
|
||||
}
|
||||
|
||||
// TODO this should perform a Bynary search
|
||||
def binarySearch(value: Long): Int = {
|
||||
var upper = (sortedKeys.length -1)
|
||||
var lower = 0
|
||||
var idx = 0
|
||||
var comp: Long = 0
|
||||
|
||||
while(lower <= upper){
|
||||
idx = (lower + upper) / 2
|
||||
comp = sortedKeys(idx)
|
||||
|
||||
if(comp == value) { return idx }
|
||||
if(comp < value) { upper = idx -1 }
|
||||
if(comp > value) { lower = idx +1 }
|
||||
}
|
||||
return upper
|
||||
}
|
||||
|
||||
// Computes the CRC-32 of the given String
|
||||
def calculateChecksum(value: String): Long = {
|
||||
val checksum = new CRC32
|
||||
checksum.update(value.getBytes)
|
||||
checksum.getValue
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
package com.redis.operations
|
||||
|
||||
/**
|
||||
* Redis key space operations
|
||||
*
|
||||
*/
|
||||
|
||||
trait KeySpaceOperations{
|
||||
|
||||
val connection: Connection
|
||||
var db: Int
|
||||
|
||||
// KEYS
|
||||
// returns all the keys matching the glob-style pattern.
|
||||
def keys(pattern: String): Array[String] = {
|
||||
connection.write("KEYS "+pattern+"\r\n")
|
||||
connection.readResponse.toString.split(" ")
|
||||
}
|
||||
|
||||
// RANDKEY
|
||||
// return a randomly selected key from the currently selected DB.
|
||||
def randomKey: String = {
|
||||
connection.write("RANDOMKEY\r\n")
|
||||
connection.readResponse.toString.split('+')(1)
|
||||
}
|
||||
|
||||
// RENAME (oldkey, newkey)
|
||||
// atomically renames the key oldkey to newkey.
|
||||
def rename(oldkey: String, newkey: String): Boolean = {
|
||||
connection.write("RENAME "+oldkey+" "+newkey+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// RENAMENX (oldkey, newkey)
|
||||
// rename oldkey into newkey but fails if the destination key newkey already exists.
|
||||
def renamenx(oldkey: String, newkey: String): Boolean = {
|
||||
connection.write("RENAMENX "+oldkey+" "+newkey+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// DBSIZE
|
||||
// return the size of the db.
|
||||
def dbSize: Int = {
|
||||
connection.write("DBSIZE\r\n")
|
||||
connection.readInt
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package com.redis.operations
|
||||
|
||||
/**
|
||||
* Redis list operations
|
||||
*
|
||||
*/
|
||||
|
||||
trait ListOperations{
|
||||
|
||||
def getConnection(key: String): Connection
|
||||
|
||||
// add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key.
|
||||
// If the key does not exist an empty list is created just before the append operation. If the key exists but is not a List an error is returned.
|
||||
// LPUSH
|
||||
def pushHead(key: String, value: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("LPUSH "+key+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// RPUSH
|
||||
def pushTail(key: String, value: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("RPUSH "+key+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// LPOP
|
||||
// atomically return and remove the first (LPOP) or last (RPOP) element of the list
|
||||
def popHead(key: String): String = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("LPOP "+key+"\r\n")
|
||||
connection.readString
|
||||
}
|
||||
|
||||
// RPOP
|
||||
// atomically return and remove the first (LPOP) or last (RPOP) element of the list
|
||||
def popTail(key: String): String = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("RPOP "+key+"\r\n")
|
||||
connection.readString
|
||||
}
|
||||
|
||||
// LINDEX
|
||||
// return the especified element of the list stored at the specified key. 0 is the first element, 1 the second and so on.
|
||||
// Negative indexes are supported, for example -1 is the last element, -2 the penultimate and so on.
|
||||
def listIndex(key: String, index: Int): String = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("LINDEX "+key+" "+index+"\r\n")
|
||||
connection.readString
|
||||
}
|
||||
|
||||
// LSET
|
||||
// set the list element at index with the new value. Out of range indexes will generate an error
|
||||
def listSet(key: String, index: Int, value: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("LSET "+key+" "+index+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// LLEN
|
||||
// return the length of the list stored at the specified key.
|
||||
// If the key does not exist zero is returned (the same behaviour as for empty lists). If the value stored at key is not a list an error is returned.
|
||||
def listLength(key: String): Int = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("LLEN "+key+"\r\n")
|
||||
connection.readInt
|
||||
}
|
||||
|
||||
// LRANGE
|
||||
// return the specified elements of the list stored at the specified key.
|
||||
// Start and end are zero-based indexes. 0 is the first element of the list (the list head), 1 the next element and so on.
|
||||
def listRange(key: String, start: Int, end: Int): List[String] = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("LRANGE "+key+" "+start+" "+end+"\r\n")
|
||||
connection.readList
|
||||
}
|
||||
|
||||
// LTRIM
|
||||
// Trim an existing list so that it will contain only the specified range of elements specified.
|
||||
def listTrim(key: String, start: Int, end: Int): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("LTRIM "+key+" "+start+" "+end+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// LREM
|
||||
// Remove the first count occurrences of the value element from the list.
|
||||
def listRem(key: String, count: Int, value: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("LREM "+key+" "+count+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package com.redis.operations
|
||||
|
||||
/**
|
||||
* Redis node operations
|
||||
*
|
||||
*/
|
||||
|
||||
trait NodeOperations {
|
||||
|
||||
val connection: Connection
|
||||
var db: Int
|
||||
|
||||
// SAVE
|
||||
// save the DB on disk now.
|
||||
def save: Boolean = {
|
||||
connection.write("SAVE\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// BGSAVE
|
||||
// save the DB in the background.
|
||||
def bgSave: Boolean = {
|
||||
connection.write("BGSAVE\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// LASTSAVE
|
||||
// return the UNIX TIME of the last DB SAVE executed with success.
|
||||
def lastSave: Int = {
|
||||
connection.write("LASTSAVE\r\n")
|
||||
connection.readInt
|
||||
}
|
||||
|
||||
// SHUTDOWN
|
||||
// Stop all the clients, save the DB, then quit the server.
|
||||
def shutdown: Boolean = {
|
||||
connection.write("SHUTDOWN\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// MGET (key, key, key, ...)
|
||||
// get the values of all the specified keys.
|
||||
def mget(keys: String*) = {
|
||||
connection.write("MGET "+keys.mkString(" ")+"\r\n")
|
||||
connection.readList
|
||||
}
|
||||
|
||||
// INFO
|
||||
// the info command returns different information and statistics about the server.
|
||||
def info = {
|
||||
connection.write("INFO\r\n")
|
||||
connection.readResponse
|
||||
}
|
||||
|
||||
// MONITOR
|
||||
// is a debugging command that outputs the whole sequence of commands received by the Redis server.
|
||||
def monitor: Boolean = {
|
||||
connection.write("MONITOR\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// SLAVEOF
|
||||
// The SLAVEOF command can change the replication settings of a slave on the fly.
|
||||
def slaveOf(options: Any): Boolean = options match {
|
||||
case (host: String, port: Int) => {
|
||||
connection.write("SLAVEOF "+host+" "+port.toString+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
case _ => setAsMaster
|
||||
}
|
||||
|
||||
def setAsMaster: Boolean = {
|
||||
connection.write("SLAVEOF NO ONE\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// SELECT (index)
|
||||
// selects the DB to connect, defaults to 0 (zero).
|
||||
def selectDb(index: Int): Boolean = {
|
||||
connection.write("SELECT "+index+"\r\n")
|
||||
connection.readBoolean match {
|
||||
case true => { db = index; true }
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
// FLUSHDB the DB
|
||||
// removes all the DB data.
|
||||
def flushDb: Boolean = {
|
||||
connection.write("FLUSHDB\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// FLUSHALL the DB's
|
||||
// removes data from all the DB's.
|
||||
def flushAll: Boolean = {
|
||||
connection.write("FLUSHALL\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// MOVE
|
||||
// Move the specified key from the currently selected DB to the specified destination DB.
|
||||
def move(key: String, db: Int) = {
|
||||
connection.write("MOVE "+key+" "+db.toString+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// QUIT
|
||||
// exits the server.
|
||||
def quit: Boolean = {
|
||||
connection.write("QUIT\r\n")
|
||||
connection.disconnect
|
||||
}
|
||||
|
||||
// AUTH
|
||||
// auths with the server.
|
||||
def auth(secret: String): Boolean = {
|
||||
connection.write("AUTH "+secret+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package com.redis.operations
|
||||
|
||||
/**
|
||||
* Redis operations
|
||||
*
|
||||
*/
|
||||
|
||||
trait Operations{
|
||||
|
||||
def getConnection(key: String): Connection
|
||||
|
||||
// SET (key, value)
|
||||
// SET (key, value, expiry)
|
||||
// sets the key with the specified value, and with an optional expiry.
|
||||
def set(key: String, value: String) = setKey(key, value)
|
||||
def set(key: String, value: String, expiry: Int) = { setKey(key, value) && expire(key, expiry) }
|
||||
|
||||
// SET KEY (key, value)
|
||||
// sets the key with the specified value.
|
||||
def setKey(key: String, value: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SET "+key+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// EXPIRE (key, expiry)
|
||||
// sets the expire time (in sec.) for the specified key.
|
||||
def expire(key: String, expiry: Int): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("EXPIRE "+key+" "+expiry+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// GET (key)
|
||||
// gets the value for the specified key.
|
||||
def get(key: String): String = {
|
||||
val connection = getConnection(key)
|
||||
val a = connection.write("GET "+key+"\r\n")
|
||||
connection.readResponse match {
|
||||
case r: String => r.toString
|
||||
case _ => null
|
||||
}
|
||||
}
|
||||
|
||||
// GETSET (key, value)
|
||||
// is an atomic set this value and return the old value command.
|
||||
def getSet(key: String, value: String): String = {
|
||||
val connection = getConnection(key)
|
||||
val a = connection.write("GETSET "+key+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readResponse match {
|
||||
case r: String => r.toString
|
||||
case _ => null
|
||||
}
|
||||
}
|
||||
|
||||
// SETNX (key, value)
|
||||
// sets the value for the specified key, only if the key is not there.
|
||||
def setUnlessExists(key: String, value: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SETNX "+key+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// DELETE (key)
|
||||
// deletes the specified key.
|
||||
def delete(key: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("DEL "+key+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// INCR (key)
|
||||
// INCR (key, increment)
|
||||
// increments the specified key, optional the increment value.
|
||||
def incr(x: Any): Int = x match {
|
||||
case (key: String, increment: Int) => incrBy(key, increment)
|
||||
case (key: String) => incrOne(key)
|
||||
case _ => 0
|
||||
}
|
||||
def incrBy(key: String, increment: Int): Int = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("INCRBY "+key+" "+increment+"\r\n")
|
||||
connection.readInt
|
||||
}
|
||||
def incrOne(key: String): Int = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("INCR "+key+"\r\n")
|
||||
connection.readInt
|
||||
}
|
||||
|
||||
// DECR (key)
|
||||
// DECRBY (key, decrement)
|
||||
// decrements the specified key, optional the decrement value.
|
||||
def decr(key: String, decrement: Int) = decrBy(key, decrement)
|
||||
def decr(key: String) = decrOne(key)
|
||||
|
||||
def decrBy(key: String, decrement: Int): Int = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("DECRBY "+key+" "+decrement+"\r\n")
|
||||
connection.readInt
|
||||
}
|
||||
def decrOne(key: String): Int = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("DECR "+key+"\r\n")
|
||||
connection.readInt
|
||||
}
|
||||
|
||||
// EXISTS (key)
|
||||
// test if the specified key exists.
|
||||
def exists(key: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("EXISTS "+key+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// TYPE (key)
|
||||
// return the type of the value stored at key in form of a string.
|
||||
def getType(key: String): Any = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("TYPE "+key+"\r\n")
|
||||
connection.readResponse
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package com.redis.operations
|
||||
|
||||
/**
|
||||
* Redis set operations
|
||||
*
|
||||
*/
|
||||
|
||||
trait SetOperations{
|
||||
|
||||
def getConnection(key: String): Connection
|
||||
|
||||
// SADD
|
||||
// Add the specified member to the set value stored at key.
|
||||
def setAdd(key: String, value: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SADD "+key+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// SREM
|
||||
// Remove the specified member from the set value stored at key.
|
||||
def setDelete(key: String, value: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SREM "+key+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// SCARD
|
||||
// Return the number of elements (the cardinality) of the Set at key.
|
||||
def setCount(key: String): Int = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SCARD "+key+"\r\n")
|
||||
connection.readInt
|
||||
}
|
||||
|
||||
// SMEMBERS
|
||||
// Return all the members of the Set value at key.
|
||||
def setMembers(key: String): Set[String] = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SMEMBERS "+key+"\r\n")
|
||||
connection.readSet
|
||||
}
|
||||
|
||||
// SPOP
|
||||
// Remove and return (pop) a random element from the Set value at key.
|
||||
def setPop(key: String): String = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SPOP "+key+"\r\n")
|
||||
connection.readString
|
||||
}
|
||||
|
||||
// SMOVE
|
||||
// Move the specified member from one Set to another atomically.
|
||||
def setMove(sourceKey: String, destKey: String, value: String): Boolean = {
|
||||
val connection = getConnection(sourceKey)
|
||||
connection.write("SMOVE "+sourceKey+" "+destKey+" "+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// SISMEMBER
|
||||
// Test if the specified value is a member of the Set at key.
|
||||
def setMemberExists(key: String, value: String): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SISMEMBER "+key+" "+value.length+"\r\n"+value+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// SINTER
|
||||
// Return the intersection between the Sets stored at key1, key2, ..., keyN.
|
||||
def setIntersect(keys: String*): Set[String] = {
|
||||
val connection = getConnection(keys(0))
|
||||
connection.write("SINTER "+keys.mkString(" ")+"\r\n")
|
||||
connection.readSet
|
||||
}
|
||||
|
||||
// SINTERSTORE
|
||||
// Compute the intersection between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey.
|
||||
def setInterStore(key: String, keys: String*): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SINTERSTORE "+key+" "+keys.mkString(" ")+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// SDIFF
|
||||
// Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN.
|
||||
def setDiff(keys: String*): Set[String] = {
|
||||
val connection = getConnection(keys(0))
|
||||
connection.write("SDIFF "+keys.mkString(" ")+"\r\n")
|
||||
connection.readSet
|
||||
}
|
||||
|
||||
// SDIFFSTORE
|
||||
// Compute the difference between the Set key1 and all the Sets key2, ..., keyN, and store the resulting Set at dstkey.
|
||||
def setDiffStore(key: String, keys: String*): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SDIFFSTORE "+key+" "+keys.mkString(" ")+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
|
||||
// SUNION
|
||||
// Return the union between the Sets stored at key1, key2, ..., keyN.
|
||||
def setUnion(keys: String*): Set[String] = {
|
||||
val connection = getConnection(keys(0))
|
||||
connection.write("SUNION "+keys.mkString(" ")+"\r\n")
|
||||
connection.readSet
|
||||
}
|
||||
|
||||
// SUNIONSTORE
|
||||
// Compute the union between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey.
|
||||
def setUnionStore(key: String, keys: String*): Boolean = {
|
||||
val connection = getConnection(key)
|
||||
connection.write("SUNIONSTORE "+key+" "+keys.mkString(" ")+"\r\n")
|
||||
connection.readBoolean
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.redis.operations
|
||||
|
||||
/**
|
||||
* Redis sort operations
|
||||
*
|
||||
*/
|
||||
|
||||
trait SortOperations{
|
||||
|
||||
def getConnection(key: String): Connection
|
||||
|
||||
// SORT
|
||||
// Sort a Set or a List accordingly to the specified parameters.
|
||||
def sort(args: Any): List[String] = args match {
|
||||
case (key: String, command: String) => doSort(key, command)
|
||||
case (key: String) => doSort(key, "")
|
||||
}
|
||||
|
||||
def doSort(key: String, command: String): List[String] = {
|
||||
val connection = getConnection(key)
|
||||
if(command != "") {
|
||||
connection.write("SORT "+key+" "+command+"\r\n")
|
||||
} else {
|
||||
connection.write("SORT "+key+"\r\n")
|
||||
}
|
||||
connection.readList
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.redis
|
||||
|
||||
import com.redis.operations._
|
||||
|
||||
/**
|
||||
* Redis client
|
||||
*
|
||||
*/
|
||||
|
||||
class Redis(val host: String, val port: Int) extends Operations with ListOperations with SetOperations with NodeOperations with KeySpaceOperations with SortOperations {
|
||||
|
||||
// auxiliary constructor
|
||||
def this() = this("localhost", 6379)
|
||||
|
||||
// Points to the connection to a server instance
|
||||
val connection = Connection(host, port)
|
||||
var db: Int = 0
|
||||
|
||||
// Connect and Disconnect to the Redis server
|
||||
def connect = connection.connect
|
||||
def disconnect = connection.disconnect
|
||||
def connected: Boolean = connection.connected
|
||||
|
||||
// Establish the connection to the server instance on initialize
|
||||
connect
|
||||
|
||||
// Get Redis Client connection.
|
||||
def getConnection(key: String) = getConnection
|
||||
def getConnection = connection
|
||||
|
||||
// Outputs a formatted representation of the Redis server.
|
||||
override def toString = connection.host+":"+connection.port+" <connected:"+connection.connected+">"
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.redis
|
||||
|
||||
import com.redis.operations._
|
||||
|
||||
/**
|
||||
* Redis cluster
|
||||
*
|
||||
*/
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
class RedisCluster(val hosts: String*) extends Operations with ListOperations with SetOperations with HashRing with SortOperations {
|
||||
|
||||
// Get Redis Client connection inside the HashRing.
|
||||
def getConnection(key: String) = {
|
||||
getNode(key).connection
|
||||
}
|
||||
|
||||
// Default value used on MemCache client.
|
||||
private val NUMBER_OF_REPLICAS = 160
|
||||
val replicas = NUMBER_OF_REPLICAS
|
||||
|
||||
// Outputs a formatted representation of the Redis server.
|
||||
override def toString = cluster.mkString(", ")
|
||||
|
||||
// Connect the client and add it to the cluster.
|
||||
def connectClient(host: String): Boolean = {
|
||||
val h = host.split(":")(0)
|
||||
val p = host.split(":")(1)
|
||||
val client = new Redis(h.toString, p.toString.toInt)
|
||||
addNode(client)
|
||||
client.connected
|
||||
}
|
||||
|
||||
// Connect all clients in the cluster.
|
||||
def connect = cluster.map(c => c.connect)
|
||||
|
||||
// Initialize cluster.
|
||||
def initialize_cluster = hosts.map(connectClient(_))
|
||||
|
||||
initialize_cluster
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
package com.redis
|
||||
|
||||
/**
|
||||
* Socket operations
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io._
|
||||
import java.net.Socket
|
||||
|
||||
trait SocketOperations {
|
||||
|
||||
// Response codes from the Redis server
|
||||
// they tell you what's coming next from the server.
|
||||
val ERR: String = "-"
|
||||
val OK: String = "+OK"
|
||||
val SINGLE: String = "+"
|
||||
val BULK: String = "$"
|
||||
val MULTI: String = "*"
|
||||
val INT:String = ":"
|
||||
|
||||
val host: String
|
||||
val port: Int
|
||||
|
||||
// File descriptors.
|
||||
var socket: Socket = null
|
||||
var out: OutputStream = null
|
||||
var in: BufferedReader = null
|
||||
|
||||
def getOutputStream: OutputStream = out
|
||||
def getInputStream: BufferedReader = in
|
||||
def getSocket: Socket = socket
|
||||
|
||||
def connected = { getSocket != null }
|
||||
def reconnect = { disconnect && connect; }
|
||||
|
||||
// Connects the socket, and sets the input and output streams.
|
||||
def connect: Boolean = {
|
||||
try {
|
||||
socket = new Socket(host, port)
|
||||
out = getSocket.getOutputStream
|
||||
in = new BufferedReader(new InputStreamReader(getSocket.getInputStream));
|
||||
true
|
||||
} catch {
|
||||
case _ => clear_fd; false;
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnects the socket.
|
||||
def disconnect: Boolean = {
|
||||
try {
|
||||
socket.close
|
||||
out.close
|
||||
in.close
|
||||
clear_fd
|
||||
true
|
||||
} catch {
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
def clear_fd = {
|
||||
socket = null
|
||||
out = null
|
||||
in = null
|
||||
}
|
||||
|
||||
// Reads the server responses as Scala types.
|
||||
def readString: String = readResponse.toString // Reads the server response as an Int
|
||||
def readInt: Int = Integer.parseInt(readResponse.toString) // Reads the server response as an Int
|
||||
def readList: List[String] = listReply(readResponse.toString) // Reads the server response as a List
|
||||
def readSet: Set[String] = setReply(readResponse.toString) // Reads the server response as a String
|
||||
def readBoolean: Boolean = readResponse match {
|
||||
case 1 => true
|
||||
case OK => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
// Read from Input Stream.
|
||||
def readline: String = {
|
||||
try {
|
||||
getInputStream.readLine()
|
||||
} catch {
|
||||
case _ => ERR;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the type of response the server is going to send.
|
||||
def readtype = {
|
||||
val res = readline
|
||||
if(res !=null){
|
||||
(res(0).toString(), res)
|
||||
}else{
|
||||
("-", "")
|
||||
}
|
||||
}
|
||||
|
||||
// Reads the response from the server based on the response code.
|
||||
def readResponse = {
|
||||
|
||||
val responseType = readtype
|
||||
try{
|
||||
responseType._1 match {
|
||||
case ERR => reconnect; // RECONNECT
|
||||
case SINGLE => lineReply(responseType._2)
|
||||
case BULK => bulkReply(responseType._2)
|
||||
case MULTI => responseType._2
|
||||
case INT => integerReply(responseType._2)
|
||||
case _ => reconnect; // RECONNECT
|
||||
}
|
||||
}catch{
|
||||
case e: Exception => false
|
||||
}
|
||||
}
|
||||
|
||||
def integerReply(response: String): Int = Integer.parseInt(response.split(":")(1).toString)
|
||||
|
||||
def lineReply(response: String): String = response
|
||||
|
||||
def listReply(response: String): List[String] = {
|
||||
val total = Integer.parseInt(response.split('*')(1))
|
||||
var list: List[String] = List()
|
||||
for(i <- 1 to total){
|
||||
list = (list ::: List(bulkReply(readtype._2)))
|
||||
}
|
||||
list
|
||||
}
|
||||
|
||||
def bulkReply(response: String) = {
|
||||
if(response(1).toString() != ERR){
|
||||
var length: Int = Integer.parseInt(response.split('$')(1).split("\r\n")(0))
|
||||
var line, res: String = ""
|
||||
while(length >= 0){
|
||||
line = readline
|
||||
length -= (line.length+2)
|
||||
res += line
|
||||
if(length > 0) res += "\r\n"
|
||||
}
|
||||
res
|
||||
}else{ null }
|
||||
}
|
||||
|
||||
def setReply(response: String): Set[String] = {
|
||||
val total = Integer.parseInt(response.split('*')(1))
|
||||
var set: Set[String] = Set()
|
||||
for(i <- 1 to total){
|
||||
set += bulkReply(readtype._2)
|
||||
}
|
||||
set
|
||||
}
|
||||
|
||||
// Wraper for the socket write operation.
|
||||
def write_to_socket(data: String)(op: OutputStream => Unit) = op(getOutputStream)
|
||||
|
||||
// Writes data to a socket using the specified block.
|
||||
def write(data: String) = {
|
||||
if(!connected) connect;
|
||||
write_to_socket(data){
|
||||
getSocket =>
|
||||
try {
|
||||
getSocket.write(data.getBytes)
|
||||
} catch {
|
||||
case _ => reconnect;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
import org.mockito.Mockito.doNothing
|
||||
|
||||
object RedisClientSpec extends Specification with Mockito {
|
||||
|
||||
"Redis Client" should {
|
||||
var client: Redis = null
|
||||
|
||||
"print formatted client status" in {
|
||||
client = new Redis("localhost", 121212)
|
||||
client.toString must be matching("localhost:121212 <connected:false>")
|
||||
}
|
||||
|
||||
"get the same connection when passing key para or not since it's a single node" in {
|
||||
client.getConnection("key") mustEqual client.getConnection
|
||||
}
|
||||
|
||||
"use db zero as default" in {
|
||||
client.db mustEqual 0
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
import org.mockito.Mockito.doNothing
|
||||
|
||||
object RedisClusterSpec extends Specification with Mockito {
|
||||
|
||||
"Redis Cluster" should {
|
||||
var cluster: RedisCluster = null
|
||||
var mockedRedis: Redis = null
|
||||
|
||||
doBefore {
|
||||
cluster = new RedisCluster("localhost:11221", "localhost:99991")
|
||||
mockedRedis = mock[Redis]
|
||||
}
|
||||
|
||||
"print formatted client status" in {
|
||||
cluster.toString must be matching("localhost:11221 <connected:false>, localhost:99991 <connected:false>")
|
||||
}
|
||||
|
||||
"get the connection for the specified key" in {
|
||||
cluster.getConnection("key") mustEqual Connection("localhost", 99991)
|
||||
cluster.getConnection("anotherkey") mustEqual Connection("localhost", 11221)
|
||||
}
|
||||
|
||||
"use the default number of replicas" in {
|
||||
cluster.replicas mustEqual 160
|
||||
}
|
||||
|
||||
"initialize cluster" in {
|
||||
val initializedCluster = cluster.initialize_cluster
|
||||
initializedCluster.size mustEqual 2
|
||||
initializedCluster(0) mustEqual false
|
||||
initializedCluster(1) mustEqual false
|
||||
}
|
||||
|
||||
"connect all the redis instances" in {
|
||||
cluster.cluster(1) = mockedRedis
|
||||
|
||||
cluster.cluster(1).connect returns true
|
||||
val connectResult = cluster.connect
|
||||
connectResult.size mustEqual 2
|
||||
connectResult(0) mustEqual false
|
||||
connectResult(1) mustEqual true
|
||||
cluster.cluster(1).connect was called
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
|
||||
import java.io._
|
||||
import java.net.Socket
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
|
||||
class SocketOperationTest(val host:String, val port: Int) extends SocketOperations
|
||||
|
||||
object SocketOperationsSpec extends Specification with Mockito {
|
||||
|
||||
"Socket Operations" should {
|
||||
var socketOperation: SocketOperationTest = null
|
||||
var socket: Socket = null
|
||||
var in: BufferedReader = null
|
||||
|
||||
doBefore {
|
||||
socketOperation = new SocketOperationTest("localhost", 6379666)
|
||||
socket = mock[Socket]
|
||||
in = mock[BufferedReader]
|
||||
socketOperation.socket = socket
|
||||
socketOperation.in = in
|
||||
}
|
||||
|
||||
def readOkFromInput = { when(in.readLine()).thenReturn(socketOperation.OK) }
|
||||
def readSingleFromInput = { when(in.readLine()).thenReturn(socketOperation.SINGLE) }
|
||||
def readBulkFromInput = { in.readLine() returns("$6\r\nfoobar\r\n") thenReturns("$6\r\nfoobar\r\n") }
|
||||
def readIntFromInput = { when(in.readLine()).thenReturn(socketOperation.INT+"666") }
|
||||
|
||||
"tell if it's connected" in {
|
||||
socketOperation.connected mustEqual true
|
||||
socketOperation.socket = null
|
||||
socketOperation.connected mustEqual false
|
||||
}
|
||||
|
||||
"return false when can't connect" in {
|
||||
socketOperation.connect mustEqual false
|
||||
}
|
||||
|
||||
"return current data input stream" in {
|
||||
socketOperation.getInputStream mustEqual in
|
||||
}
|
||||
|
||||
"read a line from socket" in {
|
||||
readOkFromInput
|
||||
socketOperation.in mustEqual in
|
||||
socketOperation.readline mustEqual socketOperation.OK
|
||||
}
|
||||
|
||||
"read type response" in {
|
||||
readOkFromInput
|
||||
socketOperation.readtype mustEqual ("+", socketOperation.OK)
|
||||
}
|
||||
|
||||
"when reading responses" in {
|
||||
|
||||
"read OK" in {
|
||||
readOkFromInput
|
||||
socketOperation.readResponse mustEqual socketOperation.OK
|
||||
}
|
||||
|
||||
"read single line" in {
|
||||
readSingleFromInput
|
||||
socketOperation.readResponse mustEqual socketOperation.SINGLE
|
||||
}
|
||||
|
||||
"reconnect on error" in {
|
||||
socketOperation.readResponse mustEqual false
|
||||
socket.close was called
|
||||
socketOperation.connected mustEqual true
|
||||
}
|
||||
|
||||
"read in bulk" in {
|
||||
// readBulkFromInput
|
||||
// this shouldn't be the response, it doesn't seem to work return and then returns.
|
||||
// Here's what should happen: '$6\r\n' on first readLine and then 'foobar\r\n'
|
||||
readBulkFromInput
|
||||
socketOperation.readtype mustEqual ("$", "$6\r\nfoobar\r\n")
|
||||
socketOperation.readResponse mustEqual "$6\r\nfoobar\r\n"
|
||||
socketOperation.bulkReply("$6\r\nfoobar\r\n") was called
|
||||
}
|
||||
|
||||
"read integer" in {
|
||||
readIntFromInput
|
||||
socketOperation.readInt mustEqual 666
|
||||
}
|
||||
|
||||
"read a boolean return value" in {
|
||||
readOkFromInput
|
||||
socketOperation.readBoolean mustEqual true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
import com.redis.operations._
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
import org.mockito.Mockito.doNothing
|
||||
|
||||
class RedisTestClient(val connection: Connection) extends Operations with ListOperations with SetOperations with NodeOperations with KeySpaceOperations with SortOperations {
|
||||
var db: Int = 0
|
||||
def getConnection(key: String): Connection = connection
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
import org.mockito.Mockito.doNothing
|
||||
|
||||
object KeySpaceOperationsSpec extends Specification with Mockito {
|
||||
|
||||
"Redis Client Key Operations" should {
|
||||
var client: RedisTestClient = null
|
||||
var connection: Connection = null
|
||||
|
||||
doBefore{
|
||||
connection = mock[Connection]
|
||||
client = new RedisTestClient(connection)
|
||||
}
|
||||
|
||||
"return all keys matching" in {
|
||||
connection.readResponse returns "akey anotherkey adiffkey"
|
||||
client.keys("a*")
|
||||
connection.write("KEYS a*\r\n") was called
|
||||
}
|
||||
|
||||
"return a random key" in {
|
||||
connection.readResponse returns "+somerandonkey"
|
||||
client.randomKey mustEqual "somerandonkey"
|
||||
connection.write("RANDOMKEY\r\n") was called
|
||||
}
|
||||
|
||||
"remame a key" in {
|
||||
connection.readBoolean returns true
|
||||
client.rename("a", "b") must beTrue
|
||||
connection.write("RENAME a b\r\n") was called
|
||||
}
|
||||
|
||||
"rename a key only if destintation doesn't exist" in {
|
||||
connection.readBoolean returns false
|
||||
client.renamenx("a", "b") must beFalse
|
||||
connection.write("RENAMENX a b\r\n") was called
|
||||
}
|
||||
|
||||
"tell the size of the db, # of keys" in {
|
||||
connection.readInt returns 4
|
||||
client.dbSize mustEqual 4
|
||||
connection.write("DBSIZE\r\n") was called
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
import org.mockito.Mockito.doNothing
|
||||
|
||||
object ListOperationsSpec extends Specification with Mockito {
|
||||
|
||||
"Redis Client List Operations" should {
|
||||
var client: RedisTestClient = null
|
||||
var connection: Connection = null
|
||||
|
||||
doBefore{
|
||||
connection = mock[Connection]
|
||||
client = new RedisTestClient(connection)
|
||||
}
|
||||
|
||||
"push to head" in {
|
||||
connection.readBoolean returns true
|
||||
client.pushHead("k", "v") must beTrue
|
||||
connection.write("LPUSH k 1\r\nv\r\n") was called
|
||||
}
|
||||
|
||||
"push to tail" in {
|
||||
connection.readBoolean returns true
|
||||
client.pushTail("k", "v") must beTrue
|
||||
connection.write("RPUSH k 1\r\nv\r\n") was called
|
||||
}
|
||||
|
||||
"pop from head" in {
|
||||
connection.readString returns "value"
|
||||
client.popHead("key") mustEqual "value"
|
||||
connection.write("LPOP key\r\n") was called
|
||||
}
|
||||
|
||||
"pop from tail" in {
|
||||
connection.readString returns "value"
|
||||
client.popTail("key") mustEqual "value"
|
||||
connection.write("RPOP key\r\n") was called
|
||||
}
|
||||
|
||||
"return list index" in {
|
||||
connection.readString returns "value"
|
||||
client.listIndex("k", 2) mustEqual "value"
|
||||
connection.write("LINDEX k 2\r\n") was called
|
||||
}
|
||||
|
||||
"return set element at index" in {
|
||||
connection.readBoolean returns true
|
||||
client.listSet("k", 1, "value") mustEqual true
|
||||
connection.write("LSET k 1 5\r\nvalue\r\n") was called
|
||||
}
|
||||
|
||||
"return list size" in {
|
||||
connection.readInt returns 3
|
||||
client.listLength("k") mustEqual 3
|
||||
connection.write("LLEN k\r\n") was called
|
||||
}
|
||||
|
||||
"return list range" in {
|
||||
val listResult: List[String] = List("one", "two", "three", "four", "five")
|
||||
connection.readList returns listResult
|
||||
client.listRange("k", 2, 4) mustEqual listResult
|
||||
connection.write("LRANGE k 2 4\r\n") was called
|
||||
}
|
||||
|
||||
"trim a list" in {
|
||||
connection.readBoolean returns true
|
||||
client.listTrim("k", 2, 4) mustEqual true
|
||||
connection.write("LTRIM k 2 4\r\n") was called
|
||||
}
|
||||
|
||||
"remove occurrences of a value in the list" in {
|
||||
connection.readBoolean returns true
|
||||
client.listRem("k", 2, "value") mustEqual true
|
||||
connection.write("LREM k 2 5\r\nvalue\r\n") was called
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
import org.mockito.Mockito.doNothing
|
||||
|
||||
object NodeOperationsSpec extends Specification with Mockito {
|
||||
|
||||
"Redis Client Node Operations" should {
|
||||
var client: RedisTestClient = null
|
||||
var connection: Connection = null
|
||||
|
||||
doBefore{
|
||||
connection = mock[Connection]
|
||||
client = new RedisTestClient(connection)
|
||||
}
|
||||
|
||||
"save the db to disk" in {
|
||||
connection.readBoolean returns true
|
||||
client.save must beTrue
|
||||
connection.write("SAVE\r\n") was called
|
||||
}
|
||||
|
||||
"return the last time saved data to the db" in {
|
||||
connection.readInt returns 1250421891
|
||||
client.lastSave mustEqual 1250421891
|
||||
connection.write("LASTSAVE\r\n") was called
|
||||
}
|
||||
|
||||
"return all specified keys" in {
|
||||
connection.readList returns List[String]("hola", null, null)
|
||||
client.mget("a", "b", "c") mustEqual List[String]("hola", null, null)
|
||||
connection.write("MGET a b c\r\n") was called
|
||||
}
|
||||
|
||||
"return server info" in {
|
||||
val sampleInfo = "res0: Any = \nredis_version:0.091\nconnected_clients:2\nconnected_slaves:0\nused_memory:3036\nchanges_since_last_save:0\nlast_save_time:1250440893\ntotal_connections_received:2\ntotal_commands_processed:0\nuptime_in_seconds:7\nuptime_in_days:0\n"
|
||||
connection.readResponse returns sampleInfo
|
||||
client.info mustEqual sampleInfo
|
||||
connection.write("INFO\r\n") was called
|
||||
}
|
||||
|
||||
"start monitor debug on the server" in {
|
||||
connection.readBoolean returns true
|
||||
client.monitor mustEqual true
|
||||
connection.write("MONITOR\r\n") was called
|
||||
}
|
||||
|
||||
"set a server as slave of a remote master" in {
|
||||
connection.readBoolean returns true
|
||||
client.slaveOf("localhost", 9999) mustEqual true
|
||||
connection.write("SLAVEOF localhost 9999\r\n") was called
|
||||
}
|
||||
|
||||
"set a server as master if no params sent" in {
|
||||
connection.readBoolean returns true
|
||||
client.slaveOf() mustEqual true
|
||||
connection.write("SLAVEOF NO ONE\r\n") was called
|
||||
}
|
||||
|
||||
"set a server as master" in {
|
||||
connection.readBoolean returns true
|
||||
client.setAsMaster mustEqual true
|
||||
connection.write("SLAVEOF NO ONE\r\n") was called
|
||||
}
|
||||
|
||||
"select the current db" in {
|
||||
connection.readBoolean returns true
|
||||
client.selectDb(3) mustEqual true
|
||||
client.db mustEqual 3
|
||||
connection.write("SELECT 3\r\n") was called
|
||||
}
|
||||
|
||||
"flush the db" in {
|
||||
connection.readBoolean returns true
|
||||
client.flushDb mustEqual true
|
||||
connection.write("FLUSHDB\r\n") was called
|
||||
}
|
||||
|
||||
"flush all the dbs" in {
|
||||
connection.readBoolean returns true
|
||||
client.flushAll mustEqual true
|
||||
connection.write("FLUSHALL\r\n") was called
|
||||
}
|
||||
|
||||
"shutdown the db" in {
|
||||
connection.readBoolean returns true
|
||||
client.shutdown mustEqual true
|
||||
connection.write("SHUTDOWN\r\n") was called
|
||||
}
|
||||
|
||||
"move keys from one db to another" in {
|
||||
connection.readBoolean returns true
|
||||
client.move("a", 2) mustEqual true
|
||||
connection.write("MOVE a 2\r\n") was called
|
||||
}
|
||||
|
||||
"quit" in {
|
||||
connection.disconnect returns true
|
||||
client.quit mustEqual true
|
||||
connection.write("QUIT\r\n") was called
|
||||
connection.disconnect was called
|
||||
}
|
||||
|
||||
"auth with the server" in {
|
||||
connection.readBoolean returns true
|
||||
client.auth("secret") mustEqual true
|
||||
connection.write("AUTH secret\r\n") was called
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
|
||||
object OperationsSpec extends Specification with Mockito {
|
||||
|
||||
"Redis Client Operations" should {
|
||||
|
||||
var client: RedisTestClient = null
|
||||
var connection: Connection = null
|
||||
|
||||
doBefore{
|
||||
connection = mock[Connection]
|
||||
client = new RedisTestClient(connection)
|
||||
}
|
||||
|
||||
"set a key" in {
|
||||
connection.readBoolean returns true
|
||||
client.set("a", "b") mustEqual true
|
||||
connection.write("SET a 1\r\nb\r\n") was called
|
||||
}
|
||||
|
||||
"set a key with setKey" in {
|
||||
connection.readBoolean returns true
|
||||
client.setKey("a", "b") mustEqual true
|
||||
connection.write("SET a 1\r\nb\r\n") was called
|
||||
}
|
||||
|
||||
"set a key with expiration" in {
|
||||
connection.readBoolean returns true
|
||||
client.set("a", "b", 4) mustEqual true
|
||||
connection.write("SET a 1\r\nb\r\n") was called
|
||||
connection.write("EXPIRE a 4\r\n") was called
|
||||
}
|
||||
|
||||
"expire a key" in {
|
||||
connection.readBoolean returns true
|
||||
client.expire("a", 4) mustEqual true
|
||||
connection.write("EXPIRE a 4\r\n") was called
|
||||
}
|
||||
|
||||
"get a key" in {
|
||||
connection.readResponse returns "b"
|
||||
client.get("a") mustEqual "b"
|
||||
connection.write("GET a\r\n") was called
|
||||
}
|
||||
|
||||
"get and set a key" in {
|
||||
connection.readResponse returns "old"
|
||||
client.getSet("a", "new") mustEqual "old"
|
||||
connection.write("GETSET a 3\r\nnew\r\n") was called
|
||||
}
|
||||
|
||||
"delete a key" in {
|
||||
connection.readBoolean returns true
|
||||
client.delete("a") mustEqual true
|
||||
connection.write("DEL a\r\n") was called
|
||||
}
|
||||
|
||||
"tell if a key exists" in {
|
||||
connection.readBoolean returns true
|
||||
client.exists("a") mustEqual true
|
||||
connection.write("EXISTS a\r\n") was called
|
||||
}
|
||||
|
||||
"tell if a key exists" in {
|
||||
connection.readBoolean returns true
|
||||
client.exists("a") mustEqual true
|
||||
connection.write("EXISTS a\r\n") was called
|
||||
}
|
||||
|
||||
"increment a value" in {
|
||||
connection.readInt returns 1
|
||||
client.incr("a") mustEqual 1
|
||||
connection.write("INCR a\r\n") was called
|
||||
}
|
||||
|
||||
"increment a value by N" in {
|
||||
connection.readInt returns 27
|
||||
client.incr("a", 23) mustEqual 27
|
||||
connection.write("INCRBY a 23\r\n") was called
|
||||
}
|
||||
|
||||
"decrement a value" in {
|
||||
connection.readInt returns 0
|
||||
client.decr("a") mustEqual 0
|
||||
connection.write("DECR a\r\n") was called
|
||||
}
|
||||
|
||||
"decrement a value by N" in {
|
||||
connection.readInt returns 25
|
||||
client.decr("a", 2) mustEqual 25
|
||||
connection.write("DECRBY a 2\r\n") was called
|
||||
}
|
||||
|
||||
"return type of key" in {
|
||||
connection.readResponse returns "String"
|
||||
client.getType("a") mustEqual "String"
|
||||
connection.write("TYPE a\r\n") was called
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
import org.mockito.Mockito.doNothing
|
||||
|
||||
object SetOperationsSpec extends Specification with Mockito {
|
||||
|
||||
"Redis Client Set Operations" should {
|
||||
var client: RedisTestClient = null
|
||||
var connection: Connection = null
|
||||
|
||||
doBefore{
|
||||
connection = mock[Connection]
|
||||
client = new RedisTestClient(connection)
|
||||
}
|
||||
|
||||
"add a member to a set" in {
|
||||
connection.readBoolean returns true
|
||||
client.setAdd("set", "value") must beTrue
|
||||
connection.write("SADD set 5\r\nvalue\r\n") was called
|
||||
}
|
||||
|
||||
"remove an member from a set" in {
|
||||
connection.readBoolean returns true
|
||||
client.setDelete("set", "value") must beTrue
|
||||
connection.write("SREM set 5\r\nvalue\r\n") was called
|
||||
}
|
||||
|
||||
"return the number of elements in the set" in {
|
||||
connection.readInt returns 5
|
||||
client.setCount("set") mustEqual 5
|
||||
connection.write("SCARD set\r\n") was called
|
||||
}
|
||||
|
||||
"return all the members from a set" in {
|
||||
val setResult = Set("one", "two", "three")
|
||||
connection.readSet returns setResult
|
||||
client.setMembers("set") mustEqual setResult
|
||||
connection.write("SMEMBERS set\r\n") was called
|
||||
}
|
||||
|
||||
"pop an element from the set" in {
|
||||
connection.readString returns "one"
|
||||
client.setPop("set") mustEqual "one"
|
||||
connection.write("SPOP set\r\n") was called
|
||||
}
|
||||
|
||||
"move an element from one set to another" in {
|
||||
connection.readBoolean returns true
|
||||
client.setMove("set", "toset", "value") mustEqual true
|
||||
connection.write("SMOVE set toset value\r\n") was called
|
||||
}
|
||||
|
||||
"tell if member exists on the set" in {
|
||||
connection.readBoolean returns true
|
||||
client.setMemberExists("set", "value") mustEqual true
|
||||
connection.write("SISMEMBER set 5\r\nvalue\r\n") was called
|
||||
}
|
||||
|
||||
"return the intersection between N sets" in {
|
||||
val setResult = Set("one", "two", "three")
|
||||
connection.readSet returns setResult
|
||||
client.setIntersect("set", "otherset", "another") mustEqual setResult
|
||||
connection.write("SINTER set otherset another\r\n") was called
|
||||
}
|
||||
|
||||
"return the intersection between N sets and store it a new one" in {
|
||||
connection.readBoolean returns true
|
||||
client.setInterStore("set", "oneset", "twoset") mustEqual true
|
||||
connection.write("SINTERSTORE set oneset twoset\r\n") was called
|
||||
}
|
||||
|
||||
"return the difference between N sets" in {
|
||||
val setResult = Set("one", "two", "three")
|
||||
connection.readSet returns setResult
|
||||
client.setDiff("set", "oneset", "twoset") mustEqual setResult
|
||||
connection.write("SDIFF set oneset twoset\r\n") was called
|
||||
}
|
||||
|
||||
"return the difference between N sets and store it in a new one" in {
|
||||
connection.readBoolean returns true
|
||||
client.setDiffStore("newset", "oneset", "twoset") mustEqual true
|
||||
connection.write("SDIFFSTORE newset oneset twoset\r\n") was called
|
||||
}
|
||||
|
||||
"return the union between N sets" in {
|
||||
val setResult = Set("one", "two", "three")
|
||||
connection.readSet returns setResult
|
||||
client.setUnion("set", "oneset", "twoset") mustEqual setResult
|
||||
connection.write("SUNION set oneset twoset\r\n") was called
|
||||
}
|
||||
|
||||
"return the union between N sets and store it in a new one" in {
|
||||
connection.readBoolean returns true
|
||||
client.setUnionStore("set", "oneset", "twoset") mustEqual true
|
||||
connection.write("SUNIONSTORE set oneset twoset\r\n") was called
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import org.specs._
|
||||
import com.redis._
|
||||
|
||||
import org.specs.mock.Mockito
|
||||
import org.mockito.Mock._
|
||||
import org.mockito.Mockito._
|
||||
|
||||
object SortOperationsSpec extends Specification with Mockito {
|
||||
|
||||
"Redis Client Sort Operations" should {
|
||||
|
||||
var client: RedisTestClient = null
|
||||
var connection: Connection = null
|
||||
|
||||
doBefore{
|
||||
connection = mock[Connection]
|
||||
client = new RedisTestClient(connection)
|
||||
}
|
||||
|
||||
"sort the contents of the specified key" in {
|
||||
val listResult: List[String] = List("one", "two", "three")
|
||||
connection.readList returns listResult
|
||||
client.sort("set", "ALPHA DESC") mustEqual listResult
|
||||
connection.write("SORT set ALPHA DESC\r\n") was called
|
||||
}
|
||||
|
||||
"sort the contents of the specified key with default" in {
|
||||
val listResult: List[String] = List("one", "two", "three")
|
||||
connection.readList returns listResult
|
||||
client.sort("set") mustEqual listResult
|
||||
connection.write("SORT set\r\n") was called
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user