added support for Redis based SortedSet persistence in Akka transactors
This commit is contained in:
parent
3010cd1aa2
commit
02e6f5895a
10 changed files with 404 additions and 21 deletions
|
|
@ -51,18 +51,24 @@ trait Storage {
|
||||||
def newRef: PersistentRef[ElementType]
|
def newRef: PersistentRef[ElementType]
|
||||||
def newQueue: PersistentQueue[ElementType] = // only implemented for redis
|
def newQueue: PersistentQueue[ElementType] = // only implemented for redis
|
||||||
throw new UnsupportedOperationException
|
throw new UnsupportedOperationException
|
||||||
|
def newSortedSet: PersistentSortedSet[ElementType] = // only implemented for redis
|
||||||
|
throw new UnsupportedOperationException
|
||||||
|
|
||||||
def getMap(id: String): PersistentMap[ElementType, ElementType]
|
def getMap(id: String): PersistentMap[ElementType, ElementType]
|
||||||
def getVector(id: String): PersistentVector[ElementType]
|
def getVector(id: String): PersistentVector[ElementType]
|
||||||
def getRef(id: String): PersistentRef[ElementType]
|
def getRef(id: String): PersistentRef[ElementType]
|
||||||
def getQueue(id: String): PersistentQueue[ElementType] = // only implemented for redis
|
def getQueue(id: String): PersistentQueue[ElementType] = // only implemented for redis
|
||||||
throw new UnsupportedOperationException
|
throw new UnsupportedOperationException
|
||||||
|
def getSortedSet(id: String): PersistentSortedSet[ElementType] = // only implemented for redis
|
||||||
|
throw new UnsupportedOperationException
|
||||||
|
|
||||||
def newMap(id: String): PersistentMap[ElementType, ElementType]
|
def newMap(id: String): PersistentMap[ElementType, ElementType]
|
||||||
def newVector(id: String): PersistentVector[ElementType]
|
def newVector(id: String): PersistentVector[ElementType]
|
||||||
def newRef(id: String): PersistentRef[ElementType]
|
def newRef(id: String): PersistentRef[ElementType]
|
||||||
def newQueue(id: String): PersistentQueue[ElementType] = // only implemented for redis
|
def newQueue(id: String): PersistentQueue[ElementType] = // only implemented for redis
|
||||||
throw new UnsupportedOperationException
|
throw new UnsupportedOperationException
|
||||||
|
def newSortedSet(id: String): PersistentSortedSet[ElementType] = // only implemented for redis
|
||||||
|
throw new UnsupportedOperationException
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -398,3 +404,119 @@ trait PersistentQueue[A] extends scala.collection.mutable.Queue[A]
|
||||||
transaction.get.get.register(uuid, this)
|
transaction.get.get.register(uuid, this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a template for a concrete persistent transactional sorted set based storage.
|
||||||
|
* <p/>
|
||||||
|
* Sorting is done based on a <i>zscore</i>. But the computation of zscore has been kept
|
||||||
|
* outside the abstraction.
|
||||||
|
* <p/>
|
||||||
|
* zscore can be implemented in a variety of ways by the calling class:
|
||||||
|
* <pre>
|
||||||
|
* trait ZScorable {
|
||||||
|
* def toZScore: Float
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* class Foo extends ZScorable {
|
||||||
|
* //.. implemnetation
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
* Or we can also use views:
|
||||||
|
* <pre>
|
||||||
|
* class Foo {
|
||||||
|
* //..
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* implicit def Foo2Scorable(foo: Foo): ZScorable = new ZScorable {
|
||||||
|
* def toZScore = {
|
||||||
|
* //..
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* and use <tt>foo.toZScore</tt> to compute the zscore and pass to the APIs.
|
||||||
|
*
|
||||||
|
* @author <a href="http://debasishg.blogspot.com"</a>
|
||||||
|
*/
|
||||||
|
trait PersistentSortedSet[A]
|
||||||
|
extends Transactional
|
||||||
|
with Committable {
|
||||||
|
|
||||||
|
protected val newElems = TransactionalState.newMap[A, Float]
|
||||||
|
protected val removedElems = TransactionalState.newVector[A]
|
||||||
|
|
||||||
|
val storage: SortedSetStorageBackend[A]
|
||||||
|
|
||||||
|
def commit = {
|
||||||
|
for ((element, score) <- newElems) storage.zadd(uuid, String.valueOf(score), element)
|
||||||
|
for (element <- removedElems) storage.zrem(uuid, element)
|
||||||
|
newElems.clear
|
||||||
|
removedElems.clear
|
||||||
|
}
|
||||||
|
|
||||||
|
def +(elem: A, score: Float) = add(elem, score)
|
||||||
|
|
||||||
|
def add(elem: A, score: Float) = {
|
||||||
|
register
|
||||||
|
newElems.put(elem, score)
|
||||||
|
}
|
||||||
|
|
||||||
|
def -(elem: A) = remove(elem)
|
||||||
|
|
||||||
|
def remove(elem: A) = {
|
||||||
|
register
|
||||||
|
removedElems.add(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def inStorage(elem: A): Option[Float] = storage.zscore(uuid, elem) match {
|
||||||
|
case Some(s) => Some(s.toFloat)
|
||||||
|
case None => None
|
||||||
|
}
|
||||||
|
|
||||||
|
def contains(elem: A): Boolean = {
|
||||||
|
if (newElems contains elem) true
|
||||||
|
else {
|
||||||
|
inStorage(elem) match {
|
||||||
|
case Some(f) => true
|
||||||
|
case None => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def size: Int = newElems.size + storage.zcard(uuid) - removedElems.size
|
||||||
|
|
||||||
|
def zscore(elem: A): Float = {
|
||||||
|
if (newElems contains elem) newElems.get(elem).get
|
||||||
|
inStorage(elem) match {
|
||||||
|
case Some(f) => f
|
||||||
|
case None =>
|
||||||
|
throw new Predef.NoSuchElementException(elem + " not present")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit def order(x: (A, Float)) = new Ordered[(A, Float)] {
|
||||||
|
def compare(that: (A, Float)) = x._2 compare that._2
|
||||||
|
}
|
||||||
|
|
||||||
|
def zrange(start: Int, end: Int): List[(A, Float)] = {
|
||||||
|
// need to operate on the whole range
|
||||||
|
// get all from the underlying storage
|
||||||
|
val fromStore = storage.zrangeWithScore(uuid, 0, -1)
|
||||||
|
val ts = scala.collection.immutable.TreeSet(fromStore: _*) ++ newElems.toList
|
||||||
|
val l = ts.size
|
||||||
|
|
||||||
|
// -1 means the last element, -2 means the second last
|
||||||
|
val s = if (start < 0) start + l else start
|
||||||
|
val e =
|
||||||
|
if (end < 0) end + l
|
||||||
|
else if (end >= l) (l - 1)
|
||||||
|
else end
|
||||||
|
// slice is open at the end, we need a closed end range
|
||||||
|
ts.elements.slice(s, e + 1).toList
|
||||||
|
}
|
||||||
|
|
||||||
|
private def register = {
|
||||||
|
if (transaction.get.isEmpty) throw new NoTransactionInScopeException
|
||||||
|
transaction.get.get.register(uuid, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,11 +69,15 @@ trait SortedSetStorageBackend[T] extends StorageBackend {
|
||||||
// remove item from sorted set identified by name
|
// remove item from sorted set identified by name
|
||||||
def zrem(name: String, item: T): Boolean
|
def zrem(name: String, item: T): Boolean
|
||||||
|
|
||||||
// cardinality of the set idnetified by name
|
// cardinality of the set identified by name
|
||||||
def zcard(name: String): Int
|
def zcard(name: String): Int
|
||||||
|
|
||||||
def zscore(name: String, item: T): String
|
// zscore of the item from sorted set identified by name
|
||||||
|
def zscore(name: String, item: T): Option[Float]
|
||||||
|
|
||||||
|
// zrange from the sorted set identified by name
|
||||||
def zrange(name: String, start: Int, end: Int): List[T]
|
def zrange(name: String, start: Int, end: Int): List[T]
|
||||||
}
|
|
||||||
|
|
||||||
|
// zrange with score from the sorted set identified by name
|
||||||
|
def zrangeWithScore(name: String, start: Int, end: Int): List[(T, Float)]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,20 @@ object RedisStorage extends Storage {
|
||||||
def newVector: PersistentVector[ElementType] = newVector(UUID.newUuid.toString)
|
def newVector: PersistentVector[ElementType] = newVector(UUID.newUuid.toString)
|
||||||
def newRef: PersistentRef[ElementType] = newRef(UUID.newUuid.toString)
|
def newRef: PersistentRef[ElementType] = newRef(UUID.newUuid.toString)
|
||||||
override def newQueue: PersistentQueue[ElementType] = newQueue(UUID.newUuid.toString)
|
override def newQueue: PersistentQueue[ElementType] = newQueue(UUID.newUuid.toString)
|
||||||
|
override def newSortedSet: PersistentSortedSet[ElementType] = newSortedSet(UUID.newUuid.toString)
|
||||||
|
|
||||||
def getMap(id: String): PersistentMap[ElementType, ElementType] = newMap(id)
|
def getMap(id: String): PersistentMap[ElementType, ElementType] = newMap(id)
|
||||||
def getVector(id: String): PersistentVector[ElementType] = newVector(id)
|
def getVector(id: String): PersistentVector[ElementType] = newVector(id)
|
||||||
def getRef(id: String): PersistentRef[ElementType] = newRef(id)
|
def getRef(id: String): PersistentRef[ElementType] = newRef(id)
|
||||||
override def getQueue(id: String): PersistentQueue[ElementType] = newQueue(id)
|
override def getQueue(id: String): PersistentQueue[ElementType] = newQueue(id)
|
||||||
|
override def getSortedSet(id: String): PersistentSortedSet[ElementType] = newSortedSet(id)
|
||||||
|
|
||||||
def newMap(id: String): PersistentMap[ElementType, ElementType] = new RedisPersistentMap(id)
|
def newMap(id: String): PersistentMap[ElementType, ElementType] = new RedisPersistentMap(id)
|
||||||
def newVector(id: String): PersistentVector[ElementType] = new RedisPersistentVector(id)
|
def newVector(id: String): PersistentVector[ElementType] = new RedisPersistentVector(id)
|
||||||
def newRef(id: String): PersistentRef[ElementType] = new RedisPersistentRef(id)
|
def newRef(id: String): PersistentRef[ElementType] = new RedisPersistentRef(id)
|
||||||
override def newQueue(id: String): PersistentQueue[ElementType] = new RedisPersistentQueue(id)
|
override def newQueue(id: String): PersistentQueue[ElementType] = new RedisPersistentQueue(id)
|
||||||
|
override def newSortedSet(id: String): PersistentSortedSet[ElementType] =
|
||||||
|
new RedisPersistentSortedSet(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -63,3 +67,14 @@ class RedisPersistentQueue(id: String) extends PersistentQueue[Array[Byte]] {
|
||||||
val uuid = id
|
val uuid = id
|
||||||
val storage = RedisStorageBackend
|
val storage = RedisStorageBackend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a persistent transactional sorted set based on the Redis
|
||||||
|
* storage.
|
||||||
|
*
|
||||||
|
* @author <a href="http://debasishg.blogspot.com">Debasish Ghosh</a>
|
||||||
|
*/
|
||||||
|
class RedisPersistentSortedSet(id: String) extends PersistentSortedSet[Array[Byte]] {
|
||||||
|
val uuid = id
|
||||||
|
val storage = RedisStorageBackend
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -364,11 +364,10 @@ private [akka] object RedisStorageBackend extends
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def zscore(name: String, item: Array[Byte]): String = withErrorHandling {
|
def zscore(name: String, item: Array[Byte]): Option[Float] = withErrorHandling {
|
||||||
db.zscore(new String(encode(name.getBytes)), new String(item)) match {
|
db.zscore(new String(encode(name.getBytes)), new String(item)) match {
|
||||||
case None =>
|
case Some(s) => Some(s.toFloat)
|
||||||
throw new Predef.NoSuchElementException(new String(item) + " not present")
|
case None => None
|
||||||
case Some(s) => s
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,6 +379,16 @@ private [akka] object RedisStorageBackend extends
|
||||||
s.map(_.get.getBytes)
|
s.map(_.get.getBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def zrangeWithScore(name: String, start: Int, end: Int): List[(Array[Byte], Float)] = withErrorHandling {
|
||||||
|
db.zrangeWithScore(
|
||||||
|
new String(encode(name.getBytes)), start.toString, end.toString, RedisClient.ASC) match {
|
||||||
|
case None =>
|
||||||
|
throw new Predef.NoSuchElementException(name + " not present")
|
||||||
|
case Some(l) =>
|
||||||
|
l.map{ case (elem, score) => (elem.get.getBytes, score.get.toFloat) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def flushDB = withErrorHandling(db.flushDb)
|
def flushDB = withErrorHandling(db.flushDb)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ class AccountActor extends Transactor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@serializable class PersistentFailerActor extends Transactor {
|
@serializable class PersistentFailerActor extends Transactor {
|
||||||
//timeout = 5000
|
// timeout = 5000
|
||||||
def receive = {
|
def receive = {
|
||||||
case "Failure" =>
|
case "Failure" =>
|
||||||
throw new RuntimeException("expected")
|
throw new RuntimeException("expected")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
package se.scalablesolutions.akka.persistence.redis
|
||||||
|
|
||||||
|
import org.scalatest.Spec
|
||||||
|
import org.scalatest.Assertions
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import org.scalatest.BeforeAndAfterAll
|
||||||
|
import org.scalatest.junit.JUnitRunner
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import se.scalablesolutions.akka.actor.{Actor, Transactor}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A persistent actor based on Redis sortedset storage.
|
||||||
|
* <p/>
|
||||||
|
* Needs a running Redis server.
|
||||||
|
* @author <a href="http://debasishg.blogspot.com">Debasish Ghosh</a>
|
||||||
|
*/
|
||||||
|
|
||||||
|
trait ZScorable {
|
||||||
|
def zscore: Float
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Hacker(name: String, birth: String) extends ZScorable {
|
||||||
|
def zscore = birth.toFloat
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetThresholdViolationException extends RuntimeException
|
||||||
|
|
||||||
|
// add hacker to the set
|
||||||
|
case class ADD(h: Hacker)
|
||||||
|
|
||||||
|
// remove hacker from set
|
||||||
|
case class REMOVE(h: Hacker)
|
||||||
|
|
||||||
|
// size of the set
|
||||||
|
case object SIZE
|
||||||
|
|
||||||
|
// zscore of the hacker
|
||||||
|
case class SCORE(h: Hacker)
|
||||||
|
|
||||||
|
// zrange
|
||||||
|
case class RANGE(start: Int, end: Int)
|
||||||
|
|
||||||
|
// add and remove subject to the condition that there will be at least 3 hackers
|
||||||
|
case class MULTI(add: List[Hacker], rem: List[Hacker], failer: Actor)
|
||||||
|
|
||||||
|
class SortedSetActor extends Transactor {
|
||||||
|
timeout = 100000
|
||||||
|
private lazy val hackers = RedisStorage.newSortedSet
|
||||||
|
|
||||||
|
def receive = {
|
||||||
|
case ADD(h) =>
|
||||||
|
hackers.+(h.name.getBytes, h.zscore)
|
||||||
|
reply(true)
|
||||||
|
|
||||||
|
case REMOVE(h) =>
|
||||||
|
hackers.-(h.name.getBytes)
|
||||||
|
reply(true)
|
||||||
|
|
||||||
|
case SIZE =>
|
||||||
|
reply(hackers.size)
|
||||||
|
|
||||||
|
case SCORE(h) =>
|
||||||
|
reply(hackers.zscore(h.name.getBytes))
|
||||||
|
|
||||||
|
case RANGE(s, e) =>
|
||||||
|
reply(hackers.zrange(s, e))
|
||||||
|
|
||||||
|
case MULTI(a, r, failer) =>
|
||||||
|
a.foreach{ h: Hacker =>
|
||||||
|
hackers.+(h.name.getBytes, h.zscore)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
r.foreach{ h =>
|
||||||
|
if (hackers.size <= 3)
|
||||||
|
throw new SetThresholdViolationException
|
||||||
|
hackers.-(h.name.getBytes)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
case e: Exception =>
|
||||||
|
failer !! "Failure"
|
||||||
|
}
|
||||||
|
reply((a.size, r.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import RedisStorageBackend._
|
||||||
|
|
||||||
|
@RunWith(classOf[JUnitRunner])
|
||||||
|
class RedisPersistentSortedSetSpec extends
|
||||||
|
Spec with
|
||||||
|
ShouldMatchers with
|
||||||
|
BeforeAndAfterAll {
|
||||||
|
|
||||||
|
override def beforeAll {
|
||||||
|
flushDB
|
||||||
|
println("** destroyed database")
|
||||||
|
}
|
||||||
|
|
||||||
|
override def afterAll {
|
||||||
|
flushDB
|
||||||
|
println("** destroyed database")
|
||||||
|
}
|
||||||
|
|
||||||
|
val h1 = Hacker("Alan kay", "1940")
|
||||||
|
val h2 = Hacker("Richard Stallman", "1953")
|
||||||
|
val h3 = Hacker("Yukihiro Matsumoto", "1965")
|
||||||
|
val h4 = Hacker("Claude Shannon", "1916")
|
||||||
|
val h5 = Hacker("Linus Torvalds", "1969")
|
||||||
|
val h6 = Hacker("Alan Turing", "1912")
|
||||||
|
|
||||||
|
describe("Add and report cardinality of the set") {
|
||||||
|
val qa = new SortedSetActor
|
||||||
|
qa.start
|
||||||
|
|
||||||
|
it("should enter 6 hackers") {
|
||||||
|
qa !! ADD(h1)
|
||||||
|
qa !! ADD(h2)
|
||||||
|
qa !! ADD(h3)
|
||||||
|
qa !! ADD(h4)
|
||||||
|
qa !! ADD(h5)
|
||||||
|
qa !! ADD(h6)
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should fetch correct scores for hackers") {
|
||||||
|
(qa !! SCORE(h1)).get.asInstanceOf[Float] should equal(1940.0f)
|
||||||
|
(qa !! SCORE(h5)).get.asInstanceOf[Float] should equal(1969.0f)
|
||||||
|
(qa !! SCORE(h6)).get.asInstanceOf[Float] should equal(1912.0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should fetch proper range") {
|
||||||
|
(qa !! RANGE(0, 4)).get.asInstanceOf[List[_]].size should equal(5)
|
||||||
|
(qa !! RANGE(0, 6)).get.asInstanceOf[List[_]].size should equal(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should remove and throw exception for removing non-existent hackers") {
|
||||||
|
qa !! REMOVE(h2)
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(5)
|
||||||
|
qa !! REMOVE(h3)
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(4)
|
||||||
|
val h7 = Hacker("Paul Snively", "1952")
|
||||||
|
try {
|
||||||
|
qa !! REMOVE(h7)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
case e: Predef.NoSuchElementException =>
|
||||||
|
e.getMessage should endWith("not present")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should change score for entering the same hacker name with diff score") {
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(4)
|
||||||
|
|
||||||
|
// same name as h6
|
||||||
|
val h7 = Hacker("Alan Turing", "1992")
|
||||||
|
qa !! ADD(h7)
|
||||||
|
|
||||||
|
// size remains same
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(4)
|
||||||
|
|
||||||
|
// score updated
|
||||||
|
(qa !! SCORE(h7)).get.asInstanceOf[Float] should equal(1992.0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Transaction semantics") {
|
||||||
|
it("should rollback on exception") {
|
||||||
|
val qa = new SortedSetActor
|
||||||
|
qa.start
|
||||||
|
|
||||||
|
val failer = new PersistentFailerActor
|
||||||
|
failer.start
|
||||||
|
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(0)
|
||||||
|
val add = List(h1, h2, h3, h4)
|
||||||
|
val rem = List(h2)
|
||||||
|
(qa !! MULTI(add, rem, failer)).get.asInstanceOf[Tuple2[Int, Int]] should equal((4,1))
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(3)
|
||||||
|
// size == 3
|
||||||
|
|
||||||
|
// add 2 more
|
||||||
|
val add1 = List(h5, h6)
|
||||||
|
|
||||||
|
// remove 3
|
||||||
|
val rem1 = List(h1, h3, h4)
|
||||||
|
try {
|
||||||
|
qa !! MULTI(add1, rem1, failer)
|
||||||
|
} catch { case e: Exception => {}
|
||||||
|
}
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("zrange") {
|
||||||
|
it ("should report proper range") {
|
||||||
|
val qa = new SortedSetActor
|
||||||
|
qa.start
|
||||||
|
qa !! ADD(h1)
|
||||||
|
qa !! ADD(h2)
|
||||||
|
qa !! ADD(h3)
|
||||||
|
qa !! ADD(h4)
|
||||||
|
qa !! ADD(h5)
|
||||||
|
qa !! ADD(h6)
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(6)
|
||||||
|
val l = (qa !! RANGE(0, 6)).get.asInstanceOf[List[(Array[Byte], Float)]]
|
||||||
|
l.map { case (e, s) => (new String(e), s) }.head should equal(("Alan Turing", 1912.0f))
|
||||||
|
val h7 = Hacker("Alan Turing", "1992")
|
||||||
|
qa !! ADD(h7)
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(6)
|
||||||
|
val m = (qa !! RANGE(0, 6)).get.asInstanceOf[List[(Array[Byte], Float)]]
|
||||||
|
m.map { case (e, s) => (new String(e), s) }.head should equal(("Claude Shannon", 1916.0f))
|
||||||
|
}
|
||||||
|
|
||||||
|
it ("should report proper rge") {
|
||||||
|
val qa = new SortedSetActor
|
||||||
|
qa.start
|
||||||
|
qa !! ADD(h1)
|
||||||
|
qa !! ADD(h2)
|
||||||
|
qa !! ADD(h3)
|
||||||
|
qa !! ADD(h4)
|
||||||
|
qa !! ADD(h5)
|
||||||
|
qa !! ADD(h6)
|
||||||
|
(qa !! SIZE).get.asInstanceOf[Int] should equal(6)
|
||||||
|
(qa !! RANGE(0, 5)).get.asInstanceOf[List[_]].size should equal(6)
|
||||||
|
(qa !! RANGE(0, 6)).get.asInstanceOf[List[_]].size should equal(6)
|
||||||
|
(qa !! RANGE(0, 3)).get.asInstanceOf[List[_]].size should equal(4)
|
||||||
|
(qa !! RANGE(0, 1)).get.asInstanceOf[List[_]].size should equal(2)
|
||||||
|
(qa !! RANGE(0, 0)).get.asInstanceOf[List[_]].size should equal(1)
|
||||||
|
(qa !! RANGE(3, 1)).get.asInstanceOf[List[_]].size should equal(0)
|
||||||
|
(qa !! RANGE(0, -1)).get.asInstanceOf[List[_]].size should equal(6)
|
||||||
|
(qa !! RANGE(0, -2)).get.asInstanceOf[List[_]].size should equal(5)
|
||||||
|
(qa !! RANGE(0, -4)).get.asInstanceOf[List[_]].size should equal(3)
|
||||||
|
(qa !! RANGE(-4, -1)).get.asInstanceOf[List[_]].size should equal(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -191,10 +191,10 @@ class RedisStorageBackendSpec extends
|
||||||
|
|
||||||
zcard("hackers") should equal(6)
|
zcard("hackers") should equal(6)
|
||||||
|
|
||||||
zscore("hackers", "alan turing".getBytes) should equal("1912")
|
zscore("hackers", "alan turing".getBytes).get should equal(1912.0f)
|
||||||
zscore("hackers", "richard stallman".getBytes) should equal("1953")
|
zscore("hackers", "richard stallman".getBytes).get should equal(1953.0f)
|
||||||
zscore("hackers", "claude shannon".getBytes) should equal("1916")
|
zscore("hackers", "claude shannon".getBytes).get should equal(1916.0f)
|
||||||
zscore("hackers", "linus torvalds".getBytes) should equal("1969")
|
zscore("hackers", "linus torvalds".getBytes).get should equal(1969.0f)
|
||||||
|
|
||||||
val s: List[Array[Byte]] = zrange("hackers", 0, 2)
|
val s: List[Array[Byte]] = zrange("hackers", 0, 2)
|
||||||
s.size should equal(3)
|
s.size should equal(3)
|
||||||
|
|
@ -206,6 +206,10 @@ class RedisStorageBackendSpec extends
|
||||||
val t: List[Array[Byte]] = zrange("hackers", 0, -1)
|
val t: List[Array[Byte]] = zrange("hackers", 0, -1)
|
||||||
t.size should equal(6)
|
t.size should equal(6)
|
||||||
t.map(new String(_)) should equal(sorted)
|
t.map(new String(_)) should equal(sorted)
|
||||||
|
|
||||||
|
val u: List[(Array[Byte], Float)] = zrangeWithScore("hackers", 0, -1)
|
||||||
|
u.size should equal(6)
|
||||||
|
u.map{ case (e, s) => new String(e) } should equal(sorted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.redis</groupId>
|
|
||||||
<artifactId>redisclient</artifactId>
|
|
||||||
<version>1.1</version>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
</project>
|
|
||||||
|
|
@ -240,7 +240,7 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AkkaRedisProject(info: ProjectInfo) extends DefaultProject(info) {
|
class AkkaRedisProject(info: ProjectInfo) extends DefaultProject(info) {
|
||||||
val redis = "com.redis" % "redisclient" % "1.1" % "compile"
|
val redis = "com.redis" % "redisclient" % "1.2-SNAPSHOT" % "compile"
|
||||||
override def testOptions = TestFilter((name: String) => name.endsWith("Test")) :: Nil
|
override def testOptions = TestFilter((name: String) => name.endsWith("Test")) :: Nil
|
||||||
lazy val dist = deployTask(info, distPath) dependsOn(`package`) describedAs("Deploying")
|
lazy val dist = deployTask(info, distPath) dependsOn(`package`) describedAs("Deploying")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue