ScalaQueryの使い方&サンプル集

Scala Advent Calendar jp 2011 - PARTAKEの2日目の記事です。


Scala から RDB を使う必要があり、 ScalaQuery をいろいろ調べてみました。

DBオブジェクトの取得

Database.forURL とかを使います。

scala> import org.scalaquery.session.Database
import org.scalaquery.session.Database

scala> val db = Database.forURL("jdbc:postgresql:scala_query_example", driver="org.postgresql.Driver", user="user", password="password")
db: org.scalaquery.session.Database = org.scalaquery.session.Database$$anon$2@5b34329b

DBへの接続

withSession、withTransaction を使います。
おなじみLoanPatternです。
withSession、withTransactionのブロックを抜けると自動的に接続が切れます。

逆に言うと、このブロックの中でしかクエリは発行できません。

db withSession { /* この中でいろいろやる */}
db withTransaction { /* この中でいろいろやる */}

テストデータ

ビートルズのみなさんです。

CREATE TABLE beatles (
       id INTEGER PRIMARY KEY
       , first_name TEXT
       , last_name TEXT
);

INSERT INTO beatles (id, first_name, last_name) VALUES (1, 'John', 'Lennon');
INSERT INTO beatles (id, first_name, last_name) VALUES (2, 'Paul', 'McCartney');
INSERT INTO beatles (id, first_name, last_name) VALUES (3, 'George', 'Harrison');
INSERT INTO beatles (id, first_name, last_name) VALUES (4, 'Peter', 'Best');
> select * from beatle;                                                      
 id | first_name | last_name 
----+------------+-----------
  1 | John       | Lennon
  2 | Paul       | McCartney
  3 | George     | Harrison
  4 | Peter      | Best

SQLをそのまま実行

org.scalaquery.simple.StaticQueryを使えばSQLをそのまま使えます。
ScalaQuery で SQL を実行する - tototoshiの日記



これもなかなか便利ですが、やっぱORMとして使いたいですよね。
ってことで

テーブルの定義

org.scalaquery.ql.basic 以下のBasicTableを使います。
ExtendedTable ていうのもあるけど各DB固有の機能のものかな?大抵はBasicのほうで事足りるでしょう。


テーブルは
Table[(カラムの型)]("テーブル名")
カラムは
column[カラムの型]("カラム名")
という書き方をします。

カラムにはオプション(NotNull, PrimaryKeyなど)も渡せます

* というメソッドは必須です。

scala> import org.scalaquery.ql._
import org.scalaquery.ql._

scala> import basic.{ BasicTable => Table }
import basic.{BasicTable=>Table}

scala> object Beatles extends Table[(Int, String, String)]("beatles") {
     |   def id = column[Int]("id", O PrimaryKey)
     |   def first_name = column[String]("first_name")
     |   def last_name = column[String]("last_name")  
     |   def * = id~first_name~last_name
     | }
defined module Beatles

クエリの組み立て

Implicit Conversion の import を忘れずに!!
一応ドキュメントにも載っているのだけどわかりにくい。

scala> import org.scalaquery.ql.basic.BasicDriver.Implicit._  /* これを忘れずに!! */
import org.scalaquery.ql.basic.BasicDriver.Implicit._

scala> val q = for (b <- Beatles) yield { b.* }
q: org.scalaquery.ql.Query[(Int, java.lang.String, java.lang.String)] = Query

scala> val q2 = for (b <- Beatles) yield { b.first_name ~ b.last_name }
q2: org.scalaquery.ql.Query[(java.lang.String, java.lang.String)] = Query

scala のコレクションのように、DBのテーブルを扱うことができます。

クエリの実行

list() などはimplicitにSessionを要求するのでこんな書き方をします。

scala> import org.scalaquery.session.Session
import org.scalaquery.session.Session

scala> db withSession { implicit s: Session => q.list() }
res4: List[(Int, java.lang.String, java.lang.String)] = List((1,John,Lennon), (2,Paul,McCartney), (3,George,Harrison), (4,Ringo,Star))

scala> db withSession { implicit s: Session => q2.list() }
res5: List[(java.lang.String, java.lang.String)] = List((John,Lennon), (Paul,McCartney), (George,Harrison), (Ringo,Star))


org.scalaquery.session.Database.threadLocalSession(implicit定義されている) を importする手もあります。

scala> import org.scalaquery.session.Database.threadLocalSession
import org.scalaquery.session.Database.threadLocalSession

scala> db withSession { q.list() }
res7: List[(Int, java.lang.String, java.lang.String)] = List((1,John,Lennon), (2,Paul,McCartney), (3,George,Harrison), (4,Peter,Best))

サンプル集

テーブルのCREATE, DROP & CRUD はこんな感じ。

CREATE TABLE

ddl.create を使います。

    /*
     CREATE TABLE beatles (
         id INTEGER PRIMARY KEY
       , first_name TEXT
       , last_name TEXT
     );
    */

    db withSession { implicit s: Session =>
      Beatles.ddl.create
    }
INSERT

insert, または insertAll を使います。

    db withSession { implicit s: Session =>
      Beatles.insert((1, "John", "Lennon"))
      Beatles.insertAll((2, "Paul", "McCartney"),
                        (3, "George", "Harrison"),
                        (4, "Peter", "Best"))
    }
SELECT
    // SELECT first_name, lastname from beatles where id = 1
    val selectWhereIdEqual1 = for (b <- Beatles if b.id === 1) yield b.first_name ~ b.last_name

    // SELECT first_name, lastname from beatles where id != 1
    val selectWhereIdNotEqual1 = for (b <- Beatles if b.id =!= 1) yield b.first_name ~ b.last_name

    // SELECT * from beatles
    val selectAll = for (b <- Beatles) yield b.*


    db withSession { implicit s: Session =>
      val res1 = selectWhereIdEqual1.list()
      println(res1)  // List((John,Lennon))

      val res2 = selectWhereIdEqual1.first()
      println(res2) // (John,Lennon)

      val res3 = selectWhereIdEqual1.firstOption()
      println(res3) // Some((John,Lennon))

      val res4 = selectWhereIdNotEqual1.list()
      println(res4) // List((Paul,McCartney), (George,Harrison), (Peter,Best))

      val res5 = selectAll.to[Array]()
      println(res5.mkString("Array(", ", ", ")")) // Array((1,John,Lennon), (2,Paul,McCartney), (3,George,Harrison), (4,Peter,Best))
    }

== の代わりに ===
!= の代わりに =!= っていうのを使います。 *1


firstでひとつだけ取ってこれます。
firstOptionも最初のひとつを取ってきますが、なかった場合Noneになります。
ListのheadOptionみたいなもんですね。

first で値がなかった場合、例外飛んで死亡するので気をつけましょう。

UPDATE

ピート・ベストをリンゴに替えましょう

    // UPDATE Beatles SET first_name = Ringo, last_name = Starr WHERE first_name == Peter
    db withSession { implicit s: Session =>
      val q = for (b <- Beatles if b.first_name === "Peter") yield b.first_name ~ b.last_name
      q.update("Ringo", "Star")

      val res = selectAll.list()
      println(res) // List((1,John,Lennon), (2,Paul,McCartney), (3,George,Harrison), (4,Ringo,Star))
    }

これはちょっときもいかも。

DELETE

ジョンが死んでしまいました。テーブルから消しましょう。

    // DELETE FROM Beatles WHERE id = 1
    db withSession { implicit s: Session =>
      val before = selectAll.list()
      println(before) // List((1,John,Lennon), (2,Paul,McCartney), (3,George,Harrison), (4,Ringo,Starr))

      Beatles.where(_.id === 1).delete

      val after = selectAll.list()
      println(after) // List((2,Paul,McCartney), (3,George,Harrison), (4,Ringo,Starr))

    }

where ってのもあるんですよっと。

DROP TABLE
    // drop table beatles
    // drop table songs
    db withSession { implicit s: Session =>
      Beatles.ddl.drop
    }

DROP は CREATE 同様、 ddl を使います。

ここから随時追記ゾーン

COUNT

2012/02/01

    import org.scalaquery.ql.ColumnOps._
    val countQuery = Beatles map CountAll
    db withSession { implicit s: Session =>
      val count = countQuery.first
    }
OFFSET, LIMIT

take, dropでやります。わかりやすい。
extended driver が必要です。

    import org.scalaquery.ql.extended.PostgresDriver.Implicit._
    db withSession { implicit s: Session =>
      val result = q.drop(offset).take(limit).list()
    }

もっと知りたい人は

ここで説明してないこと↓

  • ORDER は?
  • GROUP BY は ?
  • JOIN は?

公式のドキュメントを見ましょう。 http://scalaquery.org/doc/
ドキュメント、いまいち少ないですが、 ScalaQuery_Commerzbank_2011.pdf というスライドはやたら気合いが入っててオススメ。


そのほか、 githubscala-query-example というプロジェクトがあります。
https://github.com/szeiger/scalaquery-examples


それから、scalaqueryのテストコードも参考にできます。
これだけ情報があればとりあえず大丈夫ではないでしょうか!


以上です!

*1:トンボみたい