Haskell で DB操作

最近 Yesod を弄っているが、Yesod で使ってるやつ (Database.Persistent) を使って、普通に DB 操作したいな、と。

sqlite と mongodb をやってみる。

github にあげた分 → https://github.com/rf0444/haskell-db

とりあえず自分の環境 (Mac) で runhaskell で確認した。
cabal はめんどそうなのでまた今度。

sqlite

Yesod の Persistent の通りにやればまあ動く。
構成を Yesod のプロジェクトっぽくしたかったので、yesod init した後の Model.hs を持ってきて、Yesod 関係のものを書き換える。

{-# LANGUAGE TypeFamilies, TemplateHaskell, FlexibleContexts, GADTs #-}
module Model where

import Data.Text (Text)
import Database.Persist.Quasi
import Database.Persist.Sqlite
import Database.Persist.TH

share [mkPersist sqlSettings, mkMigrate "migrateAll"]
    $(persistFileWith lowerCaseSettings "config/models")

こんな感じ。拡張が豪華だ・・・。
(5/13 いらなかった拡張を消しました)


config/models は、

User
    email Text
    password Text Maybe
    UniqueUser email

こんな感じ。テスト用なのでとても単純に。


本体 (main.hs) は、

{-# LANGUAGE OverloadedStrings #-}

import Database.Persist.Sqlite

import Model

main = withSqliteConn path $ runSqlConn $ do
  runMigration migrateAll
  insert $ User {
    userEmail = "hoge@hoge.jp",
    userPassword = Just "hoge"
  }
  return ()
 where
  path = "hogesql.sqlite3"

こんな感じ。


runhaskell を 2回たたくと、ちゃんと2 回目は error になってくれる。

$ runhaskell main.hs

$ runhaskell main.hs
main.hs: user error (SQLite3 returned ErrorConstraint while attempting to perform step.)

mongodb 編

config/models はそのままで、Model.hs と main.hs を書き換える。


Model.hs は、

{-# LANGUAGE TypeFamilies, TemplateHaskell, FlexibleContexts, GADTs #-}
module Model where

import Data.Text (Text)
import Database.Persist.Quasi
import Database.Persist.MongoDB
import Database.Persist.TH
import Language.Haskell.TH.Syntax

share [mkPersist MkPersistSettings { mpsBackend = ConT ''Action }, mkMigrate "migrateAll"]
    $(persistFileWith lowerCaseSettings "config/models")

こんな感じ。


sqlite 版との違いは、Database.Persistent.Sqlite が Database.Persistent.MongoDB になったのと、
mkPersist の引数が変わっているところ。ConT ''Action とかやってるので、Language.Haskell.TH.Syntax を import する。


main.hs は、

{-# LANGUAGE OverloadedStrings #-}

import Database.Persist.MongoDB

import Model

main = withMongoDBConn dbname hostname $ runMongoDBConn master $ do
  insert $ User {
    userEmail = "hoge@hoge.jp",
    userPassword = Just "hoge"
  }
  return ()
 where
  hostname = "localhost"
  dbname = "test"

こんな感じ。


sqlite 版との違いは、Database.Persistent.Sqlite が Database.Persistent.MongoDB になったのと、
withSqliteConn/runSqlConn が、withMongoDBConn/runMongoDBConn になったのと、
runMigration migrateAll がなくなったこと。


runMongoDBConn の第1引数には、書き込むならとりあえす master でいいっぽい。
詳しくは hackage の Database.Persist.MongoDB とか。
というか、これ 0.6.3 までしかドキュメントがない・・・。


sqlite の時は config/models の変更の際にテーブル構造を変更したりするように runMigration migrateAll をしていたが、
mongodb ではそもそもそういう構造を管理しないので、必要ないそう。

If you were using the MongoDB backend, migration would not be needed.

http://www.yesodweb.com/blog/2012/01/blog-example


で、runhaskell をたたくと動いてくれた。

$ runhaskell main.hs 

$ mongo
MongoDB shell version: 2.0.4
connecting to: test
> db.user.find()
{ "_id" : ObjectId("4f9e59686aface0ae8000000"), "email" : "hoge@hoge.jp", "password" : "hoge" }

が、もう一回たたくと・・・

$ runhaskell main.hs 

$ mongo
MongoDB shell version: 2.0.4
connecting to: test
> db.user.find()
{ "_id" : ObjectId("4f9e59686aface0ae8000000"), "email" : "hoge@hoge.jp", "password" : "hoge" }
{ "_id" : ObjectId("4f9e598c6aface0aff000000"), "email" : "hoge@hoge.jp", "password" : "hoge" }

・・・あれ? 入ってる・・・。


yesod init で普通にプロジェクト作ってやってみても、やっぱり重複して入れれるっぽい。
この辺 migration とかでやってほしい気も。


手で unique index はってみると、

$ mongo
MongoDB shell version: 2.0.4
connecting to: test
> db.user.remove()
> db.user.ensureIndex({email: 1}, {unique: true})
> exit

$ runhaskell main.hs 

$ runhaskell main.hs 
main.hs: PersistMongoDBError "WriteFailure 11000 \"E11000 duplicate key error index: test.user.$email_1  dup key: { : \\\"hoge@hoge.jp\\\" }\""

ちゃんと 2回目は error になってくれた。