Skip to content

Commit

Permalink
Optimize Seq and Map encoders
Browse files Browse the repository at this point in the history
  • Loading branch information
Georgi Krastev committed Jul 8, 2021
1 parent afde2a1 commit 770a524
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 20 deletions.
14 changes: 9 additions & 5 deletions src/main/scala/scynamo/ScynamoDecoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,15 @@ object StackFrame {

private[scynamo] def push[A](
encoded: EitherNec[ScynamoEncodeError, A],
frame: => StackFrame
): EitherNec[ScynamoEncodeError, A] = {
lazy val stackFrame = frame
encoded.leftMap(_.map(_.push(stackFrame)))
}
frame: StackFrame
): EitherNec[ScynamoEncodeError, A] =
encoded.leftMap(push(_, frame))

private[scynamo] def push[A](
errors: NonEmptyChain[ScynamoEncodeError],
frame: StackFrame
): NonEmptyChain[ScynamoEncodeError] =
errors.map(_.push(frame))
}

trait ScynamoDecoder[A] extends ScynamoDecoderFunctions { self =>
Expand Down
48 changes: 34 additions & 14 deletions src/main/scala/scynamo/ScynamoEncoder.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package scynamo

import cats.Contravariant
import cats.data.EitherNec
import cats.data.{Chain, EitherNec, NonEmptyChain}
import cats.syntax.all._
import scynamo.StackFrame.{Index, MapKey}
import scynamo.generic.auto.AutoDerivationUnlocked
Expand All @@ -12,7 +12,8 @@ import shapeless.tag.@@
import software.amazon.awssdk.services.dynamodb.model.AttributeValue

import java.time.Instant
import java.util.UUID
import java.util.{Collections, UUID}
import scala.collection.compat._
import scala.collection.immutable.Seq
import scala.concurrent.duration.{Duration, FiniteDuration}

Expand Down Expand Up @@ -80,10 +81,15 @@ trait DefaultScynamoEncoderInstances extends ScynamoIterableEncoder {
stringEncoder.contramap(_.toString)

implicit def seqEncoder[A](implicit element: ScynamoEncoder[A]): ScynamoEncoder[Seq[A]] =
ScynamoEncoder.instance {
_.zipWithIndex
.parTraverse { case (x, i) => StackFrame.push(element.encode(x), Index(i)) }
.map(xs => AttributeValue.builder.l(xs: _*).build())
ScynamoEncoder.instance { xs =>
var allErrors = Chain.empty[ScynamoEncodeError]
val attrValues = List.newBuilder[AttributeValue]
for ((x, i) <- xs.iterator.zipWithIndex) element.encode(x) match {
case Right(attr) => attrValues += attr
case Left(errors) => allErrors ++= StackFrame.push(errors, Index(i)).toChain
}

NonEmptyChain.fromChain(allErrors).toLeft(AttributeValue.builder.l(attrValues.result(): _*).build())
}

implicit def listEncoder[A: ScynamoEncoder]: ScynamoEncoder[List[A]] =
Expand Down Expand Up @@ -111,10 +117,17 @@ trait DefaultScynamoEncoderInstances extends ScynamoIterableEncoder {
numberStringEncoder.contramap(_.toNanos.toString)

implicit def mapEncoder[A, B](implicit key: ScynamoKeyEncoder[A], value: ScynamoEncoder[B]): ScynamoEncoder[Map[A, B]] =
ScynamoEncoder.instance {
_.toVector
.parTraverse { case (k, v) => StackFrame.push((key.encode(k), value.encode(v)).parTupled, MapKey(k)) }
.map(kvs => AttributeValue.builder.m(ScynamoEncoder.attributes(kvs)).build())
ScynamoEncoder.instance { kvs =>
var allErrors = Chain.empty[ScynamoEncodeError]
val attrValues = new java.util.HashMap[String, AttributeValue](kvs.size)
kvs.foreachEntry { (k, v) =>
(key.encode(k), value.encode(v)).parTupled match {
case Right((k, attr)) => if (!attr.nul) attrValues.put(k, attr)
case Left(errors) => allErrors ++= StackFrame.push(errors, MapKey(k)).toChain
}
}

NonEmptyChain.fromChain(allErrors).toLeft(AttributeValue.builder.m(attrValues).build())
}

implicit val attributeValueEncoder: ScynamoEncoder[AttributeValue] = { value =>
Expand Down Expand Up @@ -172,10 +185,17 @@ object ObjectScynamoEncoder extends SemiautoDerivationEncoder {
}

implicit def mapEncoder[A](implicit value: ScynamoEncoder[A]): ObjectScynamoEncoder[Map[String, A]] =
instance {
_.toVector
.parTraverse { case (k, v) => value.encode(v).tupleLeft(k) }
.map(ScynamoEncoder.attributes)
instance { kvs =>
var allErrors = Chain.empty[ScynamoEncodeError]
val attrValues = new java.util.HashMap[String, AttributeValue](kvs.size)
kvs.foreachEntry { (k, v) =>
value.encode(v) match {
case Right(attr) => if (!attr.nul) attrValues.put(k, attr)
case Left(errors) => allErrors ++= StackFrame.push(errors, MapKey(k)).toChain
}
}

NonEmptyChain.fromChain(allErrors).toLeft(Collections.unmodifiableMap(attrValues))
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/scynamo/generic/GenericScynamoEncoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package scynamo.generic
import scynamo.ObjectScynamoEncoder
import shapeless.{LabelledGeneric, Lazy}

import java.util.Collections

trait GenericScynamoEncoder[A] extends ObjectScynamoEncoder[A]

object GenericScynamoEncoder extends GenericScynamoEncoderInstances
Expand All @@ -12,5 +14,5 @@ trait GenericScynamoEncoderInstances {
gen: LabelledGeneric.Aux[F, G],
sg: Lazy[ShapelessScynamoEncoder[F, G]]
): GenericScynamoEncoder[F] =
value => sg.value.encodeMap(gen.to(value))
value => sg.value.encodeMap(gen.to(value)).map(Collections.unmodifiableMap(_))
}

0 comments on commit 770a524

Please sign in to comment.