@@ -15,6 +15,10 @@ object RedisClient {
15
15
case object MIN extends Aggregate
16
16
case object MAX extends Aggregate
17
17
18
+ sealed trait Mode
19
+ case object SINGLE extends Mode
20
+ case object BATCH extends Mode
21
+
18
22
private def extractDatabaseNumber (connectionUri : java.net.URI ): Int = {
19
23
Option (connectionUri.getPath).map(path =>
20
24
if (path.isEmpty) 0
@@ -24,11 +28,22 @@ object RedisClient {
24
28
}
25
29
}
26
30
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
+
28
37
29
38
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
+ }
32
47
} catch {
33
48
case e : RedisConnectionException =>
34
49
if (disconnect) send(command, args)(result)
@@ -38,15 +53,26 @@ trait Redis extends IO with Protocol {
38
53
else throw e
39
54
}
40
55
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
+ }
44
70
} catch {
45
71
case e : RedisConnectionException =>
46
- if (disconnect) send(command)(result)
72
+ if (disconnect) send(command, submissionMode )(result)
47
73
else throw e
48
74
case e : SocketException =>
49
- if (disconnect) send(command)(result)
75
+ if (disconnect) send(command, submissionMode )(result)
50
76
else throw e
51
77
}
52
78
@@ -57,7 +83,7 @@ trait Redis extends IO with Protocol {
57
83
58
84
}
59
85
60
- trait RedisCommand extends Redis
86
+ abstract class RedisCommand ( batch : Mode ) extends Redis (batch)
61
87
with BaseOperations
62
88
with GeoOperations
63
89
with NodeOperations
@@ -74,7 +100,7 @@ trait RedisCommand extends Redis
74
100
val database : Int = 0
75
101
val secret : Option [Any ] = None
76
102
77
- override def onConnect : Unit = {
103
+ override def onConnect () : Unit = {
78
104
secret.foreach {s =>
79
105
auth(s)
80
106
}
@@ -89,13 +115,12 @@ trait RedisCommand extends Redis
89
115
private def authenticate (): Unit = {
90
116
secret.foreach(auth _)
91
117
}
92
-
93
118
}
94
119
95
-
96
120
class 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 {
99
124
100
125
def this () = this (" localhost" , 6379 )
101
126
def this (connectionUri : java.net.URI ) = this (
@@ -110,18 +135,19 @@ class RedisClient(override val host: String, override val port: Int,
110
135
)
111
136
override def toString : String = host + " :" + String .valueOf(port) + " /" + database
112
137
138
+ // with MULTI/EXEC
113
139
def pipeline (f : PipelineClient => Any ): Option [List [Any ]] = {
114
- send(" MULTI" )(asString) // flush reply stream
140
+ send(" MULTI" , false )(asString) // flush reply stream
115
141
try {
116
142
val pipelineClient = new PipelineClient (this )
117
143
try {
118
144
f(pipelineClient)
119
145
} catch {
120
146
case e : Exception =>
121
- send(" DISCARD" )(asString)
147
+ send(" DISCARD" , false )(asString)
122
148
throw e
123
149
}
124
- send(" EXEC" )(asExec(pipelineClient.handlers ))
150
+ send(" EXEC" , false )(asExec(pipelineClient.responseHandlers ))
125
151
} catch {
126
152
case e : RedisMultiExecException =>
127
153
None
@@ -135,7 +161,7 @@ class RedisClient(override val host: String, override val port: Int,
135
161
/**
136
162
* Redis pipelining API without the transaction semantics. The implementation has a non-blocking
137
163
* 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.
139
165
* If an exception is raised in executing any of the commands, then the corresponding <tt>Promise</tt> holds
140
166
* the exception. Here's a sample usage:
141
167
* <pre>
@@ -179,20 +205,32 @@ class RedisClient(override val host: String, override val port: Int,
179
205
ps
180
206
}
181
207
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 {
183
221
import com .redis .serialization .Parse
184
222
185
- var handlers : Vector [() => Any ] = Vector .empty
223
+ var responseHandlers : Vector [() => Any ] = Vector .empty
186
224
187
225
override def send [A ](command : String , args : Seq [Any ])(result : => A )(implicit format : Format ): A = {
188
226
write(Commands .multiBulk(command.getBytes(" UTF-8" ) +: (args map (format.apply))))
189
- handlers :+= (() => result)
227
+ responseHandlers :+= (() => result)
190
228
receive(singleLineReply).map(Parse .parseDefault)
191
229
null .asInstanceOf [A ] // ugh... gotta find a better way
192
230
}
193
- override def send [A ](command : String )(result : => A ): A = {
231
+ override def send [A ](command : String , submissionMode : Boolean = false )(result : => A ): A = {
194
232
write(Commands .multiBulk(List (command.getBytes(" UTF-8" ))))
195
- handlers :+= (() => result)
233
+ responseHandlers :+= (() => result)
196
234
receive(singleLineReply).map(Parse .parseDefault)
197
235
null .asInstanceOf [A ]
198
236
}
@@ -207,7 +245,7 @@ class RedisClient(override val host: String, override val port: Int,
207
245
override def connected = parent.connected
208
246
override def connect = parent.connect
209
247
override def disconnect = parent.disconnect
210
- override def clearFd = parent.clearFd
248
+ override def clearFd () = parent.clearFd()
211
249
override def write (data : Array [Byte ]) = parent.write(data)
212
250
override def readLine = parent.readLine
213
251
override def readCounted (count : Int ) = parent.readCounted(count)
0 commit comments