quill で DDL を実行
quill は Scala 用の DB ライブラリで、マクロを使ってコンパイル時に SQL や CQL(Cassandra)を組み立てるのが特徴となっています。
quill には Infix
という機能が用意されており、これを使うと FOR UPDATE
のような(quillが)未サポートの SQL 構文に対応したり、select 文を直接指定したりできるようですが、CREATE TABLE
のような DDL(データ定義言語)の実行は無理そうでした。
そこで、API やソースを調べてみたところ、SQL を直接実行する probe
や executeAction
という関数を見つけたので、これを使って CREATE TABLE を実行してみたいと思います。
ソースは http://github.com/fits/try_samples/tree/master/blog/20180502/
はじめに
今回は Gradle を使ってビルド・実行し、DB には H2 を(インメモリーで)使います。
build.gradle
apply plugin: 'scala' apply plugin: 'application' mainClassName = 'sample.SampleApp' repositories { jcenter() } dependencies { compile 'org.scala-lang:scala-library:2.12.6' compile 'io.getquill:quill-jdbc_2.12:2.4.2' runtime 'com.h2database:h2:1.4.192' runtime 'org.slf4j:slf4j-simple:1.8.0-beta2' }
DB の接続設定は以下のようにしました。
ctx
の部分は任意の文字列を用いることができ、H2JdbcContext
を new する際の configPrefix 引数で指定します。
src/main/resources/application.conf
ctx.dataSourceClassName=org.h2.jdbcx.JdbcDataSource ctx.dataSource.url="jdbc:h2:mem:sample" ctx.dataSource.user=sa
1. probe・executeAction で DDL を実行
それでは、probe
と executeAction
をそれぞれ使って CREATE TABLE を実行してみます。
JdbcContext
における probe
の戻り値は Try[Boolean]
で executeAction
の戻り値は Long
となっています。
sample1/src/main/scala/sample/SampleApp.scala
package sample import io.getquill.{H2JdbcContext, SnakeCase} case class Item(itemId: String, name: String) case class Stock(stockId: String, itemId: String, qty: Int) object SampleApp extends App { lazy val ctx = new H2JdbcContext(SnakeCase, "ctx") import ctx._ // probe を使った CREATE TABLE の実行 val r1 = probe("CREATE TABLE item(item_id VARCHAR(10) PRIMARY KEY, name VARCHAR(10))") println(s"create table1: $r1") // executeAction を使った CREATE TABLE の実行 val r2 = executeAction("CREATE TABLE stock(stock_id VARCHAR(10) PRIMARY KEY, item_id VARCHAR(10), qty INT)") println(s"create table2: $r2") // item への insert println( run(query[Item].insert(lift(Item("item1", "A1")))) ) println( run(query[Item].insert(lift(Item("item2", "B2")))) ) // stock への insert println( run(query[Stock].insert(lift(Stock("stock1", "item1", 5)))) ) println( run(query[Stock].insert(lift(Stock("stock2", "item2", 3)))) ) // item の select println( run(query[Item]) ) // stock の select println( run(query[Stock]) ) // Infix を使った select val selectStocks = quote( infix"""SELECT stock_id AS "_1", name AS "_2", qty AS "_3" FROM stock s join item i on i.item_id = s.item_id""".as[Query[(String, String, Int)]] ) println( run(selectStocks) ) }
実行結果は以下の通りで CREATE TABLE に成功しています。
probe の結果は Success(false)
で executeAction の結果は 0
となりました。
実行結果
> cd sample1 > gradle run ・・・ [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. create table1: Success(false) create table2: 0 1 1 1 1 List(Item(item1,A1), Item(item2,B2)) List(Stock(stock1,item1,5), Stock(stock2,item2,3)) List((stock1,A1,5), (stock2,B2,3)) ・・・
2. モナドの利用
quill には IO
モナドが用意されていたので、これを使って処理を組み立ててみます。
IO は run
の代わりに runIO
を使う事で取得でき、IO の結果は performIO
で取得します。
probe の結果である Try[A]
は IO.fromTry
を使う事で IO にできます。
また、クエリー query[A]
では flatMap 等が使えるので for 内包表記で直接合成できましたが(selectStocks
の箇所)、query[A].insert(・・・)
は flatMap 等を使えなかったので runIO しています。(insertItemAndStock
の箇所)
sample2/src/main/scala/sample/SampleApp.scala
package sample import io.getquill.{H2JdbcContext, SnakeCase} case class Item(itemId: String, name: String) case class Stock(stockId: String, itemId: String, qty: Int) object SampleApp extends App { lazy val ctx = new H2JdbcContext(SnakeCase, "ctx") import ctx._ // CREATE TABLE val createTables = for { it <- probe("CREATE TABLE item(item_id VARCHAR(10) PRIMARY KEY, name VARCHAR(10))") st <- probe("CREATE TABLE stock(stock_id VARCHAR(10) PRIMARY KEY, item_id VARCHAR(10), qty INT)") } yield (it, st) // item と stock へ insert val insertItemAndStock = (itemId: String, name: String, stockId: String, qty: Int) => for { _ <- runIO( query[Item].insert(lift(Item(itemId, name))) ) _ <- runIO( query[Stock].insert(lift(Stock(stockId, itemId, qty))) ) } yield () // stock と item の select(stock と該当する item をタプル化) val selectStocks = quote { for { s <- query[Stock] i <- query[Item] if i.itemId == s.itemId } yield (i, s) } // 処理の合成 val proc = for { r1 <- IO.fromTry(createTables) _ <- insertItemAndStock("item1", "A1", "stock1", 5) _ <- insertItemAndStock("item2", "B2", "stock2", 3) r2 <- runIO(selectStocks) } yield (r1, r2) // 結果 println( performIO(proc) ) // トランザクションを適用する場合は以下のようにする //println( performIO(proc.transactional) ) }
実行結果
> cd sample2 > gradle run ・・・ [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting... [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed. ((false,false),List((Item(item1,A1),Stock(stock1,item1,5)), (Item(item2,B2),Stock(stock2,item2,3)))) ・・・