Skip to content

Commit

Permalink
Return the new value after an update
Browse files Browse the repository at this point in the history
  • Loading branch information
Philip Wills committed Sep 23, 2016
1 parent c18777b commit de2e6c6
Show file tree
Hide file tree
Showing 8 changed files with 44 additions and 37 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,11 @@ scala> case class Team(name: String, goals: Int, scorers: List[String], mascot:
scala> val teamTable = Table[Team]("teams")
scala> val operations = for {
| _ <- teamTable.put(Team("Watford", 1, List("Blissett"), Some("Harry the Hornet")))
| _ <- teamTable.update('name -> "Watford", set('goals -> 2) and append('scorers -> "Barnes") and remove('mascot))
| watford <- teamTable.get('name -> "Watford")
| } yield watford
| updated <- teamTable.update('name -> "Watford", set('goals -> 2) and append('scorers -> "Barnes") and remove('mascot))
| } yield updated

scala> Scanamo.exec(client)(operations)
res1: Option[cats.data.Xor[error.DynamoReadError, Team]] = Some(Right(Team(Watford,2,List(Blissett, Barnes),None)))
res1: cats.data.Xor[error.DynamoReadError, Team] = Right(Team(Watford,2,List(Blissett, Barnes),None))
```

### Using Indexes
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/com/gu/scanamo/Scanamo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ object Scanamo {
* List(Right(Forecast(London,Sun)))
* }}}
*/
def update[T: UpdateExpression](client: AmazonDynamoDB)(tableName: String)(key: UniqueKey[_], expression: T): UpdateItemResult =
exec(client)(ScanamoFree.update(tableName)(key)(expression))
def update[V: DynamoFormat, U: UpdateExpression](client: AmazonDynamoDB)(tableName: String)(key: UniqueKey[_], expression: U): Xor[DynamoReadError, V] =
exec(client)(ScanamoFree.update[V, U](tableName)(key)(expression))

/**
* Scans all elements of a table
Expand Down
7 changes: 4 additions & 3 deletions src/main/scala/com/gu/scanamo/ScanamoAsync.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ object ScanamoAsync {
(implicit ec: ExecutionContext): Future[DeleteItemResult] =
exec(client)(ScanamoFree.delete(tableName)(key))

def update[T: UpdateExpression](client: AmazonDynamoDBAsync)(tableName: String)(key: UniqueKey[_], expression: T)
(implicit ec: ExecutionContext): Future[UpdateItemResult] =
exec(client)(ScanamoFree.update(tableName)(key)(expression))
def update[V: DynamoFormat, U: UpdateExpression](client: AmazonDynamoDBAsync)(tableName: String)(
key: UniqueKey[_], expression: U)(implicit ec: ExecutionContext
): Future[Xor[DynamoReadError,V]] =
exec(client)(ScanamoFree.update[V, U](tableName)(key)(expression))

def scan[T: DynamoFormat](client: AmazonDynamoDBAsync)(tableName: String)
(implicit ec: ExecutionContext): Future[List[Xor[DynamoReadError, T]]] =
Expand Down
12 changes: 7 additions & 5 deletions src/main/scala/com/gu/scanamo/ScanamoFree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ object ScanamoFree {
import cats.syntax.traverse._
import collection.convert.decorateAsJava._

def given[T: ConditionExpression](tableName: String)(condition: T): ConditionalOperation[T] =
ConditionalOperation(tableName, condition)

def put[T](tableName: String)(item: T)(implicit f: DynamoFormat[T]): ScanamoOps[PutItemResult] =
ScanamoOps.put(ScanamoPutRequest(tableName, f.write(item), None))

Expand Down Expand Up @@ -76,9 +73,14 @@ object ScanamoFree {
def queryIndexWithLimit[T: DynamoFormat](tableName: String, indexName: String)(query: Query[_], limit: Int): ScanamoOps[List[Xor[DynamoReadError, T]]] =
QueryResultStream.stream[T](query(new QueryRequest().withTableName(tableName)).withIndexName(indexName).withLimit(limit))

def update[T](tableName: String)(key: UniqueKey[_])(expression: T)(implicit update: UpdateExpression[T]) =
def update[T, U](tableName: String)(key: UniqueKey[_])(expression: U)(
implicit update: UpdateExpression[U], format: DynamoFormat[T]
): ScanamoOps[Xor[DynamoReadError, T]] =
ScanamoOps.update(ScanamoUpdateRequest(
tableName, key.asAVMap, update.expression(expression), update.attributeNames(expression), update.attributeValues(expression), None))
tableName, key.asAVMap, update.expression(expression), update.attributeNames(expression), update.attributeValues(expression), None)
).map(
r => format.read(new AttributeValue().withM(r.getAttributes))
)

/**
* {{{
Expand Down
29 changes: 13 additions & 16 deletions src/main/scala/com/gu/scanamo/Table.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ case class Table[V: DynamoFormat](name: String) {
def index(indexName: String) = Index[V](name, indexName)

/**
* Updates an attribute that is not part of the key
* Updates an attribute that is not part of the key and returns the updated row
*
* To set an attribute:
*
Expand All @@ -80,12 +80,11 @@ case class Table[V: DynamoFormat](name: String) {
* ... import com.gu.scanamo.syntax._
* ... val operations = for {
* ... _ <- forecast.put(Forecast("London", "Rain"))
* ... _ <- forecast.update('location -> "London", set('weather -> "Sun"))
* ... results <- forecast.scan()
* ... } yield results.toList
* ... updated <- forecast.update('location -> "London", set('weather -> "Sun"))
* ... } yield updated
* ... Scanamo.exec(client)(operations)
* ... }
* List(Right(Forecast(London,Sun)))
* Right(Forecast(London,Sun))
* }}}
*
* List attributes can also be appended or prepended to:
Expand Down Expand Up @@ -116,13 +115,12 @@ case class Table[V: DynamoFormat](name: String) {
* ... import com.gu.scanamo.syntax._
* ... val operations = for {
* ... _ <- foos.put(Foo("x", 0, List("First")))
* ... _ <- foos.update('name -> "x",
* ... updated <- foos.update('name -> "x",
* ... append('l -> "Second") and set('bar -> 1))
* ... results <- foos.scan()
* ... } yield results.toList
* ... } yield updated
* ... Scanamo.exec(client)(operations)
* ... }
* List(Right(Foo(x,1,List(First, Second))))
* Right(Foo(x,1,List(First, Second)))
* }}}
*
* It's also possible to perform `ADD` and `DELETE` updates
Expand All @@ -136,16 +134,15 @@ case class Table[V: DynamoFormat](name: String) {
* ... _ <- bars.put(Bar("x", 1L, Set("First")))
* ... _ <- bars.update('name -> "x",
* ... add('counter -> 10L) and add('set -> Set("Second")))
* ... _ <- bars.update('name -> "x", delete('set -> Set("First")))
* ... results <- bars.scan()
* ... } yield results.toList
* ... updatedBar <- bars.update('name -> "x", delete('set -> Set("First")))
* ... } yield updatedBar
* ... Scanamo.exec(client)(operations)
* ... }
* List(Right(Bar(x,11,Set(Second))))
* Right(Bar(x,11,Set(Second)))
* }}}
*/
def update[T: UpdateExpression](key: UniqueKey[_], expression: T) =
ScanamoFree.update(name)(key)(expression)
def update[U: UpdateExpression](key: UniqueKey[_], expression: U): ScanamoOps[Xor[DynamoReadError, V]] =
ScanamoFree.update[V, U](name)(key)(expression)

/**
* Query or scan a table, limiting the number of items evaluated by Dynamo
Expand Down Expand Up @@ -311,7 +308,7 @@ case class Table[V: DynamoFormat](name: String) {
* List(Right(Gremlin(2,true,false)), Right(Gremlin(1,false,true)))
* }}}
*/
def given[T: ConditionExpression](condition: T) = ScanamoFree.given(name)(condition)
def given[T: ConditionExpression](condition: T) = ConditionalOperation[V,T](name, condition)

/**
* Scans all elements of a table
Expand Down
7 changes: 5 additions & 2 deletions src/main/scala/com/gu/scanamo/error/DynamoReadError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package com.gu.scanamo.error
import cats.{Semigroup, Show}
import cats.data.NonEmptyList
import cats.instances.list._
import com.amazonaws.services.dynamodbv2.model.AttributeValue
import com.amazonaws.services.dynamodbv2.model.{AttributeValue, ConditionalCheckFailedException}

sealed abstract class DynamoReadError
sealed abstract class ScanamoError
final case class ConditionNotMet(e: ConditionalCheckFailedException) extends ScanamoError

sealed abstract class DynamoReadError extends ScanamoError
final case class NoPropertyOfType(propertyType: String, actual: AttributeValue) extends DynamoReadError
final case class TypeCoercionError(t: Throwable) extends DynamoReadError
final case object MissingProperty extends DynamoReadError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ object ScanamoInterpreters {
.withUpdateExpression(req.updateExpression)
.withExpressionAttributeNames(req.attributeNames.asJava)
.withExpressionAttributeValues(req.attributeValues.asJava)
.withReturnValues(ReturnValue.ALL_NEW)
)((r, c) =>
c.attributeValues.foldLeft(
r.withConditionExpression(c.expression).withExpressionAttributeNames(
Expand Down
14 changes: 9 additions & 5 deletions src/main/scala/com/gu/scanamo/query/ConditionExpression.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ package com.gu.scanamo.query
import cats.data.Xor
import com.amazonaws.services.dynamodbv2.model._
import com.gu.scanamo.DynamoFormat
import com.gu.scanamo.error.{ConditionNotMet, ScanamoError}
import com.gu.scanamo.ops.ScanamoOps
import com.gu.scanamo.request.{RequestCondition, ScanamoDeleteRequest, ScanamoPutRequest, ScanamoUpdateRequest}
import com.gu.scanamo.update.UpdateExpression
import simulacrum.typeclass

case class ConditionalOperation[T](tableName: String, t: T)(implicit state: ConditionExpression[T]) {
def put[V](item: V)(implicit f: DynamoFormat[V]): ScanamoOps[Xor[ConditionalCheckFailedException, PutItemResult]] = {
val unconditionalRequest = ScanamoPutRequest(tableName, f.write(item), None)
case class ConditionalOperation[V, T](tableName: String, t: T)(
implicit state: ConditionExpression[T], format: DynamoFormat[V]) {
def put(item: V): ScanamoOps[Xor[ConditionalCheckFailedException, PutItemResult]] = {
val unconditionalRequest = ScanamoPutRequest(tableName, format.write(item), None)
ScanamoOps.conditionalPut(unconditionalRequest.copy(
condition = Some(state.apply(t)(unconditionalRequest.condition))))
}
Expand All @@ -22,12 +24,14 @@ case class ConditionalOperation[T](tableName: String, t: T)(implicit state: Cond
}

def update[U](key: UniqueKey[_], expression: U)(implicit update: UpdateExpression[U]):
ScanamoOps[Xor[ConditionalCheckFailedException, UpdateItemResult]] = {
ScanamoOps[Xor[ScanamoError, V]] = {

val unconditionalRequest = ScanamoUpdateRequest(
tableName, key.asAVMap, update.expression(expression), update.attributeNames(expression), update.attributeValues(expression), None)
ScanamoOps.conditionalUpdate(unconditionalRequest.copy(
condition = Some(state.apply(t)(unconditionalRequest.condition))))
condition = Some(state.apply(t)(unconditionalRequest.condition)))
).map(xor => xor.leftMap(ConditionNotMet(_)).flatMap(
r => format.read(new AttributeValue().withM(r.getAttributes))))
}
}

Expand Down

0 comments on commit de2e6c6

Please sign in to comment.