未验证 提交 7dabd5b5 编写于 作者: D Dragutin Marjanović 提交者: GitHub

Change return type of CLIENT INFO and CLIENT LIST (#813)

* Remove unused class

* Parse responses to ClientInfo

* Improve docs

* Address code remarks

* Add 'from(char)' method to ClientFlag object

* Add 'final' modifier

* Make parsing ClientInfo safe

* Use scala-collection-compat

* Format code
上级 b457e8ea
......@@ -44,11 +44,12 @@ lazy val redis =
.settings(stdSettings("zio-redis"))
.settings(
libraryDependencies ++= List(
"dev.zio" %% "zio-streams" % zioVersion,
"dev.zio" %% "zio-schema" % zioSchemaVersion,
"dev.zio" %% "zio-schema-protobuf" % zioSchemaVersion % Test,
"dev.zio" %% "zio-test" % zioVersion % Test,
"dev.zio" %% "zio-test-sbt" % zioVersion % Test
"dev.zio" %% "zio-streams" % zioVersion,
"dev.zio" %% "zio-schema" % zioSchemaVersion,
"dev.zio" %% "zio-schema-protobuf" % zioSchemaVersion % Test,
"dev.zio" %% "zio-test" % zioVersion % Test,
"dev.zio" %% "zio-test-sbt" % zioVersion % Test,
"org.scala-lang.modules" %% "scala-collection-compat" % "2.9.0"
),
testFrameworks := List(new TestFramework("zio.test.sbt.ZTestFramework"))
)
......
......@@ -727,10 +727,26 @@ object Output {
}
}
case object ClientInfoOutput extends Output[ClientInfo] {
protected def tryDecode(respValue: RespValue): ClientInfo =
respValue match {
case RespValue.BulkString(s) => ClientInfo.from(s.asString)
case other => throw ProtocolError(s"$other isn't a bulk string")
}
}
case object ClientListOutput extends Output[Chunk[ClientInfo]] {
protected def tryDecode(respValue: RespValue): Chunk[ClientInfo] =
respValue match {
case RespValue.BulkString(s) => ClientInfo.from(s.asString.split("\r\n").filter(_.nonEmpty))
case other => throw ProtocolError(s"$other isn't a bulk string")
}
}
case object ClientTrackingInfoOutput extends Output[ClientTrackingInfo] {
protected def tryDecode(respValue: RespValue): ClientTrackingInfo =
respValue match {
case RespValue.NullArray => throw ProtocolError(s"Array must not be empty")
case RespValue.NullArray => throw ProtocolError("Array must not be empty")
case RespValue.Array(values) if values.length % 2 == 0 =>
val fields = values.toList
.grouped(2)
......
......@@ -96,10 +96,10 @@ trait Connection extends RedisEnvironment {
* format.
*
* @return
* a unique string composed of 'property=value' fields separated by a space character.
* information and statistics about the current client
*/
final def clientInfo: IO[RedisError, String] = {
val command = RedisCommand(ClientInfo, NoInput, MultiStringOutput, executor)
final def clientInfo: IO[RedisError, ClientInfo] = {
val command = RedisCommand(ClientInfo, NoInput, ClientInfoOutput, executor)
command.run(())
}
......@@ -153,17 +153,17 @@ trait Connection extends RedisEnvironment {
* @param clientIds
* filters the list by client IDs
* @return
* a unique string composed of 'property=value' fields separated by a space character
* a chunk of information and statistics about clients
*/
final def clientList(
clientType: Option[ClientType] = None,
clientIds: Option[(Long, List[Long])] = None
): IO[RedisError, String] = {
): IO[RedisError, Chunk[ClientInfo]] = {
val command =
RedisCommand(
ClientList,
Tuple2(OptionalInput(ClientTypeInput), OptionalInput(IdsInput)),
MultiStringOutput,
ClientListOutput,
executor
)
......
......@@ -16,53 +16,67 @@
package zio.redis.options
import zio.Duration
import zio.{Chunk, Duration}
import java.net.InetAddress
import scala.collection.compat._
trait Connection {
sealed case class Address(ip: InetAddress, port: Int) {
private[redis] final def asString: String = s"${ip.getHostAddress}:$port"
}
object Address {
private[redis] final def fromString(addr: String): Option[Address] =
addr.split(":").toList match {
case ip :: port :: Nil => port.toIntOption.map(new Address(InetAddress.getByName(ip), _))
case _ => None
}
}
sealed case class ClientEvents(readable: Boolean = false, writable: Boolean = false)
sealed trait ClientFlag
object ClientFlag {
case object ToBeClosedAsap extends ClientFlag
case object Blocked extends ClientFlag
case object ToBeClosedAfterReply extends ClientFlag
case object WatchedKeysModified extends ClientFlag
case object IsMaster extends ClientFlag
case object MonitorMode extends ClientFlag
case object PubSub extends ClientFlag
case object ReadOnlyMode extends ClientFlag
case object Replica extends ClientFlag
case object Unblocked extends ClientFlag
case object UnixDomainSocket extends ClientFlag
case object MultiExecContext extends ClientFlag
case object KeysTrackingEnabled extends ClientFlag
case object Blocked extends ClientFlag
case object BroadcastTrackingMode extends ClientFlag
case object IsMaster extends ClientFlag
case object KeysTrackingEnabled extends ClientFlag
case object MonitorMode extends ClientFlag
case object MultiExecContext extends ClientFlag
case object PubSub extends ClientFlag
case object ReadOnlyMode extends ClientFlag
case object Replica extends ClientFlag
case object ToBeClosedAfterReply extends ClientFlag
case object ToBeClosedAsap extends ClientFlag
case object TrackingTargetClientInvalid extends ClientFlag
case object BroadcastTrackingMode extends ClientFlag
case object Unblocked extends ClientFlag
case object UnixDomainSocket extends ClientFlag
case object WatchedKeysModified extends ClientFlag
private[redis] lazy val Flags =
Map(
'A' -> ClientFlag.ToBeClosedAsap,
'b' -> ClientFlag.Blocked,
'B' -> ClientFlag.BroadcastTrackingMode,
'c' -> ClientFlag.ToBeClosedAfterReply,
'd' -> ClientFlag.WatchedKeysModified,
'M' -> ClientFlag.IsMaster,
'O' -> ClientFlag.MonitorMode,
'P' -> ClientFlag.PubSub,
'r' -> ClientFlag.ReadOnlyMode,
'R' -> ClientFlag.TrackingTargetClientInvalid,
'S' -> ClientFlag.Replica,
't' -> ClientFlag.KeysTrackingEnabled,
'u' -> ClientFlag.Unblocked,
'U' -> ClientFlag.UnixDomainSocket,
'x' -> ClientFlag.MultiExecContext
)
}
sealed case class ClientInfo(
id: Long,
id: Option[Long] = None,
name: Option[String] = None,
address: Option[Address] = None,
localAddress: Option[Address] = None,
......@@ -71,9 +85,9 @@ trait Connection {
idle: Option[Duration] = None,
flags: Set[ClientFlag] = Set.empty,
databaseId: Option[Long] = None,
subscriptions: Int = 0,
patternSubscriptions: Int = 0,
multiCommands: Int = 0,
subscriptions: Option[Int] = None,
patternSubscriptions: Option[Int] = None,
multiCommands: Option[Int] = None,
queryBufferLength: Option[Int] = None,
queryBufferFree: Option[Int] = None,
outputBufferLength: Option[Int] = None,
......@@ -87,6 +101,42 @@ trait Connection {
user: Option[String] = None
)
object ClientInfo {
private[redis] final def from(line: String): ClientInfo = {
val data = line.trim.split(" ").map(_.split("=").toList).collect { case k :: v :: Nil => k -> v }.toMap
val events = data.get("events")
new ClientInfo(
id = data.get("id").flatMap(_.toLongOption),
name = data.get("name"),
address = data.get("addr").flatMap(Address.fromString),
localAddress = data.get("laddr").flatMap(Address.fromString),
fileDescriptor = data.get("fd").flatMap(_.toLongOption),
age = data.get("age").flatMap(_.toLongOption).map(Duration.fromSeconds),
idle = data.get("idle").flatMap(_.toLongOption).map(Duration.fromSeconds),
flags = data
.get("flags")
.fold(Set.empty[ClientFlag])(_.foldLeft(Set.empty[ClientFlag])((fs, f) => fs ++ ClientFlag.Flags.get(f))),
databaseId = data.get("id").flatMap(_.toLongOption),
subscriptions = data.get("sub").flatMap(_.toIntOption),
patternSubscriptions = data.get("psub").flatMap(_.toIntOption),
multiCommands = data.get("multi").flatMap(_.toIntOption),
queryBufferLength = data.get("qbuf").flatMap(_.toIntOption),
queryBufferFree = data.get("qbuf-free").flatMap(_.toIntOption),
outputListLength = data.get("oll").flatMap(_.toIntOption),
outputBufferMem = data.get("omem").flatMap(_.toLongOption),
events = ClientEvents(readable = events.exists(_.contains("r")), writable = events.exists(_.contains("w"))),
lastCommand = data.get("cmd"),
argvMemory = data.get("argv-mem").flatMap(_.toLongOption),
totalMemory = data.get("total-mem").flatMap(_.toLongOption),
redirectionClientId = data.get("redir").flatMap(_.toLongOption),
user = data.get("user")
)
}
private[redis] final def from(lines: Array[String]): Chunk[ClientInfo] =
Chunk.fromArray(lines.map(from))
}
sealed trait ClientKillFilter
object ClientKillFilter {
......
......@@ -61,8 +61,6 @@ trait Streams {
counter: Long
)
sealed case class Group[G, C](group: G, consumer: C)
case object NoAck {
private[redis] def asString: String = "NOACK"
}
......
......@@ -48,8 +48,10 @@ trait ConnectionSpec extends BaseSpec {
suite("clientInfo")(
test("get client info") {
for {
info <- ZIO.serviceWithZIO[Redis](_.clientInfo)
} yield assert(info)(isNonEmptyString)
redis <- ZIO.service[Redis]
id <- redis.clientId
info <- ZIO.serviceWithZIO[Redis](_.clientInfo)
} yield assert(info.id)(isSome(equalTo(id))) && assert(info.name)(isNone)
}
),
suite("clientKill")(
......@@ -76,23 +78,25 @@ trait ConnectionSpec extends BaseSpec {
test("get clients' info") {
for {
info <- ZIO.serviceWithZIO[Redis](_.clientList())
} yield assert(info)(isNonEmptyString)
} yield assert(info)(isNonEmpty)
},
test("get clients' info filtered by type") {
for {
redis <- ZIO.service[Redis]
infoNormal <- redis.clientList(Some(ClientType.Normal))
infoPubSub <- redis.clientList(Some(ClientType.PubSub))
} yield assert(infoNormal)(isNonEmptyString) && assert(infoPubSub)(isEmptyString)
} yield assert(infoNormal)(isNonEmpty) && assert(infoPubSub)(isEmpty)
},
test("get clients' info filtered by client IDs") {
for {
redis <- ZIO.service[Redis]
id <- redis.clientId
nonExistingId = id + 1
info <- ZIO.serviceWithZIO[Redis](_.clientList(clientIds = Some((id, Nil))))
infoNonExisting <- ZIO.serviceWithZIO[Redis](_.clientList(clientIds = Some((nonExistingId, Nil))))
} yield assert(info)(isNonEmptyString) && assert(infoNonExisting)(isEmptyString)
info <- redis.clientList(clientIds = Some((id, Nil)))
infoNonExisting <- redis.clientList(clientIds = Some((nonExistingId, Nil)))
} yield assert(info)(isNonEmpty) && assert(info.head.id)(isSome(equalTo(id))) && assert(infoNonExisting)(
isEmpty
)
}
),
suite("clientGetRedir")(
......
......@@ -88,9 +88,12 @@ object BuildHelper {
private def extraOptions(scalaVersion: String, optimize: Boolean) =
CrossVersion.partialVersion(scalaVersion) match {
case Some((3, _)) =>
List("-language:implicitConversions", "-Xignore-scala2-macros")
List("-language:implicitConversions", "-Xignore-scala2-macros", "-Wconf:origin=scala.collection.compat.*:s")
case Some((2, 13)) =>
List("-Ywarn-unused:params,-implicits") ++ std2xOptions ++ optimizerOptions(optimize)
List(
"-Ywarn-unused:params,-implicits",
"-Wconf:origin=scala.collection.compat.*:s"
) ++ std2xOptions ++ optimizerOptions(optimize)
case Some((2, 12)) =>
List(
"-opt-warnings",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册