aboutsummaryrefslogtreecommitdiff
path: root/core-database/src/main/scala/xyz/driver/core/database/database.scala
diff options
context:
space:
mode:
authorJakob Odersky <jakob@driver.xyz>2018-09-12 17:30:33 -0700
committerJakob Odersky <jakob@odersky.com>2018-10-09 16:19:39 -0700
commiteb6f97b4cac548999cbf192ee83d9ba9a253b7c8 (patch)
tree6d75a23efc841a6f51e780913387000206d1fe94 /core-database/src/main/scala/xyz/driver/core/database/database.scala
parent4d1197099ce4e721c18bf4cacbb2e1980e4210b5 (diff)
downloaddriver-core-eb6f97b4cac548999cbf192ee83d9ba9a253b7c8.tar.gz
driver-core-eb6f97b4cac548999cbf192ee83d9ba9a253b7c8.tar.bz2
driver-core-eb6f97b4cac548999cbf192ee83d9ba9a253b7c8.zip
Move database-related functionality to separate project
This committ includes a breaking change. The database-specific utility "Converters" trait threw an exception "DatabaseException" defined in the rest package, thus breaking the dependency graph. The solution was to move the DatabaseException class from rest to database and not inherit ServiceExceptio any more. Unfortunately, the rest classes also require the database exception in propagating errors so this funtionality has been removed. The rationale is: 1. Database exceptions are rare and result in 500 errors anyway making the status code opaque to what actual error caused it. 2. In core 2.0, an improved tracing framework will make diagnosing and following database errors easier, thereby attenuating the need to forward details on service exceptions in responses.
Diffstat (limited to 'core-database/src/main/scala/xyz/driver/core/database/database.scala')
-rw-r--r--core-database/src/main/scala/xyz/driver/core/database/database.scala178
1 files changed, 178 insertions, 0 deletions
diff --git a/core-database/src/main/scala/xyz/driver/core/database/database.scala b/core-database/src/main/scala/xyz/driver/core/database/database.scala
new file mode 100644
index 0000000..bd20b54
--- /dev/null
+++ b/core-database/src/main/scala/xyz/driver/core/database/database.scala
@@ -0,0 +1,178 @@
+package xyz.driver.core
+
+import slick.basic.DatabaseConfig
+import slick.jdbc.JdbcProfile
+import xyz.driver.core.date.Date
+import xyz.driver.core.time.Time
+
+import scala.concurrent.Future
+import com.typesafe.config.Config
+
+package database {
+
+ import java.sql.SQLDataException
+ import java.time.{Instant, LocalDate}
+
+ import eu.timepit.refined.api.{Refined, Validate}
+ import eu.timepit.refined.refineV
+
+ trait Database {
+ val profile: JdbcProfile
+ val database: JdbcProfile#Backend#Database
+ }
+
+ object Database {
+ def fromConfig(config: Config, databaseName: String): Database = {
+ val dbConfig: DatabaseConfig[JdbcProfile] = DatabaseConfig.forConfig(databaseName, config)
+
+ new Database {
+ val profile: JdbcProfile = dbConfig.profile
+ val database: JdbcProfile#Backend#Database = dbConfig.db
+ }
+ }
+
+ def fromConfig(databaseName: String): Database = {
+ fromConfig(com.typesafe.config.ConfigFactory.load(), databaseName)
+ }
+ }
+
+ trait ColumnTypes {
+ val profile: JdbcProfile
+ }
+
+ trait NameColumnTypes extends ColumnTypes {
+ import profile.api._
+ implicit def `xyz.driver.core.Name.columnType`[T]: BaseColumnType[Name[T]]
+ }
+
+ object NameColumnTypes {
+ trait StringName extends NameColumnTypes {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.Name.columnType`[T]: BaseColumnType[Name[T]] =
+ MappedColumnType.base[Name[T], String](_.value, Name[T])
+ }
+ }
+
+ trait DateColumnTypes extends ColumnTypes {
+ import profile.api._
+ implicit def `xyz.driver.core.time.Date.columnType`: BaseColumnType[Date]
+ implicit def `java.time.LocalDate.columnType`: BaseColumnType[LocalDate]
+ }
+
+ object DateColumnTypes {
+ trait SqlDate extends DateColumnTypes {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.time.Date.columnType`: BaseColumnType[Date] =
+ MappedColumnType.base[Date, java.sql.Date](dateToSqlDate, sqlDateToDate)
+
+ override implicit def `java.time.LocalDate.columnType`: BaseColumnType[LocalDate] =
+ MappedColumnType.base[LocalDate, java.sql.Date](java.sql.Date.valueOf, _.toLocalDate)
+ }
+ }
+
+ trait RefinedColumnTypes[T, Predicate] extends ColumnTypes {
+ import profile.api._
+ implicit def `eu.timepit.refined.api.Refined`(
+ implicit columnType: BaseColumnType[T],
+ validate: Validate[T, Predicate]): BaseColumnType[T Refined Predicate]
+ }
+
+ object RefinedColumnTypes {
+ trait RefinedValue[T, Predicate] extends RefinedColumnTypes[T, Predicate] {
+ import profile.api._
+ override implicit def `eu.timepit.refined.api.Refined`(
+ implicit columnType: BaseColumnType[T],
+ validate: Validate[T, Predicate]): BaseColumnType[T Refined Predicate] =
+ MappedColumnType.base[T Refined Predicate, T](
+ _.value, { dbValue =>
+ refineV[Predicate](dbValue) match {
+ case Left(refinementError) =>
+ throw new SQLDataException(
+ s"Value in the database doesn't match the refinement constraints: $refinementError")
+ case Right(refinedValue) =>
+ refinedValue
+ }
+ }
+ )
+ }
+ }
+
+ trait IdColumnTypes extends ColumnTypes {
+ import profile.api._
+ implicit def `xyz.driver.core.Id.columnType`[T]: BaseColumnType[Id[T]]
+ }
+
+ object IdColumnTypes {
+ trait UUID extends IdColumnTypes {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.Id.columnType`[T] =
+ MappedColumnType
+ .base[Id[T], java.util.UUID](id => java.util.UUID.fromString(id.value), uuid => Id[T](uuid.toString))
+ }
+ trait SerialId extends IdColumnTypes {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.Id.columnType`[T] =
+ MappedColumnType.base[Id[T], Long](_.value.toLong, serialId => Id[T](serialId.toString))
+ }
+ trait NaturalId extends IdColumnTypes {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.Id.columnType`[T] =
+ MappedColumnType.base[Id[T], String](_.value, Id[T])
+ }
+ }
+
+ trait TimestampColumnTypes extends ColumnTypes {
+ import profile.api._
+ implicit def `xyz.driver.core.time.Time.columnType`: BaseColumnType[Time]
+ implicit def `java.time.Instant.columnType`: BaseColumnType[Instant]
+ }
+
+ object TimestampColumnTypes {
+ trait SqlTimestamp extends TimestampColumnTypes {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.time.Time.columnType`: BaseColumnType[Time] =
+ MappedColumnType.base[Time, java.sql.Timestamp](
+ time => new java.sql.Timestamp(time.millis),
+ timestamp => Time(timestamp.getTime))
+
+ override implicit def `java.time.Instant.columnType`: BaseColumnType[Instant] =
+ MappedColumnType.base[Instant, java.sql.Timestamp](java.sql.Timestamp.from, _.toInstant)
+ }
+
+ trait PrimitiveTimestamp extends TimestampColumnTypes {
+ import profile.api._
+
+ override implicit def `xyz.driver.core.time.Time.columnType`: BaseColumnType[Time] =
+ MappedColumnType.base[Time, Long](_.millis, Time.apply)
+
+ override implicit def `java.time.Instant.columnType`: BaseColumnType[Instant] =
+ MappedColumnType.base[Instant, Long](_.toEpochMilli, Instant.ofEpochMilli)
+ }
+ }
+
+ trait KeyMappers extends ColumnTypes {
+ import profile.api._
+
+ def uuidKeyMapper[T] =
+ MappedColumnType
+ .base[Id[T], java.util.UUID](id => java.util.UUID.fromString(id.value), uuid => Id[T](uuid.toString))
+ def serialKeyMapper[T] = MappedColumnType.base[Id[T], Long](_.value.toLong, serialId => Id[T](serialId.toString))
+ def naturalKeyMapper[T] = MappedColumnType.base[Id[T], String](_.value, Id[T])
+ }
+
+ trait DatabaseObject extends ColumnTypes {
+ def createTables(): Future[Unit]
+ def disconnect(): Unit
+ }
+
+ abstract class DatabaseObjectAdapter extends DatabaseObject {
+ def createTables(): Future[Unit] = Future.successful(())
+ def disconnect(): Unit = {}
+ }
+}