2014年8月31日日曜日

[haskell][persistent][sqlite] Haskellでデータベースを利用するには

HaskellでDBの読み書きをするにはどうすればいいか?
yesodのpersistentパッケージが利用できます。本家のサイトを参考にpersistentパッケージの特徴と、簡単な処理を行うサンプルをまとめました。

persistentパッケージの特徴

  • 「型安全、簡潔、宣言的」という原則に従う
  • DB(PostgreSQL, SQLite, MySQL, MongoDB, etc.)に依存しないAPI
  • 安全で生産的なクエリーインターフェース
  • 他のプログラミング言語からのDBアクセスも考慮
  • yesodに含まれているが、独立したライブラリとして利用可能

主要な型の説明

  • PersistValue
    • persistentの基本構成要素。DBから出し入れする値を表すデータ型。
    • 
      data PersistValue = PersistText Text
                        | PersistByteString ByteString
                        | PersistInt64 Int64
                        | PersistDouble Double
                        | PersistRational Rational
                        | PersistBool Bool
                        | PersistDay Day
                        | PersistTimeOfDay TimeOfDay
                        | PersistUTCTime UTCTime
                        | PersistZonedTime ZT
                        | PersistNull
                        | PersistList [PersistValue]
                        | PersistMap [(Text, PersistValue)]
                        | PersistObjectId ByteString -- ^ intended especially for MongoDB backend
      
      
      
  • PersistField
    • リレーショナルデータベースのカラムに対応。Haskellの任意のデータ型(datatype)とPersistValueを相互にどうマーシャリングする手順を定義する。
  • PersistEntity
    • PersistentEntityのインスタンスはリレーショナルデータベースのテーブルに対応する。
  • PersistentStore
    • 各データストア(PostgreSQL, SQLite, MongoDB, etc.)はPersistentStoreのインスタンスを持つ。PersistentValueからDB固有の値への変換が行われる。
    • runSqliteは引数で渡されたconnection stringを用いて、DBへのコネクションを一つ生成する。一回のrunSqliteの呼び出しは、一つのトランザクションの中で実行される。
  • 対応関係
    • persistentの型と、データベースの対応関係を整理すると以下のようになる。
    • persistentSQL
      PersistValueデータの型(VARCHAR, INTEGER, etc)
      PersistFieldカラム
      PersistEntityテーブル
      PersistStoreデータストア(PostgreSQL, SQLite, MySQL, MongoDB)

データのINSERT/SELECTサンプル

以下の処理を行うサンプルを掲載しておきます。個々の処理が何を意味しているのかはコメントを参考にしてください。
  • personテーブルを生成
  • レコードを一つINSERT
  • SELECTで登録したレコードを取得
  • 取得したレコードを表示


{-# LANGUAGE EmptyDataDecls    #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE GADTs             #-}
{-# 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パッケージ

-- mkPersist
--   mkPersist :: MkPersistSettings -> [EntityDef SqlType] -> Q [Dec]
--   データ型、PersistentEntityインスタンスを生成
-- sqlSettings
--   mkPersistの挙動を変える設定値
-- mkMigrate "migrateAll"
--   マイグレーション処理を定義
-- QuansiQuotes [xx|..|] でスキーマ定義。
--   persistentLowaCaseはテーブル名・フィールド名に、"_"区切り小文字を
--   利用することを指示している。
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person             -- personテーブル。プライマリキーとして"id"カラム自動生成。
    name String    -- nameカラム(VARCHAR, NOT NULL制約)
    age Int Maybe  -- ageカラム(INTEGER, MaybeはNULLを許容することを意味する)
    deriving Show
|]

main :: IO ()
main = runSqlite ":memory:" $ do -- DBオープン時の引数。メモリDB利用。"test.db"等の
                                 -- ファイル名を渡すとファイルDBがオープンされる。
    -- personテーブル生成
    runMigration migrateAll

    -- name: "Michael", age: 26 のレコードを INSERT
    -- id(プライマリキー)が返値として返される。
    michaelId <- insert $ Person "Michael" $ Just 26
    -- id でレコードを SELECT。該当レコードが返される。
    michael <- get michaelId
    -- 返されたレコード(Personインスタンス)を表示。
    liftIO $ print michael



Uniqueness

大文字から始まるデータ型の宣言を追加することで、指定のカラムにUNIQUE制約を付与することができます。
以下にINSERT/SELECTサンプルをベースに、addressおよび、firstName+lastNameにUNIQUE制約を付与したサンプルコードを掲載しておきます。UNIQUE制約に関連する処理のみコメントで説明を加えています。


{-# LANGUAGE EmptyDataDecls    #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE GADTs             #-}
{-# 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

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
    firstName String -- nameをfistNameと
    lastName String  -- lastNameに分解
    age Int Maybe
    address String  -- addressカラム(VARCHAR)を新たに追加
    Address address -- addressカラムをUNIQUE制約に(先頭大文字でAddressを宣言)
    PersonName firstName lastName -- firstName, lastNameの組合せをUNIQUE制約に
    deriving Show
|]

main :: IO ()
main = runSqlite ":memory:" $ do
    runMigration migrateAll

    -- addressも引数に追加。bobのデータも登録してみる。
    let michaelAddress = "1-2 xx Tokyo"
    michaelId <- insert $ Person "Michael" "Snoyman" (Just 26) michaelAddress
    bobId <- insert $ Person "Bob" "Marley" (Just 29) "2-20 yyy Hokkaido"

    -- UNIQUE制約フィールドは getBy で SELECTがかけられる
    bob <- getBy $ Address "2-20 yyy Hokkaido"
    -- bob の情報を表示。
    liftIO $ print bob

    -- PersonNameでもSELECTが可能。fistName, lastNameを渡してインスタンスを生成
    michael <- getBy $ PersonName "Michael" "Snoyman"
    liftIO $ print michael

    -- 登録されていないAddressでSELECTするとNothingが返される
    fail <- getBy $ Address "123-456 Fukuoka"
    liftIO $ print fail

    -- UNIQUE制約に違反するレコード(Michaelと重複)を登録しようとするとエラーになる
    markId <- insert $ Person "Mark" "Twain" (Just 29) michaelAddress
    -- エラー出力:
    -- uniqueness: user error (SQLite3 returned ErrorConstraint while attempting to perform step.)
    mark <- get markId
    liftIO $ print mark



参考にしたサイト



2014年8月23日土曜日

[emacs][cygwin][mac] cygwin及びmac環境へのcmigemoのインストール

cmigemoという便利なツールあることを知って、Windows(cygwin)とmacにインストールしました。cmigemoを利用することで、emacsのI-searchでひらがな・カタカナ・漢字を検索できるようになり非常に便利です!
例えば、C-sもしくはC-rに続き"kara"を入力すると、メインバッファ中の「から」「」「(からす)」「カラオケ」「karaoke」「小」がインクリメンタルサーチにかかります。

cygwinでのインストール手順

  • Cygwin SetupからC/Migemoのビルドに必要なパッケージをインストール
    • cygwinディレクトリのsetup_x64_64.exe(setup_x64.exe)を起動し、以下のパッケージを選択、インストールします。
      • Devel
        • gcc
        • make
      • Libs
        • libiconv
      • Web
        • wget
  • qkcのインストール
    • QKC Home Pageから"For UNIX"版のzipアーカイブをダウンロードしてインストールします。
    • 
      % unzip qkcc100.zip
      % make
      % cp qkc.exe /usr/local/bin
      
      
  • cmigemoのインストール
    • 以下のコマンドを実行することでインストールできます。オフィシャルな手順はcmigemo/doc/README_j.txtに記載されています。
    • 
      % git clone https://github.com/koron/cmigemo.git
      % cd cmigemo
      % ./configure
      % make cyg
      % make cyg-dict
      % make cyg-install
      
      
    • ./configureで"$'\r': コマンドが見つかりません"というエラーが出る場合はこちらを参考にしてエラーを回避してください。
    • 最後のmake cyg-installの実行で/usr/local以下に関連ファイルがコピーされます。アンインストールしたい場合にはmake cyg-uninstallを実行してください。
      • <要確認>インストールには管理者権限が必要(管理者権限を付与したターミナル上で実行)?
  • migemo.elのインストール
    • emacsを起動し以下のコマンドを実行
    • 
      M-x package-refresh-contents
      M-x package-install migemo
      
      
    • .emacsに以下のコードを追加
    • 
      (require 'migemo)
      (setq migemo-command "cmigemo")
      (setq migemo-options '("-q" "--emacs"))
      ;; Set your installed path
      (setq migemo-dictionary "/usr/local/share/migemo/utf-8/migemo-dict")
      (setq migemo-user-dictionary nil)
      (setq migemo-regex-dictionary nil)
      (setq migemo-coding-system 'utf-8-unix)
      (load-library "migemo")
      (migemo-init)
      
      

macでのインストール手順

  • cmigemoのインストール
    • 以下のコマンドを実行することでcmigemoをインストールできます。デフォルトでインストールされているnkf, iconvが利用されるため、cygwinで行ったセットアップは不要です。
    • 
      % git clone https://github.com/koron/cmigemo.git
      % cd cmigemo
      % ./configure
      % make osx
      % make osx-dict
      % sudo make osx-install
      
      
    • デフォルトのインストール先は/usr/localになります。/usr/local/bin, /usr/local/libがPATH, LD_LIBRARY_PATHに設定されていない場合にはこれらを追加する必要があります。
  • migemo.elのインストール
    • emacsを起動し以下のコマンドを実行
    • 
      M-x package-refresh-contents
      M-x package-install migemo
      
      
    • .emacsに以下のコードを追加。utf8を利用する設定が紹介されておりcygwin環境ではそれを用いましたが、自分のmac環境ではutf8では後述の問題が出たためeuc-jpを設定しています。
    • 
      (require 'migemo)
      (setq migemo-command "cmigemo")
      (setq migemo-options '("-q" "--emacs"))
      ;; Set your installed path
      (setq migemo-dictionary "/usr/local/share/migemo/euc-jp/migemo-dict") ; macではutf8はNG?
      (setq migemo-user-dictionary nil)
      (setq migemo-regex-dictionary nil)
      (setq migemo-coding-system 'euc-jp) ; macではutf8はNG?
      (load-library "migemo")
      (migemo-init)
      
      
  • (補足)macではutf8の設定では正しくI-searchできない?
    • cygwinと同じ手順で「mac環境でも幸せ〜」になる予定だったのですが、アルファベットのI-searchができず不幸せな状態になってしまいました。問題の状況は"compile"を検索しようとして"co"をI-searchで入力しても検索に失敗してしまう、という状態でした。ミニバッファには以下のような情報が出力されています。"compi"まで入力すれば検索できる状態になります。
      • [MIGEMO] I-search: co [incomplete input]
    • この情報を見つけて以下のコマンドを実行してみましたが状況は改善せず。
      • M-x migemo-pattern-alist-clear
    • 試行錯誤した結果、エンコードをutf8からeuc-jpに変更すると正しく動作するようになったため、現状はこの設定で利用しています。

参考

[cygwin] 改行コードにCR+LFが用いられているスクリプトを実行する手順

キャリッジリターン(CR, コードは0x0d)が改行コードとして用いられているスクリプトファイルをcygwin上で実行しようとすると、以下のようなエラーになります。

./configure: line 16: $'\r': コマンドが見つかりません


ネット上で解決方法を探してみたところ、.bashrcで以下の設定をしておけばよいとのこと。

export SHELLOPTS
set -o igncr


ところが自分のcygwin環境ではシェルにtcshを用いているために、上記の対応では問題を解決できませんでした。tcsh上でファイル先頭に

#!/bin/sh

が記載された、bash/shのスクリプトを起動したタイミングではホームディレクトリの.bashrcはロードしてくれないようです。/etc/bash.bashrc, ~/.startxwinrcに設定をいれてみても状況は改善せず…。
最終的に/etc/profileにSHELLOPTSの設定を入れることで、意図した動作になりました。

参考