All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redis.RedisProtocol.scala Maven / Gradle / Ivy

package com.redis

import serialization.Parse
import Parse.{Implicits => Parsers}

private [redis] object Commands {

  // Response codes from the Redis server
  val ERR    = '-'
  val OK     = "OK".getBytes("UTF-8")
  val QUEUED = "QUEUED".getBytes("UTF-8")
  val SINGLE = '+'
  val BULK   = '$'
  val MULTI  = '*'
  val INT    = ':'

  val LS     = "\r\n".getBytes("UTF-8")

  def multiBulk(args: Seq[Array[Byte]]): Array[Byte] = {
    val b = new scala.collection.mutable.ArrayBuilder.ofByte
    b ++= "*%d".format(args.size).getBytes
    b ++= LS
    args foreach { arg =>
      b ++= "$%d".format(arg.size).getBytes
      b ++= LS
      b ++= arg
      b ++= LS
    }
    b.result
  }
}

import Commands._

case class RedisConnectionException(message: String) extends RuntimeException(message)
case class RedisMultiExecException(message: String) extends RuntimeException(message)

private [redis] trait Reply {

  type Reply[T] = PartialFunction[(Char, Array[Byte]), T]
  type SingleReply = Reply[Option[Array[Byte]]]
  type MultiReply = Reply[Option[List[Option[Array[Byte]]]]]

  def readLine: Array[Byte]
  def readCounted(c: Int): Array[Byte]
  def reconnect: Boolean

  val integerReply: Reply[Option[Int]] = {
    case (INT, s) => Some(Parsers.parseInt(s))
    case (BULK, s) if Parsers.parseInt(s) == -1 => None
  }

  val singleLineReply: SingleReply = {
    case (SINGLE, s) => Some(s)
    case (INT, s) => Some(s)
  }

  val bulkReply: SingleReply = {
    case (BULK, s) => 
      Parsers.parseInt(s) match {
        case -1 => None
        case l => {
          val str = readCounted(l)
          val ignore = readLine // trailing newline
          Some(str)
        }
      }
  }

  val multiBulkReply: MultiReply = {
    case (MULTI, str) =>
      Parsers.parseInt(str) match {
        case -1 => None
        case n => Some(List.fill(n)(receive(bulkReply orElse singleLineReply)))
      }
  }

  def execReply(handlers: Seq[() => Any]): PartialFunction[(Char, Array[Byte]), Option[List[Any]]] = {
    case (MULTI, str) =>
      Parsers.parseInt(str) match {
        case -1 => None
        case n if n == handlers.size => 
          Some(handlers.map(_.apply).toList)
        case n => throw new Exception("Protocol error: Expected "+handlers.size+" results, but got "+n)
      }
  }

  val errReply: Reply[Nothing] = {
    case (ERR, s) => reconnect; throw new Exception(Parsers.parseString(s))
    case x => reconnect; throw new Exception("Protocol error: Got " + x + " as initial reply byte")
  }

  def queuedReplyInt: Reply[Option[Int]] = {
    case (SINGLE, QUEUED) => Some(Int.MaxValue)
  }

  def queuedReplyList: MultiReply = {
    case (SINGLE, QUEUED) => Some(List(Some(QUEUED)))
  }

  def receive[T](pf: Reply[T]): T = readLine match {
    case null =>
      throw new RedisConnectionException("Connection dropped ..")
    case line =>
      (pf orElse errReply) apply ((line(0).toChar,line.slice(1,line.length)))
  }
}

private [redis] trait R extends Reply {
  def asString: Option[String] = receive(singleLineReply) map Parsers.parseString

  def asBulk[T](implicit parse: Parse[T]): Option[T] =  receive(bulkReply) map parse
  
  def asInt: Option[Int] =  receive(integerReply orElse queuedReplyInt)

  def asBoolean: Boolean = receive(integerReply orElse singleLineReply) match {
    case Some(n: Int) => n > 0
    case Some(s: Array[Byte]) => Parsers.parseString(s) match {
      case "OK" => true
      case "QUEUED" => true
      case _ => false
    }
    case _ => false
  }

  def asList[T](implicit parse: Parse[T]): Option[List[Option[T]]] = receive(multiBulkReply).map(_.map(_.map(parse)))

  def asListPairs[A,B](implicit parseA: Parse[A], parseB: Parse[B]): Option[List[Option[(A,B)]]] =
    receive(multiBulkReply).map(_.grouped(2).flatMap{
      case List(Some(a), Some(b)) => Iterator.single(Some((parseA(a), parseB(b))))
      case _ => Iterator.single(None)
    }.toList)

  def asQueuedList: Option[List[Option[String]]] = receive(queuedReplyList).map(_.map(_.map(Parsers.parseString)))

  def asExec(handlers: Seq[() => Any]): Option[List[Any]] = receive(execReply(handlers))

  def asSet[T: Parse]: Option[Set[Option[T]]] = asList map (_.toSet)

  def asAny = receive(integerReply orElse singleLineReply orElse bulkReply orElse multiBulkReply)
}

trait Protocol extends R




© 2015 - 2025 Weber Informatics LLC | Privacy Policy