Added correct ordering for TimeBasedUUID offset (#28387)

This commit is contained in:
Didac 2019-12-18 01:39:45 +01:00 committed by Helena Edelson
parent aa2493c475
commit da0a906b08
3 changed files with 105 additions and 1 deletions

View file

@ -0,0 +1,66 @@
/*
* Copyright (C) 2016-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.util
import java.util.Comparator
import java.util.UUID
/**
* Scala implementation of UUIDComparator in
* https://github.com/cowtowncoder/java-uuid-generator
* Apache License 2.0.
*/
class UUIDComparator extends Comparator[UUID] {
def compare(u1: UUID, u2: UUID): Int = {
// First: major sorting by types
val version = u1.version()
val diff = version - u2.version()
if (diff != 0) {
diff
} else {
// Second: for time-based variant, order by time stamp:
if (version == 1) {
val diff2 = compareULongs(u1.timestamp(), u2.timestamp())
if (diff2 == 0) {
// or if that won't work, by other bits lexically
compareULongs(u1.getLeastSignificantBits(), u2.getLeastSignificantBits())
} else
diff2
} else {
// note: java.util.Uuids compares with sign extension, IMO that's wrong, so:
val diff2 = compareULongs(u1.getMostSignificantBits(), u2.getMostSignificantBits())
if (diff2 == 0) {
compareULongs(u1.getLeastSignificantBits(), u2.getLeastSignificantBits())
} else
diff2
}
}
}
private def compareULongs(l1: Long, l2: Long): Int = {
val diff = compareUInts((l1 >> 32).toInt, (l2 >> 32).toInt)
if (diff == 0)
compareUInts(l1.toInt, l2.toInt)
else
diff
}
private def compareUInts(i1: Int, i2: Int): Int =
/* bit messier due to java's insistence on signed values: if both
* have same sign, normal comparison (by subtraction) works fine;
* but if signs don't agree need to resolve differently
*/
if (i1 < 0) {
if (i2 < 0) (i1 - i2) else 1
} else {
if (i2 < 0) -1 else (i1 - i2)
}
}
object UUIDComparator {
val comparator = new UUIDComparator
}

View file

@ -6,6 +6,8 @@ package akka.persistence.query
import java.util.UUID
import akka.util.UUIDComparator
object Offset {
// factories to aid discoverability
@ -44,7 +46,7 @@ final case class TimeBasedUUID(value: UUID) extends Offset with Ordered[TimeBase
throw new IllegalArgumentException("UUID " + value + " is not a time-based UUID")
}
override def compare(other: TimeBasedUUID): Int = value.compareTo(other.value)
override def compare(other: TimeBasedUUID): Int = UUIDComparator.comparator.compare(value, other.value)
}
/**

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.persistence.query
import java.util.UUID
import org.scalatest.{ Matchers, WordSpecLike }
import scala.util.Random
class OffsetSpec extends WordSpecLike with Matchers {
"TimeBasedUUID offset" must {
"be ordered correctly" in {
val uuid1 = TimeBasedUUID(UUID.fromString("49225740-2019-11ea-a752-ffae2393b6e4")) //2019-12-16T15:32:36.148Z[UTC]
val uuid2 = TimeBasedUUID(UUID.fromString("91be23d0-2019-11ea-a752-ffae2393b6e4")) //2019-12-16T15:34:37.965Z[UTC]
val uuid3 = TimeBasedUUID(UUID.fromString("91f95810-2019-11ea-a752-ffae2393b6e4")) //2019-12-16T15:34:38.353Z[UTC]
uuid1.value.timestamp() should be < uuid2.value.timestamp()
uuid2.value.timestamp() should be < uuid3.value.timestamp()
List(uuid2, uuid1, uuid3).sorted shouldEqual List(uuid1, uuid2, uuid3)
List(uuid3, uuid2, uuid1).sorted shouldEqual List(uuid1, uuid2, uuid3)
}
}
"Sequence offset" must {
"be ordered correctly" in {
val sequenceBasedList = List(1L, 2L, 3L).map(Sequence)
Random.shuffle(sequenceBasedList).sorted shouldEqual sequenceBasedList
}
}
}