├── .gitignore
├── README.md
├── project
├── Build.scala
├── build.properties
└── plugins.sbt
└── src
├── benchmark
└── scala
│ └── net
│ └── fyrie
│ └── redis
│ └── Main.scala
├── main
└── scala
│ └── net
│ └── fyrie
│ └── redis
│ ├── ConnectionPool.scala
│ ├── Queued.scala
│ ├── RedisClient.scala
│ ├── ResultFunctor.scala
│ ├── actors
│ └── Actors.scala
│ ├── commands
│ ├── Hashes.scala
│ ├── Keys.scala
│ ├── Lists.scala
│ ├── PubSub.scala
│ ├── Script.scala
│ ├── Servers.scala
│ ├── Sets.scala
│ ├── SortedSets.scala
│ └── Strings.scala
│ ├── lua.scala
│ ├── messages
│ └── Messages.scala
│ ├── package.scala
│ ├── protocol
│ ├── Constants.scala
│ └── Iteratees.scala
│ ├── pubsub
│ └── PubSub.scala
│ ├── serialization
│ └── Serialization.scala
│ └── types
│ └── Types.scala
└── test
└── scala
└── net
└── fyrie
└── redis
├── DBSpec.scala
├── HashSpec.scala
├── ListSpec.scala
├── PubSubSpec.scala
├── ScriptsSpec.scala
├── SetSpec.scala
├── SortedSetSpec.scala
├── StringSpec.scala
└── TestServer.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .ensime
3 | lib_managed
4 | src_managed
5 | project/boot
6 | project/plugins/project
7 | project/plugins/src_managed
8 | target
9 | target/
10 | target/**/*
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fyrie Redis Scala client
2 |
3 | ## Key features of the library
4 |
5 | - All commands are pipelined asynchronously.
6 | - User defined formatting of command parameters and parsing of replies.
7 | - Uses nonblocking IO and actors for increased throughput and responsiveness while minimizing the use of threads and sockets.
8 |
9 | ## Information about Redis
10 |
11 | Redis is an advanced key-value store. It is similar to memcached but the dataset is not volatile, and values can be strings, exactly like in memcached, but also lists, sets, and ordered sets.
12 |
13 | [code.google.com/p/redis](http://code.google.com/p/redis/)
14 |
15 | ### Key features of Redis
16 |
17 | - Fast in-memory store with asynchronous save to disk.
18 | - Key value get, set, delete, etc.
19 | - Atomic operations on sets and lists, union, intersection, trim, etc.
20 |
21 | ## Requirements
22 |
23 | Fyrie Redis version numbers match the version of Akka that it is compatible with. The recommended version is currently 1.2, which can be found on the ['akka-1.2' branch](https://github.com/derekjw/fyrie-redis/tree/akka-1.2).
24 |
25 | If building from source sbt 0.10 ([github.com/harrah/xsbt/wiki](https://github.com/harrah/xsbt/wiki)) is required.
26 |
27 | Releases and snapshots are located at [repo.fyrie.net](http://repo.fyrie.net), which can be used in sbt like this:
28 |
29 | resolvers += "fyrie snapshots" at "http://repo.fyrie.net/snapshots"
30 |
31 | libraryDependencies += "net.fyrie" %% "fyrie-redis" % "1.2-SNAPSHOT"
32 |
33 | ## All tests are functional tests and require a running instance of Redis
34 |
35 | ## Usage
36 |
37 | Start your redis instance (usually redis-server will do it). Tests default to port 6379, but can be changed by editing src/test/resources/akka.conf. Do not run tests on a production redis server as all data on the redis server will be deleted.
38 |
39 | $ cd fyrie-redis
40 | $ sbt
41 | > update
42 | > test (optional to run the tests)
43 | > console
44 |
45 | And you are ready to start issuing commands to the server(s)
46 |
47 | let's connect and get a key:
48 |
49 | scala> import net.fyrie.redis._
50 | scala> val r = new RedisClient("localhost", 6379)
51 | scala> r.set("key", "some value")
52 | scala> r.sync.get("key").parse[String]
53 |
54 | There are 3 ways to run a command:
55 |
56 | - using 'async': result will be returned asynchronously within a Future ([akka.io/docs/akka/snapshot/scala/futures.html](http://akka.io/docs/akka/snapshot/scala/futures.html)).
57 | - using 'sync': will wait for the result. Only recommended when testing.
58 | - using 'quiet': No result will be returned. Command is sent asynchronously. Good for quickly making updates.
59 |
60 | The default is 'async' if none of the above are used.
61 |
62 | MultiExec commands can also be given:
63 |
64 | val result = r.multi { rq =>
65 | for {
66 | _ <- rq.set("testkey1", "testvalue1")
67 | _ <- rq.set("testkey2", "testvalue2")
68 | x <- rq.mget(List("testkey1", "testkey2")).parse[String]
69 | } yield x
70 | }
71 |
72 | The above example will return a 'Future[List[String]]'
73 |
74 | Another MultiExec example:
75 |
76 | r.lpush("mylist", "value1")
77 | r.lpush("mylist", "value2")
78 |
79 | val result = r.multi { rq =>
80 | for {
81 | _ <- rq.lpush("mylist", "value3")
82 | a <- rq.rpop("mylist").parse[String]
83 | b <- rq.rpop("mylist").parse[String]
84 | c <- rq.rpop("mylist").parse[String]
85 | } yield (a, b, c)
86 | }
87 |
88 | This example will return a '(Future[String], Future[String], Future[String])'
89 |
90 | ## Documentation
91 |
92 | The api is published at ([http://derekjw.github.com/fyrie-redis](http://derekjw.github.com/fyrie-redis)), but it's documentation is far from complete.
93 |
94 | Until proper documentation is completed, the tests can be used as examples: ([github.com/derekjw/fyrie-redis/tree/master/src/test/scala/net/fyrie/redis](https://github.com/derekjw/fyrie-redis/tree/master/src/test/scala/net/fyrie/redis))
95 |
96 | ## TODO
97 |
98 | - Support clustering
99 | - Documentation
100 |
101 | ## Acknowledgements
102 |
103 | I would like to thank Alejandro Crosa and Debasish Ghosh for the work they have done creating [scala-redis](http://github.com/debasishg/scala-redis). I began this project as a fork of scala-redis, but after my third rewriting of it I decided I should split this off into a seperate project. Without the starting point that their work provided I never would have created this.
104 |
105 | ## License
106 |
107 | This software is licensed under the Apache 2 license, quoted below.
108 |
109 | Licensed under the Apache License, Version 2.0 (the "License"); you may not
110 | use this file except in compliance with the License. You may obtain a copy of
111 | the License at
112 |
113 | http://www.apache.org/licenses/LICENSE-2.0
114 |
115 | Unless required by applicable law or agreed to in writing, software
116 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
117 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
118 | License for the specific language governing permissions and limitations under
119 | the License.
120 |
121 |
--------------------------------------------------------------------------------
/project/Build.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 |
4 | import com.typesafe.sbtscalariform.ScalariformPlugin
5 | import com.typesafe.sbtscalariform.ScalariformPlugin.ScalariformKeys
6 |
7 | object FyrieRedisBuild extends Build {
8 | lazy val core = (Project("fyrie-redis", file("."))
9 | configs(Benchmark)
10 | settings(coreSettings: _*))
11 |
12 | val coreSettings = Defaults.defaultSettings ++ inConfig(Benchmark)(Defaults.configSettings) ++ ScalariformPlugin.scalariformSettings ++ Seq(
13 | scalaVersion := "2.9.1",
14 | crossScalaVersions := Seq("2.9.0-1", "2.9.1", "2.10.0-M1"),
15 | name := "fyrie-redis",
16 | organization := "net.fyrie",
17 | version := "2.0-SNAPSHOT",
18 | resolvers ++= Seq("Sonatype OSS Repo" at "http://oss.sonatype.org/content/repositories/snapshots",
19 | "Akka Snapshot Repo" at "http://akka.io/snapshots"),
20 | libraryDependencies ++= Seq("com.typesafe.akka" % "akka-actor" % "2.0-20120124-000638",
21 | "com.typesafe.akka" % "akka-testkit" % "2.0-20120124-000638" % "test",
22 | "org.specs2" % "specs2_2.9.1" % "1.6.1",
23 | "org.specs2" % "specs2-scalaz-core_2.9.1" % "6.0.1" % "test"),
24 | //"com.google.code.caliper" % "caliper" % "1.0-SNAPSHOT" % "benchmark",
25 | //"com.google.code.gson" % "gson" % "1.7.1" % "benchmark"),
26 | parallelExecution := false,
27 | scalacOptions ++= Seq("-encoding", "UTF-8", "-deprecation", "-unchecked", "-optimize"),
28 | ScalariformKeys.preferences in Compile := formattingPreferences,
29 | ScalariformKeys.preferences in Test := formattingPreferences,
30 | runner in Benchmark in run <<= (thisProject, taskTemporaryDirectory, scalaInstance, baseDirectory, javaOptions, outputStrategy, javaHome, connectInput) map {
31 | (tp, tmp, si, base, options, strategy, javaHomeDir, connectIn) =>
32 | new BenchmarkRunner(tp.id, ForkOptions(scalaJars = si.jars, javaHome = javaHomeDir, connectInput = connectIn, outputStrategy = strategy,
33 | runJVMOptions = options, workingDirectory = Some(base)) )
34 | },
35 | publishTo <<= (version) { version: String =>
36 | val repo = (s: String) =>
37 | Resolver.ssh(s, "repo.fyrie.net", "/home/repo/" + s + "/") as("derek", file("/home/derek/.ssh/id_rsa")) withPermissions("0644")
38 | Some(if (version.trim.endsWith("SNAPSHOT")) repo("snapshots") else repo("releases"))
39 | })
40 |
41 | def formattingPreferences = {
42 | import scalariform.formatter.preferences._
43 | FormattingPreferences()
44 | .setPreference(RewriteArrowSymbols, true)
45 | .setPreference(AlignParameters, true)
46 | .setPreference(AlignSingleLineCaseStatements, true)
47 | }
48 |
49 | lazy val Benchmark = config("benchmark") extend(Test)
50 |
51 | }
52 |
53 | class BenchmarkRunner(subproject: String, config: ForkScalaRun) extends sbt.ScalaRun {
54 | def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Option[String] = {
55 | log.info("Running " + subproject + " " + mainClass + " " + options.mkString(" "))
56 |
57 | val javaOptions = classpathOption(classpath) ::: mainClass :: options.toList
58 | val strategy = config.outputStrategy getOrElse LoggedOutput(log)
59 | val process = Fork.java.fork(config.javaHome,
60 | /* Seq("-XX:+UseConcMarkSweepGC", "-XX:+TieredCompilation", "-XX:SurvivorRatio=1", "-Xmn1g", "-Xms2g", "-Xmx2g") ++ */ config.runJVMOptions ++ javaOptions,
61 | config.workingDirectory,
62 | Map.empty,
63 | config.connectInput,
64 | strategy)
65 | def cancel() = {
66 | log.warn("Run canceled.")
67 | process.destroy()
68 | 1
69 | }
70 | val exitCode = try process.exitValue() catch { case e: InterruptedException => cancel() }
71 | processExitCode(exitCode, "runner")
72 | }
73 | private def classpathOption(classpath: Seq[File]) = "-classpath" :: Path.makeString(classpath) :: Nil
74 | private def processExitCode(exitCode: Int, label: String) = {
75 | if(exitCode == 0) None
76 | else Some("Nonzero exit code returned from " + label + ": " + exitCode)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=0.11.2
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | resolvers += "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/"
2 |
3 | addSbtPlugin("com.typesafe.sbtscalariform" % "sbtscalariform" % "0.3.1")
4 |
--------------------------------------------------------------------------------
/src/benchmark/scala/net/fyrie/redis/Main.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import akka.actor.ActorSystem
4 | import akka.dispatch.{ Future, Await }
5 | import akka.util.{ ByteString, Duration }
6 |
7 | import annotation.tailrec
8 | import com.google.caliper.{
9 | Runner => CaliperRunner,
10 | Param,
11 | SimpleBenchmark
12 | }
13 |
14 | object Main extends App {
15 | //CaliperRunner.main(classOf[Benchmark], (args :+ "--vm" :+ "java -XX:+TieredCompilation"): _*)
16 |
17 | CaliperRunner.main(classOf[Benchmark], (args :+ "--vm" :+ "java -XX:+UseParallelOldGC -XX:+TieredCompilation -XX:SurvivorRatio=1 -Xmn1g -Xms2g -Xmx2g"): _*)
18 |
19 | //CaliperRunner.main(classOf[Benchmark], (args :+ "-Jgc=-XX:+UseParallelOldGC,-XX:+UseConcMarkSweepGC,-XX:+UseG1GC" :+ "--vm" :+ "java -XX:+TieredCompilation -XX:SurvivorRatio=1 -Xmn1g -Xms2g -Xmx2g"): _*)
20 |
21 | /*
22 | val bm = new Benchmark
23 | bm.setUp
24 |
25 | while(true) {
26 | val ops = 100000
27 | val start = System.nanoTime
28 | bm.timeScatterGather(ops)
29 | val end = System.nanoTime
30 | println("ops/sec: " + (ops.toDouble / ((end - start).toDouble / 1000000000.0)).toInt)
31 | System.gc
32 | }
33 |
34 | bm.system.shutdown
35 | */
36 | }
37 |
38 | class Benchmark extends SimpleScalaBenchmark {
39 |
40 | // default key:
41 |
42 | // amount of unique keys to use, not implemented
43 | // val keyspace = 1
44 |
45 | val dataSize = 3
46 |
47 | val data = ByteString("x" * dataSize)
48 |
49 | implicit val system: ActorSystem = TestSystem.system
50 |
51 | var client: RedisClient = _
52 |
53 | override def setUp() {
54 | client = RedisClient(config = RedisClientConfig(retryOnReconnect = false))
55 | client.sync.flushdb
56 | }
57 |
58 | override def tearDown() {
59 | // client.sync.flushdb
60 | client.disconnect
61 | client = null
62 | }
63 |
64 | def timeSet(reps: Int) = {
65 | repeat(reps) { client set ("foo:rand:000000000000", data) }
66 |
67 | val result = client.sync get "foo:rand:000000000000"
68 |
69 | assert(result == Some(data))
70 |
71 | result
72 | }
73 |
74 | def timeGet(reps: Int) = {
75 | client.quiet set ("foo:rand:000000000000", data)
76 | repeat(reps) { client get "foo:rand:000000000000" }
77 |
78 | val result = client.sync get "foo:rand:000000000000"
79 |
80 | assert(result == Some(data))
81 |
82 | result
83 | }
84 |
85 | def timeIncrFast(reps: Int) = {
86 | repeat(reps) { client.quiet incr "counter:rand:000000000000" }
87 |
88 | val result = (client.sync getset ("counter:rand:000000000000", 0)).parse[Int]
89 |
90 | assert(result == Some(reps))
91 |
92 | result
93 | }
94 |
95 | def timeIncr(reps: Int) = {
96 | repeat(reps) { client incr "counter:rand:000000000000" }
97 |
98 | val result = (client.sync getset ("counter:rand:000000000000", 0)).parse[Int]
99 |
100 | assert(result == Some(reps))
101 |
102 | result
103 | }
104 |
105 | def timeScatterGather(reps: Int) = {
106 | val keys = (1 to 100).toList map ("list_" + _)
107 |
108 | val ops = reps / 200 // 2 ops per rep
109 |
110 | val scatter = { (key: String) ⇒
111 | (1 to ops) foreach (i ⇒ client.quiet rpush (key, i))
112 | client llen key map (x ⇒ assert(x == ops))
113 | }
114 |
115 | val gather = { (key: String) ⇒
116 | val sum = (Future(0) /: (1 to ops)) { (fr, _) ⇒
117 | for {
118 | n ← client lpop key
119 | r ← fr
120 | } yield n.parse[Int].get + r
121 | }
122 | client llen key map (x ⇒ assert(x == 0)) flatMap (_ ⇒ sum)
123 | }
124 |
125 | val future = for {
126 | _ ← Future.traverse(keys)(scatter)
127 | n ← Future.traverse(keys)(gather)
128 | } yield n.sum
129 |
130 | val result = Await.result(future, Duration.Inf)
131 |
132 | assert(result == ((1 to ops).sum * 100))
133 |
134 | }
135 |
136 | }
137 |
138 | trait SimpleScalaBenchmark extends SimpleBenchmark {
139 |
140 | def repeat[@specialized A](reps: Int)(f: => A): A = {
141 | def run(i: Int, result: A): A = {
142 | if (i == 0) result else run(i - 1, f)
143 | }
144 | run(reps - 1, f)
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/ConnectionPool.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie
2 | package redis
3 |
4 | import messages.{ RequestClient, ReleaseClient, Disconnect }
5 | import akka.actor._
6 | import akka.dispatch.{ Promise, Future }
7 | import collection.immutable.Queue
8 |
9 | private[redis] class ConnectionPool(initialSize: Range, factory: (ActorRefFactory) ⇒ RedisClientPoolWorker) extends Actor {
10 | var ready: List[RedisClientPoolWorker] = Nil
11 | var active: Set[RedisClientPoolWorker] = Set.empty
12 | var limit: Range = initialSize
13 | var size: Int = 0
14 | var queue: Queue[Promise[RedisClientPoolWorker]] = Queue.empty
15 |
16 | def receive = {
17 | case RequestClient(promise) ⇒
18 | if (active.size >= limit.max)
19 | queue = queue enqueue promise
20 | else {
21 | val client = ready match {
22 | case h :: t ⇒
23 | ready = t
24 | h
25 | case _ ⇒
26 | size += 1
27 | factory(context)
28 | }
29 | active += client
30 | promise complete Right(client)
31 | }
32 | case ReleaseClient(client) ⇒
33 | if (active.size > limit.max) {
34 | killClient(client)
35 | } else if (queue.nonEmpty) {
36 | val (promise, rest) = queue.dequeue
37 | promise complete Right(client)
38 | queue = rest
39 | } else if ((size - active.size) == limit.min) {
40 | killClient(client)
41 | } else {
42 | ready ::= client
43 | active -= client
44 | }
45 | }
46 |
47 | def killClient(client: RedisClientPoolWorker) {
48 | client.disconnect
49 | active -= client
50 | size -= 1
51 | }
52 |
53 | override def postStop {
54 | queue foreach (_ complete Left(RedisConnectionException("Connection pool shutting down")))
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/Queued.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import types._
4 |
5 | import akka.dispatch.{ Promise, Future }
6 | import akka.util.ByteString
7 |
8 | object Queued {
9 | private[redis] def apply[A](value: A, request: (ByteString, Promise[RedisType]), response: Promise[RedisType]): Queued[A] =
10 | new QueuedSingle(value, request, response)
11 | def apply[A](value: A): Queued[A] = new QueuedValue(value)
12 |
13 | private[redis] final class QueuedValue[+A](val value: A) extends Queued[A] {
14 | def requests = Vector.empty
15 | def responses = Vector.empty
16 | def flatMap[B](f: A ⇒ Queued[B]): Queued[B] = f(value)
17 | def map[B](f: A ⇒ B): Queued[B] = new QueuedValue(f(value))
18 | }
19 |
20 | private[redis] final class QueuedSingle[+A](val value: A, val request: (ByteString, Promise[RedisType]), val response: Promise[RedisType]) extends Queued[A] {
21 | def requests = Vector(request)
22 | def responses = Vector(response)
23 | def flatMap[B](f: A ⇒ Queued[B]): Queued[B] = {
24 | val that = f(value)
25 | new QueuedList(that.value, request +: that.requests, response +: that.responses)
26 | }
27 | def map[B](f: A ⇒ B): Queued[B] = new QueuedSingle(f(value), request, response)
28 | }
29 |
30 | private[redis] final class QueuedList[+A](val value: A, val requests: Vector[(ByteString, Promise[RedisType])], val responses: Vector[Promise[RedisType]]) extends Queued[A] {
31 | def flatMap[B](f: A ⇒ Queued[B]): Queued[B] = {
32 | f(value) match {
33 | case that: QueuedList[_] ⇒
34 | new QueuedList(that.value, requests ++ that.requests, responses ++ that.responses)
35 | case that: QueuedSingle[_] ⇒
36 | new QueuedList(that.value, requests :+ that.request, responses :+ that.response)
37 | case that: QueuedValue[_] ⇒
38 | new QueuedList(that.value, requests, responses)
39 | }
40 | }
41 | def map[B](f: A ⇒ B): Queued[B] = new QueuedList(f(value), requests, responses)
42 | }
43 | }
44 |
45 | sealed trait Queued[+A] {
46 | def value: A
47 | private[redis] def requests: Vector[(ByteString, Promise[RedisType])]
48 | private[redis] def responses: Vector[Promise[RedisType]]
49 | def flatMap[B](f: A ⇒ Queued[B]): Queued[B]
50 | def map[B](f: A ⇒ B): Queued[B]
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/RedisClient.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie
2 | package redis
3 |
4 | import actors._
5 | import messages._
6 | import protocol.Constants
7 | import Constants.EOL
8 | import types._
9 | import serialization.{ Parse, Store }
10 |
11 | import akka.actor._
12 | import akka.dispatch.{ Future, Promise, Await }
13 | import akka.util.{ ByteString, ByteStringBuilder, Timeout, Duration }
14 |
15 | case class RedisClientConfig(connections: Int = 1,
16 | autoReconnect: Boolean = true,
17 | retryOnReconnect: Boolean = true,
18 | poolSize: Range = 50 to 50)
19 |
20 | object RedisClient {
21 | def apply(host: String = "localhost", port: Int = 6379, config: RedisClientConfig = RedisClientConfig())(implicit system: ActorSystem) =
22 | new RedisClient(host, port, config)
23 |
24 | def subscriber(listener: ActorRef)(host: String = "localhost", port: Int = 6379, config: RedisClientConfig = RedisClientConfig())(implicit system: ActorSystem): ActorRef =
25 | system.actorOf(Props(new RedisSubscriberSession(listener)(host, port, config)))
26 | }
27 |
28 | final class RedisClient(val host: String, val port: Int, val config: RedisClientConfig)(implicit val system: ActorSystem) extends RedisClientAsync(config) {
29 |
30 | protected val actor = system.actorOf(Props(new RedisClientSession(host, port, config)))
31 |
32 | protected val pool: ActorRef = system.actorOf(Props(new ConnectionPool(config.poolSize, parent ⇒
33 | new RedisClientPoolWorker(parent.actorOf(Props(new RedisClientSession(host, port, config))), config, pool))))
34 |
35 | def disconnect = {
36 | system stop actor
37 | system stop pool
38 | }
39 |
40 | val sync: RedisClientSync = new RedisClientSync(config) {
41 | def system = RedisClient.this.system
42 | def actor = RedisClient.this.actor
43 |
44 | def async: RedisClientAsync = RedisClient.this
45 | def quiet: RedisClientQuiet = RedisClient.this.quiet
46 | }
47 |
48 | val quiet: RedisClientQuiet = new RedisClientQuiet(config) {
49 | def system = RedisClient.this.system
50 | def actor = RedisClient.this.actor
51 |
52 | def async: RedisClientAsync = RedisClient.this
53 | def sync: RedisClientSync = RedisClient.this.sync
54 | }
55 |
56 | def multi[T](block: (RedisClientMulti) ⇒ Queued[T]): T = {
57 | val rq = new RedisClientMulti(config) {
58 | def system = RedisClient.this.system
59 | def actor = RedisClient.this.actor
60 | }
61 | rq.exec(block(rq))
62 | }
63 |
64 | def atomic[T](block: (RedisClientWatch) ⇒ Future[Queued[T]]): Future[T] = {
65 | val promise = Promise[RedisClientPoolWorker]()
66 | pool ! RequestClient(promise)
67 | promise flatMap (_.run(block))
68 | }
69 |
70 | def subscriber(listener: ActorRef): ActorRef =
71 | system.actorOf(Props(new RedisSubscriberSession(listener)(host, port, config)))
72 |
73 | /**
74 | * Callback to be run when a request is sent to the server. The callback
75 | * must accept 2 Longs, the first is the id for the request (sequentially
76 | * incremented starting at 1, and is not shared between clients), and the
77 | * second is the time the request was made (in millis).
78 | */
79 | def onRequest(callback: (Long, Long) ⇒ Unit) {
80 | actor ! RequestCallback(callback)
81 | }
82 |
83 | /**
84 | * Callback to be run when a result is received from the sender. The callback
85 | * must accept 2 Longs, the first is the id for the result (sequentially
86 | * incremented, and is not shared between clients), and the second is the time
87 | * the result was fully received (in millis).
88 | *
89 | * FIXME: The id should match with the request id, but if any requests were
90 | * lost due to connection failures they may lose sync. A more reliable method
91 | * will be implemented later.
92 | */
93 | def onResult(callback: (Long, Long) ⇒ Unit) {
94 | actor ! ResultCallback(callback)
95 | }
96 |
97 | }
98 |
99 | sealed trait ConfigurableRedisClient {
100 |
101 | def sync: RedisClientSync
102 | def async: RedisClientAsync
103 | def quiet: RedisClientQuiet
104 |
105 | }
106 |
107 | sealed abstract class RedisClientAsync(config: RedisClientConfig) extends Commands[Future] with ConfigurableRedisClient {
108 | val async: RedisClientAsync = this
109 |
110 | final private[redis] def send(in: List[ByteString]): Future[Any] = {
111 | val promise = Promise[RedisType]()
112 | actor ! Request(PromiseRequestor(promise), format(in))
113 | promise
114 | }
115 | }
116 |
117 | sealed abstract class RedisClientSync(config: RedisClientConfig) extends Commands[({ type λ[α] = α })#λ] with ConfigurableRedisClient {
118 | val sync: RedisClientSync = this
119 |
120 | final private[redis] def send(in: List[ByteString]): Any = {
121 | val promise = Promise[RedisType]()
122 | actor ! Request(PromiseRequestor(promise), format(in))
123 | Await.result(promise, Duration.Inf)
124 | }
125 | }
126 |
127 | sealed abstract class RedisClientQuiet(config: RedisClientConfig) extends Commands[({ type λ[_] = Unit })#λ] with ConfigurableRedisClient {
128 | val quiet: RedisClientQuiet = this
129 |
130 | final private[redis] def send(in: List[ByteString]) = actor ! Request(NoRequestor, format(in))
131 | }
132 |
133 | sealed abstract class RedisClientMulti(config: RedisClientConfig) extends Commands[({ type λ[α] = Queued[Future[α]] })#λ]()(ResultFunctor.multi) {
134 |
135 | implicit def system: ActorSystem
136 |
137 | final private[redis] def send(in: List[ByteString]): Queued[Future[Any]] = {
138 | val result = Promise[RedisType]()
139 | Queued(result, (format(in), Promise[RedisType]()), result)
140 | }
141 |
142 | final private[redis] def exec[T](q: Queued[T]): T = {
143 | val promise = Promise[RedisType]()
144 | promise foreach {
145 | case RedisMulti(m) ⇒
146 | var responses = q.responses
147 | (q.responses.iterator zip (q.requests.iterator map (_._2.value))) foreach {
148 | case (resp, Some(Right(status))) ⇒ try { toStatus(status) } catch { case e ⇒ resp complete Left(e) }
149 | case (resp, _) ⇒ resp complete Left(RedisProtocolException("Unexpected response"))
150 | }
151 | for {
152 | list ← m
153 | rtype ← list
154 | } {
155 | while (responses.head.isCompleted) { responses = responses.tail }
156 | responses.head complete Right(rtype)
157 | responses = responses.tail
158 | }
159 | case RedisError(e) ⇒
160 | val re = RedisErrorException(e)
161 | q.responses foreach (_.complete(Left(re)))
162 | case _ ⇒
163 | val re = RedisProtocolException("Unexpected response")
164 | q.responses foreach (_.complete(Left(re)))
165 | }
166 | actor ! MultiRequest(PromiseRequestor(promise), format(List(Constants.MULTI)), q.requests, format(List(Constants.EXEC)))
167 | q.value
168 | }
169 | }
170 |
171 | sealed abstract class RedisClientWatch(config: RedisClientConfig) extends Commands[Future]()(ResultFunctor.async) {
172 | self: RedisClientPoolWorker ⇒
173 |
174 | final private[redis] def send(in: List[ByteString]): Future[Any] = {
175 | val promise = Promise[RedisType]()
176 | actor ! Request(PromiseRequestor(promise), format(in))
177 | promise
178 | }
179 |
180 | final private[redis] def run[T](block: (RedisClientWatch) ⇒ Future[Queued[T]]): Future[T] = {
181 | val promise = Promise[T]()
182 | exec(promise, block)
183 | promise onComplete { _ ⇒ this.release }
184 | }
185 |
186 | final private[redis] def exec[T](promise: Promise[T], block: (RedisClientWatch) ⇒ Future[Queued[T]]): Unit = {
187 | block(this) onFailure { case e ⇒ promise complete Left(e) } foreach { q ⇒
188 | val multiPromise = Promise[RedisType]()
189 | multiPromise foreach {
190 | case RedisMulti(None) ⇒
191 | exec(promise, block) // TODO: add timeout?
192 | case RedisMulti(m) ⇒
193 | var responses = q.responses
194 | (q.responses.iterator zip (q.requests.iterator map (_._2.value))) foreach {
195 | case (resp, Some(Right(status))) ⇒ try { toStatus(status) } catch { case e ⇒ resp complete Left(e) }
196 | case (resp, _) ⇒ resp complete Left(RedisProtocolException("Unexpected response"))
197 | }
198 | for {
199 | list ← m
200 | rtype ← list
201 | } {
202 | while (responses.head.isCompleted) { responses = responses.tail }
203 | responses.head complete Right(rtype)
204 | responses = responses.tail
205 | }
206 | promise complete Right(q.value)
207 | case RedisError(e) ⇒
208 | val re = RedisErrorException(e)
209 | q.responses foreach (_.complete(Left(re)))
210 | promise complete Left(re)
211 | case _ ⇒
212 | val re = RedisProtocolException("Unexpected response")
213 | q.responses foreach (_.complete(Left(re)))
214 | promise complete Left(re)
215 | }
216 | actor ! MultiRequest(PromiseRequestor(multiPromise), format(List(Constants.MULTI)), q.requests, format(List(Constants.EXEC)))
217 | }
218 | }
219 |
220 | private val rq = new RedisClientMulti(config) {
221 | def system = RedisClientWatch.this.system
222 | def actor = RedisClientWatch.this.actor
223 | }
224 |
225 | def multi[T](block: (RedisClientMulti) ⇒ Queued[T]): Queued[T] = block(rq)
226 |
227 | def watch[A: serialization.Store](key: A): Future[Unit] = send(Constants.WATCH :: serialization.Store(key) :: Nil)
228 |
229 | }
230 |
231 | private[redis] final class RedisClientPoolWorker(protected val actor: ActorRef, config: RedisClientConfig, pool: ActorRef)(implicit val system: ActorSystem) extends RedisClientWatch(config) {
232 | def disconnect = actor ! Disconnect
233 | def release = pool ! ReleaseClient(this)
234 | }
235 |
236 | private[redis] final class RedisClientSub(protected val actor: ActorRef, config: RedisClientConfig, val system: ActorSystem) extends Commands[({ type X[_] = ByteString })#X] {
237 | import Constants._
238 |
239 | final def send(in: List[ByteString]): ByteString = format(in)
240 |
241 | def subscribe[A: Store](channels: Iterable[A]): ByteString = send(SUBSCRIBE :: (channels.map(Store(_))(collection.breakOut): List[ByteString]))
242 |
243 | def unsubscribe[A: Store](channels: Iterable[A]): ByteString = send(UNSUBSCRIBE :: (channels.map(Store(_))(collection.breakOut): List[ByteString]))
244 |
245 | def psubscribe[A: Store](patterns: Iterable[A]): ByteString = send(PSUBSCRIBE :: (patterns.map(Store(_))(collection.breakOut): List[ByteString]))
246 |
247 | def punsubscribe[A: Store](patterns: Iterable[A]): ByteString = send(PUNSUBSCRIBE :: (patterns.map(Store(_))(collection.breakOut): List[ByteString]))
248 |
249 | }
250 |
251 | import commands._
252 | private[redis] sealed abstract class Commands[Result[_]](implicit rf: ResultFunctor[Result]) extends Keys[Result] with Servers[Result] with Strings[Result] with Lists[Result] with Sets[Result] with SortedSets[Result] with Hashes[Result] with PubSub[Result] with Scripts[Result] {
253 |
254 | implicit def system: ActorSystem
255 |
256 | protected def actor: ActorRef
257 |
258 | private[redis] def send(in: List[ByteString]): Result[Any]
259 |
260 | final private[redis] def format(in: Seq[ByteString]): ByteString = {
261 | val builder = new ByteStringBuilder
262 | val count = in.length
263 | builder ++= ByteString("*" + count)
264 | builder ++= EOL
265 | in foreach { bytes ⇒
266 | builder ++= ByteString("$" + bytes.length)
267 | builder ++= EOL
268 | builder ++= bytes
269 | builder ++= EOL
270 | }
271 | builder.result.compact
272 | }
273 |
274 | final private[redis] implicit def resultAsRedisType(raw: Result[Any]): Result[RedisType] = rf.fmap(raw)(toRedisType)
275 | final private[redis] implicit def resultAsMultiBulk(raw: Result[Any]): Result[Option[List[Option[ByteString]]]] = rf.fmap(raw)(toMultiBulk)
276 | final private[redis] implicit def resultAsMultiBulkList(raw: Result[Any]): Result[List[Option[ByteString]]] = rf.fmap(raw)(toMultiBulkList)
277 | final private[redis] implicit def resultAsMultiBulkFlat(raw: Result[Any]): Result[Option[List[ByteString]]] = rf.fmap(raw)(toMultiBulkFlat)
278 | final private[redis] implicit def resultAsMultiBulkFlatList(raw: Result[Any]): Result[List[ByteString]] = rf.fmap(raw)(toMultiBulkFlatList)
279 | final private[redis] implicit def resultAsMultiBulkSet(raw: Result[Any]): Result[Set[ByteString]] = rf.fmap(raw)(toMultiBulkSet)
280 | final private[redis] implicit def resultAsMultiBulkMap(raw: Result[Any]): Result[Map[ByteString, ByteString]] = rf.fmap(raw)(toMultiBulkMap)
281 | final private[redis] implicit def resultAsMultiBulkScored(raw: Result[Any]): Result[List[(ByteString, Double)]] = rf.fmap(raw)(toMultiBulkScored)
282 | final private[redis] implicit def resultAsMultiBulkSinglePair(raw: Result[Any]): Result[Option[(ByteString, ByteString)]] = rf.fmap(raw)(toMultiBulkSinglePair)
283 | final private[redis] implicit def resultAsMultiBulkSinglePairK[K: Parse](raw: Result[Any]): Result[Option[(K, ByteString)]] = rf.fmap(raw)(toMultiBulkSinglePair(_).map(kv ⇒ (Parse(kv._1), kv._2)))
284 | final private[redis] implicit def resultAsBulk(raw: Result[Any]): Result[Option[ByteString]] = rf.fmap(raw)(toBulk)
285 | final private[redis] implicit def resultAsDouble(raw: Result[Any]): Result[Double] = rf.fmap(raw)(toDouble)
286 | final private[redis] implicit def resultAsDoubleOption(raw: Result[Any]): Result[Option[Double]] = rf.fmap(raw)(toDoubleOption)
287 | final private[redis] implicit def resultAsLong(raw: Result[Any]): Result[Long] = rf.fmap(raw)(toLong)
288 | final private[redis] implicit def resultAsInt(raw: Result[Any]): Result[Int] = rf.fmap(raw)(toLong(_).toInt)
289 | final private[redis] implicit def resultAsIntOption(raw: Result[Any]): Result[Option[Int]] = rf.fmap(raw)(toIntOption)
290 | final private[redis] implicit def resultAsBool(raw: Result[Any]): Result[Boolean] = rf.fmap(raw)(toBool)
291 | final private[redis] implicit def resultAsStatus(raw: Result[Any]): Result[String] = rf.fmap(raw)(toStatus)
292 | final private[redis] implicit def resultAsOkStatus(raw: Result[Any]): Result[Unit] = rf.fmap(raw)(toOkStatus)
293 |
294 | final private[redis] val toRedisType: Any ⇒ RedisType = _ match {
295 | case r: RedisType ⇒ r
296 | case _ ⇒ throw RedisProtocolException("Unexpected response")
297 | }
298 |
299 | final private[redis] val toMultiBulk: Any ⇒ Option[List[Option[ByteString]]] = _ match {
300 | case RedisMulti(m) ⇒ m map (_ map toBulk)
301 | case RedisError(e) ⇒ throw RedisErrorException(e)
302 | case _ ⇒ throw RedisProtocolException("Unexpected response")
303 | }
304 |
305 | final private[redis] val toMultiBulkList: Any ⇒ List[Option[ByteString]] = _ match {
306 | case RedisMulti(Some(m)) ⇒ m map toBulk
307 | case RedisMulti(None) ⇒ Nil
308 | case RedisError(e) ⇒ throw RedisErrorException(e)
309 | case _ ⇒ throw RedisProtocolException("Unexpected response")
310 | }
311 |
312 | final private[redis] val toMultiBulkFlat: Any ⇒ Option[List[ByteString]] = _ match {
313 | case RedisMulti(m) ⇒ m map (_ flatMap (toBulk(_).toList))
314 | case RedisError(e) ⇒ throw RedisErrorException(e)
315 | case _ ⇒ throw RedisProtocolException("Unexpected response")
316 | }
317 |
318 | final private[redis] val toMultiBulkFlatList: Any ⇒ List[ByteString] = _ match {
319 | case RedisMulti(Some(m)) ⇒ m flatMap (toBulk(_).toList)
320 | case RedisMulti(None) ⇒ Nil
321 | case RedisError(e) ⇒ throw RedisErrorException(e)
322 | case _ ⇒ throw RedisProtocolException("Unexpected response")
323 | }
324 |
325 | final private[redis] val toMultiBulkSet: Any ⇒ Set[ByteString] = _ match {
326 | case RedisMulti(Some(m)) ⇒ m.flatMap(toBulk(_).toList)(collection.breakOut)
327 | case RedisMulti(None) ⇒ Set.empty
328 | case RedisError(e) ⇒ throw RedisErrorException(e)
329 | case _ ⇒ throw RedisProtocolException("Unexpected response")
330 | }
331 |
332 | final private[redis] val toMultiBulkMap: Any ⇒ Map[ByteString, ByteString] = _ match {
333 | case RedisMulti(Some(m)) ⇒ m.grouped(2).collect {
334 | case List(RedisBulk(Some(a)), RedisBulk(Some(b))) ⇒ (a, b)
335 | } toMap
336 | case RedisMulti(None) ⇒ Map.empty
337 | case RedisError(e) ⇒ throw RedisErrorException(e)
338 | case _ ⇒ throw RedisProtocolException("Unexpected response")
339 | }
340 |
341 | final private[redis] val toMultiBulkScored: Any ⇒ List[(ByteString, Double)] = _ match {
342 | case RedisMulti(Some(m)) ⇒ m.grouped(2).collect {
343 | case List(RedisBulk(Some(a)), RedisBulk(Some(b))) ⇒ (a, Parse[Double](b))
344 | } toList
345 | case RedisMulti(None) ⇒ Nil
346 | case RedisError(e) ⇒ throw RedisErrorException(e)
347 | case _ ⇒ throw RedisProtocolException("Unexpected response")
348 | }
349 |
350 | final private[redis] val toMultiBulkSinglePair: Any ⇒ Option[(ByteString, ByteString)] = _ match {
351 | case RedisMulti(Some(List(RedisBulk(Some(a)), RedisBulk(Some(b))))) ⇒ Some((a, b))
352 | case RedisMulti(_) ⇒ None
353 | case RedisError(e) ⇒ throw RedisErrorException(e)
354 | case _ ⇒ throw RedisProtocolException("Unexpected response")
355 | }
356 |
357 | final private[redis] val toBulk: Any ⇒ Option[ByteString] = _ match {
358 | case RedisBulk(b) ⇒ b
359 | case RedisError(e) ⇒ throw RedisErrorException(e)
360 | case _ ⇒ throw RedisProtocolException("Unexpected response")
361 | }
362 |
363 | final private[redis] val toDouble: Any ⇒ Double = _ match {
364 | case RedisBulk(Some(b)) ⇒ Parse[Double](b)
365 | case RedisError(e) ⇒ throw RedisErrorException(e)
366 | case _ ⇒ throw RedisProtocolException("Unexpected response")
367 | }
368 |
369 | final private[redis] val toDoubleOption: Any ⇒ Option[Double] = _ match {
370 | case RedisBulk(b) ⇒ b map (Parse[Double](_))
371 | case RedisError(e) ⇒ throw RedisErrorException(e)
372 | case _ ⇒ throw RedisProtocolException("Unexpected response")
373 | }
374 |
375 | final private[redis] val toLong: Any ⇒ Long = _ match {
376 | case RedisInteger(n) ⇒ n
377 | case RedisError(e) ⇒ throw RedisErrorException(e)
378 | case _ ⇒ throw RedisProtocolException("Unexpected response")
379 | }
380 |
381 | final private[redis] val toIntOption: Any ⇒ Option[Int] = _ match {
382 | case RedisInteger(n) ⇒ if (n >= 0) Some(n.toInt) else None
383 | case RedisBulk(None) ⇒ None
384 | case RedisError(e) ⇒ throw RedisErrorException(e)
385 | case _ ⇒ throw RedisProtocolException("Unexpected response")
386 | }
387 |
388 | final private[redis] val toBool: Any ⇒ Boolean = _ match {
389 | case RedisInteger(1) ⇒ true
390 | case RedisInteger(0) ⇒ false
391 | case RedisError(e) ⇒ throw RedisErrorException(e)
392 | case _ ⇒ throw RedisProtocolException("Unexpected response")
393 | }
394 |
395 | final private[redis] val toStatus: Any ⇒ String = _ match {
396 | case RedisString(s) ⇒ s
397 | case RedisBulk(Some(b)) ⇒ Parse[String](b)
398 | case RedisError(e) ⇒ throw RedisErrorException(e)
399 | case _ ⇒ throw RedisProtocolException("Unexpected response")
400 | }
401 |
402 | final private[redis] val toOkStatus: Any ⇒ Unit = _ match {
403 | case RedisString("OK") ⇒ Unit
404 | case RedisError(e) ⇒ throw RedisErrorException(e)
405 | case _ ⇒ throw RedisProtocolException("Unexpected response")
406 | }
407 | }
408 |
409 | case class RedisErrorException(message: String) extends RuntimeException(message)
410 | case class RedisProtocolException(message: String) extends RuntimeException(message)
411 | case class RedisConnectionException(message: String) extends RuntimeException(message)
412 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/ResultFunctor.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import akka.dispatch.Future
4 | import akka.util.ByteString
5 |
6 | private[redis] sealed abstract class ResultFunctor[R[_]] {
7 | def fmap[A, B](a: R[A])(f: A ⇒ B): R[B]
8 | }
9 |
10 | private[redis] object ResultFunctor {
11 | implicit val async: ResultFunctor[Future] = new ResultFunctor[Future] {
12 | def fmap[A, B](a: Future[A])(f: A ⇒ B): Future[B] = a map f
13 | }
14 | implicit val sync: ResultFunctor[({ type λ[α] = α })#λ] = new ResultFunctor[({ type λ[α] = α })#λ] {
15 | def fmap[A, B](a: A)(f: A ⇒ B): B = f(a)
16 | }
17 | implicit val quiet: ResultFunctor[({ type λ[_] = Unit })#λ] = new ResultFunctor[({ type λ[_] = Unit })#λ] {
18 | def fmap[A, B](a: Unit)(f: A ⇒ B): Unit = ()
19 | }
20 | implicit val multi: ResultFunctor[({ type λ[α] = Queued[Future[α]] })#λ] = new ResultFunctor[({ type λ[α] = Queued[Future[α]] })#λ] {
21 | def fmap[A, B](a: Queued[Future[A]])(f: A ⇒ B): Queued[Future[B]] = a map (_ map f)
22 | }
23 | implicit val raw: ResultFunctor[({ type X[_] = ByteString })#X] = new ResultFunctor[({ type X[_] = ByteString })#X] {
24 | def fmap[A, B](a: ByteString)(f: A ⇒ B): ByteString = a
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/actors/Actors.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie
2 | package redis
3 | package actors
4 |
5 | import messages._
6 | import types._
7 | import pubsub._
8 | import protocol._
9 |
10 | import akka.actor._
11 | import akka.util.ByteString
12 | import akka.util.duration._
13 | import akka.event.Logging
14 |
15 | import java.util.concurrent.TimeUnit
16 | import java.net.ConnectException
17 |
18 | import scala.collection.mutable.Queue
19 |
20 | private[redis] final class RedisClientSession(host: String, port: Int, config: RedisClientConfig) extends Actor {
21 | import Constants._
22 | import Iteratees._
23 |
24 | val log = Logging(context.system, this)
25 |
26 | var sockets: Vector[IO.SocketHandle] = _
27 |
28 | var nextSocket: Int = 0
29 |
30 | val state = IO.IterateeRef.Map.async[IO.Handle]()(context.dispatcher)
31 |
32 | var results = 0L
33 | var resultCallbacks = Seq.empty[(Long, Long) ⇒ Unit]
34 | var requestCallbacks = Seq.empty[(Long, Long) ⇒ Unit]
35 |
36 | val waiting = Queue.empty[RequestMessage]
37 | var requests = 0L
38 |
39 | override def preStart = {
40 | log info ("Connecting")
41 | sockets = Vector.fill(config.connections)(IOManager(context.system).connect(host, port))
42 | }
43 |
44 | def receive = {
45 |
46 | case req @ Request(requestor, bytes) ⇒
47 | val socket = sockets(nextSocket)
48 | nextSocket = (nextSocket + 1) % config.connections
49 | socket write bytes
50 | onRequest(req)
51 | for {
52 | _ ← state(socket)
53 | result ← readResult
54 | } yield {
55 | requestor respond result
56 | onResult()
57 | }
58 |
59 | case req @ MultiRequest(requestor, _, cmds, _) ⇒
60 | val socket = sockets(nextSocket)
61 | nextSocket = (nextSocket + 1) % config.connections
62 | sendMulti(req, socket)
63 | onRequest(req)
64 | for {
65 | _ ← state(socket)
66 | _ ← readResult
67 | _ ← IO.fold((), cmds.map(_._2))((_, p) ⇒ readResult map (p success))
68 | exec ← readResult
69 | } yield {
70 | requestor respond exec
71 | onResult()
72 | }
73 |
74 | case IO.Read(handle, bytes) ⇒
75 | state(handle)(IO Chunk bytes)
76 |
77 | case RequestCallback(callback) ⇒
78 | requestCallbacks +:= callback
79 |
80 | case ResultCallback(callback) ⇒
81 | resultCallbacks +:= callback
82 | /*
83 | case IO.Closed(handle, Some(cause: ConnectException)) if socket == handle && config.autoReconnect ⇒
84 | log info ("Connection refused, retrying in 1 second")
85 | context.system.scheduler.scheduleOnce(1 second, self, IO.Closed(handle, None))
86 |
87 | case IO.Closed(handle, cause) if socket == handle && config.autoReconnect ⇒
88 | log info ("Reconnecting" + (cause map (e ⇒ ", cause: " + e.toString) getOrElse ""))
89 | socket = IOManager(context.system).connect(host, port)
90 | if (config.retryOnReconnect) {
91 | waiting foreach {
92 | case req: Request ⇒ socket write req.bytes
93 | case req: MultiRequest ⇒ sendMulti(req)
94 | }
95 | if (waiting.nonEmpty) log info ("Retrying " + waiting.length + " commands")
96 | } */
97 |
98 | case IO.Closed(handle, cause) if sockets contains handle ⇒
99 | log info ("Connection closed" + (cause map (e ⇒ ", cause: " + e.toString) getOrElse ""))
100 | // FIXME: stop actors
101 | // sendToSupervisor(Disconnect)
102 | state(handle)(IO EOF cause)
103 |
104 | case Received ⇒
105 | //waiting.dequeue
106 |
107 | }
108 |
109 | def onRequest(req: RequestMessage): Unit = {
110 | //if (config.retryOnReconnect) waiting enqueue req
111 | requests += 1L
112 | if (requestCallbacks.nonEmpty) {
113 | val atTime = System.currentTimeMillis
114 | requestCallbacks foreach (_(requests, atTime))
115 | }
116 | }
117 |
118 | def sendMulti(req: MultiRequest, socket: IO.SocketHandle): Unit = {
119 | socket write req.multi
120 | req.cmds foreach { cmd ⇒
121 | socket write cmd._1
122 | }
123 | socket write req.exec
124 | }
125 |
126 | def onResult() {
127 | //if (config.retryOnReconnect) self ! Received
128 | results += 1L
129 | if (resultCallbacks.nonEmpty) {
130 | val atTime = System.currentTimeMillis
131 | resultCallbacks foreach (_(results, atTime))
132 | }
133 | }
134 |
135 | override def postStop() {
136 | log info ("Shutting down")
137 | sockets.foreach(_.close)
138 | // TODO: Complete all waiting requests with a RedisConnectionException
139 | }
140 |
141 | }
142 |
143 | private[redis] final class RedisSubscriberSession(listener: ActorRef)(host: String, port: Int, config: RedisClientConfig) extends Actor {
144 | import Iteratees._
145 |
146 | val log = Logging(context.system, this)
147 |
148 | var socket: IO.SocketHandle = _
149 | val state = IO.IterateeRef.async()(context.dispatcher)
150 |
151 | val client = new RedisClientSub(self, config, context.system)
152 |
153 | override def preStart = {
154 | log info ("Connecting")
155 | socket = IOManager(context.system).connect(host, port)
156 | state flatMap (_ ⇒ subscriber(listener))
157 | }
158 |
159 | def receive = {
160 |
161 | case Subscribe(channels) ⇒
162 | socket write (client.subscribe(channels))
163 |
164 | case Unsubscribe(channels) ⇒
165 | socket write (client.unsubscribe(channels))
166 |
167 | case PSubscribe(patterns) ⇒
168 | socket write (client.psubscribe(patterns))
169 |
170 | case PUnsubscribe(patterns) ⇒
171 | socket write (client.punsubscribe(patterns))
172 |
173 | case IO.Read(handle, bytes) ⇒
174 | state(IO Chunk bytes)
175 |
176 | case IO.Closed(handle, cause) if socket == handle ⇒
177 | log info ("Connection closed" + (cause map (e ⇒ ", cause: " + e.toString) getOrElse ""))
178 | // FIXME: stop actors
179 | // sendToSupervisor(Disconnect)
180 | state(IO EOF cause)
181 | }
182 |
183 | override def postStop() {
184 | socket.close
185 | }
186 |
187 | def subscriber(listener: ActorRef): IO.Iteratee[Unit] = readResult flatMap { result ⇒
188 | result match {
189 | case RedisMulti(Some(List(RedisBulk(Some(Constants.message)), RedisBulk(Some(channel)), RedisBulk(Some(message))))) ⇒
190 | listener ! pubsub.Message(channel, message)
191 | case RedisMulti(Some(List(RedisBulk(Some(Constants.pmessage)), RedisBulk(Some(pattern)), RedisBulk(Some(channel)), RedisBulk(Some(message))))) ⇒
192 | listener ! pubsub.PMessage(pattern, channel, message)
193 | case RedisMulti(Some(List(RedisBulk(Some(Constants.subscribe)), RedisBulk(Some(channel)), RedisInteger(count)))) ⇒
194 | listener ! pubsub.Subscribed(channel, count)
195 | case RedisMulti(Some(List(RedisBulk(Some(Constants.unsubscribe)), RedisBulk(Some(channel)), RedisInteger(count)))) ⇒
196 | listener ! pubsub.Unsubscribed(channel, count)
197 | case RedisMulti(Some(List(RedisBulk(Some(Constants.psubscribe)), RedisBulk(Some(pattern)), RedisInteger(count)))) ⇒
198 | listener ! pubsub.PSubscribed(pattern, count)
199 | case RedisMulti(Some(List(RedisBulk(Some(Constants.punsubscribe)), RedisBulk(Some(pattern)), RedisInteger(count)))) ⇒
200 | listener ! pubsub.PUnsubscribed(pattern, count)
201 | case other ⇒
202 | throw RedisProtocolException("Unexpected response")
203 | }
204 | subscriber(listener)
205 | }
206 |
207 | }
208 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/commands/Hashes.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package commands
3 |
4 | import serialization._
5 | import akka.util.ByteString
6 |
7 | private[redis] trait Hashes[Result[_]] {
8 | this: Commands[Result] ⇒
9 | import protocol.Constants._
10 |
11 | def hdel[K: Store, F: Store](key: K, field: F): Result[Boolean] =
12 | send(HDEL :: Store(key) :: Store(field) :: Nil)
13 |
14 | def hexists[K: Store, F: Store](key: K, field: F): Result[Boolean] =
15 | send(HEXISTS :: Store(key) :: Store(field) :: Nil)
16 |
17 | def hget[K: Store, F: Store](key: K, field: F): Result[Option[ByteString]] =
18 | send(HGET :: Store(key) :: Store(field) :: Nil)
19 |
20 | def hgetall[K: Store](key: K): Result[Map[ByteString, ByteString]] =
21 | send(HGETALL :: Store(key) :: Nil)
22 |
23 | def hincrby[K: Store, F: Store](key: K, field: F, value: Long = 1): Result[Long] =
24 | send(HINCRBY :: Store(key) :: Store(field) :: Store(value) :: Nil)
25 |
26 | def hkeys[K: Store](key: K): Result[Set[ByteString]] =
27 | send(HKEYS :: Store(key) :: Nil)
28 |
29 | def hlen[K: Store](key: K): Result[Long] =
30 | send(HLEN :: Store(key) :: Nil)
31 |
32 | def hmget[K: Store, F: Store](key: K, fields: Seq[F]): Result[List[Option[ByteString]]] =
33 | send(HMGET :: Store(key) :: (fields.map(Store(_))(collection.breakOut): List[ByteString]))
34 |
35 | def hmset[K: Store, F: Store, V: Store](key: K, fvs: Iterable[Product2[F, V]]): Result[Unit] =
36 | send(HMSET :: Store(key) :: (fvs.flatMap(fv ⇒ Iterable(Store(fv._1), Store(fv._2)))(collection.breakOut): List[ByteString]))
37 |
38 | def hset[K: Store, F: Store, V: Store](key: K, field: F, value: V): Result[Boolean] =
39 | send(HSET :: Store(key) :: Store(field) :: Store(value) :: Nil)
40 |
41 | def hsetnx[K: Store, F: Store, V: Store](key: K, field: F, value: V): Result[Boolean] =
42 | send(HSETNX :: Store(key) :: Store(field) :: Store(value) :: Nil)
43 |
44 | def hvals[K: Store](key: K): Result[Set[ByteString]] =
45 | send(HVALS :: Store(key) :: Nil)
46 |
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/commands/Keys.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package commands
3 |
4 | import serialization._
5 | import akka.util.ByteString
6 |
7 | private[redis] trait Keys[Result[_]] {
8 | this: Commands[Result] ⇒
9 | import protocol.Constants._
10 |
11 | /**
12 | * Request keys matching `pattern`.
13 | *
14 | * Returns: `Set[ByteString]`
15 | *
16 | * @param pattern Use "*" as wildcard
17 | *
18 | * @see Redis Command Reference
19 | */
20 | def keys[A: Store](pattern: A): Result[Set[ByteString]] = send(KEYS :: Store(pattern) :: Nil)
21 | def keys(): Result[Set[ByteString]] = send(KEYS :: ALLKEYS :: Nil)
22 |
23 | /**
24 | * Request a random key.
25 | *
26 | * Returns: `Option[ByteString]`
27 | *
28 | * @see Redis Command Reference
29 | */
30 | def randomkey(): Result[Option[ByteString]] = send(List(RANDOMKEY))
31 |
32 | /**
33 | * Rename a key.
34 | *
35 | * Returns: `Unit`
36 | *
37 | * @param oldkey The existing key to rename.
38 | * @param newkey The new key.
39 | *
40 | * @see Redis Command Reference
41 | */
42 | def rename[A: Store, B: Store](oldkey: A, newkey: B): Result[Unit] =
43 | send(RENAME :: Store(oldkey) :: Store(newkey) :: Nil)
44 |
45 | /**
46 | * Rename a key if `newkey` does not already exist. Returns true if
47 | * successfully renamed.
48 | *
49 | * Returns: `Boolean`
50 | *
51 | * @param oldkey The existing key to rename.
52 | * @param newkey The new key.
53 | *
54 | * @see Redis Command Reference
55 | */
56 | def renamenx[A: Store, B: Store](oldkey: A, newkey: B): Result[Boolean] =
57 | send(RENAMENX :: Store(oldkey) :: Store(newkey) :: Nil)
58 |
59 | /**
60 | * Request the number of keys in the current database.
61 | *
62 | * Returns: `Int`
63 | *
64 | * @see Redis Command Reference
65 | */
66 | def dbsize(): Result[Int] = send(DBSIZE :: Nil)
67 |
68 | /**
69 | * Tests if `key` exists.
70 | *
71 | * Returns: `Boolean`
72 | *
73 | * @param key The key to test the existance of.
74 | *
75 | * @see Redis Command Reference
76 | */
77 | def exists[A: Store](key: A): Result[Boolean] = send(EXISTS :: Store(key) :: Nil)
78 |
79 | /**
80 | * Delete each key in `keys`. Returns the actual number of keys deleted.
81 | *
82 | * Note: Be aware that this command takes an `Iterable[Any]`, so if a
83 | * single `String` is passed it will be converted into a `Seq[Char]`
84 | * which is probably not what you want. Instead, provide a `List[String]`
85 | * or similar collection.
86 | *
87 | * Returns: `Int`
88 | *
89 | * @param keys A collection of keys to delete.
90 | *
91 | * @see Redis Command Reference
92 | */
93 | def del[A: Store](keys: Iterable[A]): Result[Int] = send(DEL :: (keys.map(Store(_))(collection.breakOut): List[ByteString]))
94 | def del[A: Store](key: A): Result[Int] = send(DEL :: Store(key) :: Nil)
95 |
96 | /**
97 | * Requests the type of the value stored at `key`.
98 | *
99 | * Returns: `String`
100 | *
101 | * @param key The key of the value to check.
102 | *
103 | * @see Redis Command Reference
104 | */
105 | def typeof[A: Store](key: A): Result[String] = send(TYPE :: Store(key) :: Nil)
106 |
107 | /**
108 | * Set a timeout of the specified key. After the timeout the key will be
109 | * automatically deleted. Returns 'true' if the expire command is successful.
110 | *
111 | * Returns: `Boolean`
112 | *
113 | * @param key The key to expire.
114 | * @param seconds The timeout in seconds.
115 | *
116 | * @see Redis Command Reference
117 | */
118 | def expire[A: Store](key: A, seconds: Long): Result[Boolean] =
119 | send(EXPIRE :: Store(key) :: Store(seconds) :: Nil)
120 |
121 | /**
122 | * Set a timeout of the specified key. After the timeout the key will be
123 | * automatically deleted. Returns 'true' if the expire command is successful.
124 | *
125 | * Returns: `Boolean`
126 | *
127 | * @param key The key to expire.
128 | * @param unixtime The timeout in the form of a UNIX timestamp.
129 | *
130 | * @see Redis Command Reference
131 | */
132 | def expireat[A: Store](key: A, unixtime: Long): Result[Boolean] =
133 | send(EXPIREAT :: Store(key) :: Store(unixtime) :: Nil)
134 |
135 | /**
136 | * Select a DB with the supplied zero-based index.
137 | *
138 | * Returns: `Unit`
139 | *
140 | * @param index Zero-based index of database.
141 | *
142 | * @see Redis Command Reference
143 | */
144 | def select(index: Int = 0): Result[Unit] = send(SELECT :: Store(index) :: Nil)
145 |
146 | /**
147 | * Delete all keys in the currently selected database.
148 | *
149 | * Returns: `Unit`
150 | *
151 | * @see Redis Command Reference
152 | */
153 | def flushdb(): Result[Unit] = send(FLUSHDB :: Nil)
154 |
155 | /**
156 | * Delete all keys in all databases.
157 | *
158 | * Returns: `Unit`
159 | *
160 | * @see Redis Command Reference
161 | */
162 | def flushall(): Result[Unit] = send(FLUSHALL :: Nil)
163 |
164 | /**
165 | * Move `key` from the currently selected database to the database at index `db`.
166 | * Returns `true` if successful.
167 | *
168 | * Returns: `Boolean`
169 | *
170 | * @param key The key to move.
171 | * @param db The zero-based index of the destination database.
172 | *
173 | * @see Redis Command Reference
174 | */
175 | def move[A: Store](key: A, db: Int = 0): Result[Boolean] = send(MOVE :: Store(key) :: Store(db) :: Nil)
176 |
177 | /**
178 | * Asks the server to close the connection.
179 | *
180 | * Returns: `Unit`
181 | *
182 | * @see Redis Command Reference
183 | */
184 | def quit(): Result[Unit] = send(QUIT :: Nil)
185 |
186 | /**
187 | * Supply a password if required to send commands.
188 | *
189 | * Returns: `Unit`
190 | *
191 | * @param secret Server authentication password.
192 | *
193 | * @see Redis Command Reference
194 | */
195 | def auth[A: Store](secret: A): Result[Unit] = send(AUTH :: Store(secret) :: Nil)
196 |
197 | def ping(): Result[String] = send(PING :: Nil)
198 |
199 | def echo[A: Store](value: A): Result[Option[ByteString]] = send(ECHO :: Store(value) :: Nil)
200 |
201 | /**
202 | * Sorts the elements contained in a List, Set, or Sorted Set value at `key`.
203 | *
204 | * Returns: `List[Option[A]]`
205 | *
206 | * @param key The key of the List, Set, or Sorted Set to sort.
207 | * @param by Optional pattern used to generate the key names of the weights used for sorting.
208 | * Use "nosort" if no sorting is required, which can be useful if using
209 | * the `get` parameter to return other values.
210 | * @param limit Optional zero-based start-index and count of items to return.
211 | * @param get List of patterns used to generate key names of values to return.
212 | * Use "#" to include the elements of the sorted list as well.
213 | * @param order Optional `SortOrder`
214 | * @param alpha If `true`, sort lexicalgraphically.
215 | *
216 | * @see Redis Command Reference
217 | */
218 | def sort[K: Store, B: Store, G: Store](key: K, by: Option[B] = Option.empty[ByteString], limit: RedisLimit = NoLimit, get: Seq[G] = List.empty[ByteString], order: Option[SortOrder] = None, alpha: Boolean = false): Result[List[Option[ByteString]]] = {
219 | var cmd: List[ByteString] = Nil
220 | if (alpha) cmd ::= ALPHA
221 | order foreach (o ⇒ cmd ::= Store(o))
222 | get.reverse foreach (g ⇒ cmd = GET :: Store(g) :: cmd)
223 | limit match {
224 | case Limit(o, c) ⇒ cmd = LIMIT :: Store(o) :: Store(c) :: cmd
225 | case NoLimit ⇒
226 | }
227 | by foreach (b ⇒ cmd = BY :: Store(b) :: cmd)
228 | send(SORT :: Store(key) :: cmd)
229 | }
230 |
231 | }
232 |
233 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/commands/Lists.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package commands
3 |
4 | import serialization._
5 | import akka.util.ByteString
6 |
7 | private[redis] trait Lists[Result[_]] {
8 | this: Commands[Result] ⇒
9 | import protocol.Constants._
10 |
11 | def blpop[K: Store: Parse](keys: Seq[K], timeout: Int = 0): Result[Option[(K, ByteString)]] =
12 | send(BLPOP :: (Store(timeout) :: (keys.map(Store(_))(collection.breakOut): List[ByteString])).reverse)
13 |
14 | def brpop[K: Store: Parse](keys: Seq[K], timeout: Int = 0): Result[Option[(K, ByteString)]] =
15 | send(BRPOP :: (Store(timeout) :: (keys.map(Store(_))(collection.breakOut): List[ByteString])).reverse)
16 |
17 | def brpoplpush[A: Store, B: Store](srcKey: A, dstKey: B, timeout: Int = 0): Result[Option[ByteString]] =
18 | send(BRPOPLPUSH :: Store(srcKey) :: Store(dstKey) :: Store(timeout) :: Nil)
19 |
20 | def lindex[K: Store](key: K, index: Int): Result[Option[ByteString]] =
21 | send(LINDEX :: Store(key) :: Store(index) :: Nil)
22 |
23 | def linsertbefore[K: Store, A: Store, B: Store](key: K, pivot: A, value: B): Result[Option[Int]] =
24 | send(LINSERT :: Store(key) :: BEFORE :: Store(pivot) :: Store(value) :: Nil)
25 |
26 | def linsertafter[K: Store, A: Store, B: Store](key: K, pivot: A, value: B): Result[Option[Int]] =
27 | send(LINSERT :: Store(key) :: AFTER :: Store(pivot) :: Store(value) :: Nil)
28 |
29 | def llen[K: Store](key: K): Result[Int] =
30 | send(LLEN :: Store(key) :: Nil)
31 |
32 | def lpop[K: Store](key: K): Result[Option[ByteString]] =
33 | send(LPOP :: Store(key) :: Nil)
34 |
35 | def lpush[K: Store, V: Store](key: K, value: V): Result[Int] =
36 | send(LPUSH :: Store(key) :: Store(value) :: Nil)
37 |
38 | def lpushx[K: Store, V: Store](key: K, value: V): Result[Int] =
39 | send(LPUSHX :: Store(key) :: Store(value) :: Nil)
40 |
41 | def lrange[K: Store](key: K, start: Int = 0, end: Int = -1): Result[Option[List[ByteString]]] =
42 | send(LRANGE :: Store(key) :: Store(start) :: Store(end) :: Nil)
43 |
44 | def lrem[K: Store, V: Store](key: K, value: V, count: Int = 0): Result[Int] =
45 | send(LREM :: Store(key) :: Store(count) :: Store(value) :: Nil)
46 |
47 | def lset[K: Store, V: Store](key: K, index: Int, value: V): Result[Unit] =
48 | send(LSET :: Store(key) :: Store(index) :: Store(value) :: Nil)
49 |
50 | def ltrim[K: Store](key: K, start: Int = 0, end: Int = -1): Result[Unit] =
51 | send(LTRIM :: Store(key) :: Store(start) :: Store(end) :: Nil)
52 |
53 | def rpop[K: Store](key: K): Result[Option[ByteString]] =
54 | send(RPOP :: Store(key) :: Nil)
55 |
56 | def rpoplpush[A: Store, B: Store](srcKey: A, dstKey: B): Result[Option[ByteString]] =
57 | send(RPOPLPUSH :: Store(srcKey) :: Store(dstKey) :: Nil)
58 |
59 | def rpush[K: Store, V: Store](key: K, value: V): Result[Int] =
60 | send(RPUSH :: Store(key) :: Store(value) :: Nil)
61 |
62 | def rpushx[K: Store, V: Store](key: K, value: V): Result[Int] =
63 | send(RPUSHX :: Store(key) :: Store(value) :: Nil)
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/commands/PubSub.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package commands
3 |
4 | import serialization._
5 | import akka.util.ByteString
6 |
7 | private[redis] trait PubSub[Result[_]] {
8 | this: Commands[Result] ⇒
9 | import protocol.Constants._
10 |
11 | def publish[A: Store, B: Store](channel: A, message: B): Result[Int] = send(PUBLISH :: Store(channel) :: Store(message) :: Nil)
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/commands/Script.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package commands
3 |
4 | import serialization._
5 | import akka.util.ByteString
6 |
7 | private[redis] trait Scripts[Result[_]] {
8 | this: Commands[Result] ⇒
9 | import protocol.Constants._
10 |
11 | def eval[S: Store, K: Store, A: Store](script: S, keys: Seq[K] = Seq.empty[Store.Dummy], args: Seq[A] = Seq.empty[Store.Dummy]): Result[types.RedisType] =
12 | send(EVAL :: Store(script) :: Store(keys.size) :: (keys.map(Store(_))(collection.breakOut): List[ByteString]) ::: (args.map(Store(_))(collection.breakOut): List[ByteString]))
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/commands/Servers.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package commands
3 |
4 | import serialization._
5 | import akka.util.ByteString
6 |
7 | private[redis] trait Servers[Result[_]] {
8 | this: Commands[Result] ⇒
9 | import protocol.Constants._
10 |
11 | def save(): Result[Unit] = send(SAVE :: Nil)
12 |
13 | def bgsave(): Result[Unit] = send(BGSAVE :: Nil)
14 |
15 | def lastsave(): Result[Int] = send(LASTSAVE :: Nil)
16 |
17 | //def shutdown(): Result[Nothing]
18 |
19 | def bgrewriteaof(): Result[Unit] = send(BGREWRITEAOF :: Nil)
20 |
21 | def info(): Result[String] = send(INFO :: Nil)
22 |
23 | //def monitor extends Command(OkStatus)
24 | /*
25 | def slaveof(hostPort: Option[(String, Int)])(implicit format: Format) extends Command(OkStatus) {
26 | override def args = hostPort map (x => Iterator(x._1, x._2)) getOrElse Iterator("NO ONE")
27 | }
28 |
29 | object config {
30 |
31 | def get(param: Any)(implicit format: Format, parse: Parse[A]) extends Command(MultiBulk[A]()(implicitly, parse.manifest)) {
32 | override def name = "CONFIG"
33 | override def args = Iterator("GET", param)
34 | }
35 |
36 | def set(param: Any, value: Any)(implicit format: Format) extends Command(OkStatus) {
37 | override def name = "CONFIG"
38 | override def args = Iterator("SET", param, value)
39 | }
40 |
41 | }
42 | */
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/commands/Sets.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package commands
3 |
4 | import serialization._
5 | import akka.util.ByteString
6 |
7 | private[redis] trait Sets[Result[_]] {
8 | this: Commands[Result] ⇒
9 | import protocol.Constants._
10 |
11 | def sadd[K: Store, V: Store](key: K, value: V): Result[Boolean] =
12 | send(SADD :: Store(key) :: Store(value) :: Nil)
13 |
14 | def scard[K: Store](key: K): Result[Int] =
15 | send(SCARD :: Store(key) :: Nil)
16 |
17 | def sdiff[A: Store, B: Store](key: A, diffkeys: Iterable[B]): Result[Set[ByteString]] =
18 | send(SDIFF :: Store(key) :: (diffkeys.map(Store(_))(collection.breakOut): List[ByteString]))
19 |
20 | def sdiffstore[A: Store, B: Store, C: Store](dstkey: A, key: B, diffkeys: Iterable[C]): Result[Int] =
21 | send(SDIFFSTORE :: Store(dstkey) :: Store(key) :: (diffkeys.map(Store(_))(collection.breakOut): List[ByteString]))
22 |
23 | def sinter[K: Store](keys: Iterable[K]): Result[Set[ByteString]] =
24 | send(SINTER :: (keys.map(Store(_))(collection.breakOut): List[ByteString]))
25 |
26 | def sinterstore[A: Store, B: Store](dstkey: A, keys: Iterable[B]): Result[Int] =
27 | send(SINTERSTORE :: Store(dstkey) :: (keys.map(Store(_))(collection.breakOut): List[ByteString]))
28 |
29 | def sismember[K: Store, V: Store](key: K, value: V): Result[Boolean] =
30 | send(SISMEMBER :: Store(key) :: Store(value) :: Nil)
31 |
32 | def smembers[K: Store](key: K): Result[Set[ByteString]] =
33 | send(SMEMBERS :: Store(key) :: Nil)
34 |
35 | def smove[A: Store, B: Store, V: Store](srcKey: A, dstKey: B, value: V): Result[Boolean] =
36 | send(SMOVE :: Store(srcKey) :: Store(dstKey) :: Store(value) :: Nil)
37 |
38 | def spop[K: Store](key: K): Result[Option[ByteString]] =
39 | send(SPOP :: Store(key) :: Nil)
40 |
41 | def srandmember[K: Store](key: K): Result[Option[ByteString]] =
42 | send(SRANDMEMBER :: Store(key) :: Nil)
43 |
44 | def srem[K: Store, V: Store](key: K, value: V): Result[Boolean] =
45 | send(SREM :: Store(key) :: Store(value) :: Nil)
46 |
47 | def sunion[K: Store](keys: Iterable[K]): Result[Set[ByteString]] =
48 | send(SUNION :: (keys.map(Store(_))(collection.breakOut): List[ByteString]))
49 |
50 | def sunionstore[A: Store, B: Store](dstkey: A, keys: Iterable[B]): Result[Int] =
51 | send(SUNIONSTORE :: Store(dstkey) :: (keys.map(Store(_))(collection.breakOut): List[ByteString]))
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/commands/SortedSets.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package commands
3 |
4 | import serialization._
5 | import akka.util.ByteString
6 |
7 | private[redis] trait SortedSets[Result[_]] {
8 | this: Commands[Result] ⇒
9 | import protocol.Constants._
10 |
11 | def zadd[K: Store, M: Store](key: K, member: M, score: Double = 1.0): Result[Boolean] =
12 | send(ZADD :: Store(key) :: Store(score) :: Store(member) :: Nil)
13 |
14 | def zcard[K: Store](key: K): Result[Int] =
15 | send(ZCARD :: Store(key) :: Nil)
16 |
17 | def zcount[K: Store](key: K, min: RedisScore = RedisScore.min, max: RedisScore = RedisScore.max): Result[Int] =
18 | send(ZCOUNT :: Store(key) :: Store(min) :: Store(max) :: Nil)
19 |
20 | def zincrby[K: Store, M: Store](key: K, member: M, incr: Double = 1.0): Result[Double] =
21 | send(ZINCRBY :: Store(key) :: Store(incr) :: Store(member) :: Nil)
22 |
23 | def zinterstore[D: Store, K: Store](dstKey: D, keys: Iterable[K], aggregate: Aggregate = Aggregate.Sum): Result[Int] = {
24 | var cmd = AGGREGATE :: Store(aggregate) :: Nil
25 | var keyCount = 0
26 | keys foreach { k ⇒
27 | cmd ::= Store(k)
28 | keyCount += 1
29 | }
30 | send(ZINTERSTORE :: Store(dstKey) :: Store(keyCount) :: cmd)
31 | }
32 |
33 | def zinterstoreWeighted[D: Store, K: Store](dstKey: D, kws: Iterable[Product2[K, Double]], aggregate: Aggregate = Aggregate.Sum): Result[Int] = {
34 | var cmd: List[ByteString] = Nil
35 | var keyCount = 0
36 | kws foreach { kw ⇒
37 | cmd ::= Store(kw._1)
38 | keyCount += 1
39 | }
40 | cmd ::= WEIGHTS
41 | kws foreach { kw ⇒ cmd ::= Store(kw._2) }
42 | send(ZINTERSTORE :: Store(dstKey) :: Store(keyCount) :: (Store(aggregate) :: AGGREGATE :: cmd).reverse)
43 | }
44 |
45 | def zrange[K: Store](key: K, start: Int = 0, stop: Int = -1): Result[List[ByteString]] =
46 | send(ZRANGE :: Store(key) :: Store(start) :: Store(stop) :: Nil)
47 |
48 | def zrangeWithScores[K: Store](key: K, start: Int = 0, stop: Int = -1): Result[List[(ByteString, Double)]] =
49 | send(ZRANGE :: Store(key) :: Store(start) :: Store(stop) :: WITHSCORES :: Nil)
50 |
51 | def zrangebyscore[K: Store](key: K, min: RedisScore = RedisScore.min, max: RedisScore = RedisScore.max, limit: RedisLimit = NoLimit): Result[List[ByteString]] =
52 | send(ZRANGEBYSCORE :: Store(key) :: Store(min) :: Store(max) :: (limit match {
53 | case NoLimit ⇒ Nil
54 | case Limit(o, c) ⇒ LIMIT :: Store(o) :: Store(c) :: Nil
55 | }))
56 |
57 | def zrangebyscoreWithScores[K: Store](key: K, min: RedisScore = RedisScore.min, max: RedisScore = RedisScore.max, limit: RedisLimit = NoLimit): Result[List[(ByteString, Double)]] =
58 | send(ZRANGEBYSCORE :: Store(key) :: Store(min) :: Store(max) :: WITHSCORES :: (limit match {
59 | case NoLimit ⇒ Nil
60 | case Limit(o, c) ⇒ LIMIT :: Store(o) :: Store(c) :: Nil
61 | }))
62 |
63 | def zrank[K: Store, M: Store](key: K, member: M): Result[Option[Int]] =
64 | send(ZRANK :: Store(key) :: Store(member) :: Nil)
65 |
66 | def zrem[K: Store, M: Store](key: K, member: M): Result[Boolean] =
67 | send(ZREM :: Store(key) :: Store(member) :: Nil)
68 |
69 | def zremrangebyrank[K: Store](key: K, start: Int = 0, stop: Int = -1): Result[Int] =
70 | send(ZREMRANGEBYRANK :: Store(key) :: Store(start) :: Store(stop) :: Nil)
71 |
72 | def zremrangebyscore[K: Store](key: K, min: RedisScore = RedisScore.min, max: RedisScore = RedisScore.max): Result[Int] =
73 | send(ZREMRANGEBYSCORE :: Store(key) :: Store(min) :: Store(max) :: Nil)
74 |
75 | def zrevrange[K: Store](key: K, start: Int = 0, stop: Int = -1): Result[List[ByteString]] =
76 | send(ZREVRANGE :: Store(key) :: Store(start) :: Store(stop) :: Nil)
77 |
78 | def zrevrangeWithScores[K: Store](key: K, start: Int = 0, stop: Int = -1): Result[List[(ByteString, Double)]] =
79 | send(ZREVRANGE :: Store(key) :: Store(start) :: Store(stop) :: WITHSCORES :: Nil)
80 |
81 | def zrevrangebyscore[K: Store](key: K, min: RedisScore = RedisScore.min, max: RedisScore = RedisScore.max, limit: RedisLimit = NoLimit): Result[List[ByteString]] =
82 | send(ZREVRANGEBYSCORE :: Store(key) :: Store(min) :: Store(max) :: (limit match {
83 | case NoLimit ⇒ Nil
84 | case Limit(o, c) ⇒ LIMIT :: Store(o) :: Store(c) :: Nil
85 | }))
86 |
87 | def zrevrangebyscoreWithScores[K: Store](key: K, min: RedisScore = RedisScore.min, max: RedisScore = RedisScore.max, limit: RedisLimit = NoLimit): Result[List[(ByteString, Double)]] =
88 | send(ZREVRANGEBYSCORE :: Store(key) :: Store(min) :: Store(max) :: WITHSCORES :: (limit match {
89 | case NoLimit ⇒ Nil
90 | case Limit(o, c) ⇒ LIMIT :: Store(o) :: Store(c) :: Nil
91 | }))
92 |
93 | def zrevrank[K: Store, M: Store](key: K, member: M): Result[Option[Int]] =
94 | send(ZREVRANK :: Store(key) :: Store(member) :: Nil)
95 |
96 | def zscore[K: Store, M: Store](key: K, member: M): Result[Option[Double]] =
97 | send(ZSCORE :: Store(key) :: Store(member) :: Nil)
98 |
99 | def zunionstore[D: Store, K: Store](dstKey: D, keys: Iterable[K], aggregate: Aggregate = Aggregate.Sum): Result[Int] = {
100 | var cmd = AGGREGATE :: Store(aggregate) :: Nil
101 | var keyCount = 0
102 | keys foreach { k ⇒
103 | cmd ::= Store(k)
104 | keyCount += 1
105 | }
106 | send(ZUNIONSTORE :: Store(dstKey) :: Store(keyCount) :: cmd)
107 | }
108 |
109 | def zunionstoreWeighted[D: Store, K: Store](dstKey: D, kws: Iterable[Product2[K, Double]], aggregate: Aggregate = Aggregate.Sum): Result[Int] = {
110 | var cmd: List[ByteString] = Nil
111 | var keyCount = 0
112 | kws foreach { kw ⇒
113 | cmd ::= Store(kw._1)
114 | keyCount += 1
115 | }
116 | cmd ::= WEIGHTS
117 | kws foreach { kw ⇒ cmd ::= Store(kw._2) }
118 | send(ZUNIONSTORE :: Store(dstKey) :: Store(keyCount) :: (Store(aggregate) :: AGGREGATE :: cmd).reverse)
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/commands/Strings.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package commands
3 |
4 | import serialization._
5 | import akka.util.ByteString
6 |
7 | private[redis] trait Strings[Result[_]] {
8 | this: Commands[Result] ⇒
9 | import protocol.Constants._
10 |
11 | def append[K: Store, V: Store](key: K, value: V): Result[Int] =
12 | send(APPEND :: Store(key) :: Store(value) :: Nil)
13 |
14 | def decr[K: Store](key: K): Result[Long] =
15 | send(DECR :: Store(key) :: Nil)
16 |
17 | def decrby[K: Store](key: K, decrement: Long): Result[Long] =
18 | send(DECRBY :: Store(key) :: Store(decrement) :: Nil)
19 |
20 | def get[K: Store](key: K): Result[Option[ByteString]] =
21 | send(GET :: Store(key) :: Nil)
22 |
23 | def getbit[K: Store](key: K, offset: Int): Result[Int] =
24 | send(GETBIT :: Store(key) :: Store(offset) :: Nil)
25 |
26 | def getrange[K: Store](key: K, start: Int = 0, end: Int = -1): Result[Int] =
27 | send(GETRANGE :: Store(key) :: Store(start) :: Store(end) :: Nil)
28 |
29 | def getset[K: Store, V: Store](key: K, value: V): Result[Option[ByteString]] =
30 | send(GETSET :: Store(key) :: Store(value) :: Nil)
31 |
32 | def incr[K: Store](key: K): Result[Long] =
33 | send(INCR :: Store(key) :: Nil)
34 |
35 | def incrby[K: Store](key: K, increment: Long): Result[Long] =
36 | send(INCRBY :: Store(key) :: Store(increment) :: Nil)
37 |
38 | def mget[K: Store](keys: Seq[K]): Result[List[Option[ByteString]]] =
39 | send(MGET :: (keys.map(Store(_))(collection.breakOut): List[ByteString]))
40 |
41 | def mset[K: Store, V: Store](kvs: Iterable[Product2[K, V]]): Result[Unit] =
42 | send(MSET :: (kvs.flatMap(kv ⇒ Iterable(Store(kv._1), Store(kv._2)))(collection.breakOut): List[ByteString]))
43 |
44 | def msetnx[K: Store, V: Store](kvs: Iterable[Product2[K, V]]): Result[Boolean] =
45 | send(MSETNX :: (kvs.flatMap(kv ⇒ Iterable(Store(kv._1), Store(kv._2)))(collection.breakOut): List[ByteString]))
46 |
47 | def set[K: Store, V: Store](key: K, value: V): Result[Unit] =
48 | send(SET :: Store(key) :: Store(value) :: Nil)
49 |
50 | def setbit[K: Store](key: K, offset: Int, value: Int): Result[Int] =
51 | send(SETBIT :: Store(key) :: Store(offset) :: Store(value) :: Nil)
52 |
53 | def setex[K: Store, V: Store](key: K, seconds: Int, value: V): Result[Unit] =
54 | send(SETEX :: Store(key) :: Store(seconds) :: Store(value) :: Nil)
55 |
56 | def setnx[K: Store, V: Store](key: K, value: V): Result[Boolean] =
57 | send(SETNX :: Store(key) :: Store(value) :: Nil)
58 |
59 | def setrange[K: Store, V: Store](key: K, offset: Int, value: V): Result[Int] =
60 | send(SETRANGE :: Store(key) :: Store(offset) :: Store(value) :: Nil)
61 |
62 | def strlen[K: Store](key: K): Result[Int] =
63 | send(STRLEN :: Store(key) :: Nil)
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/lua.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie
2 | package redis
3 | package lua
4 |
5 | import akka.util.ByteString
6 |
7 | private[lua] object Literals {
8 | val ADD = ByteString(" + ")
9 | val ASSIGN = ByteString(" = ")
10 | val COMMA = ByteString(", ")
11 | val DIV = ByteString(" / ")
12 | val DO = ByteString("do ")
13 | val ELSE = ByteString("else ")
14 | val ELSEIF = ByteString("elseif ")
15 | val END = ByteString("end")
16 | val EQ = ByteString(" == ")
17 | val GT = ByteString(" > ")
18 | val GTEQ = ByteString(" >= ")
19 | val IF = ByteString("if ")
20 | val LT = ByteString(" < ")
21 | val LTEQ = ByteString(" <= ")
22 | val MULT = ByteString(" * ")
23 | val PARENL = ByteString("(")
24 | val PARENR = ByteString(")")
25 | val REPEAT = ByteString("repeat ")
26 | val RETURN = ByteString("return ")
27 | val SEMICOLON = ByteString("; ")
28 | val SUB = ByteString(" - ")
29 | val THEN = ByteString(" then ")
30 | val UNTIL = ByteString("until ")
31 | val WHILE = ByteString("while ")
32 | val CURLYL = ByteString("{")
33 | val CURLYR = ByteString("}")
34 | }
35 |
36 | import Literals._
37 |
38 | sealed trait Chunk {
39 | def bytes: ByteString
40 | }
41 | sealed trait Block extends Chunk {
42 | def ::(stat: Stat) = StatList(stat, this)
43 | }
44 |
45 | case class StatList(stat: Stat, next: Block) extends Block {
46 | val bytes = stat.bytes ++ SEMICOLON ++ next.bytes
47 | }
48 |
49 | sealed trait Stat {
50 | def bytes: ByteString
51 | }
52 | //case class Call(fun: String) extends Stat
53 | case class Assign(name: Var, exp: Exp) extends Stat {
54 | val bytes = name.bytes ++ ASSIGN ++ exp.bytes
55 | }
56 | //case class Local(name: Var, exp: Exp) extends Stat
57 | case class Do(block: Block) extends Stat {
58 | val bytes = DO ++ block.bytes ++ END
59 | }
60 | case class While(test: Exp, block: Block) extends Stat {
61 | val bytes = WHILE ++ test.bytes ++ DO ++ block.bytes ++ END
62 | }
63 | case class Repeat(block: Block, test: Exp) extends Stat {
64 | val bytes = REPEAT ++ block.bytes ++ UNTIL ++ test.bytes
65 | }
66 | case class If(test: Exp) {
67 | def Then(block: Block) = IfThen(test, block, Vector.empty)
68 | }
69 | case class IfThen(test: Exp, then: Block, elseIf: Vector[ElseIfThen]) extends Stat {
70 | def ElseIf(test: Exp) = lua.ElseIf(test, this)
71 | def Else(block: Block) = IfThenElse(test, then, elseIf, block)
72 | val bytes = ((IF ++ test.bytes ++ THEN ++ then.bytes) /: elseIf)(_ ++ _.bytes) ++ END
73 | }
74 | case class IfThenElse(test: Exp, then: Block, elseIf: Vector[ElseIfThen], `else`: Block) extends Stat {
75 | val bytes = ((IF ++ test.bytes ++ THEN ++ then.bytes) /: elseIf)(_ ++ _.bytes) ++ ELSE ++ `else`.bytes ++ END
76 | }
77 | case class ElseIf(test: Exp, parent: IfThen) {
78 | def Then(block: Block) = parent.copy(elseIf = parent.elseIf :+ ElseIfThen(test, block))
79 | }
80 | case class ElseIfThen(test: Exp, block: Block) {
81 | val bytes = ELSEIF ++ test.bytes ++ THEN ++ block.bytes
82 | }
83 |
84 | sealed trait LastStat extends Block
85 | case class Return(exps: Exp*) extends LastStat {
86 | val bytes = RETURN ++ (if (exps.nonEmpty) (exps.head.bytes /: exps.tail)(_ ++ COMMA ++ _.bytes) else ByteString.empty)
87 | }
88 | case object Break extends LastStat {
89 | def bytes = ByteString("break")
90 | }
91 | case object End extends LastStat {
92 | def bytes = ByteString.empty
93 | }
94 |
95 | object Exp {
96 | def apply(exp: Exp) = exp match {
97 | case _: BinOp ⇒ Par(exp)
98 | case _ ⇒ exp
99 | }
100 | implicit def boolToExp(b: Boolean): Exp = if (b) True else False
101 | implicit def intToExp(n: Int): Exp = Num(n)
102 | implicit def strToExp(s: String): Exp = Str(s)
103 | }
104 |
105 | sealed trait Exp {
106 | def bytes: ByteString
107 | def :+(that: Exp) = Add(this, Exp(that))
108 | def :-(that: Exp) = Sub(this, Exp(that))
109 | def :*(that: Exp) = Mult(this, Exp(that))
110 | def :\(that: Exp) = Div(this, Exp(that))
111 | def :<(that: Exp) = Lt(this, Exp(that))
112 | def :<=(that: Exp) = LtEq(this, Exp(that))
113 | def :>(that: Exp) = Lt(this, Exp(that))
114 | def :>=(that: Exp) = LtEq(this, Exp(that))
115 | def :==(that: Exp) = Eq(this, Exp(that))
116 | }
117 | case class Par(exp: Exp) extends Exp {
118 | val bytes = PARENL ++ exp.bytes ++ PARENR
119 | }
120 | //case class Func
121 | case class Var(name: String) extends Exp {
122 | val bytes = ByteString(name)
123 | def :=(exp: Exp) = Assign(this, exp)
124 | }
125 | case object True extends Exp {
126 | val bytes = ByteString("true")
127 | }
128 | case object False extends Exp {
129 | val bytes = ByteString("false")
130 | }
131 | case object nil extends Exp {
132 | val bytes = ByteString("nil")
133 | }
134 | case class Num(value: Double) extends Exp {
135 | val bytes = ByteString(value.toString)
136 | }
137 | case class Str(value: String) extends Exp {
138 | val bytes = ByteString("\"" + value + "\"")
139 | }
140 | case class Table(fields: Field*) extends Exp {
141 | val bytes = CURLYL ++ (if (fields.nonEmpty) (fields.head.bytes /: fields.tail)(_ ++ COMMA ++ _.bytes) else ByteString.empty) ++ CURLYR
142 | }
143 | object Field {
144 | implicit def expToField(exp: Exp): Field = FieldV(exp)
145 | }
146 | sealed trait Field {
147 | def bytes: ByteString
148 | }
149 | case class FieldV(value: Exp) extends Field {
150 | val bytes = value.bytes
151 | }
152 | /* case class Fun extends Exp {
153 | def bytes = this.toString
154 | }
155 | */
156 |
157 | sealed trait BinOp extends Exp {
158 | def a: Exp
159 | def b: Exp
160 | def bytes: ByteString
161 | }
162 | case class Add(a: Exp, b: Exp) extends BinOp {
163 | val bytes = a.bytes ++ ADD ++ b.bytes
164 | }
165 | case class Sub(a: Exp, b: Exp) extends BinOp {
166 | val bytes = a.bytes ++ SUB ++ b.bytes
167 | }
168 | case class Mult(a: Exp, b: Exp) extends BinOp {
169 | val bytes = a.bytes ++ MULT ++ b.bytes
170 | }
171 | case class Div(a: Exp, b: Exp) extends BinOp {
172 | val bytes = a.bytes ++ DIV ++ b.bytes
173 | }
174 | case class Lt(a: Exp, b: Exp) extends BinOp {
175 | val bytes = a.bytes ++ LT ++ b.bytes
176 | }
177 | case class LtEq(a: Exp, b: Exp) extends BinOp {
178 | val bytes = a.bytes ++ LTEQ ++ b.bytes
179 | }
180 | case class Gt(a: Exp, b: Exp) extends BinOp {
181 | val bytes = a.bytes ++ GT ++ b.bytes
182 | }
183 | case class GtEq(a: Exp, b: Exp) extends BinOp {
184 | val bytes = a.bytes ++ GTEQ ++ b.bytes
185 | }
186 | case class Eq(a: Exp, b: Exp) extends BinOp {
187 | val bytes = a.bytes ++ EQ ++ b.bytes
188 | }
189 |
190 | /*
191 | sealed trait UnOp extends Exp
192 | case class Minus(exp: Exp) extends UnOp
193 | case class Not(exp: Exp) extends UnOp*/
194 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/messages/Messages.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie
2 | package redis
3 | package messages
4 |
5 | import akka.util.ByteString
6 | import akka.actor.{ IO, ActorRef }
7 | import akka.dispatch.Promise
8 |
9 | import types.RedisType
10 | import serialization.Store
11 |
12 | private[redis] sealed trait Requestor {
13 | def respond(value: RedisType): Unit
14 | def failure(err: Exception): Unit
15 | }
16 | private[redis] case object NoRequestor extends Requestor {
17 | def respond(value: RedisType) = ()
18 | def failure(err: Exception) = ()
19 | }
20 | private[redis] case class PromiseRequestor(promise: Promise[RedisType]) extends Requestor {
21 | def respond(value: RedisType) = promise success value
22 | def failure(err: Exception) = promise failure err
23 | }
24 |
25 | private[redis] sealed trait Message
26 | private[redis] sealed trait RequestMessage extends Message
27 | private[redis] case class Request(requestor: Requestor, bytes: ByteString) extends RequestMessage
28 | private[redis] case class MultiRequest(requestor: Requestor, multi: ByteString, cmds: Seq[(ByteString, Promise[RedisType])], exec: ByteString) extends RequestMessage
29 | private[redis] case object Disconnect extends Message
30 | private[redis] case class MultiRun(requestor: Requestor, promises: Seq[Promise[RedisType]]) extends Message
31 | private[redis] case class Socket(handle: IO.SocketHandle) extends Message
32 | private[redis] case object Received extends Message
33 | private[redis] case class Subscriber(listener: ActorRef) extends Message
34 | private[redis] case class RequestCallback(callback: (Long, Long) ⇒ Unit) extends Message
35 | private[redis] case class ResultCallback(callback: (Long, Long) ⇒ Unit) extends Message
36 | private[redis] case class ReleaseClient(client: RedisClientPoolWorker) extends Message
37 | private[redis] case class RequestClient(promise: Promise[RedisClientPoolWorker]) extends Message
38 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/package.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie
2 |
3 | import akka.util.{ ByteString, Duration }
4 | import akka.dispatch.{ Future, Promise }
5 | import redis.serialization.Parse
6 |
7 | package redis {
8 |
9 | object RedisScore {
10 | val default: RedisScore = InclusiveScore(1.0)
11 | val max: RedisScore = InclusiveScore(Double.PositiveInfinity)
12 | val min: RedisScore = InclusiveScore(Double.NegativeInfinity)
13 | }
14 | sealed trait RedisScore {
15 | def value: Double
16 | def inclusive: InclusiveScore
17 | def exclusive: ExclusiveScore
18 | }
19 | case class InclusiveScore(value: Double) extends RedisScore {
20 | def inclusive = this
21 | def exclusive = ExclusiveScore(value)
22 | }
23 | case class ExclusiveScore(value: Double) extends RedisScore {
24 | def inclusive = InclusiveScore(value)
25 | def exclusive = this
26 | }
27 |
28 | sealed trait RedisLimit
29 | case class Limit(offset: Int, count: Int) extends RedisLimit
30 | case object NoLimit extends RedisLimit
31 |
32 | sealed trait SortOrder
33 | object SortOrder {
34 | case object Asc extends SortOrder
35 | case object Desc extends SortOrder
36 | }
37 |
38 | sealed trait Aggregate
39 | object Aggregate {
40 | case object Sum extends Aggregate
41 | case object Min extends Aggregate
42 | case object Max extends Aggregate
43 | }
44 |
45 | private[redis] class BoxRedisFloat
46 |
47 | }
48 |
49 | package object redis {
50 | implicit def doubleToRedisScore(value: Double): RedisScore = InclusiveScore(value)
51 |
52 | implicit def parseBulk(result: Option[ByteString]) = new ParseBulk[({ type λ[α] = α })#λ](result)
53 | implicit def parseBulk(result: Future[Option[ByteString]]) = new ParseBulk[Future](result)
54 | implicit def parseBulk(result: Queued[Future[Option[ByteString]]]) = new ParseBulk[({ type λ[α] = Queued[Future[α]] })#λ](result)
55 |
56 | implicit def parseMultiBulk(result: Option[List[Option[ByteString]]]) = new ParseMultiBulk[({ type λ[α] = α })#λ](result)
57 | implicit def parseMultiBulk(result: Future[Option[List[Option[ByteString]]]]) = new ParseMultiBulk[Future](result)
58 | implicit def parseMultiBulk(result: Queued[Future[Option[List[Option[ByteString]]]]]) = new ParseMultiBulk[({ type λ[α] = Queued[Future[α]] })#λ](result)
59 |
60 | implicit def parseMultiBulkList(result: List[Option[ByteString]]) = new ParseMultiBulkList[({ type λ[α] = α })#λ](result)
61 | implicit def parseMultiBulkList(result: Future[List[Option[ByteString]]]) = new ParseMultiBulkList[Future](result)
62 | implicit def parseMultiBulkList(result: Queued[Future[List[Option[ByteString]]]]) = new ParseMultiBulkList[({ type λ[α] = Queued[Future[α]] })#λ](result)
63 |
64 | implicit def parseMultiBulkFlat(result: Option[List[ByteString]]) = new ParseMultiBulkFlat[({ type λ[α] = α })#λ](result)
65 | implicit def parseMultiBulkFlat(result: Future[Option[List[ByteString]]]) = new ParseMultiBulkFlat[Future](result)
66 | implicit def parseMultiBulkFlat(result: Queued[Future[Option[List[ByteString]]]]) = new ParseMultiBulkFlat[({ type λ[α] = Queued[Future[α]] })#λ](result)
67 |
68 | implicit def parseMultiBulkFlatList(result: List[ByteString]) = new ParseMultiBulkFlatList[({ type λ[α] = α })#λ](result)
69 | implicit def parseMultiBulkFlatList(result: Future[List[ByteString]]) = new ParseMultiBulkFlatList[Future](result)
70 | implicit def parseMultiBulkFlatList(result: Queued[Future[List[ByteString]]]) = new ParseMultiBulkFlatList[({ type λ[α] = Queued[Future[α]] })#λ](result)
71 |
72 | implicit def parseMultiBulkSet(result: Set[ByteString]) = new ParseMultiBulkSet[({ type λ[α] = α })#λ](result)
73 | implicit def parseMultiBulkSet(result: Future[Set[ByteString]]) = new ParseMultiBulkSet[Future](result)
74 | implicit def parseMultiBulkSet(result: Queued[Future[Set[ByteString]]]) = new ParseMultiBulkSet[({ type λ[α] = Queued[Future[α]] })#λ](result)
75 |
76 | implicit def parseMultiBulkMap(result: Map[ByteString, ByteString]) = new ParseMultiBulkMap[({ type λ[α] = α })#λ](result)
77 | implicit def parseMultiBulkMap(result: Future[Map[ByteString, ByteString]]) = new ParseMultiBulkMap[Future](result)
78 | implicit def parseMultiBulkMap(result: Queued[Future[Map[ByteString, ByteString]]]) = new ParseMultiBulkMap[({ type λ[α] = Queued[Future[α]] })#λ](result)
79 |
80 | implicit def parseMultiBulkScored(result: List[(ByteString, Double)]) = new ParseMultiBulkScored[({ type λ[α] = α })#λ](result)
81 | implicit def parseMultiBulkScored(result: Future[List[(ByteString, Double)]]) = new ParseMultiBulkScored[Future](result)
82 | implicit def parseMultiBulkScored(result: Queued[Future[List[(ByteString, Double)]]]) = new ParseMultiBulkScored[({ type λ[α] = Queued[Future[α]] })#λ](result)
83 |
84 | private[redis] class ParseBulk[Result[_]](value: Result[Option[ByteString]])(implicit f: ResultFunctor[Result]) {
85 | def parse[A: Parse]: Result[Option[A]] = f.fmap(value)(_.map(Parse(_)))
86 | }
87 | private[redis] class ParseMultiBulk[Result[_]](value: Result[Option[List[Option[ByteString]]]])(implicit f: ResultFunctor[Result]) {
88 | def parse[A: Parse]: Result[Option[List[Option[A]]]] = f.fmap(value)(_.map(_.map(_.map(Parse(_)))))
89 | }
90 | private[redis] class ParseMultiBulkList[Result[_]](value: Result[List[Option[ByteString]]])(implicit f: ResultFunctor[Result]) {
91 | def parse[A: Parse]: Result[List[Option[A]]] = f.fmap(value)(_.map(_.map(Parse(_))))
92 | def parse[A: Parse, B: Parse] = f.fmap(value)(_.grouped(2).collect {
93 | case List(a, b) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)))
94 | } toList)
95 | def parse[A: Parse, B: Parse, C: Parse] = f.fmap(value)(_.grouped(3).collect {
96 | case List(a, b, c) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)))
97 | } toList)
98 | def parse[A: Parse, B: Parse, C: Parse, D: Parse] = f.fmap(value)(_.grouped(4).collect {
99 | case List(a, b, c, d) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)))
100 | } toList)
101 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse] = f.fmap(value)(_.grouped(5).collect {
102 | case List(a, b, c, d, e) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)))
103 | } toList)
104 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse] = f.fmap(value)(_.grouped(6).collect {
105 | case List(a, b, c, d, e, f) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)))
106 | } toList)
107 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse] = f.fmap(value)(_.grouped(7).collect {
108 | case List(a, b, c, d, e, f, g) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)))
109 | } toList)
110 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse] = f.fmap(value)(_.grouped(8).collect {
111 | case List(a, b, c, d, e, f, g, h) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)))
112 | } toList)
113 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse] = f.fmap(value)(_.grouped(9).collect {
114 | case List(a, b, c, d, e, f, g, h, i) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)))
115 | } toList)
116 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse] = f.fmap(value)(_.grouped(10).collect {
117 | case List(a, b, c, d, e, f, g, h, i, j) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)))
118 | } toList)
119 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse] = f.fmap(value)(_.grouped(11).collect {
120 | case List(a, b, c, d, e, f, g, h, i, j, k) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)))
121 | } toList)
122 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse] = f.fmap(value)(_.grouped(12).collect {
123 | case List(a, b, c, d, e, f, g, h, i, j, k, l) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)))
124 | } toList)
125 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse] = f.fmap(value)(_.grouped(13).collect {
126 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)))
127 | } toList)
128 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse, N: Parse] = f.fmap(value)(_.grouped(14).collect {
129 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m, n) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)), n map (Parse[N](_)))
130 | } toList)
131 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse, N: Parse, O: Parse] = f.fmap(value)(_.grouped(15).collect {
132 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)), n map (Parse[N](_)), o map (Parse[O](_)))
133 | } toList)
134 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse, N: Parse, O: Parse, P: Parse] = f.fmap(value)(_.grouped(16).collect {
135 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)), n map (Parse[N](_)), o map (Parse[O](_)), p map (Parse[P](_)))
136 | } toList)
137 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse, N: Parse, O: Parse, P: Parse, Q: Parse] = f.fmap(value)(_.grouped(17).collect {
138 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)), n map (Parse[N](_)), o map (Parse[O](_)), p map (Parse[P](_)), q map (Parse[Q](_)))
139 | } toList)
140 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse, N: Parse, O: Parse, P: Parse, Q: Parse, R: Parse] = f.fmap(value)(_.grouped(18).collect {
141 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)), n map (Parse[N](_)), o map (Parse[O](_)), p map (Parse[P](_)), q map (Parse[Q](_)), r map (Parse[R](_)))
142 | } toList)
143 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse, N: Parse, O: Parse, P: Parse, Q: Parse, R: Parse, S: Parse] = f.fmap(value)(_.grouped(19).collect {
144 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)), n map (Parse[N](_)), o map (Parse[O](_)), p map (Parse[P](_)), q map (Parse[Q](_)), r map (Parse[R](_)), s map (Parse[S](_)))
145 | } toList)
146 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse, N: Parse, O: Parse, P: Parse, Q: Parse, R: Parse, S: Parse, T: Parse] = f.fmap(value)(_.grouped(20).collect {
147 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)), n map (Parse[N](_)), o map (Parse[O](_)), p map (Parse[P](_)), q map (Parse[Q](_)), r map (Parse[R](_)), s map (Parse[S](_)), t map (Parse[T](_)))
148 | } toList)
149 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse, N: Parse, O: Parse, P: Parse, Q: Parse, R: Parse, S: Parse, T: Parse, U: Parse] = f.fmap(value)(_.grouped(21).collect {
150 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)), n map (Parse[N](_)), o map (Parse[O](_)), p map (Parse[P](_)), q map (Parse[Q](_)), r map (Parse[R](_)), s map (Parse[S](_)), t map (Parse[T](_)), u map (Parse[U](_)))
151 | } toList)
152 | def parse[A: Parse, B: Parse, C: Parse, D: Parse, E: Parse, F: Parse, G: Parse, H: Parse, I: Parse, J: Parse, K: Parse, L: Parse, M: Parse, N: Parse, O: Parse, P: Parse, Q: Parse, R: Parse, S: Parse, T: Parse, U: Parse, V: Parse] = f.fmap(value)(_.grouped(22).collect {
153 | case List(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v) ⇒ (a map (Parse[A](_)), b map (Parse[B](_)), c map (Parse[C](_)), d map (Parse[D](_)), e map (Parse[E](_)), f map (Parse[F](_)), g map (Parse[G](_)), h map (Parse[H](_)), i map (Parse[I](_)), j map (Parse[J](_)), k map (Parse[K](_)), l map (Parse[L](_)), m map (Parse[M](_)), n map (Parse[N](_)), o map (Parse[O](_)), p map (Parse[P](_)), q map (Parse[Q](_)), r map (Parse[R](_)), s map (Parse[S](_)), t map (Parse[T](_)), u map (Parse[U](_)), v map (Parse[V](_)))
154 | } toList)
155 | }
156 | private[redis] class ParseMultiBulkFlat[Result[_]](value: Result[Option[List[ByteString]]])(implicit f: ResultFunctor[Result]) {
157 | def parse[A: Parse]: Result[Option[List[A]]] = f.fmap(value)(_.map(_.map(Parse(_))))
158 | }
159 | private[redis] class ParseMultiBulkFlatList[Result[_]](value: Result[List[ByteString]])(implicit f: ResultFunctor[Result]) {
160 | def parse[A: Parse]: Result[List[A]] = f.fmap(value)(_.map(Parse(_)))
161 | }
162 | private[redis] class ParseMultiBulkSet[Result[_]](value: Result[Set[ByteString]])(implicit f: ResultFunctor[Result]) {
163 | def parse[A: Parse]: Result[Set[A]] = f.fmap(value)(_.map(Parse(_)))
164 | }
165 | private[redis] class ParseMultiBulkMap[Result[_]](value: Result[Map[ByteString, ByteString]])(implicit f: ResultFunctor[Result]) {
166 | def parse[K: Parse, V: Parse]: Result[Map[K, V]] = f.fmap(value)(_.map(kv ⇒ (Parse[K](kv._1), Parse[V](kv._2))))
167 | }
168 | private[redis] class ParseMultiBulkScored[Result[_]](value: Result[List[(ByteString, Double)]])(implicit f: ResultFunctor[Result]) {
169 | def parse[A: Parse]: Result[List[(A, Double)]] = f.fmap(value)(_.map(kv ⇒ (Parse(kv._1), kv._2)))
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/protocol/Constants.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package protocol
3 |
4 | import akka.util.ByteString
5 |
6 | private[redis] object Constants {
7 | final val AFTER = ByteString("AFTER")
8 | final val AGGREGATE = ByteString("AGGREGATE")
9 | final val ALLKEYS = ByteString("*")
10 | final val ALPHA = ByteString("ALPHA")
11 | final val APPEND = ByteString("APPEND")
12 | final val ASC = ByteString("ASC")
13 | final val AUTH = ByteString("AUTH")
14 | final val BEFORE = ByteString("BEFORE")
15 | final val BGREWRITEAOF = ByteString("BGREWRITEAOF")
16 | final val BGSAVE = ByteString("BGSAVE")
17 | final val BLPOP = ByteString("BLPOP")
18 | final val BRPOP = ByteString("BRPOP")
19 | final val BRPOPLPUSH = ByteString("BRPOPLPUSH")
20 | final val BY = ByteString("BY")
21 | final val CONFIG = ByteString("CONFIG")
22 | final val DBSIZE = ByteString("DBSIZE")
23 | final val DEBUG = ByteString("DEBUG")
24 | final val DECR = ByteString("DECR")
25 | final val DECRBY = ByteString("DECRBY")
26 | final val DEL = ByteString("DEL")
27 | final val DESC = ByteString("DESC")
28 | final val DISCARD = ByteString("DISCARD")
29 | final val ECHO = ByteString("ECHO")
30 | final val EOL = ByteString("\r\n")
31 | final val EVAL = ByteString("EVAL")
32 | final val EXEC = ByteString("EXEC")
33 | final val EXISTS = ByteString("EXISTS")
34 | final val EXPIRE = ByteString("EXPIRE")
35 | final val EXPIREAT = ByteString("EXPIREAT")
36 | final val FLUSHDB = ByteString("FLUSHDB")
37 | final val FLUSHALL = ByteString("FLUSHALL")
38 | final val GET = ByteString("GET")
39 | final val GETBIT = ByteString("GETBIT")
40 | final val GETRANGE = ByteString("GETRANGE")
41 | final val GETSET = ByteString("GETSET")
42 | final val HDEL = ByteString("HDEL")
43 | final val HEXISTS = ByteString("HEXISTS")
44 | final val HGET = ByteString("HGET")
45 | final val HGETALL = ByteString("HGETALL")
46 | final val HINCRBY = ByteString("HINCRBY")
47 | final val HKEYS = ByteString("HKEYS")
48 | final val HLEN = ByteString("HLEN")
49 | final val HMGET = ByteString("HMGET")
50 | final val HMSET = ByteString("HMSET")
51 | final val HSET = ByteString("HSET")
52 | final val HSETNX = ByteString("HSETNX")
53 | final val HVALS = ByteString("HVALS")
54 | final val INCR = ByteString("INCR")
55 | final val INCRBY = ByteString("INCRBY")
56 | final val INFO = ByteString("INFO")
57 | final val INFPOS = ByteString("+inf")
58 | final val INFNEG = ByteString("-inf")
59 | final val KEYS = ByteString("KEYS")
60 | final val LASTSAVE = ByteString("LASTSAVE")
61 | final val LIMIT = ByteString("LIMIT")
62 | final val LINDEX = ByteString("LINDEX")
63 | final val LINSERT = ByteString("LINSERT")
64 | final val LLEN = ByteString("LLEN")
65 | final val LPOP = ByteString("LPOP")
66 | final val LPUSH = ByteString("LPUSH")
67 | final val LPUSHX = ByteString("LPUSHX")
68 | final val LRANGE = ByteString("LRANGE")
69 | final val LREM = ByteString("LREM")
70 | final val LSET = ByteString("LSET")
71 | final val LTRIM = ByteString("LTRIM")
72 | final val MAX = ByteString("MAX")
73 | final val message = ByteString("message")
74 | final val MGET = ByteString("MGET")
75 | final val MIN = ByteString("MIN")
76 | final val MONITOR = ByteString("MONITOR")
77 | final val MOVE = ByteString("MOVE")
78 | final val MSET = ByteString("MSET")
79 | final val MSETNX = ByteString("MSETNX")
80 | final val MULTI = ByteString("MULTI")
81 | final val OBJECT = ByteString("OBJECT")
82 | final val PERSIST = ByteString("PERSIST")
83 | final val PING = ByteString("PING")
84 | final val pmessage = ByteString("pmessage")
85 | final val PSUBSCRIBE = ByteString("PSUBSCRIBE")
86 | final val psubscribe = ByteString("psubscribe")
87 | final val PUBLISH = ByteString("PUBLISH")
88 | final val PUNSUBSCRIBE = ByteString("PUNSUBSCRIBE")
89 | final val punsubscribe = ByteString("punsubscribe")
90 | final val QUIT = ByteString("QUIT")
91 | final val RANDOMKEY = ByteString("RANDOMKEY")
92 | final val RENAME = ByteString("RENAME")
93 | final val RENAMENX = ByteString("RENAMENX")
94 | final val RESETSTAT = ByteString("RESETSTAT")
95 | final val RPOP = ByteString("RPOP")
96 | final val RPOPLPUSH = ByteString("RPOPLPUSH")
97 | final val RPUSH = ByteString("RPUSH")
98 | final val RPUSHX = ByteString("RPUSHX")
99 | final val SADD = ByteString("SADD")
100 | final val SAVE = ByteString("SAVE")
101 | final val SCARD = ByteString("SCARD")
102 | final val SDIFF = ByteString("SDIFF")
103 | final val SDIFFSTORE = ByteString("SDIFFSTORE")
104 | final val SEGFAULT = ByteString("SEGFAULT")
105 | final val SELECT = ByteString("SELECT")
106 | final val SET = ByteString("SET")
107 | final val SETBIT = ByteString("SETBIT")
108 | final val SETEX = ByteString("SETEX")
109 | final val SETNX = ByteString("SETNX")
110 | final val SETRANGE = ByteString("SETRANGE")
111 | final val SHUTDOWN = ByteString("SHUTDOWN")
112 | final val SINTER = ByteString("SINTER")
113 | final val SINTERSTORE = ByteString("SINTERSTORE")
114 | final val SISMEMBER = ByteString("SISMEMBER")
115 | final val SLAVEOF = ByteString("SLAVEOF")
116 | final val SMEMBERS = ByteString("SMEMBERS")
117 | final val SMOVE = ByteString("SMOVE")
118 | final val SORT = ByteString("SORT")
119 | final val SPOP = ByteString("SPOP")
120 | final val SRANDMEMBER = ByteString("SRANDMEMBER")
121 | final val SREM = ByteString("SREM")
122 | final val STRLEN = ByteString("STRLEN")
123 | final val SUBSCRIBE = ByteString("SUBSCRIBE")
124 | final val subscribe = ByteString("subscribe")
125 | final val SUM = ByteString("SUM")
126 | final val SUNION = ByteString("SUNION")
127 | final val SUNIONSTORE = ByteString("SUNIONSTORE")
128 | final val SYNC = ByteString("SYNC")
129 | final val TTL = ByteString("TTL")
130 | final val TYPE = ByteString("TYPE")
131 | final val UNSUBSCRIBE = ByteString("UNSUBSCRIBE")
132 | final val unsubscribe = ByteString("unsubscribe")
133 | final val UNWATCH = ByteString("UNWATCH")
134 | final val WATCH = ByteString("WATCH")
135 | final val WEIGHTS = ByteString("WEIGHTS")
136 | final val WITHSCORES = ByteString("WITHSCORES")
137 | final val ZADD = ByteString("ZADD")
138 | final val ZCARD = ByteString("ZCARD")
139 | final val ZCOUNT = ByteString("ZCOUNT")
140 | final val ZINCRBY = ByteString("ZINCRBY")
141 | final val ZINTERSTORE = ByteString("ZINTERSTORE")
142 | final val ZRANGE = ByteString("ZRANGE")
143 | final val ZRANGEBYSCORE = ByteString("ZRANGEBYSCORE")
144 | final val ZRANK = ByteString("ZRANK")
145 | final val ZREM = ByteString("ZREM")
146 | final val ZREMRANGEBYRANK = ByteString("ZREMRANGEBYRANK")
147 | final val ZREMRANGEBYSCORE = ByteString("ZREMRANGEBYSCORE")
148 | final val ZREVRANGE = ByteString("ZREVRANGE")
149 | final val ZREVRANGEBYSCORE = ByteString("ZREVRANGEBYSCORE")
150 | final val ZREVRANK = ByteString("ZREVRANK")
151 | final val ZSCORE = ByteString("ZSCORE")
152 | final val ZUNIONSTORE = ByteString("ZUNIONSTORE")
153 | }
154 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/protocol/Iteratees.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package protocol
3 |
4 | import types._
5 | import akka.util.ByteString
6 | import akka.actor.IO
7 |
8 | private[redis] object Iteratees {
9 | import Constants.EOL
10 |
11 | final val readUntilEOL = IO takeUntil EOL
12 |
13 | final val readType = (_: ByteString).head match {
14 | case 43 /* '+' */ ⇒ readString
15 | case 45 /* '-' */ ⇒ readError
16 | case 58 /* ':' */ ⇒ readInteger
17 | case 36 /* '$' */ ⇒ readBulk
18 | case 42 /* '*' */ ⇒ readMulti
19 | case x ⇒ IO throwErr RedisProtocolException("Invalid result type: " + x)
20 | }
21 |
22 | final val readResult: IO.Iteratee[RedisType] = IO take 1 flatMap readType
23 |
24 | final val bytesToString = (bytes: ByteString) ⇒ RedisString(bytes.utf8String)
25 | final val readString: IO.Iteratee[RedisString] = readUntilEOL map bytesToString
26 |
27 | final val bytesToError = (bytes: ByteString) ⇒ RedisError(bytes.utf8String)
28 | final val readError: IO.Iteratee[RedisError] = readUntilEOL map bytesToError
29 |
30 | final val bytesToInteger = (bytes: ByteString) ⇒ RedisInteger(bytes.utf8String.toLong)
31 | final val readInteger: IO.Iteratee[RedisInteger] = readUntilEOL map bytesToInteger
32 |
33 | final val notFoundBulk = IO Done RedisBulk.notfound
34 | final val emptyBulk = IO Done RedisBulk.empty
35 | final val bytesToBulk = (_: ByteString).utf8String.toInt match {
36 | case -1 ⇒ notFoundBulk
37 | case 0 ⇒ emptyBulk
38 | case n ⇒ for (bytes ← IO take n; _ ← readUntilEOL) yield RedisBulk(Some(bytes))
39 | }
40 | final val readBulk: IO.Iteratee[RedisBulk] = readUntilEOL flatMap bytesToBulk
41 |
42 | final val notFoundMulti = IO Done RedisMulti.notfound
43 | final val emptyMulti = IO Done RedisMulti.empty
44 | final val bytesToMulti = (bytes: ByteString) ⇒ bytes.utf8String.toInt match {
45 | case -1 ⇒ notFoundMulti
46 | case 0 ⇒ emptyMulti
47 | case n ⇒ IO.takeList(n)(readResult) map (x ⇒ RedisMulti(Some(x)))
48 | }
49 | final val readMulti: IO.Iteratee[RedisMulti] = readUntilEOL flatMap bytesToMulti
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/pubsub/PubSub.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package pubsub
3 |
4 | import akka.util.ByteString
5 | import serialization.Store
6 |
7 | sealed trait PubSubMessage
8 | case class Subscribed(channel: ByteString, count: Long) extends PubSubMessage
9 | case class Unsubscribed(channel: ByteString, count: Long) extends PubSubMessage
10 | case class Message(channel: ByteString, content: ByteString) extends PubSubMessage
11 | case class PSubscribed(pattern: ByteString, count: Long) extends PubSubMessage
12 | case class PUnsubscribed(pattern: ByteString, count: Long) extends PubSubMessage
13 | case class PMessage(pattern: ByteString, channel: ByteString, content: ByteString) extends PubSubMessage
14 |
15 | object Subscribe {
16 | def apply[A: Store](channel: A): Subscribe = new Subscribe(List(Store(channel)))
17 | def apply[A: Store](channels: Iterable[A]): Subscribe = new Subscribe(channels.map(Store(_))(collection.breakOut))
18 | }
19 | case class Subscribe(channels: List[ByteString]) extends PubSubMessage
20 |
21 | object Unsubscribe {
22 | def apply[A: Store](channel: A): Unsubscribe = new Unsubscribe(List(Store(channel)))
23 | def apply[A: Store](channels: Iterable[A]): Unsubscribe = new Unsubscribe(channels.map(Store(_))(collection.breakOut))
24 | }
25 | case class Unsubscribe(channels: List[ByteString]) extends PubSubMessage
26 |
27 | object PSubscribe {
28 | def apply[A: Store](pattern: A): PSubscribe = new PSubscribe(List(Store(pattern)))
29 | def apply[A: Store](patterns: Iterable[A]): PSubscribe = new PSubscribe(patterns.map(Store(_))(collection.breakOut))
30 | }
31 | case class PSubscribe(patterns: List[ByteString]) extends PubSubMessage
32 |
33 | object PUnsubscribe {
34 | def apply[A: Store](pattern: A): PUnsubscribe = new PUnsubscribe(List(Store(pattern)))
35 | def apply[A: Store](patterns: Iterable[A]): PUnsubscribe = new PUnsubscribe(patterns.map(Store(_))(collection.breakOut))
36 | }
37 | case class PUnsubscribe(patterns: List[ByteString]) extends PubSubMessage
38 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/serialization/Serialization.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 | package serialization
3 |
4 | import protocol.Constants
5 |
6 | import akka.util.ByteString
7 |
8 | import java.util.Date
9 |
10 | trait Store[-A] {
11 | def apply(value: A): ByteString
12 | }
13 |
14 | object Store {
15 | sealed trait Dummy
16 |
17 | def apply[A](value: A)(implicit store: Store[A]): ByteString = store(value)
18 |
19 | def asString[A]: Store[A] = new Store[A] { def apply(value: A) = ByteString(value.toString) }
20 |
21 | implicit val storeByteString = new Store[ByteString] { def apply(value: ByteString) = value }
22 | implicit val storeByteArray = new Store[Array[Byte]] { def apply(value: Array[Byte]) = ByteString(value) }
23 | implicit val storeString = new Store[String] { def apply(value: String) = ByteString(value) }
24 | implicit val storeInt = Store.asString[Int]
25 | implicit val storeLong = Store.asString[Long]
26 | implicit val storeFloat = Store.asString[Float]
27 | implicit val storeDouble = Store.asString[Double]
28 | implicit val storeDummy = new Store[Dummy] { def apply(value: Dummy) = sys.error("Don't store the Dummy!") }
29 | implicit val storeLua = new Store[lua.Chunk] { def apply(value: lua.Chunk) = value.bytes }
30 | implicit val storeDate = new Store[Date] { def apply(value: Date) = storeLong(value.getTime) }
31 | implicit val storeScore = new Store[RedisScore] {
32 | def apply(score: RedisScore) = score match {
33 | case _ if score.value == Double.PositiveInfinity ⇒ Constants.INFPOS
34 | case _ if score.value == Double.NegativeInfinity ⇒ Constants.INFNEG
35 | case InclusiveScore(d) ⇒ ByteString(d.toString)
36 | case ExclusiveScore(d) ⇒ ByteString("(" + d.toString)
37 | }
38 | }
39 | implicit val storeAggregate = new Store[Aggregate] {
40 | def apply(value: Aggregate) = value match {
41 | case Aggregate.Sum ⇒ Constants.SUM
42 | case Aggregate.Min ⇒ Constants.MIN
43 | case Aggregate.Max ⇒ Constants.MAX
44 | }
45 | }
46 | implicit val storeSortOrder = new Store[SortOrder] {
47 | def apply(value: SortOrder) = value match {
48 | case SortOrder.Asc ⇒ Constants.ASC
49 | case SortOrder.Desc ⇒ Constants.DESC
50 | }
51 | }
52 | }
53 |
54 | trait Parse[+A] {
55 | def apply(bytes: ByteString): A
56 | }
57 |
58 | object Parse {
59 | def apply[A](bytes: ByteString)(implicit parse: Parse[A]): A = parse(bytes)
60 |
61 | implicit val parseByteString = new Parse[ByteString] { def apply(bytes: ByteString) = bytes.compact }
62 | implicit val parseByteArray = new Parse[Array[Byte]] { def apply(bytes: ByteString) = bytes.toArray }
63 | implicit val parseString = new Parse[String] { def apply(bytes: ByteString) = bytes.utf8String }
64 | implicit val parseInt = new Parse[Int] { def apply(bytes: ByteString) = bytes.utf8String.toInt }
65 | implicit val parseLong = new Parse[Long] { def apply(bytes: ByteString) = bytes.utf8String.toLong }
66 | implicit val parseFloat = new Parse[Float] { def apply(bytes: ByteString) = bytes.utf8String.toFloat }
67 | implicit val parseDouble = new Parse[Double] { def apply(bytes: ByteString) = bytes.utf8String.toDouble }
68 | implicit val parseDate = new Parse[Date] { def apply(bytes: ByteString) = new Date(parseLong(bytes)) }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/scala/net/fyrie/redis/types/Types.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis.types
2 |
3 | import akka.util.ByteString
4 |
5 | sealed trait RedisType
6 |
7 | case class RedisString(value: String) extends RedisType
8 |
9 | case class RedisInteger(value: Long) extends RedisType
10 |
11 | object RedisBulk {
12 | val notfound = new RedisBulk(None)
13 | val empty = new RedisBulk(Some(ByteString.empty))
14 | }
15 |
16 | case class RedisBulk(value: Option[ByteString]) extends RedisType
17 |
18 | object RedisMulti {
19 | val notfound = new RedisMulti(None)
20 | val empty = new RedisMulti(Some(Nil))
21 | }
22 |
23 | case class RedisMulti(value: Option[List[RedisType]]) extends RedisType
24 |
25 | case class RedisError(value: String) extends RedisType
26 |
--------------------------------------------------------------------------------
/src/test/scala/net/fyrie/redis/DBSpec.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import org.specs2._
4 |
5 | import akka.dispatch.Future
6 | import akka.testkit.{ filterEvents, EventFilter }
7 |
8 | class KeysSpec extends mutable.Specification with TestClient {
9 |
10 | "keys" >> {
11 | "should fetch keys" ! client { r ⇒
12 | r.set("anshin-1", "debasish")
13 | r.set("anshin-2", "maulindu")
14 | r.sync.keys("anshin*").size must_== 2
15 | }
16 |
17 | "should fetch keys with spaces" ! client { r ⇒
18 | r.set("anshin 1", "debasish")
19 | r.set("anshin 2", "maulindu")
20 | r.sync.keys("anshin*").size must_== 2
21 | }
22 | }
23 |
24 | "randomkey" >> {
25 | "should give" ! client { r ⇒
26 | r.set("anshin-1", "debasish")
27 | r.set("anshin-2", "maulindu")
28 | r.sync.randomkey().parse[String].get must startWith("anshin")
29 | }
30 | }
31 |
32 | "rename" >> {
33 | "should give" ! client { r ⇒
34 | r.set("anshin-1", "debasish")
35 | r.set("anshin-2", "maulindu")
36 | r.rename("anshin-2", "anshin-2-new")
37 | r.sync.rename("anshin-2", "anshin-2-new") must throwA[RedisErrorException]("ERR no such key")
38 | }
39 | }
40 |
41 | "renamenx" >> {
42 | "should give" ! client { r ⇒
43 | r.set("anshin-1", "debasish")
44 | r.set("anshin-2", "maulindu")
45 | r.sync.renamenx("anshin-2", "anshin-2-new") must_== true
46 | r.sync.renamenx("anshin-1", "anshin-2-new") must_== false
47 | }
48 | }
49 |
50 | "dbsize" >> {
51 | "should give" ! client { r ⇒
52 | r.set("anshin-1", "debasish")
53 | r.set("anshin-2", "maulindu")
54 | r.sync.dbsize() must_== 2
55 | }
56 | }
57 |
58 | "exists" >> {
59 | "should give" ! client { r ⇒
60 | r.set("anshin-1", "debasish")
61 | r.set("anshin-2", "maulindu")
62 | r.sync.exists("anshin-2") must_== true
63 | r.sync.exists("anshin-1") must_== true
64 | r.sync.exists("anshin-3") must_== false
65 | }
66 | }
67 |
68 | "del" >> {
69 | "should give" ! client { r ⇒
70 | r.set("anshin-1", "debasish")
71 | r.set("anshin-2", "maulindu")
72 | r.sync.del(Set("anshin-2", "anshin-1")) must_== 2
73 | r.sync.del(Set("anshin-2", "anshin-1")) must_== 0
74 | }
75 | }
76 |
77 | "type" >> {
78 | "should give" ! client { r ⇒
79 | r.set("anshin-1", "debasish")
80 | r.set("anshin-2", "maulindu")
81 | r.sync.typeof("anshin-2") must_== "string"
82 | }
83 | }
84 |
85 | "expire" >> {
86 | "should give" ! client { r ⇒
87 | r.set("anshin-1", "debasish")
88 | r.set("anshin-2", "maulindu")
89 | r.sync.expire("anshin-2", 1000) must_== true
90 | r.sync.expire("anshin-3", 1000) must_== false
91 | }
92 | }
93 |
94 | /* "quit" >> {
95 | "should reconnect" ! client { r ⇒
96 | r.quit
97 | r.set("key1", "value1")
98 | r.quit
99 | r.sync.get("key1").parse[String] must_== Some("value1")
100 | }
101 | /* ignore("should reconnect with many commands") { // ignore until fixed
102 | (1 to 1000) foreach (_ ⇒ r.incr("incKey"))
103 | r.quit
104 | val result1 = r.get("incKey").parse[Int]
105 | (1 to 1000) foreach (_ ⇒ r.incr("incKey"))
106 | val result2 = r.get("incKey").parse[Int]
107 | r.quit
108 | (1 to 1000) foreach (_ ⇒ r.incr("incKey"))
109 | val result3 = r.get("incKey").parse[Int]
110 | result1.get must_==(Some(1000))
111 | result2.get must_==(Some(2000))
112 | result3.get must_==(Some(3000))
113 | }*/
114 | } */
115 |
116 | "Multi exec commands" >> {
117 | "should work with single commands" ! client { r ⇒
118 | r.multi { rq ⇒
119 | rq.set("testkey1", "testvalue1")
120 | } map (_ === ())
121 | }
122 | "should work with several commands" ! client { r ⇒
123 | r.multi { rq ⇒
124 | for {
125 | _ ← rq.set("testkey1", "testvalue1")
126 | _ ← rq.set("testkey2", "testvalue2")
127 | x ← rq.mget(List("testkey1", "testkey2")).parse[String]
128 | } yield x
129 | } map (_ === List(Some("testvalue1"), Some("testvalue2")))
130 | }
131 | "should work with a list of commands" ! client { r ⇒
132 | val values = List.range(1, 100)
133 | Future sequence {
134 | r.multi { rq ⇒
135 | val setq = (Queued[Any](()) /: values)((q, v) ⇒ q flatMap (_ ⇒ rq.set(v, v * 2)))
136 | (setq.map(_ ⇒ List[Future[Option[Int]]]()) /: values)((q, v) ⇒ q flatMap (l ⇒ rq.get(v).parse[Int].map(_ :: l))).map(_.reverse)
137 | }
138 | } map (_.flatten === values.map(2*))
139 | }
140 | "should throw an error" ! client { r ⇒
141 | filterEvents(EventFilter[RedisErrorException]("ERR Operation against a key holding the wrong kind of value")) {
142 | val result = r.multi { rq ⇒
143 | for {
144 | _ ← rq.set("a", "abc")
145 | x ← rq.lpop("a").parse[String]
146 | y ← rq.get("a").parse[String]
147 | } yield (x, y)
148 | }
149 | for {
150 | a ← result._1 recover { case _: RedisErrorException ⇒ success }
151 | b ← result._2
152 | } yield {
153 | b === Some("abc")
154 | }
155 | }
156 | }
157 | "should handle invalid requests" ! client { r ⇒
158 | filterEvents(EventFilter[RedisErrorException]("ERR Operation against a key holding the wrong kind of value")) {
159 | val result = r.multi { rq ⇒
160 | for {
161 | _ ← rq.set("testkey1", "testvalue1")
162 | _ ← rq.set("testkey2", "testvalue2")
163 | x ← rq.mget(List[String]()).parse[String]
164 | y ← rq.mget(List("testkey1", "testkey2")).parse[String]
165 | } yield (x, y)
166 | }
167 | for {
168 | a ← result._1 recover { case _: RedisErrorException ⇒ success }
169 | b ← result._2
170 | } yield {
171 | b === List(Some("testvalue1"), Some("testvalue2"))
172 | }
173 | }
174 | }
175 | }
176 |
177 | "watch" >> {
178 | "should fail without watch" ! client { r ⇒
179 | r.set("key", 0) flatMap { _ ⇒
180 | val clients = List.fill(10)(RedisClient())
181 | val futures = for (client ← clients; _ ← 1 to 10) yield client.get("key").parse[Int] flatMap { n ⇒ client.set("key", n.get + 1) }
182 | Future sequence futures flatMap { _ ⇒
183 | clients foreach (_.disconnect)
184 | r.get("key").parse[Int] map (_ must_!= Some(100))
185 | }
186 | }
187 | }
188 | "should succeed with watch" ! client { r ⇒
189 | r.sync.set("key", 0)
190 | val futures = for (_ ← 1 to 100) yield {
191 | r atomic { rw ⇒
192 | rw watch "key"
193 | for {
194 | Some(n) ← rw.get("key").parse[Int]
195 | } yield rw multi (_.set("key", n + 1))
196 | }
197 | }
198 | Future sequence futures flatMap { _ ⇒
199 | r.get("key").parse[Int] map (_ === Some(100))
200 | }
201 | }
202 | "should handle complex request" ! client { r ⇒
203 | r.sync.rpush("mykey1", 5)
204 | r.set("mykey2", "hello")
205 | r.hset("mykey3", "hello", 7)
206 | val result = r atomic { rw ⇒
207 | for {
208 | _ ← rw.watch("mykey1")
209 | _ ← rw.watch("mykey2")
210 | _ ← rw.watch("mykey3")
211 | Some(a) ← rw.lindex("mykey1", 0).parse[Int]
212 | Some(b) ← rw.get("mykey2").parse[String]
213 | Some(c) ← rw.hget("mykey3", b).parse[Int]
214 | } yield rw.multi { rq ⇒
215 | for {
216 | _ ← rq.rpush("mykey1", a + 1)
217 | _ ← rq.hset("mykey3", b, c + 1)
218 | } yield (a, b, c)
219 | }
220 | }
221 | for {
222 | a ← result
223 | b ← r.lrange("mykey1").parse[Int]
224 | c ← r.hget("mykey3", "hello").parse[Int]
225 | } yield {
226 | a === (5, "hello", 7)
227 | b === Some(List(5, 6))
228 | c === Some(8)
229 | }
230 | }
231 | }
232 |
233 | "sort" >> {
234 | "should do a simple sort" ! client { r ⇒
235 | List(6, 3, 5, 47, 1, 1, 4, 9) foreach (r.lpush("sortlist", _))
236 | r.sync.sort("sortlist").parse[Int].flatten must_== List(1, 1, 3, 4, 5, 6, 9, 47)
237 | }
238 | "should do a lexical sort" ! client { r ⇒
239 | List("lorem", "ipsum", "dolor", "sit", "amet") foreach (r.lpush("sortlist", _))
240 | List(3, 7) foreach (r.lpush("sortlist", _))
241 | r.sync.sort("sortlist", alpha = true).parse[String].flatten must_== List("3", "7", "amet", "dolor", "ipsum", "lorem", "sit")
242 | }
243 | "should return an empty list if key not found" ! client { r ⇒
244 | r.sync.sort("sortnotfound") must beEmpty
245 | }
246 | "should return multiple items" ! client { r ⇒
247 | val list = List(("item1", "data1", 1, 4),
248 | ("item2", "data2", 2, 8),
249 | ("item3", "data3", 3, 1),
250 | ("item4", "data4", 4, 6),
251 | ("item5", "data5", 5, 3))
252 | for ((key, data, num, rank) ← list) {
253 | r.quiet.sadd("items", key)
254 | r.quiet.set("data::" + key, data)
255 | r.quiet.set("num::" + key, num)
256 | r.quiet.set("rank::" + key, rank)
257 | }
258 | r.quiet.del(List("num::item1"))
259 | r.sync.sort("items",
260 | get = Seq("#", "data::*", "num::*"),
261 | by = Some("rank::*"),
262 | limit = Limit(1, 3)).parse[String] must_== List(Some("item5"), Some("data5"), Some("5"),
263 | Some("item1"), Some("data1"), None,
264 | Some("item4"), Some("data4"), Some("4"))
265 | r.sync.sort("items",
266 | get = Seq("#", "data::*", "num::*"),
267 | by = Some("rank::*"),
268 | limit = Limit(1, 3)).parse[String, String, Int] must_== (List((Some("item5"), Some("data5"), Some(5)),
269 | (Some("item1"), Some("data1"), None),
270 | (Some("item4"), Some("data4"), Some(4))))
271 | }
272 | }
273 |
274 | }
275 |
276 |
--------------------------------------------------------------------------------
/src/test/scala/net/fyrie/redis/HashSpec.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import org.specs2._
4 |
5 | class HashSpec extends mutable.Specification with TestClient {
6 |
7 | "hset" >> {
8 | "should set and get fields" ! client { r ⇒
9 | r.quiet.hset("hash1", "field1", "val")
10 | r.sync.hget("hash1", "field1").parse[String] must_== Some("val")
11 | }
12 |
13 | "should set and get maps" ! client { r ⇒
14 | r.quiet.hmset("hash2", Map("field1" -> "val1", "field2" -> "val2"))
15 | r.sync.hmget("hash2", Seq("field1")).parse[String] must_== List(Some("val1"))
16 | r.sync.hmget("hash2", Seq("field1", "field2")).parse[String] must_== List(Some("val1"), Some("val2"))
17 | r.sync.hmget("hash2", Seq("field1", "field2", "field3")).parse[String] must_== List(Some("val1"), Some("val2"), None)
18 | }
19 |
20 | "should increment map values" ! client { r ⇒
21 | r.sync.hincrby("hash3", "field1", 1) must_== 1
22 | r.sync.hget("hash3", "field1").parse[String] must_== Some("1")
23 | }
24 |
25 | "should check existence" ! client { r ⇒
26 | r.quiet.hset("hash4", "field1", "val")
27 | r.sync.hexists("hash4", "field1") must beTrue
28 | r.sync.hexists("hash4", "field2") must beFalse
29 | }
30 |
31 | "should delete fields" ! client { r ⇒
32 | r.quiet.hset("hash5", "field1", "val")
33 | r.sync.hexists("hash5", "field1") must beTrue
34 | r.quiet.hdel("hash5", "field1")
35 | r.sync.hexists("hash5", "field1") must beFalse
36 | }
37 |
38 | "should return the length of the fields" ! client { r ⇒
39 | r.quiet.hmset("hash6", Map("field1" -> "val1", "field2" -> "val2"))
40 | r.sync.hlen("hash6") must_== 2
41 | }
42 |
43 | "should return the aggregates" ! client { r ⇒
44 | r.quiet.hmset("hash7", Map("field1" -> "val1", "field2" -> "val2"))
45 | r.sync.hkeys("hash7").parse[String] must_== Set("field1", "field2")
46 | r.sync.hvals("hash7").parse[String] must_== Set("val1", "val2")
47 | r.sync.hgetall("hash7").parse[String, String] must_== Map("field1" -> "val1", "field2" -> "val2")
48 | }
49 | }
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/src/test/scala/net/fyrie/redis/ListSpec.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import org.specs2._
4 |
5 | class ListSpec extends mutable.Specification with TestClient {
6 |
7 | "lpush" >> {
8 | "should add to the head of the list" ! client { r ⇒
9 | r.sync.lpush("list-1", "foo") === (1)
10 | r.sync.lpush("list-1", "bar") === (2)
11 | }
12 | "should throw if the key has a non-list value" ! client { r ⇒
13 | r.set("anshin-1", "debasish")
14 | r.sync.lpush("anshin-1", "bar") must throwA[RedisErrorException]("ERR Operation against a key holding the wrong kind of value")
15 | }
16 | }
17 |
18 | "rpush" >> {
19 | "should add to the head of the list" ! client { r ⇒
20 | r.sync.rpush("list-1", "foo") === (1)
21 | r.sync.rpush("list-1", "bar") === (2)
22 | }
23 | "should throw if the key has a non-list value" ! client { r ⇒
24 | r.set("anshin-1", "debasish")
25 | r.sync.rpush("anshin-1", "bar") must throwA[RedisErrorException]("ERR Operation against a key holding the wrong kind of value")
26 | }
27 | }
28 |
29 | "llen" >> {
30 | "should return the length of the list" ! client { r ⇒
31 | r.sync.lpush("list-1", "foo") === (1)
32 | r.sync.lpush("list-1", "bar") === (2)
33 | r.sync.llen("list-1") === (2)
34 | }
35 | "should return 0 for a non-existent key" ! client { r ⇒
36 | r.sync.llen("list-2") === (0)
37 | }
38 | "should throw for a non-list key" ! client { r ⇒
39 | r.set("anshin-1", "debasish")
40 | r.sync.llen("anshin-1") must throwA[RedisErrorException]("ERR Operation against a key holding the wrong kind of value")
41 | }
42 | }
43 |
44 | "lrange" >> {
45 | "should return the range" ! client { r ⇒
46 | r.sync.lpush("list-1", "6") === (1)
47 | r.sync.lpush("list-1", "5") === (2)
48 | r.sync.lpush("list-1", "4") === (3)
49 | r.sync.lpush("list-1", "3") === (4)
50 | r.sync.lpush("list-1", "2") === (5)
51 | r.sync.lpush("list-1", "1") === (6)
52 | r.sync.llen("list-1") === (6)
53 | r.sync.lrange("list-1", 0, 4).parse[String] === (Some(List("1", "2", "3", "4", "5")))
54 | }
55 | "should return empty list if start > end" ! client { r ⇒
56 | r.sync.lpush("list-1", "3") === (1)
57 | r.sync.lpush("list-1", "2") === (2)
58 | r.sync.lpush("list-1", "1") === (3)
59 | r.sync.lrange("list-1", 2, 0).parse[String] === (Some(Nil))
60 | }
61 | "should treat as end of list if end is over the actual end of list" ! client { r ⇒
62 | r.sync.lpush("list-1", "3") === (1)
63 | r.sync.lpush("list-1", "2") === (2)
64 | r.sync.lpush("list-1", "1") === (3)
65 | r.sync.lrange("list-1", 0, 7).parse[String] === (Some(List("1", "2", "3")))
66 | }
67 | }
68 |
69 | "ltrim" >> {
70 | "should trim to the input size" ! client { r ⇒
71 | r.sync.lpush("list-1", "6") === (1)
72 | r.sync.lpush("list-1", "5") === (2)
73 | r.sync.lpush("list-1", "4") === (3)
74 | r.sync.lpush("list-1", "3") === (4)
75 | r.sync.lpush("list-1", "2") === (5)
76 | r.sync.lpush("list-1", "1") === (6)
77 | r.ltrim("list-1", 0, 3)
78 | r.sync.llen("list-1") === (4)
79 | }
80 | "should should return empty list for start > end" ! client { r ⇒
81 | r.sync.lpush("list-1", "6") === (1)
82 | r.sync.lpush("list-1", "5") === (2)
83 | r.sync.lpush("list-1", "4") === (3)
84 | r.ltrim("list-1", 6, 3)
85 | r.sync.llen("list-1") === (0)
86 | }
87 | "should treat as end of list if end is over the actual end of list" ! client { r ⇒
88 | r.sync.lpush("list-1", "6") === (1)
89 | r.sync.lpush("list-1", "5") === (2)
90 | r.sync.lpush("list-1", "4") === (3)
91 | r.ltrim("list-1", 0, 12)
92 | r.sync.llen("list-1") === (3)
93 | }
94 | }
95 |
96 | "lindex" >> {
97 | "should return the value at index" ! client { r ⇒
98 | r.sync.lpush("list-1", "6") === (1)
99 | r.sync.lpush("list-1", "5") === (2)
100 | r.sync.lpush("list-1", "4") === (3)
101 | r.sync.lpush("list-1", "3") === (4)
102 | r.sync.lpush("list-1", "2") === (5)
103 | r.sync.lpush("list-1", "1") === (6)
104 | r.sync.lindex("list-1", 2).parse[String] === (Some("3"))
105 | r.sync.lindex("list-1", 3).parse[String] === (Some("4"))
106 | r.sync.lindex("list-1", -1).parse[String] === (Some("6"))
107 | }
108 | "should return None if the key does not point to a list" ! client { r ⇒
109 | r.set("anshin-1", "debasish")
110 | r.sync.lindex("list-1", 0).parse[String] === (None)
111 | }
112 | "should return empty string for an index out of range" ! client { r ⇒
113 | r.sync.lpush("list-1", "6") === (1)
114 | r.sync.lpush("list-1", "5") === (2)
115 | r.sync.lpush("list-1", "4") === (3)
116 | r.sync.lindex("list-1", 8).parse[String] === (None) // the protocol says it will return empty string
117 | }
118 | }
119 |
120 | "lset" >> {
121 | "should set value for key at index" ! client { r ⇒
122 | r.sync.lpush("list-1", "6") === (1)
123 | r.sync.lpush("list-1", "5") === (2)
124 | r.sync.lpush("list-1", "4") === (3)
125 | r.sync.lpush("list-1", "3") === (4)
126 | r.sync.lpush("list-1", "2") === (5)
127 | r.sync.lpush("list-1", "1") === (6)
128 | r.lset("list-1", 2, "30")
129 | r.sync.lindex("list-1", 2).parse[String] === (Some("30"))
130 | }
131 | "should generate error for out of range index" ! client { r ⇒
132 | r.sync.lpush("list-1", "6") === (1)
133 | r.sync.lpush("list-1", "5") === (2)
134 | r.sync.lpush("list-1", "4") === (3)
135 | r.sync.lset("list-1", 12, "30") must throwA[RedisErrorException]("ERR index out of range")
136 | }
137 | }
138 |
139 | "lrem" >> {
140 | "should remove count elements matching value from beginning" ! client { r ⇒
141 | r.sync.lpush("list-1", "6") === (1)
142 | r.sync.lpush("list-1", "hello") === (2)
143 | r.sync.lpush("list-1", "4") === (3)
144 | r.sync.lpush("list-1", "hello") === (4)
145 | r.sync.lpush("list-1", "hello") === (5)
146 | r.sync.lpush("list-1", "hello") === (6)
147 | r.sync.lrem("list-1", "hello", 2) === (2)
148 | r.sync.llen("list-1") === (4)
149 | }
150 | "should remove all elements matching value from beginning" ! client { r ⇒
151 | r.sync.lpush("list-1", "6") === (1)
152 | r.sync.lpush("list-1", "hello") === (2)
153 | r.sync.lpush("list-1", "4") === (3)
154 | r.sync.lpush("list-1", "hello") === (4)
155 | r.sync.lpush("list-1", "hello") === (5)
156 | r.sync.lpush("list-1", "hello") === (6)
157 | r.sync.lrem("list-1", "hello") === (4)
158 | r.sync.llen("list-1") === (2)
159 | }
160 | "should remove count elements matching value from end" ! client { r ⇒
161 | r.sync.lpush("list-1", "6") === (1)
162 | r.sync.lpush("list-1", "hello") === (2)
163 | r.sync.lpush("list-1", "4") === (3)
164 | r.sync.lpush("list-1", "hello") === (4)
165 | r.sync.lpush("list-1", "hello") === (5)
166 | r.sync.lpush("list-1", "hello") === (6)
167 | r.sync.lrem("list-1", "hello", -2) === (2)
168 | r.sync.llen("list-1") === (4)
169 | r.sync.lindex("list-1", -2).parse[String] === (Some("4"))
170 | }
171 | }
172 |
173 | "lpop" >> {
174 | "should pop the first one from head" ! client { r ⇒
175 | r.sync.lpush("list-1", "6") === (1)
176 | r.sync.lpush("list-1", "5") === (2)
177 | r.sync.lpush("list-1", "4") === (3)
178 | r.sync.lpush("list-1", "3") === (4)
179 | r.sync.lpush("list-1", "2") === (5)
180 | r.sync.lpush("list-1", "1") === (6)
181 | r.sync.lpop("list-1").parse[String] === (Some("1"))
182 | r.sync.lpop("list-1").parse[String] === (Some("2"))
183 | r.sync.lpop("list-1").parse[String] === (Some("3"))
184 | r.sync.llen("list-1") === (3)
185 | }
186 | "should give nil for non-existent key" ! client { r ⇒
187 | r.sync.lpush("list-1", "6") === (1)
188 | r.sync.lpush("list-1", "5") === (2)
189 | r.sync.lpop("list-2").parse[String] === (None)
190 | r.sync.llen("list-1") === (2)
191 | }
192 | }
193 |
194 | "rpop" >> {
195 | "should pop the first one from tail" ! client { r ⇒
196 | r.sync.lpush("list-1", "6") === (1)
197 | r.sync.lpush("list-1", "5") === (2)
198 | r.sync.lpush("list-1", "4") === (3)
199 | r.sync.lpush("list-1", "3") === (4)
200 | r.sync.lpush("list-1", "2") === (5)
201 | r.sync.lpush("list-1", "1") === (6)
202 | r.sync.rpop("list-1").parse[String] === (Some("6"))
203 | r.sync.rpop("list-1").parse[String] === (Some("5"))
204 | r.sync.rpop("list-1").parse[String] === (Some("4"))
205 | r.sync.llen("list-1") === (3)
206 | }
207 | "should give nil for non-existent key" ! client { r ⇒
208 | r.sync.lpush("list-1", "6") === (1)
209 | r.sync.lpush("list-1", "5") === (2)
210 | r.sync.rpop("list-2").parse[String] === (None)
211 | r.sync.llen("list-1") === (2)
212 | }
213 | }
214 |
215 | "rpoplpush" >> {
216 | "should do" ! client { r ⇒
217 | r.sync.rpush("list-1", "a") === (1)
218 | r.sync.rpush("list-1", "b") === (2)
219 | r.sync.rpush("list-1", "c") === (3)
220 | r.sync.rpush("list-2", "foo") === (1)
221 | r.sync.rpush("list-2", "bar") === (2)
222 | r.sync.rpoplpush("list-1", "list-2").parse[String] === (Some("c"))
223 | r.sync.lindex("list-2", 0).parse[String] === (Some("c"))
224 | r.sync.llen("list-1") === (2)
225 | r.sync.llen("list-2") === (3)
226 | }
227 |
228 | "should rotate the list when src and dest are the same" ! client { r ⇒
229 | r.sync.rpush("list-1", "a") === (1)
230 | r.sync.rpush("list-1", "b") === (2)
231 | r.sync.rpush("list-1", "c") === (3)
232 | r.sync.rpoplpush("list-1", "list-1").parse[String] === (Some("c"))
233 | r.sync.lindex("list-1", 0).parse[String] === (Some("c"))
234 | r.sync.lindex("list-1", 2).parse[String] === (Some("b"))
235 | r.sync.llen("list-1") === (3)
236 | }
237 |
238 | "should give None for non-existent key" ! client { r ⇒
239 | r.sync.rpoplpush("list-1", "list-2").parse[String] === (None)
240 | r.sync.rpush("list-1", "a") === (1)
241 | r.sync.rpush("list-1", "b") === (2)
242 | r.sync.rpoplpush("list-1", "list-2").parse[String] === (Some("b"))
243 | }
244 | }
245 |
246 | "lpush with newlines in strings" >> {
247 | "should add to the head of the list" ! client { r ⇒
248 | r.sync.lpush("list-1", "foo\nbar\nbaz") === (1)
249 | r.sync.lpush("list-1", "bar\nfoo\nbaz") === (2)
250 | r.sync.lpop("list-1").parse[String] === (Some("bar\nfoo\nbaz"))
251 | r.sync.lpop("list-1").parse[String] === (Some("foo\nbar\nbaz"))
252 | }
253 | }
254 |
255 | }
256 |
257 |
--------------------------------------------------------------------------------
/src/test/scala/net/fyrie/redis/PubSubSpec.scala:
--------------------------------------------------------------------------------
1 | /* package net.fyrie.redis
2 |
3 | import org.specs2._
4 | import execute._
5 | import specification._
6 |
7 | import akka.dispatch.{ Future, PromiseStream }
8 | import akka.testkit.{ filterEvents, EventFilter }
9 | import akka.actor.{ Actor, ActorRef }
10 | import pubsub._
11 | import serialization.Parse
12 |
13 | class PubSubSpec extends Specification with PubSubHelper {
14 |
15 | def is = args(sequential = true) ^
16 | p ^
17 | "A PubSub Subscriber" ^
18 | "subscribing to channels and a pattern" ! {
19 | pubsub.subscribe("Test Channel", "Test Channel 2") and pubsub.psubscribe("Test*")
20 | } ^
21 | "should be notified of subscription" ! {
22 | pubsub.get("[Test Channel] Subscribed", "[Test Channel 2] Subscribed", "[Test*] Subscribed")
23 | } ^
24 | "and when messages are published" ! {
25 | pubsub.publish("Test Channel", "Hello!") and pubsub.publish("Test Channel 3", "World!")
26 | } ^
27 | "they should be received by the subscriber." ! {
28 | pubsub.get("[Test Channel] Message: Hello!", "[Test*][Test Channel] Message: Hello!", "[Test*][Test Channel 3] Message: World!")
29 | } ^
30 | pubsub.stop ^
31 | end
32 |
33 | }
34 |
35 | trait PubSubHelper { self: Specification ⇒
36 | val pubsub = new {
37 | implicit val system = TestSystem.system
38 | val redis = RedisClient()
39 | val ps = PromiseStream[String]()
40 | val sub = system.actorOf(new TestSubscriber(redis, ps))
41 |
42 | def get(msgs: String*) = ((success: Result) /: msgs)((r, m) ⇒ r and (ps.dequeue.get must_== m))
43 |
44 | def subscribe(channels: String*) = {
45 | sub ! Subscribe(channels)
46 | success
47 | }
48 |
49 | def psubscribe(patterns: String*) = {
50 | sub ! PSubscribe(patterns)
51 | success
52 | }
53 |
54 | def publish(channel: String, msg: String) = {
55 | redis.publish(channel, msg)
56 | success
57 | }
58 |
59 | def stop = Step {
60 | sub.stop
61 | redis.disconnect
62 | }
63 |
64 | }
65 | }
66 |
67 | class TestSubscriber(redisClient: RedisClient, ps: PromiseStream[String]) extends Actor {
68 | var client: ActorRef = _
69 | override def preStart {
70 | client = redisClient.subscriber(self)
71 | }
72 |
73 | def receive = {
74 | case msg: Subscribe ⇒ client ! msg
75 | case msg: Unsubscribe ⇒ client ! msg
76 | case msg: PSubscribe ⇒ client ! msg
77 | case msg: PUnsubscribe ⇒ client ! msg
78 | case Subscribed(channel, count) ⇒ ps enqueue ("[" + Parse[String](channel) + "] Subscribed")
79 | case Unsubscribed(channel, count) ⇒ ps enqueue ("[" + Parse[String](channel) + "] Unsubscribed")
80 | case Message(channel, bytes) ⇒ ps enqueue ("[" + Parse[String](channel) + "] Message: " + Parse[String](bytes))
81 | case PSubscribed(pattern, count) ⇒ ps enqueue ("[" + Parse[String](pattern) + "] Subscribed")
82 | case PUnsubscribed(pattern, count) ⇒ ps enqueue ("[" + Parse[String](pattern) + "] Unsubscribed")
83 | case PMessage(pattern, channel, bytes) ⇒ ps enqueue ("[" + Parse[String](pattern) + "][" + Parse[String](channel) + "] Message: " + Parse[String](bytes))
84 | }
85 | }
86 |
87 | */
88 |
--------------------------------------------------------------------------------
/src/test/scala/net/fyrie/redis/ScriptsSpec.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import org.specs2._
4 |
5 | import net.fyrie.redis.types._
6 | import net.fyrie.redis.serialization._
7 |
8 | class ScriptsSpec extends mutable.Specification with UnstableClient {
9 |
10 | "eval" >> {
11 | "should eval simple script returning a number" ! client { r ⇒
12 | r.sync.eval("return ARGV[1] + ARGV[2]", args = List(4, 9)) === RedisInteger(13)
13 | }
14 | }
15 |
16 | "lua dsl" >> {
17 | "should produce valid lua" ! client { r ⇒
18 | import lua._
19 |
20 | val v1 = Var("v1")
21 | val v2 = Var("v2")
22 | val v3 = Var("v3")
23 |
24 | val script = {
25 | (v1 := 34) ::
26 | Do {
27 | (v2 := true) ::
28 | End
29 | } ::
30 | (v3 := "hello") ::
31 | Return(Table(v1, v2, v3))
32 | }
33 |
34 | script.bytes.utf8String === "v1 = 34.0; do v2 = true; end; v3 = \"hello\"; return {v1, v2, v3}"
35 | r.sync.eval(script) === RedisMulti(Some(List(RedisInteger(34), RedisInteger(1), RedisBulk(Some(Store("hello"))))))
36 | }
37 | "should provide If/Then/Else statement" ! client { r ⇒
38 | import lua._
39 |
40 | def script(x: Exp) = {
41 | val v = Var("result")
42 |
43 | {
44 | If(x :== "a") Then {
45 | (v := 1) :: End
46 | } ElseIf (x :== "b") Then {
47 | (v := 6) :: End
48 | } ElseIf (x :== "c") Then {
49 | (v := 3) :: End
50 | } ElseIf (x :== "d") Then {
51 | (v := 8) :: End
52 | } Else {
53 | (v := "I don't know!") :: End
54 | }
55 | } :: Return(v)
56 | }
57 |
58 | r.sync.eval(script("a")) === RedisInteger(1)
59 | r.sync.eval(script("b")) === RedisInteger(6)
60 | r.sync.eval(script("c")) === RedisInteger(3)
61 | r.sync.eval(script("d")) === RedisInteger(8)
62 | r.sync.eval(script("e")) === RedisBulk(Some(Store("I don't know!")))
63 | }
64 | "should produce valid arithmetic expressions" ! client { r ⇒
65 | import lua._
66 |
67 | val n = Var("n")
68 |
69 | (n :+ 3 :* n :- 5).bytes.utf8String === "n + 3.0 * n - 5.0"
70 | (Exp(n) :+ 3 :* n :- 5).bytes.utf8String === "n + 3.0 * n - 5.0"
71 | (n :+ 3 :* (n :- 5)).bytes.utf8String === "n + 3.0 * (n - 5.0)"
72 | (Exp(n :+ 3) :* (n :- 5)).bytes.utf8String === "(n + 3.0) * (n - 5.0)"
73 | }
74 | }
75 |
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/src/test/scala/net/fyrie/redis/SetSpec.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import org.specs2._
4 |
5 | class SetSpec extends mutable.Specification with TestClient {
6 |
7 | "sadd" >> {
8 | "should add a non-existent value to the set" ! client { r ⇒
9 | r.sync.sadd("set-1", "foo") === true
10 | r.sync.sadd("set-1", "bar") === true
11 | }
12 | "should not add an existing value to the set" ! client { r ⇒
13 | r.sync.sadd("set-1", "foo") === true
14 | r.sync.sadd("set-1", "foo") === false
15 | }
16 | "should fail if the key points to a non-set" ! client { r ⇒
17 | r.sync.lpush("list-1", "foo") === (1)
18 | r.sync.sadd("list-1", "foo") must throwA[RedisErrorException]("ERR Operation against a key holding the wrong kind of value")
19 | }
20 | }
21 |
22 | "srem" >> {
23 | "should remove a value from the set" ! client { r ⇒
24 | r.sync.sadd("set-1", "foo") === true
25 | r.sync.sadd("set-1", "bar") === true
26 | r.sync.srem("set-1", "bar") === true
27 | r.sync.srem("set-1", "foo") === true
28 | }
29 | "should not do anything if the value does not exist" ! client { r ⇒
30 | r.sync.sadd("set-1", "foo") === true
31 | r.sync.srem("set-1", "bar") === false
32 | }
33 | "should fail if the key points to a non-set" ! client { r ⇒
34 | r.sync.lpush("list-1", "foo") === 1
35 | r.sync.srem("list-1", "foo") must throwA[RedisErrorException]("ERR Operation against a key holding the wrong kind of value")
36 | }
37 | }
38 |
39 | "spop" >> {
40 | "should pop a random element" ! client { r ⇒
41 | r.sync.sadd("set-1", "foo") === true
42 | r.sync.sadd("set-1", "bar") === true
43 | r.sync.sadd("set-1", "baz") === true
44 | r.sync.spop("set-1").parse[String] must beOneOf(Some("foo"), Some("bar"), Some("baz"))
45 | }
46 | "should return nil if the key does not exist" ! client { r ⇒
47 | r.sync.spop("set-1") === (None)
48 | }
49 | }
50 |
51 | "smove" >> {
52 | "should move from one set to another" ! client { r ⇒
53 | r.sync.sadd("set-1", "foo") === true
54 | r.sync.sadd("set-1", "bar") === true
55 | r.sync.sadd("set-1", "baz") === true
56 |
57 | r.sync.sadd("set-2", "1") === true
58 | r.sync.sadd("set-2", "2") === true
59 |
60 | r.sync.smove("set-1", "set-2", "baz") === true
61 | r.sync.sadd("set-2", "baz") === false
62 | r.sync.sadd("set-1", "baz") === true
63 | }
64 | "should return 0 if the element does not exist in source set" ! client { r ⇒
65 | r.sync.sadd("set-1", "foo") === true
66 | r.sync.sadd("set-1", "bar") === true
67 | r.sync.sadd("set-1", "baz") === true
68 | r.sync.smove("set-1", "set-2", "bat") === false
69 | r.sync.smove("set-3", "set-2", "bat") === false
70 | }
71 | "should give error if the source or destination key is not a set" ! client { r ⇒
72 | r.sync.lpush("list-1", "foo") === (1)
73 | r.sync.lpush("list-1", "bar") === (2)
74 | r.sync.lpush("list-1", "baz") === (3)
75 | r.sync.sadd("set-1", "foo") === true
76 | r.sync.smove("list-1", "set-1", "bat") must throwA[RedisErrorException]("ERR Operation against a key holding the wrong kind of value")
77 | }
78 | }
79 |
80 | "scard" >> {
81 | "should return cardinality" ! client { r ⇒
82 | r.sync.sadd("set-1", "foo") === true
83 | r.sync.sadd("set-1", "bar") === true
84 | r.sync.sadd("set-1", "baz") === true
85 | r.sync.scard("set-1") === (3)
86 | }
87 | "should return 0 if key does not exist" ! client { r ⇒
88 | r.sync.scard("set-1") === (0)
89 | }
90 | }
91 |
92 | "sismember" >> {
93 | "should return true for membership" ! client { r ⇒
94 | r.sync.sadd("set-1", "foo") === true
95 | r.sync.sadd("set-1", "bar") === true
96 | r.sync.sadd("set-1", "baz") === true
97 | r.sync.sismember("set-1", "foo") === true
98 | }
99 | "should return false for no membership" ! client { r ⇒
100 | r.sync.sadd("set-1", "foo") === true
101 | r.sync.sadd("set-1", "bar") === true
102 | r.sync.sadd("set-1", "baz") === true
103 | r.sync.sismember("set-1", "fo") === false
104 | }
105 | "should return false if key does not exist" ! client { r ⇒
106 | r.sync.sismember("set-1", "fo") === false
107 | }
108 | }
109 |
110 | "sinter" >> {
111 | "should return intersection" ! client { r ⇒
112 | r.sync.sadd("set-1", "foo") === true
113 | r.sync.sadd("set-1", "bar") === true
114 | r.sync.sadd("set-1", "baz") === true
115 |
116 | r.sync.sadd("set-2", "foo") === true
117 | r.sync.sadd("set-2", "bat") === true
118 | r.sync.sadd("set-2", "baz") === true
119 |
120 | r.sync.sadd("set-3", "for") === true
121 | r.sync.sadd("set-3", "bat") === true
122 | r.sync.sadd("set-3", "bay") === true
123 |
124 | r.sync.sinter(Set("set-1", "set-2")).parse[String] === (Set("foo", "baz"))
125 | r.sync.sinter(Set("set-1", "set-3")).parse[String] === (Set.empty)
126 | }
127 | "should return empty set for non-existing key" ! client { r ⇒
128 | r.sync.sadd("set-1", "foo") === true
129 | r.sync.sadd("set-1", "bar") === true
130 | r.sync.sadd("set-1", "baz") === true
131 | r.sync.sinter(Set("set-1", "set-4")).parse[String] === (Set.empty)
132 | }
133 | }
134 |
135 | "sinterstore" >> {
136 | "should store intersection" ! client { r ⇒
137 | r.sync.sadd("set-1", "foo") === true
138 | r.sync.sadd("set-1", "bar") === true
139 | r.sync.sadd("set-1", "baz") === true
140 |
141 | r.sync.sadd("set-2", "foo") === true
142 | r.sync.sadd("set-2", "bat") === true
143 | r.sync.sadd("set-2", "baz") === true
144 |
145 | r.sync.sadd("set-3", "for") === true
146 | r.sync.sadd("set-3", "bat") === true
147 | r.sync.sadd("set-3", "bay") === true
148 |
149 | r.sync.sinterstore("set-r", Set("set-1", "set-2")) === (2)
150 | r.sync.scard("set-r") === (2)
151 | r.sync.sinterstore("set-s", Set("set-1", "set-3")) === (0)
152 | r.sync.scard("set-s") === (0)
153 | }
154 | "should return empty set for non-existing key" ! client { r ⇒
155 | r.sync.sadd("set-1", "foo") === true
156 | r.sync.sadd("set-1", "bar") === true
157 | r.sync.sadd("set-1", "baz") === true
158 | r.sync.sinterstore("set-r", Seq("set-1", "set-4")) === (0)
159 | r.sync.scard("set-r") === (0)
160 | }
161 | }
162 |
163 | "sunion" >> {
164 | "should return union" ! client { r ⇒
165 | r.sync.sadd("set-1", "foo") === true
166 | r.sync.sadd("set-1", "bar") === true
167 | r.sync.sadd("set-1", "baz") === true
168 |
169 | r.sync.sadd("set-2", "foo") === true
170 | r.sync.sadd("set-2", "bat") === true
171 | r.sync.sadd("set-2", "baz") === true
172 |
173 | r.sync.sadd("set-3", "for") === true
174 | r.sync.sadd("set-3", "bat") === true
175 | r.sync.sadd("set-3", "bay") === true
176 |
177 | r.sync.sunion(Set("set-1", "set-2")).parse[String] === (Set("foo", "bar", "baz", "bat"))
178 | r.sync.sunion(Set("set-1", "set-3")).parse[String] === (Set("foo", "bar", "baz", "for", "bat", "bay"))
179 | }
180 | "should return empty set for non-existing key" ! client { r ⇒
181 | r.sync.sadd("set-1", "foo") === true
182 | r.sync.sadd("set-1", "bar") === true
183 | r.sync.sadd("set-1", "baz") === true
184 | r.sync.sunion(Seq("set-1", "set-2")).parse[String] === (Set("foo", "bar", "baz"))
185 | }
186 | }
187 |
188 | "sunionstore" >> {
189 | "should store union" ! client { r ⇒
190 | r.sync.sadd("set-1", "foo") === true
191 | r.sync.sadd("set-1", "bar") === true
192 | r.sync.sadd("set-1", "baz") === true
193 |
194 | r.sync.sadd("set-2", "foo") === true
195 | r.sync.sadd("set-2", "bat") === true
196 | r.sync.sadd("set-2", "baz") === true
197 |
198 | r.sync.sadd("set-3", "for") === true
199 | r.sync.sadd("set-3", "bat") === true
200 | r.sync.sadd("set-3", "bay") === true
201 |
202 | r.sync.sunionstore("set-r", Set("set-1", "set-2")) === (4)
203 | r.sync.scard("set-r") === (4)
204 | r.sync.sunionstore("set-s", Set("set-1", "set-3")) === (6)
205 | r.sync.scard("set-s") === (6)
206 | }
207 | "should treat non-existing keys as empty sets" ! client { r ⇒
208 | r.sync.sadd("set-1", "foo") === true
209 | r.sync.sadd("set-1", "bar") === true
210 | r.sync.sadd("set-1", "baz") === true
211 | r.sync.sunionstore("set-r", Set("set-1", "set-4")) === (3)
212 | r.sync.scard("set-r") === (3)
213 | }
214 | }
215 |
216 | "sdiff" >> {
217 | "should return diff" ! client { r ⇒
218 | r.sync.sadd("set-1", "foo") === true
219 | r.sync.sadd("set-1", "bar") === true
220 | r.sync.sadd("set-1", "baz") === true
221 |
222 | r.sync.sadd("set-2", "foo") === true
223 | r.sync.sadd("set-2", "bat") === true
224 | r.sync.sadd("set-2", "baz") === true
225 |
226 | r.sync.sadd("set-3", "for") === true
227 | r.sync.sadd("set-3", "bat") === true
228 | r.sync.sadd("set-3", "bay") === true
229 |
230 | r.sync.sdiff("set-1", Set("set-2", "set-3")).parse[String] === (Set("bar"))
231 | }
232 | "should treat non-existing keys as empty sets" ! client { r ⇒
233 | r.sync.sadd("set-1", "foo") === true
234 | r.sync.sadd("set-1", "bar") === true
235 | r.sync.sadd("set-1", "baz") === true
236 | r.sync.sdiff("set-1", Set("set-2")).parse[String] === (Set("foo", "bar", "baz"))
237 | }
238 | }
239 |
240 | "smembers" >> {
241 | "should return members of a set" ! client { r ⇒
242 | r.sync.sadd("set-1", "foo") === true
243 | r.sync.sadd("set-1", "bar") === true
244 | r.sync.sadd("set-1", "baz") === true
245 | r.sync.smembers("set-1").parse[String] === (Set("foo", "bar", "baz"))
246 | }
247 | "should return None for an empty set" ! client { r ⇒
248 | r.sync.smembers("set-1").parse[String] === (Set.empty)
249 | }
250 | }
251 |
252 | "srandmember" >> {
253 | "should return a random member" ! client { r ⇒
254 | r.sync.sadd("set-1", "foo") === true
255 | r.sync.sadd("set-1", "bar") === true
256 | r.sync.sadd("set-1", "baz") === true
257 | r.sync.srandmember("set-1").parse[String] must beOneOf(Some("foo"), Some("bar"), Some("baz"))
258 | }
259 | "should return None for a non-existing key" ! client { r ⇒
260 | r.sync.srandmember("set-1").parse[String] === (None)
261 | }
262 | }
263 | }
264 |
265 |
--------------------------------------------------------------------------------
/src/test/scala/net/fyrie/redis/SortedSetSpec.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import org.specs2._
4 |
5 | class SortedSetSpec extends mutable.Specification with TestClient {
6 |
7 | private def add(r: RedisClient) = {
8 | r.sync.zadd("hackers", "yukihiro matsumoto", 1965) === (true)
9 | r.sync.zadd("hackers", "richard stallman", 1953) === (true)
10 | r.sync.zadd("hackers", "claude shannon", 1916) === (true)
11 | r.sync.zadd("hackers", "linus torvalds", 1969) === (true)
12 | r.sync.zadd("hackers", "alan kay", 1940) === (true)
13 | r.sync.zadd("hackers", "alan turing", 1912) === (true)
14 | }
15 |
16 | "zadd" >> {
17 | "should add based on proper sorted set semantics" ! client { r ⇒
18 | add(r)
19 | r.sync.zadd("hackers", "alan turing", 1912) === (false)
20 | r.sync.zcard("hackers") === (6)
21 | }
22 | }
23 |
24 | "zrange" >> {
25 | "should get the proper range" ! client { r ⇒
26 | add(r)
27 | r.sync.zrange("hackers", 0, -1) must have size (6)
28 | r.sync.zrangeWithScores("hackers", 0, -1) must have size (6)
29 | }
30 | }
31 |
32 | "zrank" >> {
33 | "should give proper rank" ! client { r ⇒
34 | add(r)
35 | r.sync.zrank("hackers", "yukihiro matsumoto") === (Some(4))
36 | r.sync.zrevrank("hackers", "yukihiro matsumoto") === (Some(1))
37 | }
38 | }
39 |
40 | "zremrangebyrank" >> {
41 | "should remove based on rank range" ! client { r ⇒
42 | add(r)
43 | r.sync.zremrangebyrank("hackers", 0, 2) === (3)
44 | }
45 | }
46 |
47 | "zremrangebyscore" >> {
48 | "should remove based on score range" ! client { r ⇒
49 | add(r)
50 | r.sync.zremrangebyscore("hackers", 1912, 1940) === (3)
51 | r.sync.zremrangebyscore("hackers", 0, 3) === (0)
52 | }
53 | }
54 |
55 | "zunion" >> {
56 | "should do a union" ! client { r ⇒
57 | r.sync.zadd("hackers 1", "yukihiro matsumoto", 1965) === (true)
58 | r.sync.zadd("hackers 1", "richard stallman", 1953) === (true)
59 | r.sync.zadd("hackers 2", "claude shannon", 1916) === (true)
60 | r.sync.zadd("hackers 2", "linus torvalds", 1969) === (true)
61 | r.sync.zadd("hackers 3", "alan kay", 1940) === (true)
62 | r.sync.zadd("hackers 4", "alan turing", 1912) === (true)
63 |
64 | // union with weight = 1
65 | r.sync.zunionstore("hackers", Set("hackers 1", "hackers 2", "hackers 3", "hackers 4")) === (6)
66 | r.sync.zcard("hackers") === (6)
67 |
68 | r.sync.zrangeWithScores("hackers").parse[String] === (List(("alan turing", 1912), ("claude shannon", 1916), ("alan kay", 1940), ("richard stallman", 1953), ("yukihiro matsumoto", 1965), ("linus torvalds", 1969)))
69 |
70 | // union with modified weights
71 | r.sync.zunionstoreWeighted("hackers weighted", Map(("hackers 1", 1), ("hackers 2", 2), ("hackers 3", 3), ("hackers 4", 4))) === (6)
72 | r.sync.zrangeWithScores("hackers weighted").parse[String] map (_._2) must_== (List(1953, 1965, 3832, 3938, 5820, 7648))
73 | }
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/src/test/scala/net/fyrie/redis/StringSpec.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import org.specs2._
4 |
5 | class StringSpec extends mutable.Specification with TestClient {
6 |
7 | "set" >> {
8 | "should set key/value pairs" ! client { r ⇒
9 | for {
10 | a ← r.set("anshin-1", "debasish")
11 | b ← r.set("anshin-2", "maulindu")
12 | } yield {
13 | a === ()
14 | b === ()
15 | }
16 | }
17 | }
18 |
19 | "get" >> {
20 | "should retrieve key/value pairs for existing keys" ! client { r ⇒
21 | r.quiet.set("anshin-1", "debasish")
22 | r.get("anshin-1").parse[String] map (_ === Some("debasish"))
23 | }
24 | "should fail for non-existent keys" ! client { r ⇒
25 | r.get("anshin-2").parse[String] map (_ === None)
26 | }
27 | }
28 |
29 | "getset" >> {
30 | "should set new values and return old values" ! client { r ⇒
31 | for {
32 | _ ← r.set("anshin-1", "debasish")
33 | a ← r.get("anshin-1").parse[String]
34 | b ← r.getset("anshin-1", "maulindu").parse[String]
35 | c ← r.get("anshin-1").parse[String]
36 | } yield {
37 | a === Some("debasish")
38 | b === Some("debasish")
39 | c === Some("maulindu")
40 | }
41 | }
42 | }
43 |
44 | "setnx" >> {
45 | "should set only if the key does not exist" ! client { r ⇒
46 | r.set("anshin-1", "debasish")
47 | r.sync.setnx("anshin-1", "maulindu") === (false)
48 | r.sync.setnx("anshin-2", "maulindu") === (true)
49 | }
50 | }
51 |
52 | "incr" >> {
53 | "should increment by 1 for a key that contains a number" ! client { r ⇒
54 | r.set("anshin-1", "10")
55 | r.sync.incr("anshin-1") === (11)
56 | }
57 | "should reset to 0 and then increment by 1 for a key that contains a diff type" ! client { r ⇒
58 | r.set("anshin-2", "debasish")
59 | r.sync.incr("anshin-2") must throwA[RedisErrorException].like {
60 | case e ⇒ e.getMessage must startWith("ERR value is not an integer")
61 | }
62 | }
63 | "should increment by 5 for a key that contains a number" ! client { r ⇒
64 | r.set("anshin-3", "10")
65 | r.sync.incrby("anshin-3", 5) === (15)
66 | }
67 | "should reset to 0 and then increment by 5 for a key that contains a diff type" ! client { r ⇒
68 | r.set("anshin-4", "debasish")
69 | r.sync.incrby("anshin-4", 5) must throwA[RedisErrorException].like {
70 | case e ⇒ e.getMessage must startWith("ERR value is not an integer")
71 | }
72 | }
73 | }
74 |
75 | "decr" >> {
76 | "should decrement by 1 for a key that contains a number" ! client { r ⇒
77 | r.set("anshin-1", "10")
78 | r.sync.decr("anshin-1") === (9)
79 | }
80 | "should reset to 0 and then decrement by 1 for a key that contains a diff type" ! client { r ⇒
81 | r.set("anshin-2", "debasish")
82 | r.sync.decr("anshin-2") must throwA[RedisErrorException].like {
83 | case e ⇒ e.getMessage must startWith("ERR value is not an integer")
84 | }
85 | }
86 | "should decrement by 5 for a key that contains a number" ! client { r ⇒
87 | r.set("anshin-3", "10")
88 | r.sync.decrby("anshin-3", 5) === (5)
89 | }
90 | "should reset to 0 and then decrement by 5 for a key that contains a diff type" ! client { r ⇒
91 | r.set("anshin-4", "debasish")
92 | r.sync.decrby("anshin-4", 5) must throwA[RedisErrorException].like {
93 | case e ⇒ e.getMessage must startWith("ERR value is not an integer")
94 | }
95 | }
96 | }
97 |
98 | "mget" >> {
99 | "should get values for existing keys" ! client { r ⇒
100 | r.set("anshin-1", "debasish")
101 | r.set("anshin-2", "maulindu")
102 | r.set("anshin-3", "nilanjan")
103 | r.sync.mget(Seq("anshin-1", "anshin-2", "anshin-3")).parse[String] === (List(Some("debasish"), Some("maulindu"), Some("nilanjan")))
104 | }
105 | "should give None for non-existing keys" ! client { r ⇒
106 | r.set("anshin-1", "debasish")
107 | r.set("anshin-2", "maulindu")
108 | r.sync.mget(Seq("anshin-1", "anshin-2", "anshin-4")).parse[String] === (List(Some("debasish"), Some("maulindu"), None))
109 | }
110 | }
111 |
112 | "mset" >> {
113 | "should set all keys irrespective of whether they exist" ! client { r ⇒
114 | r.sync.mset(Map(
115 | ("anshin-1", "debasish"),
116 | ("anshin-2", "maulindu"),
117 | ("anshin-3", "nilanjan"))) === ()
118 | }
119 |
120 | "should set all keys only if none of them exist" ! client { r ⇒
121 | r.sync.msetnx(Map(
122 | ("anshin-4", "debasish"),
123 | ("anshin-5", "maulindu"),
124 | ("anshin-6", "nilanjan"))) === (true)
125 | r.sync.msetnx(Map(
126 | ("anshin-7", "debasish"),
127 | ("anshin-8", "maulindu"),
128 | ("anshin-6", "nilanjan"))) === (false)
129 | r.sync.msetnx(Map(
130 | ("anshin-4", "debasish"),
131 | ("anshin-5", "maulindu"),
132 | ("anshin-6", "nilanjan"))) === (false)
133 | }
134 | }
135 |
136 | "get with spaces in keys" >> {
137 | "should retrieve key/value pairs for existing keys" ! client { r ⇒
138 | r.set("anshin software", "debasish ghosh")
139 | r.sync.get("anshin software").parse[String] === (Some("debasish ghosh"))
140 |
141 | r.set("test key with spaces", "I am a value with spaces")
142 | r.sync.get("test key with spaces").parse[String] === (Some("I am a value with spaces"))
143 | }
144 | }
145 |
146 | "set with newline values" >> {
147 | "should set key/value pairs" ! client { r ⇒
148 | r.sync.set("anshin-x", "debasish\nghosh\nfather") === ()
149 | r.sync.set("anshin-y", "maulindu\nchatterjee") === ()
150 | }
151 | }
152 |
153 | "get with newline values" >> {
154 | "should retrieve key/value pairs for existing keys" ! client { r ⇒
155 | r.set("anshin-x", "debasish\nghosh\nfather")
156 | r.sync.get("anshin-x").parse[String] === (Some("debasish\nghosh\nfather"))
157 | }
158 | }
159 | }
160 |
161 |
--------------------------------------------------------------------------------
/src/test/scala/net/fyrie/redis/TestServer.scala:
--------------------------------------------------------------------------------
1 | package net.fyrie.redis
2 |
3 | import org.specs2._
4 | import org.specs2.specification._
5 | import org.specs2.execute._
6 | import akka.actor.ActorSystem
7 | import akka.dispatch.{ Future, Await }
8 | import akka.util.Duration
9 |
10 | object TestSystem {
11 | val config = com.typesafe.config.ConfigFactory.parseString("""
12 | akka {
13 | event-handlers = ["akka.testkit.TestEventListener"]
14 | loglevel = "WARNING"
15 | actor {
16 | default-dispatcher {
17 | core-pool-size-min = 4
18 | core-pool-size-factor = 2.0
19 | throughput = 10
20 | }
21 | }
22 | }
23 | """)
24 |
25 | val system = ActorSystem("TestSystem", config)
26 | }
27 |
28 | trait TestClient { self: mutable.Specification ⇒
29 |
30 | implicit def futureResult[T](future: Future[T])(implicit toResult: T ⇒ Result): Result =
31 | Await.result(future map toResult, akka.util.Timeout(1000).duration) //Duration.Inf)
32 |
33 | implicit val system = TestSystem.system
34 |
35 | implicit val arguments = args(sequential = true)
36 |
37 | val config = RedisClientConfig(connections = 1)
38 |
39 | def client = new AroundOutside[RedisClient] {
40 |
41 | val r = RedisClient(config = config)
42 | r.sync.flushall
43 |
44 | def around[T <% Result](t: ⇒ T) = {
45 | val result = t
46 | r.sync.flushall
47 | r.disconnect
48 | result
49 | }
50 |
51 | def outside: RedisClient = r
52 |
53 | }
54 |
55 | }
56 |
57 | trait UnstableClient { self: mutable.Specification ⇒
58 |
59 | implicit val system = TestSystem.system
60 |
61 | implicit val arguments = args(sequential = true)
62 |
63 | def client = new AroundOutside[RedisClient] {
64 |
65 | val r = RedisClient()
66 | r.sync.flushall
67 |
68 | def around[T <% Result](t: ⇒ T) = {
69 | val result: Result = r.sync.info.lines.find(_ startsWith "redis_version").map(_.split(Array(':', '.')).tail.map(_.toInt).toList) match {
70 | case Some(a :: b :: _) if (a > 2) || ((a == 2) && (b > 4)) ⇒ t
71 | case _ ⇒ skipped
72 | }
73 | r.sync.flushall
74 | r.disconnect
75 | result
76 | }
77 |
78 | def outside: RedisClient = r
79 |
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------