From 770a52485d7f4c4f0b5c03d0c63ef35a27704170 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Thu, 8 Jul 2021 08:30:34 +0200 Subject: [PATCH] Optimize Seq and Map encoders --- src/main/scala/scynamo/ScynamoDecoder.scala | 14 ++++-- src/main/scala/scynamo/ScynamoEncoder.scala | 48 +++++++++++++------ .../generic/GenericScynamoEncoder.scala | 4 +- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/main/scala/scynamo/ScynamoDecoder.scala b/src/main/scala/scynamo/ScynamoDecoder.scala index e270d480..e19be8d2 100644 --- a/src/main/scala/scynamo/ScynamoDecoder.scala +++ b/src/main/scala/scynamo/ScynamoDecoder.scala @@ -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 => diff --git a/src/main/scala/scynamo/ScynamoEncoder.scala b/src/main/scala/scynamo/ScynamoEncoder.scala index e30ae97a..bb80b7eb 100644 --- a/src/main/scala/scynamo/ScynamoEncoder.scala +++ b/src/main/scala/scynamo/ScynamoEncoder.scala @@ -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 @@ -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} @@ -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]] = @@ -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 => @@ -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)) } } diff --git a/src/main/scala/scynamo/generic/GenericScynamoEncoder.scala b/src/main/scala/scynamo/generic/GenericScynamoEncoder.scala index 09a8a690..3ca83e13 100644 --- a/src/main/scala/scynamo/generic/GenericScynamoEncoder.scala +++ b/src/main/scala/scynamo/generic/GenericScynamoEncoder.scala @@ -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 @@ -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(_)) }