Skip to content

Commit

Permalink
Use a Set rather than a List for getAll
Browse files Browse the repository at this point in the history
The results as well as the parameter are now a Set and removes ordering of the results
  • Loading branch information
Philip Wills committed Jul 15, 2016
1 parent a4ececc commit 471a280
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 50 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,11 @@ scala> val operations = for {
| winnerList = winners.flatMap(_.toOption).toList
| temptedWinners = winnerList.map(temptWithGum)
| _ <- luckyWinners.putAll(temptedWinners.toSet)
| results <- luckyWinners.getAll('name -> List("Charlie", "Violet"))
| results <- luckyWinners.getAll('name -> Set("Charlie", "Violet"))
| } yield results

scala> Scanamo.exec(client)(operations).toList
res1: List[cats.data.Xor[error.DynamoReadError, LuckyWinner]] = List(Right(LuckyWinner(Charlie,human)), Right(LuckyWinner(Violet,blueberry)))
scala> Scanamo.exec(client)(operations)
res1: Set[cats.data.Xor[error.DynamoReadError, LuckyWinner]] = Set(Right(LuckyWinner(Charlie,human)), Right(LuckyWinner(Violet,blueberry)))
```

Note that when using `Table` no operations are actually executed against DynamoDB until `exec` is called.
Expand Down
14 changes: 7 additions & 7 deletions src/main/scala/com/gu/scanamo/Scanamo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ object Scanamo {
* ... Scanamo.putAll(client)("farmers")(Set(
* ... Farmer("Boggis", 43L, Farm(List("chicken"))), Farmer("Bunce", 52L, Farm(List("goose"))), Farmer("Bean", 55L, Farm(List("turkey")))
* ... ))
* ... Scanamo.getAll[Farmer](client)("farmers")(UniqueKeys(KeyList('name, List("Boggis", "Bean"))))
* ... Scanamo.getAll[Farmer](client)("farmers")(UniqueKeys(KeyList('name, Set("Boggis", "Bean"))))
* ... }
* List(Right(Farmer(Boggis,43,Farm(List(chicken)))), Right(Farmer(Bean,55,Farm(List(turkey)))))
* Set(Right(Farmer(Bean,55,Farm(List(turkey)))), Right(Farmer(Boggis,43,Farm(List(chicken)))))
* }}}
* or with some added syntactic sugar:
* {{{
Expand All @@ -123,23 +123,23 @@ object Scanamo {
* ... Scanamo.putAll(client)("farmers")(Set(
* ... Farmer("Boggis", 43L, Farm(List("chicken"))), Farmer("Bunce", 52L, Farm(List("goose"))), Farmer("Bean", 55L, Farm(List("turkey")))
* ... ))
* ... Scanamo.getAll[Farmer](client)("farmers")('name -> List("Boggis", "Bean"))
* ... Scanamo.getAll[Farmer](client)("farmers")('name -> Set("Boggis", "Bean"))
* ... }
* List(Right(Farmer(Boggis,43,Farm(List(chicken)))), Right(Farmer(Bean,55,Farm(List(turkey)))))
* Set(Right(Farmer(Bean,55,Farm(List(turkey)))), Right(Farmer(Boggis,43,Farm(List(chicken)))))
* }}}
* You can also retrieve items from a table with both a hash and range key
* {{{
* >>> case class Doctor(actor: String, regeneration: Int)
* >>> LocalDynamoDB.withTable(client)("doctors")('actor -> S, 'regeneration -> N) {
* ... Scanamo.putAll(client)("doctors")(
* ... Set(Doctor("McCoy", 9), Doctor("Ecclestone", 10), Doctor("Ecclestone", 11)))
* ... Scanamo.getAll[Doctor](client)("doctors")(('actor and 'regeneration) -> List("McCoy" -> 9, "Ecclestone" -> 11))
* ... Scanamo.getAll[Doctor](client)("doctors")(('actor and 'regeneration) -> Set("McCoy" -> 9, "Ecclestone" -> 11))
* ... }
* List(Right(Doctor(McCoy,9)), Right(Doctor(Ecclestone,11)))
* Set(Right(Doctor(McCoy,9)), Right(Doctor(Ecclestone,11)))
* }}}
*/
def getAll[T: DynamoFormat](client: AmazonDynamoDB)(tableName: String)(keys: UniqueKeys[_])
: List[Xor[DynamoReadError, T]] =
: Set[Xor[DynamoReadError, T]] =
exec(client)(ScanamoFree.getAll(tableName)(keys))


Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/com/gu/scanamo/ScanamoAsync.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object ScanamoAsync {
exec(client)(ScanamoFree.get[T](tableName)(key))

def getAll[T: DynamoFormat](client: AmazonDynamoDBAsync)(tableName: String)(keys: UniqueKeys[_])
(implicit ec: ExecutionContext): Future[List[Xor[DynamoReadError, T]]] =
(implicit ec: ExecutionContext): Future[Set[Xor[DynamoReadError, T]]] =
exec(client)(ScanamoFree.getAll[T](tableName)(keys))

def delete[T](client: AmazonDynamoDBAsync)(tableName: String)(key: UniqueKey[_])
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/com/gu/scanamo/ScanamoFree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ object ScanamoFree {
} yield
Option(res.getItem).map(read[T])

def getAll[T: DynamoFormat](tableName: String)(keys: UniqueKeys[_]): ScanamoOps[List[Xor[DynamoReadError, T]]] = {
def getAll[T: DynamoFormat](tableName: String)(keys: UniqueKeys[_]): ScanamoOps[Set[Xor[DynamoReadError, T]]] = {
import collection.convert.decorateAsScala._
for {
res <- ScanamoOps.batchGet(batchGetRequest(tableName)(keys))
} yield
keys.sortByKeys(res.getResponses.get(tableName).asScala.toList).map(read[T])
res.getResponses.get(tableName).asScala.toSet.map(read[T])
}

def delete(tableName: String)(key: UniqueKey[_]): ScanamoOps[DeleteItemResult] =
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/com/gu/scanamo/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ package object scanamo {

implicit def toUniqueKey[T: UniqueKeyCondition](t: T) = UniqueKey(t)

implicit def symbolListTupleToUniqueKeys[V: DynamoFormat](pair: (Symbol, List[V])) =
implicit def symbolListTupleToUniqueKeys[V: DynamoFormat](pair: (Symbol, Set[V])) =
UniqueKeys(KeyList(pair._1, pair._2))

implicit def toMultipleKeyList[H: DynamoFormat, R: DynamoFormat](pair: (HashAndRangeKeyNames, List[(H, R)])) =
implicit def toMultipleKeyList[H: DynamoFormat, R: DynamoFormat](pair: (HashAndRangeKeyNames, Set[(H, R)])) =
UniqueKeys(MultipleKeyList(pair._1.hash -> pair._1.range, pair._2))

implicit def symbolTupleToQuery[V: DynamoFormat](pair: (Symbol, V)) =
Expand Down
35 changes: 6 additions & 29 deletions src/main/scala/com/gu/scanamo/query/UniqueKeyCondition.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,51 +26,28 @@ case class UniqueKey[T: UniqueKeyCondition](t: T) {
}

@typeclass trait UniqueKeyConditions[T] {
def asAVMap(t: T): List[Map[String, AttributeValue]]
def sortByKeys(t: T, l: List[java.util.Map[String, AttributeValue]]): List[java.util.Map[String, AttributeValue]]
def asAVMap(t: T): Set[Map[String, AttributeValue]]
}

object UniqueKeyConditions {
implicit def keyList[V: DynamoFormat] = new UniqueKeyConditions[KeyList[V]] {
override def asAVMap(kl: KeyList[V]): List[Map[String, AttributeValue]] =
override def asAVMap(kl: KeyList[V]): Set[Map[String, AttributeValue]] =
kl.values.map(v => Map(kl.key.name -> DynamoFormat[V].write(v)))

override def sortByKeys(keyList: KeyList[V], l: List[java.util.Map[String, AttributeValue]]): List[java.util.Map[String, AttributeValue]] = {
val keyValueOptions = keyList.values.map(Option(_))
def keyValueOption(avMap: java.util.Map[String, AttributeValue]): Option[V] =
DynamoFormat[V].read(avMap.get(keyList.key.name)).toOption
l.sortBy(i => keyValueOptions.indexOf(keyValueOption(i)))
}


}
implicit def multipleKeyList[H: DynamoFormat, R: DynamoFormat] =
new UniqueKeyConditions[MultipleKeyList[H, R]] {
override def asAVMap(mkl: MultipleKeyList[H, R]): List[Map[String, AttributeValue]] = {
override def asAVMap(mkl: MultipleKeyList[H, R]): Set[Map[String, AttributeValue]] = {
val (hashKey, rangeKey) = mkl.keys
mkl.values.map { case (h, r) =>
Map(hashKey.name -> DynamoFormat[H].write(h), rangeKey.name -> DynamoFormat[R].write(r))
}
}

override def sortByKeys(mkl: MultipleKeyList[H, R], l: List[util.Map[String, AttributeValue]]): List[util.Map[String, AttributeValue]] = {
val (hash, range) = mkl.keys
val keyValueOptions = mkl.values.map(Option(_))
def keyValueOption(avMap: java.util.Map[String, AttributeValue]): Option[(H, R)] =
for {
h <- DynamoFormat[H].read(avMap.get(hash.name)).toOption
r <- DynamoFormat[R].read(avMap.get(range.name)).toOption
} yield (h, r)

l.sortBy(i => keyValueOptions.indexOf(keyValueOption(i)))
}
}
}

case class UniqueKeys[T: UniqueKeyConditions](t: T) {
def asAVMap: List[Map[String, AttributeValue]] = UniqueKeyConditions[T].asAVMap(t)
def sortByKeys(l: List[util.Map[String, AttributeValue]]) = UniqueKeyConditions[T].sortByKeys(t, l)
def asAVMap: Set[Map[String, AttributeValue]] = UniqueKeyConditions[T].asAVMap(t)
}

case class KeyList[T: DynamoFormat](key: Symbol, values: List[T])
case class MultipleKeyList[H: DynamoFormat, R: DynamoFormat](keys: (Symbol, Symbol), values: List[(H, R)])
case class KeyList[T: DynamoFormat](key: Symbol, values: Set[T])
case class MultipleKeyList[H: DynamoFormat, R: DynamoFormat](keys: (Symbol, Symbol), values: Set[(H, R)])
12 changes: 6 additions & 6 deletions src/test/scala/com/gu/scanamo/ScanamoAsyncTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,14 @@ class ScanamoAsyncTest extends FunSpec with Matchers with ScalaFutures {
))

ScanamoAsync.getAll[Farmer](client)("asyncFarmers")(
UniqueKeys(KeyList('name, List("Boggis", "Bean")))
UniqueKeys(KeyList('name, Set("Boggis", "Bean")))
).futureValue should equal(
List(Right(Farmer("Boggis", 43, Farm(List("chicken")))), Right(Farmer("Bean", 55, Farm(List("turkey"))))))
Set(Right(Farmer("Boggis", 43, Farm(List("chicken")))), Right(Farmer("Bean", 55, Farm(List("turkey"))))))

import com.gu.scanamo.syntax._

ScanamoAsync.getAll[Farmer](client)("asyncFarmers")('name -> List("Boggis", "Bean")).futureValue should equal(
List(Right(Farmer("Boggis", 43, Farm(List("chicken")))), Right(Farmer("Bean", 55, Farm(List("turkey"))))))
ScanamoAsync.getAll[Farmer](client)("asyncFarmers")('name -> Set("Boggis", "Bean")).futureValue should equal(
Set(Right(Farmer("Boggis", 43, Farm(List("chicken")))), Right(Farmer("Bean", 55, Farm(List("turkey"))))))
}

LocalDynamoDB.usingTable(client)("asyncDoctors")('actor -> S, 'regeneration -> N) {
Expand All @@ -284,9 +284,9 @@ class ScanamoAsyncTest extends FunSpec with Matchers with ScalaFutures {

import com.gu.scanamo.syntax._
ScanamoAsync.getAll[Doctor](client)("asyncDoctors")(
('actor and 'regeneration) -> List("McCoy" -> 9, "Ecclestone" -> 11)
('actor and 'regeneration) -> Set("McCoy" -> 9, "Ecclestone" -> 11)
).futureValue should equal(
List(Right(Doctor("McCoy", 9)), Right(Doctor("Ecclestone", 11))))
Set(Right(Doctor("McCoy", 9)), Right(Doctor("Ecclestone", 11))))

}
}
Expand Down

0 comments on commit 471a280

Please sign in to comment.