diff options
author | Stewart Stewart <stewinsalot@gmail.com> | 2016-12-20 01:54:57 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-20 01:54:57 -0500 |
commit | 687919e7c163e1ff8a002f1a2c24e4b37f75f20d (patch) | |
tree | b185f9e8c9d0bf515b989ba8c13dc0f264e6fd25 | |
parent | dd8cbb3ada14a4f05d5683b90a5b83dc4b3b35f5 (diff) | |
parent | f0a5e41ec45d2420ba7173e156806f81701f9796 (diff) | |
download | driver-core-687919e7c163e1ff8a002f1a2c24e4b37f75f20d.tar.gz driver-core-687919e7c163e1ff8a002f1a2c24e4b37f75f20d.tar.bz2 driver-core-687919e7c163e1ff8a002f1a2c24e4b37f75f20d.zip |
Merge pull request #8 from drivergroup/dal-improvementsv0.9.29
Dal improvements
-rw-r--r-- | project/plugins.sbt | 2 | ||||
-rw-r--r-- | scalastyle-config.xml | 2 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/core.scala | 5 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/database/Dal.scala | 47 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/database/database.scala (renamed from src/main/scala/xyz/driver/core/database.scala) | 63 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/database/package.scala | 31 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/date.scala | 25 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/generators.scala | 3 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/json.scala | 13 | ||||
-rw-r--r-- | src/main/scala/xyz/driver/core/time.scala | 6 | ||||
-rw-r--r-- | src/test/scala/xyz/driver/core/CoreTest.scala | 18 | ||||
-rw-r--r-- | src/test/scala/xyz/driver/core/JsonTest.scala | 16 |
12 files changed, 173 insertions, 58 deletions
diff --git a/project/plugins.sbt b/project/plugins.sbt index 981c484..a4722e8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ resolvers += "releases" at "https://drivergrp.jfrog.io/drivergrp/releases" credentials += Credentials("Artifactory Realm", "drivergrp.jfrog.io", "sbt-publisher", "ANC-d8X-Whm-USS") -addSbtPlugin("xyz.driver" % "sbt-settings" % "0.5.47") +addSbtPlugin("xyz.driver" % "sbt-settings" % "0.5.48") diff --git a/scalastyle-config.xml b/scalastyle-config.xml index 811d745..1dd0fdf 100644 --- a/scalastyle-config.xml +++ b/scalastyle-config.xml @@ -64,7 +64,7 @@ </check> <check level="error" class="org.scalastyle.scalariform.NumberOfTypesChecker" enabled="true"> <parameters> - <parameter name="maxTypes"><![CDATA[30]]></parameter> + <parameter name="maxTypes"><![CDATA[50]]></parameter> </parameters> </check> <check level="error" class="org.scalastyle.scalariform.CyclomaticComplexityChecker" enabled="true"> diff --git a/src/main/scala/xyz/driver/core/core.scala b/src/main/scala/xyz/driver/core/core.scala index 783150a..8c13aeb 100644 --- a/src/main/scala/xyz/driver/core/core.scala +++ b/src/main/scala/xyz/driver/core/core.scala @@ -20,6 +20,11 @@ package object core { resource.close() } } + + object tagging { + private[core] trait Tagged[+V, +Tag] + } + type @@[+V, +Tag] = V with tagging.Tagged[V, Tag] } package core { diff --git a/src/main/scala/xyz/driver/core/database/Dal.scala b/src/main/scala/xyz/driver/core/database/Dal.scala new file mode 100644 index 0000000..e920392 --- /dev/null +++ b/src/main/scala/xyz/driver/core/database/Dal.scala @@ -0,0 +1,47 @@ +package xyz.driver.core.database + +import scala.concurrent.{ExecutionContext, Future} + +import scalaz.{ListT, Monad} +import scalaz.std.scalaFuture._ + +trait Dal { + protected type T[D] + protected implicit val monadT: Monad[T] + + protected def execute[D](operations: T[D]): Future[D] + protected def noAction[V](v: V): T[V] + protected def customAction[R](action: => Future[R]): T[R] +} + +class FutureDal(executionContext: ExecutionContext) extends Dal { + implicit val exec = executionContext + override type T[D] = Future[D] + implicit val monadT = implicitly[Monad[Future]] + + def execute[D](operations: T[D]): Future[D] = operations + def noAction[V](v: V): T[V] = Future.successful(v) + def customAction[R](action: => Future[R]): T[R] = action +} + +class SlickDal(database: Database, executionContext: ExecutionContext) extends Dal { + import database.profile.api._ + implicit val exec = executionContext + override type T[D] = slick.dbio.DBIO[D] + + implicit protected class QueryOps[+E, U](query: Query[E, U, Seq]) { + def resultT: ListT[T, U] = ListT[T, U](query.result.map(_.toList)) + } + + override implicit val monadT: Monad[T] = new Monad[T] { + override def point[A](a: => A): T[A] = DBIO.successful(a) + override def bind[A, B](fa: T[A])(f: A => T[B]): T[B] = fa.flatMap(f) + } + + override def execute[D](readOperations: T[D]): Future[D] = { + database.database.run(readOperations.transactionally) + } + + override def noAction[V](v: V): T[V] = DBIO.successful(v) + override def customAction[R](action: => Future[R]): T[R] = DBIO.from(action) +} diff --git a/src/main/scala/xyz/driver/core/database.scala b/src/main/scala/xyz/driver/core/database/database.scala index a82e345..a8aec63 100644 --- a/src/main/scala/xyz/driver/core/database.scala +++ b/src/main/scala/xyz/driver/core/database/database.scala @@ -1,14 +1,13 @@ package xyz.driver.core +import scala.concurrent.Future + import slick.backend.DatabaseConfig -import slick.dbio.{DBIOAction, NoStream} import slick.driver.JdbcProfile import xyz.driver.core.time.Time +import xyz.driver.core.date.Date -import scala.concurrent.{ExecutionContext, Future} -import scalaz.Monad - -object database { +package database { trait Database { val profile: JdbcProfile @@ -27,11 +26,6 @@ object database { } } - type Schema = { - def create: DBIOAction[Unit, NoStream, slick.dbio.Effect.Schema] - def drop: DBIOAction[Unit, NoStream, slick.dbio.Effect.Schema] - } - trait ColumnTypes { val profile: JdbcProfile import profile.api._ @@ -43,6 +37,9 @@ object database { implicit def `xyz.driver.core.time.Time.columnType`: BaseColumnType[Time] = MappedColumnType.base[Time, Long](_.millis, Time(_)) + + implicit def `xyz.driver.core.time.Date.columnType`: BaseColumnType[Date] = + MappedColumnType.base[Date, java.sql.Date](dateToSqlDate(_), sqlDateToDate(_)) } object ColumnTypes { @@ -68,7 +65,6 @@ object database { } trait DatabaseObject extends ColumnTypes { - def createTables(): Future[Unit] def disconnect(): Unit } @@ -77,49 +73,4 @@ object database { def createTables(): Future[Unit] = Future.successful(()) def disconnect(): Unit = {} } - - trait Dal { - - type T[_] - implicit val monadT: Monad[T] - - def execute[D](operations: T[D]): Future[D] - def noAction[V](v: V): T[V] - def customAction[R](action: => Future[R]): T[R] - } - - class FutureDal(executionContext: ExecutionContext) extends Dal { - - implicit val exec = executionContext - - override type T[_] = Future[_] - implicit val monadT: Monad[T] = new Monad[T] { - override def point[A](a: => A): T[A] = Future(a) - override def bind[A, B](fa: T[A])(f: A => T[B]): T[B] = fa.flatMap(a => f(a.asInstanceOf[A])) - } - - def execute[D](operations: T[D]): Future[D] = operations.asInstanceOf[Future[D]] - def noAction[V](v: V): T[V] = Future.successful(v) - def customAction[R](action: => Future[R]): T[R] = action - } - - class SlickDal(database: Database, executionContext: ExecutionContext) extends Dal { - - import database.profile.api._ - - implicit val exec = executionContext - - override type T[_] = slick.dbio.DBIO[_] - val monadT: Monad[T] = new Monad[T] { - override def point[A](a: => A): T[A] = DBIO.successful(a) - override def bind[A, B](fa: T[A])(f: A => T[B]): T[B] = fa.flatMap(a => f(a.asInstanceOf[A])) - } - - def execute[D](readOperations: T[D]): Future[D] = { - database.database.run(readOperations.asInstanceOf[slick.dbio.DBIO[D]].transactionally) - } - - def noAction[V](v: V): slick.dbio.DBIO[V] = DBIO.successful(v) - def customAction[R](action: => Future[R]): T[R] = DBIO.from(action) - } } diff --git a/src/main/scala/xyz/driver/core/database/package.scala b/src/main/scala/xyz/driver/core/database/package.scala new file mode 100644 index 0000000..c88260b --- /dev/null +++ b/src/main/scala/xyz/driver/core/database/package.scala @@ -0,0 +1,31 @@ +package xyz.driver.core + +import java.sql.{Date => SqlDate} +import java.util.Calendar + +import date.Date +import slick.dbio.{DBIOAction, NoStream} + +package object database { + + type Schema = { + def create: DBIOAction[Unit, NoStream, slick.dbio.Effect.Schema] + def drop: DBIOAction[Unit, NoStream, slick.dbio.Effect.Schema] + } + + private[database] def sqlDateToDate(sqlDate: SqlDate): Date = { + // NOTE: SQL date does not have a time component, so this date + // should only be interpreted in the running JVMs timezone. + val cal = Calendar.getInstance() + cal.setTime(sqlDate) + Date(cal.get(Calendar.YEAR), date.tagMonth(cal.get(Calendar.MONTH)), cal.get(Calendar.DAY_OF_MONTH)) + } + + private[database] def dateToSqlDate(date: Date): SqlDate = { + val cal = Calendar.getInstance() + cal.set(Calendar.YEAR, date.year - 1900) + cal.set(Calendar.MONTH, date.month) + cal.set(Calendar.DAY_OF_MONTH, date.day) + new SqlDate(cal.getTime.getTime) + } +} diff --git a/src/main/scala/xyz/driver/core/date.scala b/src/main/scala/xyz/driver/core/date.scala new file mode 100644 index 0000000..ab19074 --- /dev/null +++ b/src/main/scala/xyz/driver/core/date.scala @@ -0,0 +1,25 @@ +package xyz.driver.core + +import java.util.Calendar + +object date { + + type Month = Int @@ Month.type + private[core] def tagMonth(value: Int): Month = value.asInstanceOf[Month] + + object Month { + val JANUARY = tagMonth(Calendar.JANUARY) + val FEBRUARY = tagMonth(Calendar.FEBRUARY) + val MARCH = tagMonth(Calendar.MARCH) + val APRIL = tagMonth(Calendar.APRIL) + val MAY = tagMonth(Calendar.MAY) + val JUNE = tagMonth(Calendar.JUNE) + val JULY = tagMonth(Calendar.JULY) + val AUGUST = tagMonth(Calendar.AUGUST) + val SEPTEMBER = tagMonth(Calendar.SEPTEMBER) + val OCTOBER = tagMonth(Calendar.OCTOBER) + val DECEMBER = tagMonth(Calendar.DECEMBER) + } + + final case class Date(year: Int, month: Month, day: Int) +} diff --git a/src/main/scala/xyz/driver/core/generators.scala b/src/main/scala/xyz/driver/core/generators.scala index c61cb94..d532ae3 100644 --- a/src/main/scala/xyz/driver/core/generators.scala +++ b/src/main/scala/xyz/driver/core/generators.scala @@ -4,6 +4,7 @@ import java.math.MathContext import xyz.driver.core.revision.Revision import xyz.driver.core.time.{Time, TimeRange} +import xyz.driver.core.date.Date import scala.reflect.ClassTag import scala.util.Random @@ -55,6 +56,8 @@ object generators { Time(scala.math.max(oneTime.millis, anotherTime.millis))) } + def nextDate(): Date = nextTime.toDate(java.util.TimeZone.getTimeZone("UTC")) + def nextBigDecimal(multiplier: Double = 1000000.00, precision: Int = 2): BigDecimal = BigDecimal(multiplier * nextDouble, new MathContext(precision)) diff --git a/src/main/scala/xyz/driver/core/json.scala b/src/main/scala/xyz/driver/core/json.scala index 277543b..5833383 100644 --- a/src/main/scala/xyz/driver/core/json.scala +++ b/src/main/scala/xyz/driver/core/json.scala @@ -7,11 +7,14 @@ import akka.http.scaladsl.unmarshalling.Unmarshaller import spray.json.{DeserializationException, JsNumber, _} import xyz.driver.core.revision.Revision import xyz.driver.core.time.Time +import xyz.driver.core.date.{Date, Month} import scala.reflect.runtime.universe._ object json { + import DefaultJsonProtocol._ + def IdInPath[T]: PathMatcher1[Id[T]] = new PathMatcher1[Id[T]] { def apply(path: Path) = path match { case Path.Segment(segment, tail) => Matched(tail, Tuple1(Id[T](segment))) @@ -66,6 +69,16 @@ object json { } } + implicit val monthFormat = new RootJsonFormat[Month] { + def write(month: Month) = JsNumber(month) + def read(value: JsValue): Month = value match { + case JsNumber(month) if 0 <= month && month <= 11 => date.tagMonth(month.toInt) + case _ => throw DeserializationException("Expected a number from 0 to 11") + } + } + + implicit val dateFormat = jsonFormat3(Date) + def RevisionInPath[T]: PathMatcher1[Revision[T]] = PathMatcher("""[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}""".r) flatMap { string => Some(Revision[T](string)) diff --git a/src/main/scala/xyz/driver/core/time.scala b/src/main/scala/xyz/driver/core/time.scala index 6ff8209..cbb86ed 100644 --- a/src/main/scala/xyz/driver/core/time.scala +++ b/src/main/scala/xyz/driver/core/time.scala @@ -26,6 +26,12 @@ object time { def isAfter(anotherTime: Time): Boolean = implicitly[Ordering[Time]].gt(this, anotherTime) def advanceBy(duration: Duration): Time = Time(millis + duration.toMillis) + + def toDate(timezone: TimeZone): date.Date = { + val cal = Calendar.getInstance(timezone) + cal.setTimeInMillis(millis) + date.Date(cal.get(Calendar.YEAR), date.tagMonth(cal.get(Calendar.MONTH)), cal.get(Calendar.DAY_OF_MONTH)) + } } object Time { diff --git a/src/test/scala/xyz/driver/core/CoreTest.scala b/src/test/scala/xyz/driver/core/CoreTest.scala index da9fd9a..e210887 100644 --- a/src/test/scala/xyz/driver/core/CoreTest.scala +++ b/src/test/scala/xyz/driver/core/CoreTest.scala @@ -60,6 +60,24 @@ class CoreTest extends FlatSpec with Matchers with MockitoSugar { (y2 === y) should be(true) } + "Time" should "use TimeZone correctly when converting to Date" in { + + import time._ + + val EST = java.util.TimeZone.getTimeZone("EST") + val PST = java.util.TimeZone.getTimeZone("PST") + + val timestamp = { + import java.util.Calendar + val cal = Calendar.getInstance(EST) + cal.set(Calendar.HOUR_OF_DAY, 1) + Time(cal.getTime().getTime()) + } + + textualDate(EST)(timestamp) should not be textualDate(PST)(timestamp) + timestamp.toDate(EST) should not be timestamp.toDate(PST) + } + "Name" should "have equality and ordering working correctly" in { (Name[String]("foo") === Name[String]("foo")) should be(true) diff --git a/src/test/scala/xyz/driver/core/JsonTest.scala b/src/test/scala/xyz/driver/core/JsonTest.scala index eb8d5d8..821b162 100644 --- a/src/test/scala/xyz/driver/core/JsonTest.scala +++ b/src/test/scala/xyz/driver/core/JsonTest.scala @@ -42,6 +42,22 @@ class JsonTest extends FlatSpec with Matchers { parsedTime should be(referenceTime) } + "Json format for Date" should "read and write correct JSON" in { + import date._ + + val referenceDate = Date(1941, Month.DECEMBER, 7) + + val writtenJson = json.dateFormat.write(referenceDate) + writtenJson.prettyPrint should be("""|{ + | "year": 1941, + | "month": 11, + | "day": 7 + |}""".stripMargin) + + val parsedDate = json.dateFormat.read(writtenJson) + parsedDate should be(referenceDate) + } + "Json format for Revision" should "read and write correct JSON" in { val referenceRevision = Revision[String]("037e2ec0-8901-44ac-8e53-6d39f6479db4") |