Skip to content

Commit 83dbcfc

Browse files
debasishgnoahlz
andauthored
fixed bug in batched pipeline (#288)
* fixed bug in batched pipeline * Fixed bug in batched pipeline to handle quotes within strings * Added more test cases in PipelineSpec * scala-redis/issues/286 added test case demontrating that ZADD stores java string values properly. (#291) * Remove commented code Co-authored-by: Noah Zucker <[email protected]>
1 parent 68c6f3f commit 83dbcfc

File tree

7 files changed

+253
-40
lines changed

7 files changed

+253
-40
lines changed

build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ lazy val commonSettings: Seq[Setting[_]] = Seq(
66
organization := "net.debasishg",
77
version := "3.42-SNAPSHOT",
88
scalaVersion := "2.13.6",
9-
crossScalaVersions := Seq("2.12.14", "2.11.12", "2.10.7"),
9+
crossScalaVersions := Seq("2.13.6", "2.12.14", "2.11.12", "2.10.7"),
1010

1111
scalacOptions in Compile ++= Seq( "-unchecked", "-feature", "-language:postfixOps", "-deprecation" ),
1212

src/main/scala/com/redis/BaseOperations.scala

+9-9
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ trait BaseOperations extends BaseApi {
4747
send("KEYS", List(pattern))(asList)
4848

4949
override def time[A](implicit format: Format, parse: Parse[A]): Option[List[Option[A]]] =
50-
send("TIME", false)(asList)
50+
send("TIME")(asList)
5151

5252
@deprecated("use randomkey", "2.8")
5353
def randkey[A](implicit parse: Parse[A]): Option[A] =
54-
send("RANDOMKEY", false)(asBulk)
54+
send("RANDOMKEY")(asBulk)
5555

5656
override def randomkey[A](implicit parse: Parse[A]): Option[A] =
57-
send("RANDOMKEY", false)(asBulk)
57+
send("RANDOMKEY")(asBulk)
5858

5959
override def rename(oldkey: Any, newkey: Any)(implicit format: Format): Boolean =
6060
send("RENAME", List(oldkey, newkey))(asBoolean)
@@ -63,7 +63,7 @@ trait BaseOperations extends BaseApi {
6363
send("RENAMENX", List(oldkey, newkey))(asBoolean)
6464

6565
override def dbsize: Option[Long] =
66-
send("DBSIZE", false)(asLong)
66+
send("DBSIZE")(asLong)
6767

6868
override def exists(key: Any)(implicit format: Format): Boolean =
6969
send("EXISTS", List(key))(asBoolean)
@@ -104,16 +104,16 @@ trait BaseOperations extends BaseApi {
104104
})
105105

106106
override def flushdb: Boolean =
107-
send("FLUSHDB", false)(asBoolean)
107+
send("FLUSHDB")(asBoolean)
108108

109109
override def flushall: Boolean =
110-
send("FLUSHALL", false)(asBoolean)
110+
send("FLUSHALL")(asBoolean)
111111

112112
override def move(key: Any, db: Int)(implicit format: Format): Boolean =
113113
send("MOVE", List(key, db))(asBoolean)
114114

115115
override def quit: Boolean =
116-
send("QUIT", false)(disconnect)
116+
send("QUIT")(disconnect)
117117

118118
override def auth(secret: Any)(implicit format: Format): Boolean =
119119
send("AUTH", List(secret))(asBoolean)
@@ -125,13 +125,13 @@ trait BaseOperations extends BaseApi {
125125
send("SCAN", cursor :: ((x: List[Any]) => if (pattern == "*") x else "match" :: pattern :: x) (if (count == 10) Nil else List("count", count)))(asPair)
126126

127127
override def ping: Option[String] =
128-
send("PING", false)(asString)
128+
send("PING")(asString)
129129

130130
override def watch(key: Any, keys: Any*)(implicit format: Format): Boolean =
131131
send("WATCH", key :: keys.toList)(asBoolean)
132132

133133
override def unwatch(): Boolean =
134-
send("UNWATCH", false)(asBoolean)
134+
send("UNWATCH")(asBoolean)
135135

136136
override def getConfig(key: Any = "*")(implicit format: Format): Option[Map[String, Option[String]]] =
137137
send("CONFIG", List("GET", key))(asList).map { ls =>

src/main/scala/com/redis/NodeOperations.scala

+7-7
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@ trait NodeOperations extends NodeApi {
66
self: Redis =>
77

88
override def save: Boolean =
9-
send("SAVE", false)(asBoolean)
9+
send("SAVE")(asBoolean)
1010

1111
override def bgsave: Boolean =
12-
send("BGSAVE", false)(asBoolean)
12+
send("BGSAVE")(asBoolean)
1313

1414
override def lastsave: Option[Long] =
15-
send("LASTSAVE", false)(asLong)
15+
send("LASTSAVE")(asLong)
1616

1717
override def shutdown: Boolean =
18-
send("SHUTDOWN", false)(asBoolean)
18+
send("SHUTDOWN")(asBoolean)
1919

2020
override def bgrewriteaof: Boolean =
21-
send("BGREWRITEAOF", false)(asBoolean)
21+
send("BGREWRITEAOF")(asBoolean)
2222

2323
override def info: Option[String] =
24-
send("INFO", false)(asBulk)
24+
send("INFO")(asBulk)
2525

2626
override def monitor: Boolean =
27-
send("MONITOR", false)(asBoolean)
27+
send("MONITOR")(asBoolean)
2828

2929
override def slaveof(options: Any): Boolean = options match {
3030
case (h: String, p: Int) =>

src/main/scala/com/redis/PubSub.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ trait PubSub extends PubOperations { self: Redis =>
7777
}
7878

7979
def pUnsubscribe(): Unit = {
80-
send("PUNSUBSCRIBE", false)(())
80+
send("PUNSUBSCRIBE")(())
8181
}
8282

8383
def pUnsubscribe(channel: String, channels: String*): Unit = {
@@ -98,7 +98,7 @@ trait PubSub extends PubOperations { self: Redis =>
9898
}
9999

100100
def unsubscribe(): Unit = {
101-
val r = send("UNSUBSCRIBE", false)(())
101+
val r = send("UNSUBSCRIBE")(())
102102
pubSub = false
103103
r
104104
}

src/main/scala/com/redis/RedisClient.scala

+31-21
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ object RedisClient {
2626
)
2727
.getOrElse(0)
2828
}
29+
30+
case class CommandToSend(command: String, args: Seq[Array[Byte]])
2931
}
3032

3133
import RedisClient._
3234
abstract class Redis(batch: Mode) extends IO with Protocol {
3335
var handlers: Vector[(String, () => Any)] = Vector.empty
34-
var commandBuffer: StringBuffer = new StringBuffer
35-
val crlf = "\r\n"
36-
36+
val commandBuffer = collection.mutable.ListBuffer.empty[CommandToSend]
3737

3838
def send[A](command: String, args: Seq[Any])(result: => A)(implicit format: Format): A = try {
3939
if (batch == BATCH) {
4040
handlers :+= ((command, () => result))
41-
commandBuffer.append((List(command) ++ args.toList).mkString(" ") ++ crlf)
41+
commandBuffer += CommandToSend(command, args.map(format.apply))
4242
null.asInstanceOf[A] // hack
4343
} else {
4444
write(Commands.multiBulk(command.getBytes("UTF-8") +: (args map (format.apply))))
@@ -53,26 +53,36 @@ abstract class Redis(batch: Mode) extends IO with Protocol {
5353
else throw e
5454
}
5555

56-
def send[A](command: String, submissionMode: Boolean = false)(result: => A): A = try {
56+
def send[A](command: String)(result: => A): A = try {
5757
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-
}
58+
handlers :+= ((command, () => result))
59+
commandBuffer += CommandToSend(command, Seq.empty[Array[Byte]])
60+
null.asInstanceOf[A]
6661
} else {
6762
write(Commands.multiBulk(List(command.getBytes("UTF-8"))))
6863
result
6964
}
7065
} catch {
7166
case e: RedisConnectionException =>
72-
if (disconnect) send(command, submissionMode)(result)
67+
if (disconnect) send(command)(result)
68+
else throw e
69+
case e: SocketException =>
70+
if (disconnect) send(command)(result)
71+
else throw e
72+
}
73+
74+
def send[A](commands: List[CommandToSend])(result: => A): A = try {
75+
val cs = commands.map { command =>
76+
command.command.getBytes("UTF-8") +: command.args
77+
}
78+
write(Commands.multiMultiBulk(cs))
79+
result
80+
} catch {
81+
case e: RedisConnectionException =>
82+
if (disconnect) send(commands)(result)
7383
else throw e
7484
case e: SocketException =>
75-
if (disconnect) send(command, submissionMode)(result)
85+
if (disconnect) send(commands)(result)
7686
else throw e
7787
}
7888

@@ -143,17 +153,17 @@ class RedisClient(override val host: String, override val port: Int,
143153
* @see https://redis.io/commands/multi
144154
*/
145155
def pipeline(f: PipelineClient => Any): Option[List[Any]] = {
146-
send("MULTI", false)(asString) // flush reply stream
156+
send("MULTI")(asString) // flush reply stream
147157
try {
148158
val pipelineClient = new PipelineClient(this)
149159
try {
150160
f(pipelineClient)
151161
} catch {
152162
case e: Exception =>
153-
send("DISCARD", false)(asString)
163+
send("DISCARD")(asString)
154164
throw e
155165
}
156-
send("EXEC", false)(asExec(pipelineClient.responseHandlers))
166+
send("EXEC")(asExec(pipelineClient.responseHandlers))
157167
} catch {
158168
case e: RedisMultiExecException =>
159169
None
@@ -226,9 +236,9 @@ class RedisClient(override val host: String, override val port: Int,
226236
commands.foreach { command =>
227237
command()
228238
}
229-
val r = send(commandBuffer.toString, true)(Some(handlers.map(_._2).map(_()).toList))
239+
val r = send(commandBuffer.toList)(Some(handlers.map(_._2).map(_()).toList))
230240
handlers = Vector.empty
231-
commandBuffer.setLength(0)
241+
commandBuffer.clear()
232242
r
233243
}
234244

@@ -248,7 +258,7 @@ class RedisClient(override val host: String, override val port: Int,
248258
receive(singleLineReply).map(Parse.parseDefault)
249259
null.asInstanceOf[A] // ugh... gotta find a better way
250260
}
251-
override def send[A](command: String, submissionMode: Boolean = false)(result: => A): A = {
261+
override def send[A](command: String)(result: => A): A = {
252262
write(Commands.multiBulk(List(command.getBytes("UTF-8"))))
253263
responseHandlers :+= (() => result)
254264
receive(singleLineReply).map(Parse.parseDefault)

src/main/scala/com/redis/RedisProtocol.scala

+12
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ private [redis] object Commands {
3434
}
3535
b.result()
3636
}
37+
38+
def multiMultiBulk(commands: Seq[Seq[Array[Byte]]]): Array[Byte] = {
39+
val no = commands.size
40+
val b = new scala.collection.mutable.ArrayBuilder.ofByte
41+
// b ++= "*%d".format(no).getBytes
42+
// b ++= LS
43+
commands.foreach { command =>
44+
b ++= multiBulk(command)
45+
b ++= LS
46+
}
47+
b.result()
48+
}
3749
}
3850

3951
import Commands._

0 commit comments

Comments
 (0)