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

com.avsystem.commons.redis.Sequencer.scala Maven / Gradle / Ivy

package com.avsystem.commons
package redis

import com.avsystem.commons.redis.RedisBatch.Index
import com.avsystem.commons.redis.protocol.RedisReply

import scala.annotation.implicitNotFound
import scala.collection.compat._
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

/**
  * Typeclass for easy merging ("sequencing") of multiple [[RedisBatch]] instances into one. This is done in
  * order to guarantee that some set of operations is sent to Redis as a single batch (most likely single network
  * message).
  *
  * The parameter `Ops` represents batches that will be sequenced into one. It may be a collection, a tuple or
  * any other "collection-like" type for which type class instance is provided.
  *
  * `Res` is the type of result of the batch created by "sequencing". This type is automatically inferred from
  * the `Ops` type. For example, if `Ops` is `(RedisBatch[Int], RedisBatch[String])` (tuple) then `Res` will be
  * `(Int, String)`. If `Ops` is `List[RedisBatch[Int]]` then `Res` will be `List[Int]`.
  *
  * Nesting is also possible. For example, if `Ops` is `(List[RedisBatch[Int]], RedisBatch[String])` then
  * `Res` will be `(List[Int], String)`.
  *
  * In order to perform "sequencing", simply call [[RedisBatch.SequenceOps#sequence sequence]]
  * on your collection of batches, e.g.
  *
  * {{{
  *   import RedisApi.Batches.StringTyped._
  *
  *   val tupleBatch: RedisBatch[(Long, Opt[String])] = (incr("key1"), get("key2")).sequence
  *   val listBatch: RedisBatch[List[Long]] = List("key1", "key2").map(key => incr(key)).sequence
  * }}}
  */
@implicitNotFound("${Ops} is not something that can be transformed into RedisBatch")
trait Sequencer[Ops, Res] {
  def sequence(ops: Ops): RedisBatch[Res]
}
object Sequencer extends TupleSequencers {
  private val reusableTrivialSequencer = new Sequencer[RedisBatch[Any], Any] with (RedisBatch[Any] => RedisBatch[Any]) {
    def sequence(ops: RedisBatch[Any]): RedisBatch[Any] = ops
    def apply(ops: RedisBatch[Any]): RedisBatch[Any] = ops
  }

  implicit def trivialSequencer[A]: Sequencer[RedisBatch[A], A] =
    reusableTrivialSequencer.asInstanceOf[Sequencer[RedisBatch[A], A]]

  implicit def collectionSequencer[ElOps, ElRes, M[X] <: IterableOnce[X], That](
    implicit elSequencer: Sequencer[ElOps, ElRes], bf: BuildFrom[M[ElOps], ElRes, That]): Sequencer[M[ElOps], That] =

    (ops: M[ElOps]) => {
      val batches: Iterable[RedisBatch[ElRes]] =
        if ((elSequencer eq reusableTrivialSequencer) && ops.isInstanceOf[Iterable[Any]])
          ops.asInstanceOf[Iterable[RedisBatch[ElRes]]]
        else {
          val buf = new ArrayBuffer[RedisBatch[ElRes]]
          ops.iterator.foreach(el => buf += elSequencer.sequence(el))
          buf
        }
      new CollectionBatch[ElRes, That](batches, () => bf.newBuilder(ops))
    }
}

final class CollectionBatch[A, C](batches: Iterable[RedisBatch[A]], builderCreator: () => mutable.Builder[A, C])
  extends RedisBatch[C] with RawCommandPacks {

  def rawCommandPacks: CollectionBatch[A, C] = this
  def emitCommandPacks(consumer: RawCommandPack => Unit): Unit =
    batches.foreach(_.rawCommandPacks.emitCommandPacks(consumer))
  def computeSize(limit: Int): Int =
    if (limit <= 0) limit else batches.foldLeft(0)((s, b) => s + b.rawCommandPacks.computeSize(limit - s))
  def decodeReplies(replies: Int => RedisReply, index: Index, inTransaction: Boolean): C = {
    // we must invoke all decoders regardless of intermediate errors because index must be properly advanced
    var failure: Opt[Throwable] = Opt.Empty
    val builder = builderCreator()
    batches.foreach { batch =>
      try {
        builder += batch.decodeReplies(replies, index, inTransaction)
      } catch {
        case NonFatal(cause) =>
          failure = failure orElse cause.opt
      }
    }
    failure match {
      case Opt.Empty => builder.result()
      case Opt(cause) => throw cause
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy