以下のテーブルを例に説明していきます。
CREATE TABLE `coffee` (
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`supplier` BIGINT NOT NULL,
PRIMARY KEY (`id`));
slick-codegenを使用すると以下のようなモデルが作成されます。
case class CoffeeRow(id: Long, name: String, supplier: Long)
implicit def GetResultCoffeeRow(implicit e0: GR[Long], e1: GR[String]): GR[CoffeeRow] = GR{prs => import prs._CoffeeRow.tupled((<<[Long], <<[String], <<[Long]))
}
class Coffee(_tableTag: Tag) extends Table[CoffeeRow](_tableTag, "coffee") {
def * = (id, name, supplier) <> (CoffeeRow.tupled, CoffeeRow.unapply)
def ? = (Rep.Some(id), Rep.Some(name), Rep.Some(supplier)).shaped.<>({r=>import r._; _1.map(_=> CoffeeRow.tupled((_1.get, _2.get, _3.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported."))
val id: Rep[Long] = column[Long]("id", O.PrimaryKey)
val name: Rep[String] = column[String]("name", O.Length(255,varying=true))
val supplier: Rep[Long] = column[Long]("supplier")
}
lazy val Coffee = new TableQuery(tag => new Coffee(tag))
テーブル数やカラム数が多ければ多いほど、このCaseClassを使いまわしたいと思いますよね。
見ての通りカラムがNOT NULLでslick-codegenを使用するとCaseClassの項目はOPTION型ではないのでNONEは使えません。
しかし、値を渡さずに使いたい時もある!ということで作ってみました
uedashuheiさんのコードをベースにしています。
http://qiita.com/uedashuhei/items/25d5a6e786075729d3b3#comments
1.AutoIncrementをOPTION型にする
この一行をoverrideして記述するだけ
override def autoIncLastAsOption = true
2.インデックス(主キー・外部キー含む)なしのカラムをOPTION型にする
DBから型情報を収集し終わったタイミングで割り込んで書き換えていきます
3.外部キー、インデックスのかかっているカラムをOPTION型にする
2.と同様に書き換えていくのですが、このままだと適用できません
コードを以下のように書き換えてください
slick.model.Table(t.name, c, t.primaryKey, t.foreignKeys, t.indices, t.options)
↓↓↓↓↓↓↓↓
slick.model.Table(t.name, cc, t.primaryKey, ArrayBuffer(), ArrayBuffer(), t.options)
インデックス(主キー、外部キー)を設定した状態でnullableをtrueに指定するとAssertで落ちます
この設定をすると外部キーの恩恵を受けられないので良い方法があったら教えて欲しいです
import slick.driver.JdbcProfile
import scala.concurrent.ExecutionContext.Implicits.global
import slick.driver.MySQLDriver.api._
import slick.driver.MySQLDriver
import slick.driver.JdbcProfile
import slick.{model => m}
import slick.codegen.SourceCodeGenerator
import slick.model.Model
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext}
class CustomGenerator(model: m.Model) extends SourceCodeGenerator(model) {
override def Table = new Table(_) {
override def autoIncLastAsOption = true
override def Column = new Column(_) {
override def rawType = model.tpe match {
case _ => {
super.rawType
}
}
}
}
}
object CodeGenWithJodaTime extends App {
val slickDriver = "slick.driver.MySQLDriver"
val jdbcDriver = "com.mysql.jdbc.Driver"
val url = "jdbc:mysql://localhost/myschema"
val outputFolder = "src/main/scala"
val schemas = "myschema"
val pkg = "mypkg"
val user = "username"
val password = "userpassword"
val driver: JdbcProfile = slick.driver.MySQLDriver // TODO: replace this with your Slick driver
val db = { Database.forURL(url, driver = jdbcDriver, user = user, password = password) }
// 非同期をぶった斬って処理する
val model = Await.result(db.run(driver.createModel(None, false)(ExecutionContext.global).withPinnedSession), Duration.Inf)
val ts = (for {
c <- model.tables
} yield {
val cc = c.head.table match {
case QualifiedName(x, _, _) =>
(for(a <- c) yield {
// 全テーブルに適用したい場合はカラム名のみ指定する
if (a.name == "supplier") {a.copy(nullable = true, options = Set(SqlType("LONG"), Default(Some(None))))}
// 何個でも指定可能
else if (a.name == "・・・") {a.copy(nullable = true, options = Set(SqlType("BYTE"), Default(Some(1))))}
// テーブルで絞りたい場合は条件にテーブル名を指定する
else if (x == "coffee" && a.name == "name") {a.copy(nullable = true, options = Set(SqlType("VARCHAR"), Default(Some(None))))}
// どの条件にも満たない場合はそのまま返す
else a
})
case _ => c
}
slick.model.Table(t.name, c, t.primaryKey, t.foreignKeys, t.indices, t.options) // <- 通常はこのまま
//slick.model.Table(t.name, cc, t.primaryKey, ArrayBuffer(), ArrayBuffer(), t.options) <- 外部キー、インデックスのかかったカラムにOPTIONをつける場合はこちらを使用する
})
val fModel = Model(tables = ts)
val codeGenFuture = new CustomGenerator(fModel).writeToFile(slickDriver, outputFolder , pkg, "Tables", "Tables.scala")
}