summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2009-09-02 10:34:27 +0200
committerantirez <antirez@gmail.com>2009-09-02 10:34:27 +0200
commit7c44bbb1108c9460027776138f4079f8ec46934c (patch)
tree3d8b240d6caa72ae6349da16483d05c8062f9178
parent45636487143097d360dfba4f3fa602b08927e355 (diff)
downloadredis-7c44bbb1108c9460027776138f4079f8ec46934c.tar.gz
Scala client added thanks to Alejanro Crosa
-rw-r--r--Changelog11
-rw-r--r--client-libraries/scala/.gitignore6
-rw-r--r--client-libraries/scala/README.md46
-rw-r--r--client-libraries/scala/project/build.properties8
-rw-r--r--client-libraries/scala/project/build/RedisClientProject.scala12
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/Connection.scala8
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/HashRing.scala66
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/Operations/KeySpaceOperations.scala47
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/Operations/ListOperations.scala94
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/Operations/NodeOperations.scala121
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/Operations/Operations.scala124
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/Operations/SetOperations.scala115
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/Operations/SortOperations.scala28
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/RedisClient.scala33
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/RedisCluster.scala42
-rw-r--r--client-libraries/scala/src/main/scala/com/redis/SocketOperations.scala167
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/RedisClientSpec.scala27
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/RedisClusterSpec.scala51
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/SocketOperationsSpec.scala97
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/helpers/RedisClientTestHelper.scala13
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/operations/KeySpaceOperationsSpec.scala50
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/operations/ListOperationsSpec.scala81
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/operations/NodeOperationsSpec.scala113
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/operations/OperationsSpec.scala105
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/operations/SetOperationsSpec.scala102
-rw-r--r--client-libraries/scala/src/test/scala/com/redis/operations/SortOperationsSpec.scala34
26 files changed, 1601 insertions, 0 deletions
diff --git a/Changelog b/Changelog
index c23d23eb2..12fdc3873 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,14 @@
+2009-09-02 QuickStart added
+2009-09-01 Fixed crash with only space and newline as command (issue 61), thanks to a guy having as nick "fixxxerrr"
+2009-08-11 TODO list modified
+2009-07-24 more snow leopard related fixes (for 32bit systems)
+2009-07-24 fixed compilation with Snow Leopard, thanks to Lon Baker for providing SSH access to Snow Leopard box
+2009-07-22 Fixed NetBSD compile problems
+2009-07-17 now the size of the shared pool can be really modified via config, also the number of objects in the sharing pool is logged when the log level is set to debug. Thanks to Aman Gupta
+2009-07-05 added utils/redis-copy.rb, a script that is able to copy data from one Redis server to another one on the fly.
+2009-07-04 Applied three different patches thanks to Chris Lamb, one to fix compilation and get the IP register value on Linux IA64 and other systems. One in order to log the overcommit problem on the logs instead of the standard output when Redis is demonized. The latest in order to suggest a more consistent way in order to switch to 1 the memory overcommit Linux feature.
+2009-07-03 bugfix: EXPIRE now propagates to the Slave.
+2009-06-16 Redis version modified to 0.900
2009-06-16 update-ruby-client script already points to ezmobius repo
2009-06-16 client libraries updated
2009-06-16 Redis release candidate 1
diff --git a/client-libraries/scala/.gitignore b/client-libraries/scala/.gitignore
new file mode 100644
index 000000000..f38e2ab95
--- /dev/null
+++ b/client-libraries/scala/.gitignore
@@ -0,0 +1,6 @@
+.DS_Store
+lib_managed
+project/boot
+target
+target/
+target/**/*
diff --git a/client-libraries/scala/README.md b/client-libraries/scala/README.md
new file mode 100644
index 000000000..afb28bf12
--- /dev/null
+++ b/client-libraries/scala/README.md
@@ -0,0 +1,46 @@
+# Redis Scala client
+
+## Key features of the library
+
+- Native Scala types Set and List responses.
+- Consisten Hashing on the client.
+- Support for Clustering of Redis nodes.
+
+## Information about redis
+
+Redis is a key-value database. It is similar to memcached but the dataset is not volatile, and values can be strings, exactly like in memcached, but also lists and sets with atomic operations to push/pop elements.
+
+http://code.google.com/p/redis/
+
+### Key features of Redis
+
+- Fast in-memory store with asynchronous save to disk.
+- Key value get, set, delete, etc.
+- Atomic operations on sets and lists, union, intersection, trim, etc.
+
+## Requirements
+
+- sbt (get it at http://code.google.com/p/simple-build-tool/)
+
+## Usage
+
+Start your redis instance (usually redis-server will do it)
+
+ $ cd scala-redis
+ $ sbt
+ > update
+ > test (optional to run the tests)
+ > console
+
+And you are ready to start issuing commands to the server(s)
+
+let's connect and get a key:
+
+ scala> import com.redis._
+ scala> val r = new Redis("localhost", 6379)
+ scala> val r.set("key", "some value")
+ scala> val r.get("key")
+
+
+Alejandro Crosa <<alejandrocrosa@gmail.com>>
+
diff --git a/client-libraries/scala/project/build.properties b/client-libraries/scala/project/build.properties
new file mode 100644
index 000000000..22da51f98
--- /dev/null
+++ b/client-libraries/scala/project/build.properties
@@ -0,0 +1,8 @@
+#Project properties
+#Wed Aug 19 07:54:05 ART 2009
+project.organization=com.redis
+project.name=RedisClient
+sbt.version=0.5.1
+project.version=1.0.1
+scala.version=2.7.5
+project.initialize=false
diff --git a/client-libraries/scala/project/build/RedisClientProject.scala b/client-libraries/scala/project/build/RedisClientProject.scala
new file mode 100644
index 000000000..60dff43b0
--- /dev/null
+++ b/client-libraries/scala/project/build/RedisClientProject.scala
@@ -0,0 +1,12 @@
+import sbt._
+
+class RedisClientProject(info: ProjectInfo) extends DefaultProject(info) with AutoCompilerPlugins
+{
+ override def useDefaultConfigurations = true
+
+ val scalatest = "org.scala-tools.testing" % "scalatest" % "0.9.5" % "test->default"
+ val specs = "org.scala-tools.testing" % "specs" % "1.5.0"
+ val mockito = "org.mockito" % "mockito-all" % "1.7"
+ val junit = "junit" % "junit" % "4.5"
+ val sxr = compilerPlugin("org.scala-tools.sxr" %% "sxr" % "0.2.1")
+}
diff --git a/client-libraries/scala/src/main/scala/com/redis/Connection.scala b/client-libraries/scala/src/main/scala/com/redis/Connection.scala
new file mode 100644
index 000000000..d4b51ed7d
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/Connection.scala
@@ -0,0 +1,8 @@
+package com.redis
+
+/**
+ * Redis client Connection
+ *
+ */
+
+case class Connection(val host: String, val port: Int) extends SocketOperations
diff --git a/client-libraries/scala/src/main/scala/com/redis/HashRing.scala b/client-libraries/scala/src/main/scala/com/redis/HashRing.scala
new file mode 100644
index 000000000..e16cc1825
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/HashRing.scala
@@ -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
+ }
+}
+
diff --git a/client-libraries/scala/src/main/scala/com/redis/Operations/KeySpaceOperations.scala b/client-libraries/scala/src/main/scala/com/redis/Operations/KeySpaceOperations.scala
new file mode 100644
index 000000000..13dc593cc
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/Operations/KeySpaceOperations.scala
@@ -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
+ }
+} \ No newline at end of file
diff --git a/client-libraries/scala/src/main/scala/com/redis/Operations/ListOperations.scala b/client-libraries/scala/src/main/scala/com/redis/Operations/ListOperations.scala
new file mode 100644
index 000000000..6538311b4
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/Operations/ListOperations.scala
@@ -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
+ }
+}
diff --git a/client-libraries/scala/src/main/scala/com/redis/Operations/NodeOperations.scala b/client-libraries/scala/src/main/scala/com/redis/Operations/NodeOperations.scala
new file mode 100644
index 000000000..3deb8dd01
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/Operations/NodeOperations.scala
@@ -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
+ }
+}
diff --git a/client-libraries/scala/src/main/scala/com/redis/Operations/Operations.scala b/client-libraries/scala/src/main/scala/com/redis/Operations/Operations.scala
new file mode 100644
index 000000000..fb5c23d6a
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/Operations/Operations.scala
@@ -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
+ }
+
+} \ No newline at end of file
diff --git a/client-libraries/scala/src/main/scala/com/redis/Operations/SetOperations.scala b/client-libraries/scala/src/main/scala/com/redis/Operations/SetOperations.scala
new file mode 100644
index 000000000..13432d35a
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/Operations/SetOperations.scala
@@ -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
+ }
+} \ No newline at end of file
diff --git a/client-libraries/scala/src/main/scala/com/redis/Operations/SortOperations.scala b/client-libraries/scala/src/main/scala/com/redis/Operations/SortOperations.scala
new file mode 100644
index 000000000..f3d0ca14d
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/Operations/SortOperations.scala
@@ -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
+ }
+} \ No newline at end of file
diff --git a/client-libraries/scala/src/main/scala/com/redis/RedisClient.scala b/client-libraries/scala/src/main/scala/com/redis/RedisClient.scala
new file mode 100644
index 000000000..ac051cc58
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/RedisClient.scala
@@ -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+">"
+}
diff --git a/client-libraries/scala/src/main/scala/com/redis/RedisCluster.scala b/client-libraries/scala/src/main/scala/com/redis/RedisCluster.scala
new file mode 100644
index 000000000..0a753719e
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/RedisCluster.scala
@@ -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
+}
diff --git a/client-libraries/scala/src/main/scala/com/redis/SocketOperations.scala b/client-libraries/scala/src/main/scala/com/redis/SocketOperations.scala
new file mode 100644
index 000000000..137ecae34
--- /dev/null
+++ b/client-libraries/scala/src/main/scala/com/redis/SocketOperations.scala
@@ -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;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/client-libraries/scala/src/test/scala/com/redis/RedisClientSpec.scala b/client-libraries/scala/src/test/scala/com/redis/RedisClientSpec.scala
new file mode 100644
index 000000000..804a19aef
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/RedisClientSpec.scala
@@ -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
+ }
+ }
+}
diff --git a/client-libraries/scala/src/test/scala/com/redis/RedisClusterSpec.scala b/client-libraries/scala/src/test/scala/com/redis/RedisClusterSpec.scala
new file mode 100644
index 000000000..cee08d701
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/RedisClusterSpec.scala
@@ -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
+ }
+ }
+}
diff --git a/client-libraries/scala/src/test/scala/com/redis/SocketOperationsSpec.scala b/client-libraries/scala/src/test/scala/com/redis/SocketOperationsSpec.scala
new file mode 100644
index 000000000..4f7bbac4f
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/SocketOperationsSpec.scala
@@ -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
+ }
+ }
+ }
+}
diff --git a/client-libraries/scala/src/test/scala/com/redis/helpers/RedisClientTestHelper.scala b/client-libraries/scala/src/test/scala/com/redis/helpers/RedisClientTestHelper.scala
new file mode 100644
index 000000000..402520ab4
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/helpers/RedisClientTestHelper.scala
@@ -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
+} \ No newline at end of file
diff --git a/client-libraries/scala/src/test/scala/com/redis/operations/KeySpaceOperationsSpec.scala b/client-libraries/scala/src/test/scala/com/redis/operations/KeySpaceOperationsSpec.scala
new file mode 100644
index 000000000..ced34fefa
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/operations/KeySpaceOperationsSpec.scala
@@ -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
+ }
+ }
+} \ No newline at end of file
diff --git a/client-libraries/scala/src/test/scala/com/redis/operations/ListOperationsSpec.scala b/client-libraries/scala/src/test/scala/com/redis/operations/ListOperationsSpec.scala
new file mode 100644
index 000000000..95c66b55b
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/operations/ListOperationsSpec.scala
@@ -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
+ }
+ }
+} \ No newline at end of file
diff --git a/client-libraries/scala/src/test/scala/com/redis/operations/NodeOperationsSpec.scala b/client-libraries/scala/src/test/scala/com/redis/operations/NodeOperationsSpec.scala
new file mode 100644
index 000000000..629c46353
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/operations/NodeOperationsSpec.scala
@@ -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
+ }
+ }
+}
diff --git a/client-libraries/scala/src/test/scala/com/redis/operations/OperationsSpec.scala b/client-libraries/scala/src/test/scala/com/redis/operations/OperationsSpec.scala
new file mode 100644
index 000000000..7fe26be3d
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/operations/OperationsSpec.scala
@@ -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
+ }
+ }
+}
diff --git a/client-libraries/scala/src/test/scala/com/redis/operations/SetOperationsSpec.scala b/client-libraries/scala/src/test/scala/com/redis/operations/SetOperationsSpec.scala
new file mode 100644
index 000000000..78e6a5f2c
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/operations/SetOperationsSpec.scala
@@ -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
+ }
+ }
+} \ No newline at end of file
diff --git a/client-libraries/scala/src/test/scala/com/redis/operations/SortOperationsSpec.scala b/client-libraries/scala/src/test/scala/com/redis/operations/SortOperationsSpec.scala
new file mode 100644
index 000000000..95f3c91dd
--- /dev/null
+++ b/client-libraries/scala/src/test/scala/com/redis/operations/SortOperationsSpec.scala
@@ -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
+ }
+ }
+}