Slick 3.0.0 documentation - 13 Upgrade Guides
Permalink to Upgrade Guides — Slick 3.0.0 documentation
SlickはScalaの2.10か2.11のバージョンで動作する(Scala 2.9を使ってる人は、Slickの前身であるScalaQueryを使って欲しい)。
Slickのバージョンナンバーはepoch、メジャーバージョン、マイナーバージョン、RCやSNAPSHOTといった修飾子からなる。
Slickのリリースバージョン(修飾子のついてないもの)においては、同じepochとメジャーバージョンの付くものに対し、バイナリ互換性が保証されている(2.1.2に対して、2.1.0と互換性があるが、2.0.0とは互換性の保証が無い)。Slick Extensionsは少なくともSlickの同じマイナーバージョンが必要とされる(e.g. Slick Extension 2.1.2にはSlick 2.1.2を用いて、Slick 2.1.1は用いてはならない)。slick-codegen はコンパイル時に用いられる事を期待しているため、互換性は保持されていない。
ソースコードに対する互換性は保証していないものの、同じメジャーリリースに対しては出来る限り保証しようと試みてはいる。新しいメジャーリリースにアップグレードしようとする際には、いくつかあなた側のソースを変更する必要が出てくるだろう。古い機能については非推奨にし、メジャーリリースのサイクルに対してはそのままのコードが使えるようにはしている。例として、2.1.0で非推奨になった機能は2.2.0で削除される。ただし、全ての変更に対して同じようにならない事もあるので注意して欲しい。
RC (Release candidates) は出そうと試みているバージョンと互換性が保証されるようになっている。milestonesやsnapshotは、互換性が保証されていない。
Slickはパッケージ名をscala.slickからslickへと変更された。共通の型や値が含まれていたscala.slickのパッケージオブジェクトには、非推奨なエイリアスが残されている。
ドライバからsimpleやImplicitsをインポートするのは非推奨となり、Slick 3.1で機能が削除される。その代わりに同様の機能を提供するapiを使って欲しい(ただしデータベース呼び出しをブロッキングしてしまうInvokerやExecutorというAPIは含まれていない)。ここに含まれていた機能は、新しいAPIであるモナディックデータベースI/Oアクションに取って替えられている。このAPIについての詳細はDatabase I/O Actionsを見て欲しい。
以前のouter joinオペレータはnullを含む場合に、ユーザコード内で複雑にマッピングを行う必要もあったり、正しく処理していなかった。特にネストされたouter joinを用いていたり、マッピングされたエンティティを用いたouter joinを行っていた場合に、正確な処理が行えていなかった。新しいouter joinオペレータでは、left joinやright join、full outer joinの結合時に、Optionに結果を持ち上げる事で複雑なマッピング等を行う必要がなくなった。ネストされたOptionやSlickでプリミティブ型以外に対するOptionをサポートすることにより、この機能を実現する事ができた。。
古いjoinに対応するオペレータはまだ利用可能ではあるが、非推奨となった。非推奨APIに対する警告に従い、適宜以下のように変更して欲しい。
新しいjoinセマンティックにおいては、joinオペレータにJoinTypeを明示的に渡しても何の意味もなさないし、その方法は非推奨になっている。今現在、joinはinner joinのためにのみ用いられる。
昔のInvoker APIでは、firstやfirstOptionはクエリの結果に対するコレクションの先頭の値を取得するのに使われていた。新しいAPIにおいては、同様のオペレーションはheadとheadOptionを呼び出す事で提供される。これはScalaのコレクションAPIで用いられているものとの調和を図るためである。
Column[T]という型はRep[T]のサブタイプへ包含された。個々のカラムのためにのみ利用可能であったオペレーションには、暗黙的なTypedType[T]が要求される。暗黙的なShapeを作成する際に、Optionカラムをより柔軟に扱うために、OptionとOptionでないカラムの双方を要求する。つまりこのケースにおいては、OptionTypedType[T]かBaseTypedType[T]が必要になる。もし双方を抽象化したいのなら、暗黙的パラメータとして要求されているShapeを渡し、具象型が分かる前提でそいつが呼び出し側でインスタンス化されるようにしておくと、より便利に扱えるようになる。
Column[T]は依然、Rep[T]のためのエイリアスとして非推奨ながら利用出来る。暗黙的な値が必要とされることがあるため、全ての場合において利用できるような完璧な後方互換になっているわけではない。
今やDatabaseインスタンスは関連のあるコネクションプールやスレッドプールを持っているため、それらを使い終わった際には適切にスレッドプールなどをシャットダウンするために、shutdownやcloseを呼んで欲しい。新しいアクションベースなAPIへ移行する際には、この点に気をつけてほしい。非推奨な同期APIを用いている場合に限り、この処理は厳格には必要無い。
Warning
遅延初期化に頼らないで!!Slick 3.1ではcloseする際、常に
Databaseオブジェクトが必要になる。さらに、Slick 3.1ではコネクションやスレッドプールが即座に作られるかもしれない。
slick.jdbc.metaパッケージにあるJDBCメタデータAPIは、InvokerではなくActionを作成する新しいAPIに変更された。このAPIを利用しているコードジェネレータも非同期APIを利用するように完全に書き換えられた。同じ機能とコンセプトはまだサポートされてはいるものの、コードジェネレータのカスタマイズ部分にはいくつか変更を加えなくてはならないだろう。コードジェネレータのテストとSchema Code Generationの例を読んで欲しい。
Slick 2.0から、auto incrementなカラムを無視するような挿入処理が挿入時のデフォルトの挙動となっている。他のクエリや計算された結果を用いて挿入をしたい時には、force-insertセマンティックを用いる事が出来る(auto incrementなカラムに対しても値を挿入したい時など)。新しいDBIO APIはinsert(Query)をforceInsertQuery(Query)に、insertExprをforceInsertExprに変更することで、このような処理を実現している。
JdbcProfile内に制約の無いString型のカラムは、デフォルトでVARCHAR(254)として扱われていた。既にH2DriverのようないくつかのドライバではString型のカラムに対して、制約のない型が割り当てられるよう変更されている。Slick 3.0では、PostgreSQLにおいてはVARCHARが、MySQLにおいてはTEXTが用いられている。以前は無害なものであったものの、MySQLのTEXTはGLOBとよく似た型になっており、いくつかの制約がかかる(デフォルト値を持たなかったり、長さの制約を与えないとインデックスが効かなかったり)。明示的にO.Length(254)のようなカラムのオプションを用いる事で、以前の挙動に戻す事ができるし、application.confにあるslick.driver.MySQL.defaultStringTypeというキーでデフォルト値を変更することも出来る。
JdbcDriverオブジェクトは非推奨となった。使用しているデータベースに対応する正しいドライバを用いて欲しい。
Queryは以前は2つの型引数を取っていたが、今は3つの型引数を取る。以前のQuery[T,E]はSlick 2.1におけるQuery[T,E,Seq]と等しい。3つ目の型引数は.runをクエリに対し実行した際の返り値となるべきコレクション型だ(Sli 2.0では常にSeqが返されていた)。将来的にはクエリをScalaのコレクション型の対応するものと同じ挙動を示すように変更する予定だ。例として、Query[_,_,Set]は要素のユニーク性を持っていたり、Query[_,_,List]には順序があるなど。コレクションの型は.to[C]をクエリに対し呼び出すことでCへ変更させる事が出来る。
Slick 2.1へアップグレードするには、次のいずれかの処理を行う必要がある。1つ目の方法としては、新しいQueryの型を違う名前の 何か に置き換えることだ。これはつまり、importを、import ....simple.{Query=>NewQuery,_}と記述し、それからtype Query[T,E] = NewQuery[T,E,Seq]というエイリアスを用意することに対応する。2つ目の方法としては、SeqをQueryの第三型引数に追加する事だ。次の正規表現を用いると簡単に変換出来るかもしれない。変換前: ([^a-zA-Z])Query\[([^\]]+), ?([^\]]+)\]、変換後: \1Query[\2, \3, Seq]。
.list and .first これらのメソッドは、Slick 2.0において暗黙的な引数リストの前に空の引数を渡していた。統一性のために、これらの引数は渡さなくて良くなった。.list()という呼び出しは.listで、.first()は.firstと呼び出して欲しい。
.where このメソッドはScalaのコレクションには存在しないため、非推奨となった。代わりに.filterを使って欲しい。
.isNull and .isNotNull 同様に、これらのメソッドはScalaのスタンダードライブラリには無いため非推奨となった。代わりにisEmptyとisDefinedを仕様して欲しい。今やこれらのメソッドはOptionのカラムにおいてのみ利用されている。Optionで無いカラムに対してこれらのメソッドを使うには、.?を用いてOptionなカラムになるようキャストすれば良い(e.g. someCol.?.isDefined)。これを行わなければならないのは、カラムに対して誤った型付を行っているためであり、nullになりえてOptionで無いカラムについては、Table定義を修正すべきである。
プレースホルダ構文に対するインターフェースに変更が加えられた。Slick 2.0では以下のようにかけていた。
StaticQuery.query[T,…]("select ...").list(someT)
これは、Slick 2.1では以下のように記述しなくてはならない。
StaticQuery.query[T,…]("select ...").apply(someT).list
コードジェネレータはSlick本体の開発を促進するために、異なるアーティファクトへと移動された。以前はslick.model.codegenというパッケージ名を利用していたが、今はslick.codegenに置かれている。バイナリ互換性は、コードジェネレータがコンパイル毎に利用されることを期待して、保証されていない。sbtプロジェクトでコードジェネレータを利用する際は、以下のdependencyを追加して欲しい。
"com.typesafe.slick" %% "slick-codegen" % "3.0.0"
SourceCodeGenerator#Table#compoundは、複合値の値と型双方に対して整形された異なるコードを生成する、compoundValueとcompoundTypeという2つのメソッドに分離された。
デフォルトとなるデータベースの全てのテーブルのリストを得るためのInvokerを返すSlickドライバのgetTablesというメソッドは、非推奨となった。代わりとなるものは、現時点で存在するメタテーブルをSlickに取り除かせるために、直接テーブル一覧を返すdefaultTablesというメソッドになる。
slick.jdbc.meta.createModel(tables)というメソッドはドライバの中へ移動し、H2Driver.createModel(Some(tables))のような記述で実行される。
Slickによって生成されたモデルはMySQLに対し、データベースのカラム型、文字列カラムの文字数、文字列のデフォルト値のような様々な情報を含んでいる。コードジェネレータは可搬的な長さのような情報を生成されたコードに埋め込み、可搬的でないデータベースのカラム型のような情報を生成されたコードに埋め込む事はオプショナルな機能とした。もしそのような設定にするのなら、SlickCodeGenerator#Table#Column#dbTypeをtrueにして欲しい。
jdbcのメタデータからモデルの生成をカスタマイズするために、どのようにしてコードジェネレータがカスタマイズされるのかという点において、新しいModelBuilderは拡張されている。Slickにおいてモデルを生成したり失われた特徴を組み直す際、これはjdbcドライバ間の違いやバグを上手いことフォローアップしている(dbmsにある特殊な型をサポートするなど)。
Slick2.0はSlick1.0に互換性のない拡張が含まれている。アプリケーションを1.0から2.0へ移行する際には、以下のような変更が必要になるだろう。
以前は手で書いていたテーブルへのマッピングを、2.0ではデータベーススキーマを用いて自動的に生成出来るようになった。code-generaterは柔軟にカスタマイズすることも出来るため、より最適化されたものに変更する事も出来る。詳細については、More info on code generationを参考にして欲しい。
Slick1.0では、テーブルはvalやtable objectと呼ばれるobjectによって定義がなされ、射影*では~オペレータを用いてタプルを表していた。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
object Suppliers extends Table[(Int, String, String)]("SUPPLIERS") {
  def id = column[Int]("SUP_ID", O.PrimaryKey)
  def name = column[String]("SUP_NAME")
  def street = column[String]("STREET")
  def * = id ~ name ~ street
}
Slick2.0ではTagを引数にテーブルクラスの定義を行い、実際のデータベーステーブルを表すTableQueryのインスタンスを定義する。射影*に対し、基本的なタプルを用いて定義を行うことも出来る。
class Suppliers(tag: Tag) extends Table[(Int, String, String)](tag, "SUPPLIERS") {
  def id = column[Int]("SUP_ID", O.PrimaryKey)
  def name = column[String]("SUP_NAME")
  def street = column[String]("STREET")
  def * = (id, name, street)
}
val suppliers = TableQuery[Suppliers]
以前に用いていた~シンタックスをそのまま使いたい場合には、TupleMethod._をインポートすれば良い。TableQuery[T]を用いると、内部的にはnew TableQuery(new T(_))のような処理が行われ、適切なTableQueryインスタンスが作成される。Slick1.0では共通処理に関して、静的メソッドでテーブルオブジェクトに定義がなされていた。2.0においても以下のようにカスタムされたTableQueryオブジェクトを用いて、同様の事が出来る。
object suppliers extends TableQuery(new Suppliers(_)) {
  // put extra methods here, e.g.:
  val findByID = this.findBy(_.id)
}
TableQueryはデータベーステーブルのためのQueryオブジェクトのことである。予期せぬ場所で適用されるQueryへの暗黙的な変換はもはや必要無い。Slick 1.0において生身の table object を扱っていた場所は、全て table query が代わりに用いられることになる。例として、以下に挙げられる挿入(inserting)や、外部キー関連などがある。
<>関数はオーバーロードされ、今やケースクラスのapply関数を直接渡す事が出来る。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
def * = id ~ name ~ street <> (Supplier _, Supplier.unapply)
上記のような記述はもはや3.0ではサポートされていない。その理由の1つとして、このようなオーバーロードはエラーメッセージを複雑にしすぎるためである。現在では適切なタプル型を用いて関数を定義する事が出来る。もしケースクラスをマッピングしたいのならば、コンパニオンオブジェクトの.tupledを単純に用いれば良いのである。
def * = (id, name, street) <> (Supplier.tupled, Supplier.unapply)
case class Supplier(id: Int, name: String, street: String)
object Supplier // overriding the default companion object
  extends ((Int, String, String) => Supplier) { // manually extending the correct function type
  //...
}
def * = (id, name, street) <> ((Supplier.apply _).tupled, Supplier.unapply)
Slick 1.0ではBasicProfileとExtendedProfileの2つのプロファイルを提供していた。Slick 2.0ではこれら2つのプロファイルをJdbcProfileとして統合している。今ではRelationalProfileに挙げられるようなより抽象的なプロファイルを提供している。RelationalProfileはJdbcProfileの全ての特徴を持っているわけではないが、新しく出来たHeapDriverやDistributedDriberといった機能を支えている。Slick 1.0からコードを移植する際、JdbcProfileへとプロファイルを変更して欲しい。特にSlick 2.0におけるBasicProfileは1.0におけるBasicProfilと非常に異なったものになっているので注意して欲しい。
Slick1.0では挿入時にtable objectの一部を射影していた。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
(Suppliers.name ~ Suppliers.street) insert ("foo", "bar")
suppliers.map(s => (s.name, s.street)) += ("foo", "bar")
+=オペレータはScalaコレクションとの互換性のために用いられており、insertという古い名前の関数はエイリアスとして依然用いる事が出来る。
Slick 2.0ではデータを挿入する際自動的にデフォルトでAutoIncのついたカラムを除外する。1.0では、そのようなカラムについて手動で除外した射影関数を別に用意しなくてはならなかった。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
case class Supplier(id: Int, name: String, street: String)
object Suppliers extends Table[Supplier]("SUPPLIERS") {
  def id = column[Int]("SUP_ID", O.PrimaryKey, O.AutoInc)
  def name = column[String]("SUP_NAME")
  def street = column[String]("STREET")
  // Map a Supplier case class:
  def * = id ~ name ~ street <> (Supplier.tupled, Supplier.unapply)
  // Special mapping without the 'id' field:
  def forInsert = name ~ street <> (
    { case (name, street) => Supplier(-1, name, street) },
    { sup => (sup.name, sup.street) }
  )
}
Suppliers.forInsert.insert(mySupplier)
idというカラムをSlickが除外してくれる。
case class Supplier(id: Int, name: String, street: String)
class Suppliers(tag: Tag) extends Table[Supplier](tag, "SUPPLIERS") {
def id = column[Int]("SUP_ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def * = (id, name, street) <> (Supplier.tupled, Supplier.unapply)
}
val suppliers = TableQuery[Suppliers]
suppliers += mySupplier
逆にAutoIncのついたカラムに対し値を挿入したいのならば、新しく出来たforceInsertやforceInsertAllといった関数を用いれば良い。
Slickはselect文において用いられるのと同じ方法で、update文における事前コンパイルもサポートしている。これについては、Compiled Queriesのセクションを見て欲しい。
Slick 1.0ではDatabaseのファクトリオブジェクトとして標準的なJDBCベースなDatabaseとSessionといった型がscala.slick.sessionパッケージにある。Slick 2.0からはJDBCベースなデータベースに制限せず、このパッケージは(backendとしても知られる)DatabaseComponent階層
によって置き換えられている。もしJdbcProfile抽象レベルで動かしたいのならば、以前にscala.slick.sessionにあったものをインポートし、常にJdbcBackendを用いれば良い。ただし、simple._といったインポートを行うと自動的にスコープ内にこれらの型が持ち込まれてしまうので注意して欲しい。
Slick 2.0では依然としてスレッドローカルな動的セッションと静的スコープセッションを提供している。しかしシンタックスが変わっており、静的スコープセッションを用いる際にはより簡潔な記述が推奨される。以前のthreadLocalSessionはdynamicSessionという名前に変わっており、関連するwithSessionやwithTransactionといった関数もwithDynSessionとwithDynTransactionという名前にそれぞれ変わっている。Slick 1.0では以下のようなシンタックスで記述をしていた。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
import scala.slick.session.Database.threadLocalSession
myDB withSession {
  // use the implicit threadLocalSession here
}
Slick 2.0で以下のようなシンタックスへ変わる。
import slick.jdbc.JdbcBackend.Database.dynamicSession
myDB withDynSession {
  // use the implicit dynamicSession here
}
Slick 1.0では静的スコープセッションにおける明示的な型宣言が必要だった。
myDB withSession { implicit session: Session =>
  // use the implicit session here
}
これは2.0において必要なくなる。
myDB withSession { implicit session =>
  // use the implicit session here
}
また、動的セッションを使うことは確かな情報を取得できるか分からない事から推奨されていない。静的セッションを用いる方がより安全である。
Slick 1.0のMappedTypeMapperはMappedColumnTypeへと名前が変わった。MappedColumnType.baseを用いるような基本的な操作はRelationalProfileレベル(高度な利用法をするのならば依然としてJdbcProfileが必要)において現在も利用できる。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
case class MyID(value: Int)
implicit val myIDTypeMapper =
  MappedTypeMapper.base[MyID, Int](_.value, new MyID(_))
この記述は、以下のように変わる。
case class MyID(value: Int)
implicit val myIDColumnType =
  MappedColumnType.base[MyID, Int](_.value, new MyID(_))
もしこの例のように単純なラッパー型へマッピングするのなら、MappedToを用いてもっと簡単に書くことが出来る。
case class MyID(value: Int) extends MappedTo[Int]
// No extra implicit required any more
