Search code examples
scalaplayframeworkslickplayframework-evolutions

Play framework and Slick automatic database creation


I'm using play 2.4 and Slick 3, Is it possible to generate automatically ddl scripts, it is evolutions?

In official docs I found some scripts, but where should I place it in play framework? http://slick.typesafe.com/doc/3.1.0/schemas.html

Do you know any libs to manage evolutions in code to not write plain SQL?


Solution

  • I made some workaround with PostgresDriver, I've created module, that prints DDL to file. After every code change I just need to replace 1.sql or later modify next evolution scripts:

    ComputersDatabaseModule.scala

    package bootstrap
    
    import com.google.inject.AbstractModule
    import play.api.{Mode, Play}
    
    class ComputersDatabaseModule extends AbstractModule {
    
      protected def configure() = {
        bind(classOf[CreateDDL]).asEagerSingleton()
        bind(classOf[InitialData]).asEagerSingleton()
      }
    }
    

    CreateDDL.scala

    package bootstrap
    
    import java.io.PrintWriter
    import javax.inject.Inject
    
    import dao.{CompaniesMapping, ComputersMapping}
    import play.api.db.slick.{HasDatabaseConfigProvider, DatabaseConfigProvider}
    import slick.driver.JdbcProfile
    
    
    /**
      * Creates DDL script
      */
    private[bootstrap] class CreateDDL @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] with
      ComputersMapping with CompaniesMapping {
    
      def createDDLScript() = {
        import slick.driver.PostgresDriver.api._
    
        val allSchemas = companies.schema ++ computers.schema
    
        val writer = new PrintWriter("target/migration_ddl.sql")
        writer.write("# --- !Ups\n\n")
        allSchemas.createStatements.foreach { s => writer.write(s + ";\n") }
    
        writer.write("\n\n# --- !Downs\n\n")
        allSchemas.dropStatements.foreach { s => writer.write(s + ";\n") }
    
        writer.close()
      }
    
      createDDLScript()
    }
    

    ComputersDAO.scala

    package dao
    
    import java.util.Date
    import javax.inject.{Inject, Singleton}
    import models.{Company, Computer, Page}
    import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
    import play.api.libs.concurrent.Execution.Implicits.defaultContext
    import slick.driver.JdbcProfile
    
    import scala.concurrent.Future
    
    trait ComputersMapping { self: HasDatabaseConfigProvider[JdbcProfile] =>
      import driver.api._
    
      class Computers(tag: Tag) extends Table[Computer](tag, "COMPUTER") {
    
        implicit val dateColumnType = MappedColumnType.base[Date, Long](d => d.getTime, d => new Date(d))
    
        def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
        def name = column[String]("NAME")
        def introduced = column[Option[Date]]("INTRODUCED")
        def discontinued = column[Option[Date]]("DISCONTINUED")
        def companyId = column[Option[Long]]("COMPANY_ID")
    
        def * = (id.?, name, introduced, discontinued, companyId) <> (Computer.tupled, Computer.unapply)
      }
    
      val computers = TableQuery[Computers]
    }
    
    @Singleton()
    class ComputersDAO @Inject() (protected val dbConfigProvider: DatabaseConfigProvider) extends CompaniesMapping with ComputersMapping
      with HasDatabaseConfigProvider[JdbcProfile] {
      import driver.api._
    
      /** Retrieve a computer from the id. */
      def findById(id: Long): Future[Option[Computer]] =
        db.run(computers.filter(_.id === id).result.headOption)
    
      /** Count all computers. */
      def count(): Future[Int] = {
        // this should be changed to
        // db.run(computers.length.result)
        // when https://github.com/slick/slick/issues/1237 is fixed
        db.run(computers.map(_.id).length.result)
      }
      /** Count computers with a filter. */
      def count(filter: String): Future[Int] = {
        db.run(computers.filter { computer => computer.name.toLowerCase like filter.toLowerCase }.length.result)
      }
    
      /** Return a page of (Computer,Company) */
      def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%"): Future[Page[(Computer, Company)]] = {
    
        val offset = pageSize * page
        val query =
          (for {
            (computer, company) <- computers joinLeft companies on (_.companyId === _.id)
            if computer.name.toLowerCase like filter.toLowerCase
          } yield (computer, company.map(_.id), company.map(_.name)))
            .drop(offset)
            .take(pageSize)
    
        for {
          totalRows <- count(filter)
          list = query.result.map { rows => rows.collect { case (computer, id, Some(name)) => (computer, Company(id, name)) } }
          result <- db.run(list)
        } yield Page(result, page, offset, totalRows)
      }
    
      /** Insert a new computer. */
      def insert(computer: Computer): Future[Unit] =
        db.run(computers += computer).map(_ => ())
    
      /** Insert new computers. */
      def insert(computers: Seq[Computer]): Future[Unit] =
        db.run(this.computers ++= computers).map(_ => ())
    
      /** Update a computer. */
      def update(id: Long, computer: Computer): Future[Unit] = {
        val computerToUpdate: Computer = computer.copy(Some(id))
        db.run(computers.filter(_.id === id).update(computerToUpdate)).map(_ => ())
      }
    
      /** Delete a computer. */
      def delete(id: Long): Future[Unit] =
        db.run(computers.filter(_.id === id).delete).map(_ => ())
    
    }
    

    Add in configuration (application.config):

    play.modules.enabled += "bootstrap.ComputersDatabaseModule"