初速1PV

プログラミングにまつわることの記録

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

初見でわからなかった関数をリストにまとめた。これで雰囲気はつかめるはず。

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
    • 方は(MonadIO m, PersistQueryRead backend, PersistRecordBackend record backend) => [Filter record] -> [SelectOpt record] -> ReaderT backend m [Entity record]と結構長い
    • Filter a SQLのWhere句にあたる条件のリストと SQlORDER BY ASCLIMITにあたるSelectOptのリストを引数にとり適切なデータを返す。
    • hackage

データの操作

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しかなかった。

参考