@@ -15,6 +15,10 @@ object RedisClient {
1515 case object MIN extends Aggregate
1616 case object MAX extends Aggregate
1717
18+ sealed trait Mode
19+ case object SINGLE extends Mode
20+ case object BATCH extends Mode
21+
1822 private def extractDatabaseNumber (connectionUri : java.net.URI ): Int = {
1923 Option (connectionUri.getPath).map(path =>
2024 if (path.isEmpty) 0
@@ -24,11 +28,22 @@ object RedisClient {
2428 }
2529}
2630
27- trait Redis extends IO with Protocol {
31+ import RedisClient ._
32+ abstract class Redis (batch : Mode ) extends IO with Protocol {
33+ var handlers : Vector [(String , () => Any )] = Vector .empty
34+ var commandBuffer : StringBuffer = new StringBuffer
35+ val crlf = " \r\n "
36+
2837
2938 def send [A ](command : String , args : Seq [Any ])(result : => A )(implicit format : Format ): A = try {
30- write(Commands .multiBulk(command.getBytes(" UTF-8" ) +: (args map (format.apply))))
31- result
39+ if (batch == BATCH ) {
40+ handlers :+= ((command, () => result))
41+ commandBuffer.append((List (command) ++ args.toList).mkString(" " ) ++ crlf)
42+ null .asInstanceOf [A ] // hack
43+ } else {
44+ write(Commands .multiBulk(command.getBytes(" UTF-8" ) +: (args map (format.apply))))
45+ result
46+ }
3247 } catch {
3348 case e : RedisConnectionException =>
3449 if (disconnect) send(command, args)(result)
@@ -38,15 +53,26 @@ trait Redis extends IO with Protocol {
3853 else throw e
3954 }
4055
41- def send [A ](command : String )(result : => A ): A = try {
42- write(Commands .multiBulk(List (command.getBytes(" UTF-8" ))))
43- result
56+ def send [A ](command : String , submissionMode : Boolean = false )(result : => A ): A = try {
57+ if (batch == BATCH ) {
58+ if (! submissionMode) {
59+ handlers :+= ((command, () => result))
60+ commandBuffer.append(command ++ crlf)
61+ null .asInstanceOf [A ]
62+ } else {
63+ write(command.getBytes(" UTF-8" ))
64+ result
65+ }
66+ } else {
67+ write(Commands .multiBulk(List (command.getBytes(" UTF-8" ))))
68+ result
69+ }
4470 } catch {
4571 case e : RedisConnectionException =>
46- if (disconnect) send(command)(result)
72+ if (disconnect) send(command, submissionMode )(result)
4773 else throw e
4874 case e : SocketException =>
49- if (disconnect) send(command)(result)
75+ if (disconnect) send(command, submissionMode )(result)
5076 else throw e
5177 }
5278
@@ -57,7 +83,7 @@ trait Redis extends IO with Protocol {
5783
5884}
5985
60- trait RedisCommand extends Redis
86+ abstract class RedisCommand ( batch : Mode ) extends Redis (batch)
6187 with BaseOperations
6288 with GeoOperations
6389 with NodeOperations
@@ -74,7 +100,7 @@ trait RedisCommand extends Redis
74100 val database : Int = 0
75101 val secret : Option [Any ] = None
76102
77- override def onConnect : Unit = {
103+ override def onConnect () : Unit = {
78104 secret.foreach {s =>
79105 auth(s)
80106 }
@@ -89,13 +115,12 @@ trait RedisCommand extends Redis
89115 private def authenticate (): Unit = {
90116 secret.foreach(auth _)
91117 }
92-
93118}
94119
95-
96120class RedisClient (override val host : String , override val port : Int ,
97- override val database : Int = 0 , override val secret : Option [Any ] = None , override val timeout : Int = 0 , override val sslContext : Option [SSLContext ] = None )
98- extends RedisCommand with PubSub {
121+ override val database : Int = 0 , override val secret : Option [Any ] = None , override val timeout : Int = 0 ,
122+ override val sslContext : Option [SSLContext ] = None , val batch : Mode = RedisClient .SINGLE )
123+ extends RedisCommand (batch) with PubSub {
99124
100125 def this () = this (" localhost" , 6379 )
101126 def this (connectionUri : java.net.URI ) = this (
@@ -110,18 +135,19 @@ class RedisClient(override val host: String, override val port: Int,
110135 )
111136 override def toString : String = host + " :" + String .valueOf(port) + " /" + database
112137
138+ // with MULTI/EXEC
113139 def pipeline (f : PipelineClient => Any ): Option [List [Any ]] = {
114- send(" MULTI" )(asString) // flush reply stream
140+ send(" MULTI" , false )(asString) // flush reply stream
115141 try {
116142 val pipelineClient = new PipelineClient (this )
117143 try {
118144 f(pipelineClient)
119145 } catch {
120146 case e : Exception =>
121- send(" DISCARD" )(asString)
147+ send(" DISCARD" , false )(asString)
122148 throw e
123149 }
124- send(" EXEC" )(asExec(pipelineClient.handlers ))
150+ send(" EXEC" , false )(asExec(pipelineClient.responseHandlers ))
125151 } catch {
126152 case e : RedisMultiExecException =>
127153 None
@@ -135,7 +161,7 @@ class RedisClient(override val host: String, override val port: Int,
135161 /**
136162 * Redis pipelining API without the transaction semantics. The implementation has a non-blocking
137163 * semantics and returns a <tt>List</tt> of <tt>Promise</tt>. The caller may use <tt>Future.firstCompletedOf</tt> to get the
138- * first completed task before all tasks have been completed.
164+ * first completed task before all tasks have been completed. However the commands are submitted one by one and NOT in batch.
139165 * If an exception is raised in executing any of the commands, then the corresponding <tt>Promise</tt> holds
140166 * the exception. Here's a sample usage:
141167 * <pre>
@@ -179,20 +205,32 @@ class RedisClient(override val host: String, override val port: Int,
179205 ps
180206 }
181207
182- class PipelineClient (parent : RedisClient ) extends RedisCommand with PubOperations {
208+ // batched pipelines : all commands submitted in batch
209+ def batchedPipeline (commands : List [() => Any ]): Option [List [Any ]] = {
210+ assert(batch == BATCH )
211+ commands.foreach { command =>
212+ command()
213+ }
214+ val r = send(commandBuffer.toString, true )(Some (handlers.map(_._2).map(_()).toList))
215+ handlers = Vector .empty
216+ commandBuffer.setLength(0 )
217+ r
218+ }
219+
220+ class PipelineClient (parent : RedisClient ) extends RedisCommand (parent.batch) with PubOperations {
183221 import com .redis .serialization .Parse
184222
185- var handlers : Vector [() => Any ] = Vector .empty
223+ var responseHandlers : Vector [() => Any ] = Vector .empty
186224
187225 override def send [A ](command : String , args : Seq [Any ])(result : => A )(implicit format : Format ): A = {
188226 write(Commands .multiBulk(command.getBytes(" UTF-8" ) +: (args map (format.apply))))
189- handlers :+= (() => result)
227+ responseHandlers :+= (() => result)
190228 receive(singleLineReply).map(Parse .parseDefault)
191229 null .asInstanceOf [A ] // ugh... gotta find a better way
192230 }
193- override def send [A ](command : String )(result : => A ): A = {
231+ override def send [A ](command : String , submissionMode : Boolean = false )(result : => A ): A = {
194232 write(Commands .multiBulk(List (command.getBytes(" UTF-8" ))))
195- handlers :+= (() => result)
233+ responseHandlers :+= (() => result)
196234 receive(singleLineReply).map(Parse .parseDefault)
197235 null .asInstanceOf [A ]
198236 }
@@ -207,7 +245,7 @@ class RedisClient(override val host: String, override val port: Int,
207245 override def connected = parent.connected
208246 override def connect = parent.connect
209247 override def disconnect = parent.disconnect
210- override def clearFd = parent.clearFd
248+ override def clearFd () = parent.clearFd()
211249 override def write (data : Array [Byte ]) = parent.write(data)
212250 override def readLine = parent.readLine
213251 override def readCounted (count : Int ) = parent.readCounted(count)
0 commit comments