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 になってくれた。