com.avsystem.commons.redis.commands.streams.scala Maven / Gradle / Ivy
package com.avsystem.commons
package redis.commands
import com.avsystem.commons.redis.CommandEncoder.CommandArg
import com.avsystem.commons.redis._
import com.avsystem.commons.redis.commands.ReplyDecoders._
import com.avsystem.commons.redis.protocol.ValidRedisMsg
import com.avsystem.commons.redis.util.SingletonSeq
trait StreamsApi extends ApiSubset {
type XEntry = redis.commands.XEntry[Record]
object XEntry {
def apply(id: XEntryId, data: Record): XEntry = redis.commands.XEntry(id, data)
def unapply(entry: XEntry): Opt[(XEntryId, Record)] = Opt((entry.id, entry.data))
}
/** Executes [[http://redis.io/commands/xack XACK]] */
def xack(key: Key, group: XGroup, id: XEntryId): Result[Boolean] =
execute(new Xack(key, group, new SingletonSeq(id)).map(_ > 0))
/** Executes [[http://redis.io/commands/xack XACK]] */
def xack(key: Key, group: XGroup, id: XEntryId, ids: XEntryId*): Result[Int] =
execute(new Xack(key, group, id +:: ids))
/** Executes [[http://redis.io/commands/xack XACK]] */
def xack(key: Key, group: XGroup, ids: Iterable[XEntryId]): Result[Int] =
execute(new Xack(key, group, ids))
/** Executes [[http://redis.io/commands/xadd XADD]] */
def xadd(
key: Key,
data: Record,
id: OptArg[XEntryId] = OptArg.Empty,
maxlen: OptArg[XMaxlen] = OptArg.Empty
): Result[XEntryId] =
execute(new Xadd(key, maxlen.toOpt, id.toOpt, data))
/** Executes [[http://redis.io/commands/xadd XADD]] */
def xaddEntry(key: Key, entry: XEntry, maxlen: OptArg[XMaxlen] = OptArg.Empty): Result[XEntryId] =
execute(new Xadd(key, maxlen.toOpt, entry.id.opt, entry.data))
/** Executes [[http://redis.io/commands/xclaim XCLAIM]] */
def xclaimSingle(
key: Key,
group: XGroup,
consumer: XConsumer,
minIdleMillis: Long,
id: XEntryId,
idleMillis: OptArg[Long] = OptArg.Empty,
msUnixTime: OptArg[Long] = OptArg.Empty,
retrycount: OptArg[Int] = OptArg.Empty,
force: Boolean = false
): Result[Opt[XEntry]] =
execute(new Xclaim(
key, group, consumer, minIdleMillis, new SingletonSeq(id),
idleMillis.toOpt, msUnixTime.toOpt, retrycount.toOpt, force
).map(_.headOpt))
/** Executes [[http://redis.io/commands/xclaim XCLAIM]] */
def xclaim(
key: Key,
group: XGroup,
consumer: XConsumer,
minIdleMillis: Long,
ids: Iterable[XEntryId],
idleMillis: OptArg[Long] = OptArg.Empty,
msUnixTime: OptArg[Long] = OptArg.Empty,
retrycount: OptArg[Int] = OptArg.Empty,
force: Boolean = false
): Result[Seq[XEntry]] =
execute(new Xclaim(
key, group, consumer, minIdleMillis, ids,
idleMillis.toOpt, msUnixTime.toOpt, retrycount.toOpt, force
))
/** Executes [[http://redis.io/commands/xclaim XCLAIM]] */
def xclaimJustid(
key: Key,
group: XGroup,
consumer: XConsumer,
minIdleMillis: Long,
ids: Iterable[XEntryId],
idleMillis: OptArg[Long] = OptArg.Empty,
msUnixTime: OptArg[Long] = OptArg.Empty,
retrycount: OptArg[Int] = OptArg.Empty,
force: Boolean = false
): Result[Seq[XEntryId]] =
execute(new XclaimJustid(
key, group, consumer, minIdleMillis, ids,
idleMillis.toOpt, msUnixTime.toOpt, retrycount.toOpt, force
))
/** Executes [[http://redis.io/commands/xdel XDEL]] */
def xdel(key: Key, id: XEntryId): Result[Boolean] =
execute(new Xdel(key, new SingletonSeq(id)).map(_ > 0))
/** Executes [[http://redis.io/commands/xdel XDEL]] */
def xdel(key: Key, id: XEntryId, ids: XEntryId*): Result[Long] =
execute(new Xdel(key, id +:: ids))
/** Executes [[http://redis.io/commands/xdel XDEL]] */
def xdel(key: Key, ids: Iterable[XEntryId]): Result[Long] =
execute(new Xdel(key, ids))
/** Executes [[http://redis.io/commands/xgroup XGROUP CREATE]] */
def xgroupCreate(key: Key, group: XGroup, id: OptArg[XEntryId] = OptArg.Empty, mkstream: Boolean = false): Result[Unit] =
execute(new XgroupCreate(key, group, id.toOpt, mkstream))
/** Executes [[http://redis.io/commands/xgroup XGROUP DELCONSUMER]] */
def xgroupDelconsumer(key: Key, group: XGroup, consumer: XConsumer): Result[Boolean] =
execute(new XgroupDelconsumer(key, group, consumer))
/** Executes [[http://redis.io/commands/xgroup XGROUP DESTROY]] */
def xgroupDestroy(key: Key, group: XGroup): Result[Boolean] =
execute(new XgroupDestroy(key, group))
/** Executes [[http://redis.io/commands/xgroup XGROUP SETID]] */
def xgroupSetid(key: Key, group: XGroup, id: OptArg[XEntryId] = OptArg.Empty): Result[Unit] =
execute(new XgroupSetid(key, group, id.toOpt))
/** Executes [[http://redis.io/commands/xinfo XINFO CONSUMERS]] */
def xinfoConsumers(key: Key, group: XGroup): Result[Seq[XConsumerInfo]] =
execute(new XinfoConsumers(key, group))
/** Executes [[http://redis.io/commands/xinfo XINFO GROUPS]] */
def xinfoGroups(key: Key): Result[Seq[XGroupInfo]] =
execute(new XinfoGroups(key))
/** Executes [[http://redis.io/commands/xinfo XINFO STREAM]] */
def xinfoStream(key: Key): Result[XStreamInfo[Record]] =
execute(new XinfoStream(key))
/** Executes [[http://redis.io/commands/xlen XLEN]] */
def xlen(key: Key): Result[Long] =
execute(new Xlen(key))
/** Executes [[http://redis.io/commands/xpending XPENDING]] */
def xpending(key: Key, group: XGroup): Result[XPendingOverview] =
execute(new Xpending(key, group))
/** Executes [[http://redis.io/commands/xpending XPENDING]] */
def xpendingEntries(
key: Key,
group: XGroup,
count: Int,
start: OptArg[XEntryId] = OptArg.Empty,
end: OptArg[XEntryId] = OptArg.Empty,
consumer: OptArg[XConsumer] = OptArg.Empty
): Result[Seq[XPendingEntry]] =
execute(new XpendingEntries(key, group, start.toOpt, end.toOpt, count, consumer.toOpt))
/** Executes [[http://redis.io/commands/xrange XRANGE]] */
def xrange(
key: Key,
start: OptArg[XEntryId] = OptArg.Empty,
end: OptArg[XEntryId] = OptArg.Empty,
count: OptArg[Int] = OptArg.Empty
): Result[Seq[XEntry]] =
execute(new Xrange(key, start.toOpt, end.toOpt, count.toOpt))
/** Executes [[http://redis.io/commands/xread XREAD]] */
def xreadSingle(
key: Key,
id: Opt[XEntryId],
blockMillis: OptArg[Int] = OptArg.Empty,
count: OptArg[Int] = OptArg.Empty
): Result[Seq[XEntry]] =
execute(new Xread(count.toOpt, blockMillis.toOpt, Iterator(key), Iterator(id)).map(_.getOrElse(key, Nil)))
/** Executes [[http://redis.io/commands/xread XREAD]] */
def xread(
streams: Iterable[(Key, Opt[XEntryId])],
blockMillis: OptArg[Int] = OptArg.Empty,
count: OptArg[Int] = OptArg.Empty
): Result[BMap[Key, Seq[XEntry]]] =
execute(new Xread(count.toOpt, blockMillis.toOpt, streams.iterator.map(_._1), streams.iterator.map(_._2)))
/** Executes [[http://redis.io/commands/xreadgroup XREADGROUP]] */
def xreadgroupSingle(
key: Key,
group: XGroup,
consumer: XConsumer,
id: OptArg[XEntryId] = OptArg.Empty,
blockMillis: OptArg[Int] = OptArg.Empty,
count: OptArg[Int] = OptArg.Empty,
noack: Boolean = false
): Result[Seq[XEntry]] =
execute(new Xreadgroup(group, consumer, count.toOpt, blockMillis.toOpt, noack,
Iterator(key), Iterator(id.toOpt)).map(_.getOrElse(key, Nil)))
/** Executes [[http://redis.io/commands/xreadgroup XREADGROUP]] */
def xreadgroup(
group: XGroup,
consumer: XConsumer,
streams: Iterable[(Key, Opt[XEntryId])],
blockMillis: OptArg[Int] = OptArg.Empty,
count: OptArg[Int] = OptArg.Empty,
noack: Boolean = false
): Result[BMap[Key, Seq[XEntry]]] =
execute(new Xreadgroup(group, consumer, count.toOpt, blockMillis.toOpt, noack,
streams.iterator.map(_._1), streams.iterator.map(_._2)))
/** Executes [[http://redis.io/commands/xrevrange XREVRANGE]] */
def xrevrange(
key: Key,
end: OptArg[XEntryId] = OptArg.Empty,
start: OptArg[XEntryId] = OptArg.Empty,
count: OptArg[Int] = OptArg.Empty
): Result[Seq[XEntry]] =
execute(new Xrevrange(key, end.toOpt, start.toOpt, count.toOpt))
/** Executes [[http://redis.io/commands/xtrim XTRIM]] */
def xtrim(key: Key, maxlen: Long, approx: Boolean = true): Result[Long] =
execute(new Xtrim(key, XMaxlen(maxlen, approx)))
/** Executes [[http://redis.io/commands/xtrim XTRIM]] */
def xtrim(key: Key, maxlen: XMaxlen): Result[Long] =
execute(new Xtrim(key, maxlen))
private final class Xack(key: Key, group: XGroup, ids: Iterable[XEntryId])
extends RedisIntCommand with NodeCommand {
val encoded: Encoded = encoder("XACK").key(key).add(group).add(ids).result
override def immediateResult: Opt[Int] = whenEmpty(ids, 0)
}
private final class Xadd(key: Key, maxlen: Opt[XMaxlen], id: Opt[XEntryId], data: Record)
extends AbstractRedisCommand[XEntryId](bulkAsXEntryId) with NodeCommand {
val encoded: Encoded = encoder("XADD").key(key).optAdd("MAXLEN", maxlen)
.optAdd(id, "*").dataPairs(data).result
}
private abstract class AbstractXclaim[A](entryDecoder: ReplyDecoder[A])(
key: Key, group: XGroup, consumer: XConsumer, minIdleTime: Long, ids: Iterable[XEntryId],
idle: Opt[Long], msUnixTime: Opt[Long], retrycount: Opt[Int], force: Boolean, justid: Boolean
) extends AbstractRedisCommand[Seq[A]](multiBulkAsSeq(entryDecoder)) with NodeCommand {
val encoded: Encoded = encoder("XCLAIM").key(key).add(group).add(consumer).add(minIdleTime)
.add(ids).optAdd("IDLE", idle).optAdd("TIME", msUnixTime).optAdd("RETRYCOUNT", retrycount)
.addFlag("FORCE", force).addFlag("JUSTID", justid).result
override def immediateResult: Opt[Seq[A]] = whenEmpty(ids, Seq.empty)
}
private final class Xclaim(
key: Key, group: XGroup, consumer: XConsumer, minIdleTime: Long, ids: Iterable[XEntryId],
idle: Opt[Long], msUnixTime: Opt[Long], retrycount: Opt[Int], force: Boolean
) extends AbstractXclaim[XEntry](multiBulkAsXEntryOf)(
key, group, consumer, minIdleTime, ids, idle, msUnixTime, retrycount, force, justid = false)
private final class XclaimJustid(
key: Key, group: XGroup, consumer: XConsumer, minIdleTime: Long, ids: Iterable[XEntryId],
idle: Opt[Long], msUnixTime: Opt[Long], retrycount: Opt[Int], force: Boolean
) extends AbstractXclaim[XEntryId](bulkAsXEntryId)(
key, group, consumer, minIdleTime, ids, idle, msUnixTime, retrycount, force, justid = true)
private final class Xdel(key: Key, ids: Iterable[XEntryId]) extends RedisLongCommand with NodeCommand {
val encoded: Encoded = encoder("XDEL").key(key).add(ids).result
override def immediateResult: Opt[Long] = whenEmpty(ids, 0)
}
private final class XgroupCreate(key: Key, group: XGroup, id: Opt[XEntryId], mkstream: Boolean)
extends RedisUnitCommand with NodeCommand {
val encoded: Encoded = encoder("XGROUP", "CREATE").key(key).add(group)
.optAdd(id, "$").addFlag("MKSTREAM", mkstream).result
}
private final class XgroupDelconsumer(key: Key, group: XGroup, consumer: XConsumer)
extends RedisBooleanCommand with NodeCommand {
val encoded: Encoded = encoder("XGROUP", "DELCONSUMER").key(key).add(group).add(consumer).result
}
private final class XgroupDestroy(key: Key, group: XGroup)
extends RedisBooleanCommand with NodeCommand {
val encoded: Encoded = encoder("XGROUP", "DESTROY").key(key).add(group).result
}
private final class XgroupSetid(key: Key, group: XGroup, id: Opt[XEntryId])
extends RedisUnitCommand with NodeCommand {
val encoded: Encoded = encoder("XGROUP", "SETID").key(key).add(group).optAdd(id, "$").result
}
private final class XinfoConsumers(key: Key, group: XGroup)
extends RedisSeqCommand[XConsumerInfo](multiBulkAsXConsumerInfo) with NodeCommand {
val encoded: Encoded = encoder("XINFO", "CONSUMERS").key(key).add(group).result
}
private final class XinfoGroups(key: Key)
extends RedisSeqCommand[XGroupInfo](multiBulkAsXGroupInfo) with NodeCommand {
val encoded: Encoded = encoder("XINFO", "GROUPS").key(key).result
}
private final class XinfoStream(key: Key)
extends AbstractRedisCommand[XStreamInfo[Record]](multiBulkAsXStreamInfoOf[Record]) with NodeCommand {
val encoded: Encoded = encoder("XINFO", "STREAM").key(key).result
}
private final class Xlen(key: Key) extends RedisLongCommand with NodeCommand {
val encoded: Encoded = encoder("XLEN").key(key).result
}
private final class Xpending(key: Key, group: XGroup)
extends AbstractRedisCommand[XPendingOverview](multiBulkAsXPendingOverview) with NodeCommand {
val encoded: Encoded = encoder("XPENDING").key(key).add(group).result
}
private final class XpendingEntries(key: Key, group: XGroup,
start: Opt[XEntryId], end: Opt[XEntryId], count: Int, consumer: Opt[XConsumer]
) extends RedisSeqCommand[XPendingEntry](multiBulkAsXPendingEntry) with NodeCommand {
val encoded: Encoded = encoder("XPENDING").key(key).add(group)
.optAdd(start, "-").optAdd(end, "+").add(count).optAdd(consumer).result
}
private final class Xrange(key: Key, start: Opt[XEntryId], end: Opt[XEntryId], count: Opt[Int])
extends RedisSeqCommand[XEntry](multiBulkAsXEntryOf[Record]) with NodeCommand {
val encoded: Encoded = encoder("XRANGE").key(key)
.optAdd(start, "-").optAdd(end, "+").optAdd("COUNT", count).result
}
private abstract class AbstractXread(noStreams: Boolean)
extends AbstractRedisCommand[BMap[Key, Seq[XEntry]]](
multiBulkAsXEntriesMapOf[Key, Record]) with NodeCommand {
def blockMillis: Opt[Int]
override def immediateResult: Opt[BMap[Key, Seq[XEntry]]] =
if (noStreams) Opt(Map.empty) else Opt.Empty
override def maxBlockingMillis: Int =
blockMillis.map(m => if (m <= 0) Int.MaxValue else m).getOrElse(0)
}
private final class Xread(
count: Opt[Int], val blockMillis: Opt[Int],
streamKeys: Iterator[Key], streamIds: Iterator[Opt[XEntryId]]
) extends AbstractXread(streamKeys.isEmpty) {
val encoded: Encoded = encoder("XREAD")
.optAdd("COUNT", count).optAdd("BLOCK", blockMillis)
.add("STREAMS").keys(streamKeys).add(streamIds.map(_.fold("$")(_.toString)))
.result
}
private final class Xreadgroup(
group: XGroup, consumer: XConsumer,
count: Opt[Int], val blockMillis: Opt[Int], noack: Boolean,
streamKeys: Iterator[Key], streamIds: Iterator[Opt[XEntryId]]
) extends AbstractXread(streamKeys.isEmpty) {
val encoded: Encoded = encoder("XREADGROUP").add("GROUP").add(group).add(consumer)
.optAdd("COUNT", count).optAdd("BLOCK", blockMillis).addFlag("NOACK", noack)
.add("STREAMS").keys(streamKeys).add(streamIds.map(_.fold(">")(_.toString)))
.result
}
private final class Xrevrange(key: Key, end: Opt[XEntryId], start: Opt[XEntryId], count: Opt[Int])
extends RedisSeqCommand[XEntry](multiBulkAsXEntryOf[Record]) with NodeCommand {
val encoded: Encoded = encoder("XREVRANGE").key(key)
.optAdd(end, "+").optAdd(start, "-").optAdd("COUNT", count).result
}
private final class Xtrim(key: Key, maxlen: XMaxlen) extends RedisLongCommand with NodeCommand {
val encoded: Encoded = encoder("XTRIM").key(key).add("MAXLEN").add(maxlen).result
}
}
final case class XEntryId(tstamp: Long, seq: OptArg[Long] = OptArg.Empty) extends Ordered[XEntryId] {
override def compare(that: XEntryId): Int = {
val byTstamp = java.lang.Long.compareUnsigned(tstamp, that.tstamp)
if (byTstamp != 0) byTstamp
else java.lang.Long.compareUnsigned(seq.getOrElse(0L), that.seq.getOrElse(0L))
}
def inc: XEntryId =
XEntryId(tstamp, seq.getOrElse(0L) + 1)
def fillMinSeq: XEntryId =
if (seq.isDefined) this else XEntryId(tstamp, 0L)
def fillMaxSeq: XEntryId =
if (seq.isDefined) this else XEntryId(tstamp, Long.MinValue) // unsigned!
private def ultostr(ul: Long): String =
java.lang.Long.toUnsignedString(ul)
override def toString: String =
s"${ultostr(tstamp)}${seq.fold("")(v => "-" + ultostr(v))}"
}
object XEntryId {
final val Zero: XEntryId = XEntryId(0)
private def strtoul(str: String): Long =
java.lang.Long.parseUnsignedLong(str)
def parse(str: String): XEntryId = str.indexOf('-') match {
case -1 => XEntryId(strtoul(str), OptArg.Empty)
case i => XEntryId(strtoul(str.substring(0, i)), OptArg(strtoul(str.substring(i + 1))))
}
implicit val ordering: Ordering[XEntryId] = _ compare _
implicit val commandArg: CommandArg[XEntryId] =
CommandArg((enc, eid) => enc.add(eid.toString))
}
case class XEntry[Rec](id: XEntryId, data: Rec)
case class XMaxlen(maxlen: Long, approx: Boolean = true)
object XMaxlen {
implicit val commandArg: CommandArg[XMaxlen] = CommandArg {
case (enc, XMaxlen(maxlen, approx)) => enc.addFlag("~", approx).add(maxlen)
}
}
final case class XGroup(raw: String) extends AnyVal
object XGroup {
implicit val commandArg: CommandArg[XGroup] = CommandArg((e, v) => e.add(v.raw))
}
final case class XConsumer(raw: String) extends AnyVal
object XConsumer {
implicit val commandArg: CommandArg[XConsumer] = CommandArg((e, v) => e.add(v.raw))
}
case class XPendingOverview(
count: Long,
minId: XEntryId,
maxId: XEntryId,
countByConsumer: BMap[XConsumer, Long]
)
object XPendingOverview {
final val Empty = XPendingOverview(0, XEntryId.Zero, XEntryId.Zero, Map.empty)
}
case class XPendingEntry(
id: XEntryId,
consumer: XConsumer,
idleTime: Long,
deliveredCount: Int
)
case class XGroupInfo(raw: BMap[String, ValidRedisMsg]) {
def name: XGroup = bulkAsXGroup(raw("name"))
def consumers: Int = integerAsInt(raw("consumers"))
def pending: Int = integerAsInt(raw("pending"))
def lastDeliveredId: XEntryId = bulkAsXEntryId(raw("last-delivered-id"))
}
case class XConsumerInfo(raw: BMap[String, ValidRedisMsg]) {
def name: XConsumer = bulkAsXConsumer(raw("name"))
def pending: Int = integerAsInt(raw("pending"))
def idle: Long = integerAsLong(raw("idle"))
}
case class XStreamInfo[Rec: RedisRecordCodec](raw: BMap[String, ValidRedisMsg]) {
def length: Long = integerAsLong(raw("length"))
def radixTreeKeys: Int = integerAsInt(raw("radis-tree-keys"))
def radixTreeNodes: Int = integerAsInt(raw("radis-tree-nodes"))
def groups: Int = integerAsInt(raw("groups"))
def lastGeneratedId: XEntryId = bulkAsXEntryId(raw("last-generated-id"))
def firstEntry: XEntry[Rec] = multiBulkAsXEntryOf[Rec].apply(raw("first-entry"))
def lastEntry: XEntry[Rec] = multiBulkAsXEntryOf[Rec].apply(raw("last-entry"))
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy