diff options
author | kseniya <ktomskih@datamonsters.co> | 2017-09-20 17:58:19 +0700 |
---|---|---|
committer | kseniya <ktomskih@datamonsters.co> | 2017-09-20 17:58:19 +0700 |
commit | d82c93fef0fc0bb937220334f73c264fbb1082f2 (patch) | |
tree | 44e957d95b0d5a9c9edcbd84e9e005bc89d95264 /src/main/scala/xyz/driver/pdsuicommon/synchronization/db | |
parent | b9f3ab180253c346664f4f38b418dc96f54bdc31 (diff) | |
download | rest-query-d82c93fef0fc0bb937220334f73c264fbb1082f2.tar.gz rest-query-d82c93fef0fc0bb937220334f73c264fbb1082f2.tar.bz2 rest-query-d82c93fef0fc0bb937220334f73c264fbb1082f2.zip |
Common code for synchronizers
Diffstat (limited to 'src/main/scala/xyz/driver/pdsuicommon/synchronization/db')
3 files changed, 145 insertions, 0 deletions
diff --git a/src/main/scala/xyz/driver/pdsuicommon/synchronization/db/SlickDataSource.scala b/src/main/scala/xyz/driver/pdsuicommon/synchronization/db/SlickDataSource.scala new file mode 100644 index 0000000..63514ec --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/synchronization/db/SlickDataSource.scala @@ -0,0 +1,23 @@ +package xyz.driver.pdsuicommon.synchronization.db + +import slick.dbio.DBIO + +import scalaz.OptionT + +trait SlickDataSource[T] { + + val isDictionary: Boolean = false + + /** + * @return New entity + */ + def create(x: T): DBIO[T] + + /** + * @return Updated entity + */ + def update(x: T): OptionT[DBIO, T] + + def delete(x: T): OptionT[DBIO, Unit] + +} diff --git a/src/main/scala/xyz/driver/pdsuicommon/synchronization/db/SlickDbAction.scala b/src/main/scala/xyz/driver/pdsuicommon/synchronization/db/SlickDbAction.scala new file mode 100644 index 0000000..57cc3d4 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/synchronization/db/SlickDbAction.scala @@ -0,0 +1,70 @@ +package xyz.driver.pdsuicommon.synchronization.db + +import slick.dbio.DBIO +import xyz.driver.pdsuicommon.logging._ +import xyz.driver.pdsuicommon.synchronization.utils.{FakeIdGen, FakeIdMap} + +import scala.concurrent.ExecutionContext +import scalaz.Monad + +trait SlickDbAction[+T] { + def entity: T +} + +object SlickDbAction { + + final case class Create[T](entity: T) extends SlickDbAction[T] + final case class Update[T](entity: T) extends SlickDbAction[T] + final case class Delete[T](entity: T) extends SlickDbAction[T] + + // Use it only inside of a transaction! + def unsafeRun[T](actions: List[SlickDbAction[T]], dataSource: SlickDataSource[T])( + implicit core: FakeIdGen[T], + executionContext: ExecutionContext, + dbioMonad: Monad[DBIO]): DBIO[FakeIdMap[T]] = { + unsafeRun(DBIO.successful(FakeIdMap.empty))(actions, dataSource) + } + + // Use it only inside of a transaction! + def unsafeRun[T](initial: DBIO[FakeIdMap[T]])(actions: List[SlickDbAction[T]], dataSource: SlickDataSource[T])( + implicit core: FakeIdGen[T], + executionContext: ExecutionContext, + dbioMonad: Monad[DBIO]): DBIO[FakeIdMap[T]] = { + // TODO Squash Updates and Delete to one operation, when bugs in repositories will be fixed + actions.foldLeft(initial) { + case (previousActions, Create(x)) => + for { + r <- previousActions + newArm <- dataSource.create(x) + } yield { + r + (core(newArm) -> newArm) + } + + case (previousActions, Update(x)) => + for { + r <- previousActions + updatedArm <- dataSource.update(x).getOrElse(x) + } yield { + r - core(updatedArm) + (core(updatedArm) -> updatedArm) + } + + case (previousActions, Delete(_)) if dataSource.isDictionary => + previousActions // We don't delete entities from dictionaries + + case (previousActions, Delete(x)) => + for { + r <- previousActions + _ <- dataSource.delete(x).run + } yield { + r - core(x) + } + } + } + + implicit def toPhiString[T](input: SlickDbAction[T])(implicit inner: T => PhiString): PhiString = input match { + case Create(x) => phi"Create($x)" + case Update(x) => phi"Update($x)" + case Delete(x) => phi"Delete($x)" + } + +} diff --git a/src/main/scala/xyz/driver/pdsuicommon/synchronization/db/SlickDbDiff.scala b/src/main/scala/xyz/driver/pdsuicommon/synchronization/db/SlickDbDiff.scala new file mode 100644 index 0000000..c226659 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/synchronization/db/SlickDbDiff.scala @@ -0,0 +1,52 @@ +package xyz.driver.pdsuicommon.synchronization.db + +import xyz.driver.pdsuicommon.synchronization.domain.FakeId +import xyz.driver.pdsuicommon.synchronization.utils.{FakeIdGen, Refiner} + +import scala.annotation.tailrec +import scala.collection.breakOut +import scala.collection.immutable.SortedSet + +object SlickDbDiff { + + /** + * Calculates DB-actions to synchronize origEntities with draftEntities. + */ + def calc[DraftT, OrigT](origEntities: Iterable[OrigT], draftEntities: Iterable[DraftT])( + implicit draftFakeIdGen: FakeIdGen[DraftT], + origFakeIdGen: FakeIdGen[OrigT], + refiner: Refiner[DraftT, OrigT]): List[SlickDbAction[OrigT]] = { + val origMap: Map[FakeId, OrigT] = origEntities.map(x => origFakeIdGen(x) -> x)(breakOut) + val uniqueDraftEntities = SortedSet.newBuilder[DraftT](Ordering.by[DraftT, FakeId](draftFakeIdGen)) + uniqueDraftEntities ++= draftEntities + + loop(origMap, uniqueDraftEntities.result(), List.empty) + } + + @tailrec private def loop[DraftT, OrigT](origEntitiesMap: Map[FakeId, OrigT], + draftEntities: Iterable[DraftT], + actions: List[SlickDbAction[OrigT]])( + implicit draftFakeIdGen: FakeIdGen[DraftT], + refiner: Refiner[DraftT, OrigT]): List[SlickDbAction[OrigT]] = { + draftEntities.headOption match { + case None => + // The rest original entities are not a part of draft, so we will delete them + val toDelete: List[SlickDbAction[OrigT]] = origEntitiesMap.values.map(x => SlickDbAction.Delete(x))(breakOut) + actions ++ toDelete + + case Some(currRaw) => + val rawCore = draftFakeIdGen.getFor(currRaw) + val action: Option[SlickDbAction[OrigT]] = origEntitiesMap.get(rawCore) match { + // It is a new entity, because it doesn't exist among originals + case None => Some(SlickDbAction.Create(refiner.refine(currRaw))) + case Some(orig) => + val draft = refiner.refresh(orig, currRaw) + if (draft == orig) None + else Some(SlickDbAction.Update(draft)) + } + + loop(origEntitiesMap - rawCore, draftEntities.tail, action.map(_ :: actions).getOrElse(actions)) + } + } + +} |