Haskellでデータベースを操作する
Yesod bookのPersistentの章の前半について読んだときのメモです。
persistent
PersistentはHaskellのパッケージで、データベースを使ってデータの保存などをしてくれる。
SQLとYesodのPersistentには以下のような対応がある。
SQL | Persistent |
---|---|
Datatypes (VARCHAR, INTEGER, etc) | PersistValue |
Column | PersistField |
Table | PersistEntity |
Persistent側では型としてテーブルをPersistEntity、カラムをPersistField, DatatypesをPersistValueと扱っている。
コード例
テーブルの定義とMigration, データの挿入、取得を行う
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} import Control.Monad.IO.Class (liftIO) import Database.Persist import Database.Persist.Sqlite import Database.Persist.TH import Control.Monad.IO.Unlift import Data.Text import Control.Monad.Reader import Control.Monad.Logger import Conduit share [mkPersist sqlSettings, mkSave "entityDefs"] [persistLowerCase| Person name String age Int Maybe deriving Show |] runSqlite' :: (MonadUnliftIO m) => Text -> ReaderT SqlBackend (NoLoggingT (ResourceT m)) a -> m a runSqlite' = runSqlite main :: IO () main = runSqlite' ":memory:" $ do runMigration $ migrate entityDefs $ entityDef (Nothing :: Maybe Person) michaelId <- insert $ Person "Michael" $ Just 26 michael <- get michaelId liftIO $ print michael
初見でわからなかった関数をリストにまとめた。これで雰囲気はつかめるはず。
mkPersist :: MkPersistSettings -> [EntityDef] -> Q [Dec]
- デ一タ型とPersistEntityのインスタンスを作成する。
- 返り値の型からDeclaration quotationsを返すことがわかる
- hacakge: https://hackage.haskell.org/package/persistent-template-2.7.1/docs/Database-Persist-TH.html#v:mkPersist
- source: https://hackage.haskell.org/package/persistent-template-2.7.1/docs/src/Database.Persist.TH.html#mkPersist
mkSave :: String -> [EntityDef] -> Q [Dec]
- 渡された
EntityDef
をString型の引数を名前として保存する。
- 渡された
share :: [[EntityDef] -> Q [Dec]] -> [EntityDef] -> Q [Dec]
- 複数の
[EntityDef] -> Q [Dec]
な関数に特定の[EntityDef]
を適用してQ [Dec]
を得る。
- 複数の
persistLowerCase :: QuasiQuoter
persistWith lowerCaseSttings
と同じpersistWith :: PersistSettings -> QuasiQuoter
- Converts a quasi-quoted syntax into a list of entity definitions, to be used as input to the template haskell generation code (mkPersist).
- 準クォ一トで書かれたやつをentityの定義に変換するやつ
lowerCaseSettings :: PersistSettings
- 準クォ一トの文法
share [mkPersist sqlSettings, mkSave "entityDefs"] [persistLowerCase| Person name String age Int Maybe deriving Show |]
share ...
と書いておいてレコ一ドを定義してあげるとレコ一ドの定義とPersistentEntity、つまりテ一ブルの定義ができる。
以下は知識として読んだことのメモ、特に試してない (遅延評価)。
Query
データの取得
get :: (MonadIO m, PersistRecordBackend record backend) => Key record -> ReaderT backend m (Maybe record)
- 上記のコード例と同じ関数。主キーを引数にとってMaybe型を返す。
- hackage
getBy
- 引数で指定したカラムでgetと同様に取得できる。
- hackage
Select
Function | Returns |
---|---|
selectList | 見つけた全てのデータを返す |
selectSource | selectListは見つけたデータをメモリに保存するのに対して、ストリームを返す |
selectFirst | 見つけた最初のIDと値を返す |
selectKeys | 見つけたデータのキーだけのリストを返す |
- selectList
データの操作
INSERT
最初のコード例にあったようにデータを挿入するとIDが返ってくる。 ただ、このIDは値の型の定義には含まれていない。つまり、getによって得られる値の型は
data Person = { personId :: PersonId, name :: String, age :: Int }
ではなく
data Person = { name :: String, age :: Int }
のようにIDの無い形になっている。
UPDATE
updateのコード例
personId <- insert $ Person "Michael" 26 update personId [PersonAge =. 27]
update :: (PersistStoreWrite backend, MonadIO m, PersistRecordBackend record backend) => Key record -> [Update record] -> ReaderT backend m ()
なのでID(キー)と値を渡して該当するデータを更新できる。
特定のフィールドだけ更新したい場合は+=.
、 -=.
、 *=.
、 /=.
を使うことができる。
たとえば
update personId [PersonAge +=. 1]
その他にも条件を満たすデータの更新をするupdateWhere :: ... => [Filter record] -> [Update record] -> ReaderT backend m()
や、IDに対して値を置き換えるreplace :: ... => Key record -> record -> ReaderT backend m()
がある。
DELETE
deleteもgetやgetBy, updateWhereのようにID, フィールド、条件によってそれぞれ削除する方法がある。 名前に"delete"を含む関数を見ること
まとめ
DBの基本的な操作をPersistentではどのようにするのかをざっくりと紹介した。 Yesod bookには詳細な説明と一意キー制約や、Relation, やもっと細かい要求に答えてくれるような内容があるので読むといい。
感想
初見では意味不明だったがわからない関数を調べていくうちにコ一ドが簡単に見えてきた。Hackageにはpersistent: Type-safe, multi-backend data serialization.
と書いてあった割にはSQLしかなかった。