windweller
windweller

Reputation: 2385

Slick Write a Simple Table Creation Function

Emm...I'm trying out Slick with Play 2. The table creation process has become very frustrating because unlike other ORM (like ebean), Slick doesn't detect if the database is created, if there is already a table existing, it will report an exception. I simply just don't want to drop and create every time I restart the server, so I decide to write a small function to help me:

  def databaseCreate(tables: Table*) = {
     for (table <- tables) {
       if (MTable.getTables(table.getClass.getName).list.isEmpty) table.ddl.create
     }
  }

What this does is to take in some objects like this one:

  object Tag extends Table [(Option[Int], String)]("Tags") {
    def id = column[Int]("TAG_ID", O.PrimaryKey, O.AutoInc)
    def tag_name = column[String]("TAG_NAME")

    def * = id.? ~ tag_name
  }

And use MTable method from scala.slick.jdbc.meta.MTable to know if the table exists or not. Then I kinda run into a simple Java reflection problem. If databaseCreate method takes Strings, then I can invoke .ddl.create. So I decide to pass in objects and use relfection: table.getClass.getName. The only problem is there is a type mismatch: (from my IDE)

Expected: MySQLDriver.simple.type#Table, actual: BlogData.Tag.type

BlogData is the big object I used to store all smaller table objects. How do I solve this mismatch problem?? Use a whole bunch of asInstanceOf? It would make the command unbearably long and ugly...


Corrected:

The type mismatch is a false alarm, which comes from the IDE, not from the typesafe console complier. The real problem is:

type Table takes type parameters
def databaseCreate(tables: Table*) = {
^
one error found

Then I followed the advice and changed the code:

def databaseCreate(tables: Table[_]*)(implicit session: Session) = {
         for (table <- tables) {
           if (MTable.getTables(table.tableName).list.isEmpty) table.ddl.create
         }
      }

Then I got this error:

ambiguous implicit values: both value session of type slick.driver.MySQLDriver.simple.Session and method threadLocalSession in object Database of type => scala.slick.session.Session match expected type scala.slick.session.Session
if (MTable.getTables(table.tableName).list.isEmpty) table.ddl.create
^
one error found

My imports are here:

import play.api.GlobalSettings
import play.api.Application
import models.BlogData._
import scala.slick.driver.MySQLDriver.simple._
import Database.threadLocalSession
import play.api.Play.current
import scala.slick.jdbc.meta.MTable

Upvotes: 3

Views: 2319

Answers (4)

John
John

Reputation: 2761

You should use asTry with the table create DBIOAction . It will check whether there is table existing... if not exist the DBIOAction will work for creating a table.

in HomePageDAO

   val createHomePageTableAction: DBIOAction[Int, NoStream, Effect.Schema with Effect.Write] = homePageTable.schema.create >>(homePageTable+=Homepage("No Data","No Data",0l))

in ExplorePageDAO

  val SoftwareTableCreateAction: FixedSqlAction[Unit, NoStream, Effect.Schema] = softwareTable.schema.create

And in createTablesController

package controllers

import javax.inject.{Inject, Singleton}
import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents}
import services.dbServices.{ExplorePageDAO, HomePageDAO}

import scala.concurrent.ExecutionContext

@Singleton
class CreateTablesController @Inject()(cc: ControllerComponents)(implicit assetsFinder: AssetsFinder, executionContext: ExecutionContext)
  extends AbstractController(cc) {

  import services.dbServices.dbSandBox._
  def createTable: Action[AnyContent] = Action.async {

    dbAccess.run(HomePageDAO.createHomePageTableAction .asTry >> ExplorePageDAO.SoftwareTableCreateAction.asTry).map(_=> Ok("Table Created"))
  }


}

Upvotes: 0

Michael Jess
Michael Jess

Reputation: 1907

In Slick 2: For the sake of completeness, see this and that related thread.

I use the following method:

def createIfNotExists(tables: TableQuery[_ <: Table[_]]*)(implicit session: Session) {
  tables foreach {table => if(MTable.getTables(table.baseTableRow.tableName).list.isEmpty) table.ddl.create}
}

Then you can just create your tables with the implicit session:

db withSession {
  implicit session =>
    createIfNotExists(table1, table2, ..., tablen)
}

Upvotes: 4

Synesso
Synesso

Reputation: 38958

You are using SLICK v1 I assume, as you have Table objects. I have working code for v1. This version will drop any tables that are present before recreating them (but wont drop any tables you don't wish to recreate). It will also merge the DDL into one before creation in order to get the creation sequence correct:

def create() = database withSession {
  import scala.slick.jdbc.{StaticQuery => Q}
  val tables = Seq(TableA, TableB, TableC)
  def existingTables = MTable.getTables.list().map(_.name.name)
  tables.filter(t => existingTables.contains(t.tableName)).foreach{t =>
    Q.updateNA(s"drop table ${t.tableName} cascade").execute
  }
  val tablesToCreate = tables.filterNot(t => existingTables.contains(t.tableName))
  val ddl: Option[DDL] = tablesToCreate.foldLeft(None: Option[DDL]){(ddl, table) =>
    ddl match {
      case Some(d) => Some(d ++ table.ddl)
      case _ => Some(table.ddl)
    }
  }
  ddl.foreach{_.create}
}

Upvotes: 2

cvogt
cvogt

Reputation: 11270

Not sure why you get this error message. I would need to see your imports and the place where you call databaseCreate, but what is wrong is def databaseCreate(tables: Table*) should be def databaseCreate(tables: Table[_]*) and probably take a second argument list as well def databaseCreate(tables: Table[_]*)(implicit session: Session) = ....

Also, instead of table.getClass.getName, you can use table.tableName.

Upvotes: 0

Related Questions