aboutsummaryrefslogblamecommitdiff
path: root/core-database/src/main/scala/xyz/driver/core/database/database.scala
blob: bd20b54f4efcd5b159d9026216110e2fa6bc26a3 (plain) (tree)
1
2
3
4
5
6
7
8
9
                       
 

                                 
                                


                                
                                 
 
                  
 
                                  
                                       



                                                   
                  

                                              
   
 
                   

                                                                                                
 
                    
                                                                     


                                                                



                                                                        

   
                     
                            


                                             
                        

                                                                              
 


                                              
 



                                                                                           
 


                                                                             
                                                                            
   
 


                                           
 

                                                                                          


                                                                                             
     

   


                                                              

                                                                              





                                                                               

                                                                                  













                                                                                                      






                                                                          

                          


                                                                                                               
     
                                          

                          
                                                                

                                                                                                
                                           
                          
 
                                                                
                                                            
     

   


                                                                             
                                                                        






                                                                                          


                                                        


                                                                                                





                                                                                          



                                                                                     


     






                                                                                                                      
                                                                                  

   
                                            


                                    


                                                               
                                         
   
 
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           = {}
  }
}