Slick 2.0.0 documentation - 08 User-Defined Features

Permalink to User-Defined Features — Slick 2.0.0 documentation

User-Defined Features 

ここではLifted Embedding APIにおいて、カスタムしたデータ型やデータベース関数をどのようにして用いるのか、についての説明を行う。

Scala Database functions 

もしデータベースシステムがSlickにおける関数として利用できないスカラー関数をサポートしていたのならば、それはSimpleFunctionとして別途定義する事が出来る。パラメータや返却型が固定されたunary, binary, ternaryな関数を生成するための関数が事前に用意されている。

// H2 has a day_of_week() function which extracts the day of week from a timestamp
val dayOfWeek = SimpleFunction.unary[Date, Int]("day_of_week")
...
// Use the lifted function in a query to group by day of week
val q1 = for {
  (dow, q) <- salesPerDay.map(s => (dayOfWeek(s.day), s.count)).groupBy(_._1)
} yield (dow, q.map(_._2).sum)

より柔軟に型を取り扱いたいのなら、型の不定なインスタンスを取得し、適切な型チェックを行う独自のラッパー関数を書くためにSimpleFunction.applyを用いる事ができる。

def dayOfWeek2(c: Column[Date]) =
  SimpleFunction[Int]("day_of_week").apply(Seq(c))

SimpleBinaryOperatorSimpleLiteralも同様に扱う事ができる。より柔軟なものを求めるのならば、SimpleExpressionを使うと良い。

Other Database functions and stored procedures 

完全なテーブル(complete tables)やストアドプロシージャを返却するデータベース関数を扱うのならば、Plain SQL Queriesを使えば良い。複数の結果を返却するストアドプロシージャは現在サポートしていない。

Scalar Types 

もしカスタムされたカラムが必要ならば、ColumnTypeを実装する事で扱える。最も一般的な利用法として、アプリケーション固有な型をデータベースに存在している型へとマッピングする事などが挙げられる。これはMappedColumnTypeを用いることでよりシンプルに書ける。

// An algebraic data type for booleans
sealed trait Bool
case object True extends Bool
case object False extends Bool
...
// And a ColumnType that maps it to Int values 1 and 0
implicit val boolColumnType = MappedColumnType.base[Bool, Int](
  { b => if(b == True) 1 else 0 },    // map Bool to Int
  { i => if(i == 1) True else False } // map Int to Bool
)
...
// You can now use Bool like any built-in column type (in tables, queries, etc.)

より柔軟なものを用いたいのならば、MappedjdbcTypeのサブクラスを用いれば良い。

もしある型を基礎とした独自のラッパークラスを持っているなら、マクロで生成された暗黙的なColumnTypeを自由に取得するために、そのクラスをMappedToへと拡張させれる。そのようなラッパークラスは一般的に型安全でテーブル固有な主キー型のために用いられる。

// A custom ID type for a table
case class MyID(value: Long) extends MappedTo[Long]
...
// Use it directly for this table's ID -- No extra boilerplate needed
class MyTable(tag: Tag) extends Table[(MyID, String)](tag, "MY_TABLE") {
  def id = column[MyID]("ID")
  def data = column[String]("DATA")
  def * = (id, data)
}

Record Types 

レコード型は個別に宣言された型を持つ複数の混合物を含んだデータ構造となっている。 Slickは(引数限度が22の)ScalaタプルとSLick独自の実験的な実装であるHList(引数制限が無いが、25個の引数を超えると異様にコンパイルが遅くなるもの)をサポートしている。レコード型はSlickにおいて恣意的にネストされ、混合されたものとして扱われている。

もし柔軟性を必要とするなら、暗黙的なShape定義を行う事で、独自のものをサポートする事が出来る。Pairを用いた例は以下のようになる。

// A custom record class
case class Pair[A, B](a: A, b: B)

レコード型のためのScapeの実装はMappedScaleProductShapeを拡張する事で行う。一般的にこの実装はシンプルになるが、全ての型に関連するボイラープレートをいくつか必要とする。MappedScaleProductShapeは要素に対するShapeの配列を引数に取り、buildValue(与えられた要素からレコード型のインスタンスを作成するもの)やcopy(このShapeをコピーして新しいShapeを作るもの)オペレーションを提供する。

// A Shape implementation for Pair
final class PairShape[Level <: ShapeLevel, M <: Pair[_,_], U <: Pair[_,_], P <: Pair[_,_]](
  val shapes: Seq[Shape[_, _, _, _]])
extends MappedScalaProductShape[Level, Pair[_,_], M, U, P] {
  def buildValue(elems: IndexedSeq[Any]) = Pair(elems(0), elems(1))
  def copy(shapes: Seq[Shape[_, _, _, _]]) = new PairShape(shapes)
}
...
implicit def pairShape[Level <: ShapeLevel, M1, M2, U1, U2, P1, P2](
  implicit s1: Shape[_ <: Level, M1, U1, P1], s2: Shape[_ <: Level, M2, U2, P2]
) = new PairShape[Level, Pair[M1, M2], Pair[U1, U2], Pair[P1, P2]](Seq(s1, s2))

この例では、暗黙的な関数であるpairShapeが、2つの要素型を取るPairのためのShapeを提供している。

これらの定義を用いて、タプルやHListを用いる事の出来るどんな場所においてでもPairのレコード型を用いる事が出来る。

// Use it in a table definition
class A(tag: Tag) extends Table[Pair[Int, String]](tag, "shape_a") {
  def id = column[Int]("id", O.PrimaryKey)
  def s = column[String]("s")
  def * = Pair(id, s)
}
val as = TableQuery[A]
as.ddl.create
...
// Insert data with the custom shape
as += Pair(1, "a")
as += Pair(2, "c")
as += Pair(3, "b")
...
// Use it for returning data from a query
val q2 = as
  .map { case a => Pair(a.id, (a.s ++ a.s)) }
  .filter { case Pair(id, _) => id =!= 1 }
  .sortBy { case Pair(_, ss) => ss }
  .map { case Pair(id, ss) => Pair(id, Pair(42 , ss)) }
Fork me on GitHub