ScalaQuery で SQL を実行 - StaticQuery の使用

id:fits:20110702 で使った SQL を ScalaQuery でも実行してみました。

ScalaQuery ではテーブル毎にテーブルオブジェクトを定義するのが一般的なようですので、SQL を直接使うケースは少ないかもしれませんが、とりあえず StaticQuery で SQL を実行できます。


今回使用した環境は以下の通りです。

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20110709/

準備

sbt 0.10 用のプロジェクト定義ファイル(build.sbt)を作成します。

以前の sbt で build.properties とプロジェクト定義ファイル(project/build/xxx.scala)に分かれていた設定を一つの build.sbt ファイルに定義できるようになっています。

libraryDependencies を使って依存モジュール(ScalaQuery と H2)の追加を、mainClass で実行クラスを定義しています。

build.sbt
name := "ScalaQuery StaticQuery Sample"

version := "1.0"

organization := "fits"

scalaVersion := "2.9.0-1"

libraryDependencies ++= Seq(
    "org.scalaquery" % "scalaquery_2.9.0" % "0.9.4",
    "com.h2database" % "h2" % "1.3.157"
)

mainClass in(Compile, run) := Some("fits.sample.Sample")

なお、リポジトリに ScalaQuery の Scala 2.9.0.1 版が用意されていなかったため、Scala 2.9.0 版の ScalaQuery を使用する設定にしています。(Scala 2.9.0.1 を使わないのであれば "org.scalaquery" %% "scalaquery" % "0.9.4" でよい)

StaticQuery を使った SQL の実行

今回使用する機能は以下の通りです。

  • Database.forXXX() で JDBC ドライバーを設定
  • withSession で DB 接続を自動的にクローズ
  • バインドパラメータを使わない SQL の実行に StaticQuery.queryNA[R]() を使用
    • R は実行結果の型
  • バインドパラメータを使う SQL の実行に StaticQuery.query[P,R]() を使用
    • P はバインドパラメータの型、R は実行結果の型

以下では、Tuple を使った queryNA()、ケースクラスを使った queryNA()、Tuple を使った query() を試しています。

src/main/scala/fits.sample/Sample.scala
package fits.sample

import org.scalaquery.session.{Database, Session}
import org.scalaquery.session.Database.threadLocalSession
import org.scalaquery.simple.StaticQuery
import org.scalaquery.simple.GetResult

object Sample {
    def main(args: Array[String]) = {
        Database.forURL("jdbc:h2:mem:", driver = "org.h2.Driver") withSession {
            val sql = """
                SELECT *
                FROM (
                    SELECT
                        pref_name,
                        station_g_cd,
                        station_name,
                        count(*) as lines
                    FROM
                      CSVREAD('m_station.csv') S
                      JOIN CSVREAD('m_pref.csv') P
                        ON S.pref_cd=P.pref_cd
                    GROUP BY station_g_cd, station_name
                    ORDER BY lines DESC
                )
                WHERE ROWNUM <= 10
            """

            //Tuple 版 - Tuple を使ってSQL実行結果の型を指定
            StaticQuery.queryNA[(String, Int, String, Int)](sql) foreach {r =>
                printf("%s駅 (%s) : %d\n", r._3, r._1, r._4)
            }

            println("---------------")

            //ケースクラス Station 版
            case class Station(val prefName: String, val stationGroupCode: Int, val stationName: String, val lines: Int)
            //SQL実行結果を Station に変換する定義
            implicit val getStationResult = GetResult(r => new Station(r <<, r <<, r <<, r <<))
            //queryNA の暗黙の関数パラメータ GetResult を解決するために getStationResult が適用されるため、
            //queryNA での型指定は不要
            StaticQuery.queryNA(sql) foreach {r =>
                printf("%s駅 (%s) : %d\n", r.stationName, r.prefName, r.lines)
            }

            println("---------------")

            val sql2 = """
                SELECT
                    rr_name,
                    station_name
                FROM
                    CSVREAD('m_station.csv')
                WHERE
                    pref_cd = ? AND
                    rr_name = ?
            """

            //バインドパラメータを伴う SQL の実行例
            // pref_cd=14, rr_name="JR" で検索
            StaticQuery.query[(Int, String), (String, String)](sql2) foreach (
                (14, "JR"), r => printf("%s %s駅\n", r._1, r._2)
            )
        }
    }
}

なお、threadLocalSession の import は重要で、これによって foreach 等での暗黙の関数パラメータ Session が解決されます。
そのため、threadLocalSession を import しなかった場合、以下のように自前で Session を implicit 定義するか、foreach に Session を明示的に渡す必要があります。

threadLocalSession を import しなかった場合
Database.forURL("jdbc:h2:mem:", driver = "org.h2.Driver") withSession {s: Session =>
    implicit val session = s
    ・・・

実行

sbt を使って実行します。

実行例
scala_query_sql> java -jar "c:\sbt\sbt-launch-0.10.0.jar"
・・・
> run
[info] Updating...
[info] Done updating.
[info] Compiling 1 Scala source to ・・・
[info] Running fits.sample.Sample
新宿駅 (東京都) : 12
横浜駅 (神奈川県) : 11
・・・
---------------
新宿駅 (東京都) : 12
横浜駅 (神奈川県) : 11
・・・
---------------
JR 川崎駅
JR 横浜駅
・・・