LoginSignup
10
9

More than 5 years have passed since last update.

Slickで素のSQLを使うときにマクロでちょっと楽をする

Last updated at Posted at 2015-08-30

Slickには素のSQLを文字列で書く方法もあります。
http://slick.typesafe.com/doc/3.0.2/sql.html

これを使ってusersテーブルからデータを取得する処理を書いてみます。

試したのは、Scala2.11.6, Slick3.0.0です。

case class User(id: Int, email: String, name: String, age: Option[Int], address: Option[String])

def selectUsers: DBIO[Seq[User]] = sql"""
  SELECT
    id,
    email,
    name,
    age,
    address
  FROM
    users
  """.as[User]

implicit def getUserResult = GetResult { r=> User(r.nextInt, r.nextString, r.nextString, r.nextInt, r.nextString) }

SQLの結果からオブジェクトをつくるのが getUserResult メソッドなわけですが、r.nextInt とかはJDBCのResultSetを彷彿とさせますね。

rPositionedResult型のオブジェクトです。

r.nextIntとかは、r.<<と書くとちょっと楽になるようです。

implicit def getUserResult = GetResult { r => User(r.<<, r.<<, r.<<, r.<<, r.<<) }

ちょっと楽になったけど、r.<<の数を気にするのが面倒です。他のSQLも書いてるとボイラープレート化しますし、どうにかならないかなぁと思ってマクロを作ってやってみました。

作ったマクロを使うと下のように、createObjメソッドに生成したいケースクラスの型を型パラメータに、GetResult内の関数で受け取るrをパラメータに渡すだけでよくなります。r.<<を何回も書かなくてよくなりました!

implicit def getUserResult = GetResult { r => createObj[User](r) }

rを1回しか使ってないから_を使えますね。

implicit def getUserResult = GetResult { createObj[User](_) }

マクロの実装はこんな感じです。

package mymacros

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

import slick.jdbc.PositionedResult

object MyMacro {
  // マクロを利用する側から呼び出すメソッド
  def createObj[A](r: PositionedResult): A = macro createObjImpl[A]

  // マクロの実装
  def createObjImpl[A: c.WeakTypeTag](c: blackbox.Context)(r: c.Expr[PositionedResult]) = {
    import c.universe._

    // ケースクラス名のシンボルを取得
    val caseClassSym: c.universe.Symbol = c.weakTypeOf[A].typeSymbol
    if (!caseClassSym.isClass || !caseClassSym.asClass.isCaseClass) c.abort(c.enclosingPosition, s"$caseClassSym is not a case class")

    // ケースクラスのフィールドを取得
    val fields = caseClassSym.typeSignature.decls.filter {
      case x: TermSymbol => x.isVal && x.isCaseAccessor
      case _ => false
    }

    // rr.<<, rr.<<・・・
    val params = fields.map(_ => q"rr.<<")

    // User(r.<<, r.<< ・・・) っていうコード
    val code = q"""
    val rr = $r
    ${caseClassSym.name.toTermName}(..$params)
    """

    code
  }
}

参考にさせていただきました。

初めてマクロ作ってみたんですが、コンパイル時に動作するものだから事前にコンパイルしておく必要があるんですねー。sbtで自作マクロを使うのに参考にさせていただきました。

10
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9