2015年11月29日日曜日

[haskell][persistent][sqlite] Persistentパッケージ利用時にテーブルにインデックスを生成する方法

PersistentパッケージにはMigration機能が備わっており、自動的にテーブルを生成してくれます。スキーマ変更を行った際にも、変換が可能な限りテーブル内のレコードを保持したまま新しいスキーマに変換してくれます(Migration機能については過去のエントリでまとめています)。

自分が利用する上で、インデックスやトリガーを生成する手順が紹介されておらず困っていたのですが、rawExecuteという関数を用いることで自由にDDLを発行できることがわかりました。以下その手順とサンプルを紹介しておきます。

サンプルコード:

以下は、personテーブルのnameカラムにインデックスをs生成するサンプルです。runMigration実行直後に、runExecuteを実行することでインデックスを生成しています。このサンプルではインデックスを生成しているだけですが、同じ手順でトリガーの生成(CREATE TRIGGER)も可能です。
{-# LANGUAGE EmptyDataDecls             #-}
{-# 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         -- persistentパッケージ
import Database.Persist.Sqlite  -- persistent-sqliteパッケージ
import Database.Persist.TH      -- persistent-templateパッケージ

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
    name String
    age Int Maybe
    deriving Show
|]

main :: IO ()
main = runSqlite "test.db" $ do
    -- personテーブル生成
    runMigration migrateAll
    -- nameカラムにindexを生成する。placeholderがないため第2引数は[]でよい。
    -- 2回目以降、すでにインデックスが存在している場合のためにIF NOE EXISTSが必要。
    rawExecute "CREATE INDEX IF NOT EXISTS idx_name_on_person ON person(name);" [] -- here!

    michaelId <- insert $ Person "Michael" $ Just 26
    michael <- get michaelId
    liftIO $ print michael
runExecuteの第一引数には実行するSQLを記載します。既にインデックスが生成済みの場合には何も処理を行わないよう"IF NOT EXISTS"を指定しています。第二引数にはplaceholderの値を指定しますがこの例ではplaceholderは利用していないため"[]"を渡しています。
"IF NOT EXISTS"を指定しないと既に存在しているインデックスを再度生成しようとして、以下のようなエラーが報告されます。
*** Exception: SQLite3 returned ErrorError while attempting to perform prepare "CREATE INDEX idx_name_on_person ON person(name);": index idx_name_on_person already exists

補足:

Yesod BookのPersistentの解説には、rawQueryの利用手順が紹介されています。rawQueryとrawExecuteの型はそれぞれ以下のようになっています。
rawQueryが実行結果を配列で返すのに対し、rawExecuteの結果はunit(空)となっています。このためDDL (Data Definition Language)実行時にはrawExecuteを利用するのがよいと思われます。
さらにrawSqlという関数もあるようですが、こちらの使い分けはよく分からず…。ご存じの方是非ご教授ください。m(_ _)m
(2015/12/16:追記)rawQueryはData.Conduit.Source型で結果を返します。これは結果が巨大であってもストリーム処理できることを意味しています。これに対しrawSqlは単純にリストで結果を返すという違いがあります。

参考:

0 件のコメント:

コメントを投稿