Edited at

Haskell から MySQL を使う

More than 1 year has passed since last update.

ひょんなことからHaskellで簡単な掲示板を作る機会があったので

その時に覚えたmysql-simpleの使い方を書き留めておこうと思います。


環境構築

テスト用のディレクトリとcabalファイルを作ってmysql-simpleを入れます

$ mkdir mysql-test && cd mysql-test

$ cabal sandbox init

mysql-test.cabal を作ります。これはライブラリの依存関係を記述するファイルです。

$ vim mysql-test.cabal

name: mysql-test

version: 0.0.0.1
build-type: Simple
cabal-version: >=1.20

executable app
main-is: app.hs
build-depends: base, mysql-simple
ghc-options: -Wall
default-language: Haskell2010

依存ライブラリをインストールしましょう。

$ cabal install --only-dependencies

実際にアプリケーションを書いていきます

$ vim app.hs

{-# LANGUAGE OverloadedStrings #-}

import Database.MySQL.Simple

main :: IO ()
main = do
conn <- connect defaultConnectInfo
[Only i] <- query_ conn "select 2 + 2"
print (i :: Int)

実行します。このときMySQLサーバーが立ち上がってることを確認して下さい。

$ cabal run app

4

4 が表示されれば成功です!

テスト用のDBを作っておきましょう。

$ mysql -uroot

mysql> CREATE DATABASE mysql_test;
mysql> USE mysql_test;
mysql> CREATE TABLE user (id int unsigned, name varchar(255), PRIMARY KEY (id));


CRUD


CREATE

{-# LANGUAGE OverloadedStrings #-}

import Database.MySQL.Simple

main :: IO ()
main = do
conn <- connect defaultConnectInfo { connectDatabase = "mysql_test" }
execute conn "INSERT INTO user VALUES (?, ?)" (1 :: Int, "a" :: String)
return ()

実行すると確かにデータが挿入されているはずです

mysql> SELECT * FROM user;

+----+------+
| id | name |
+----+------+
| 1 | a |
+----+------+

main関数を一行ずつ見て行きましょう

最初の行はMySQLとの接続をしています

conn <- connect defaultConnectInfo { connectDatabase = "mysql_test" }

connectdefaultConnectInfoの情報を調べてみましょう。

$ ghci

Prelude> :m Database.MySQL.Simple

Prelude> :t connect
connect :: ConnectInfo -> IO Connection

Prelude> :t defaultConnectInfo
defaultConnectInfo :: ConnectInfo

Prelude> :i ConnectInfo
data ConnectInfo
= ConnectInfo {connectHost :: String,
connectPort :: GHC.Word.Word16,
connectUser :: String,
connectPassword :: String,
connectDatabase :: String,
connectOptions :: [Database.MySQL.Base.Types.Option],
connectPath :: FilePath,
connectSSL :: Maybe Database.MySQL.Base.SSLInfo}

Prelude> defaultConnectInfo
ConnectInfo {connectHost = "localhost", connectPort = 3306, connectUser = "root", connectPassword = "", connectDatabase = "test", connectOptions = [CharsetName "utf8"], connectPath = "", connectSSL = Nothing}

ここまで見れば明らかですがdefaultConnectInfoConnectInfo型でこれはMySQLに接続するために必要な情報を格納するレコードです。connectはこれを引数にとってMySQLと接続するための関数です。defaultConnectInfoのままではtestのDBを見に行ってしまうのでレコード構文を使って先ほど作ったDBを見に行くように変えています。

defaultConnectInfo { connectDatabase = "mysql_test" }

次の行は実際にデータを挿入しているところです

execute conn "INSERT INTO user VALUES (?, ?)" (1 :: Int, "a" :: String)

mysql-simpleにはqueryexecuteの2つのクエリを実行する関数があります。queryはSELECT文のように何か結果を返すようなクエリの実行に、executeはINSERTやUPDATEのような何も結果を返さないようなクエリの実行に使います。今回はINSERTなのでexecuteを使っています。

クエリの中の"?"は後の引数のタプルの要素がサニタイジングされて代入されます。クエリ中の"?"の個数とタプルの要素数は一致していないといけません。

(1 :: Int, "a" :: String)の部分は少し見にくいですが本質的には(1,"a")のタプルで、リテラルだと型がわからないので型注釈が入った形になっています。

最後の

return ()

の行はmain関数の型を合わせるために入っています。

一気に十人ぐらいのデータを入れてみましょう。

{-# LANGUAGE OverloadedStrings #-}

import Control.Monad
import Database.MySQL.Simple

main :: IO ()
main = do
conn <- connect defaultConnectInfo { connectDatabase = "mysql_test" }
forM (zip [2..10] ['b'..]) $ \(n, c) -> do
execute conn "INSERT INTO user VALUES (?, ?)" (n :: Int, [c])
return ()


READ

先ほど入れたデータを今度は読んでみましょう

{-# LANGUAGE OverloadedStrings #-}

import Database.MySQL.Simple

main :: IO ()
main = do
conn <- connect defaultConnectInfo { connectDatabase = "mysql_test" }
rs <- query_ conn "SELECT * FROM user"
mapM_ putStrLn $ map show (rs :: [(Int, String)])

実行すると

$ cabal run app

(1,"a")
(2,"b")
(3,"c")
(4,"d")
(5,"e")
(6,"f")
(7,"g")
(8,"h")
(9,"i")
(10,"j")

ちゃんと表示されると思います。

query_, execute_はquery, executeとほとんど同じですが、クエリ中に"?"が無くタプルを引数としてとらない場合に使います。

実行結果は(rs :: [(Int, String)])のように正しく型付けしてやる必要があります。

type User = (Int, String)

のようにしておけば(rs :: [User])のように書くことも出来ます。


UPDATE, DELETE

ここまでくればUPDATEとDELETEは自然に書けると思います。


UPDATE

{-# LANGUAGE OverloadedStrings #-}

import Database.MySQL.Simple

main :: IO ()
main = do
conn <- connect defaultConnectInfo { connectDatabase = "mysql_test" }
execute_ conn "UPDATE user SET name = 'aa' WHERE id = 1"
return ()


DELETE

{-# LANGUAGE OverloadedStrings #-}

import Database.MySQL.Simple

main :: IO ()
main = do
conn <- connect defaultConnectInfo { connectDatabase = "mysql_test" }
execute_ conn "DELETE FROM user WHERE id = 10"
return ()


簡単なアプリを作ってみる

最後にさっき作ったuserテーブルを読み書きするだけの簡単なアプリを作ってみます

{-# LANGUAGE OverloadedStrings #-}

import Control.Monad
import Database.MySQL.Simple

type User = (Int, String)

getConnection:: IO Connection
getConnection = connect defaultConnectInfo { connectDatabase = "mysql_test" }

mySelect :: IO ()
mySelect = do
conn <- getConnection
rs <- query_ conn "SELECT * FROM user"
mapM_ putStrLn $ map show (rs :: [User])

myInsert :: IO ()
myInsert = do
putStrLn "Data: "
input <- getLine
let user = (read $ concat ["(", input, ")"]) :: User
conn <- getConnection
execute conn "INSERT INTO user VALUES (?, ?)" user
return ()

myUpdate :: IO ()
myUpdate = do
putStrLn "Data: "
input <- getLine
let (n, name) = (read $ concat ["(", input, ")"]) :: User
conn <- getConnection
execute conn "UPDATE user SET name = ? WHERE id = ?" (name, n)
return ()

myDelete :: IO ()
myDelete = do
putStrLn "ID: "
input <- getLine
let n = read input :: Int
conn <- getConnection
execute conn "DELETE FROM user WHERE id = ?" (Only n)
return ()

main :: IO ()
main = do
putStrLn "Command: "
command <- getLine
case command of
"show" -> mySelect >> main
"insert" -> myInsert >> main
"update" -> myUpdate >> main
"delete" -> myDelete >> main
"exit" -> putStrLn "Bye." >> return ()
_ -> putStrLn "Command: show, insert, update, deletem or exit" >> main

実際にコピペして実行してみてください。

以下のような感じで遊ぶことが出来ます

$ cabal run app

Command:
show
(1,"aa")
(2,"b")
(3,"c")
(4,"d")
(5,"e")
(6,"f")
(7,"g")
(8,"h")
(9,"i")
Command:
insert
Data:
10, "haskell"
Command:
delete
ID:
8
Command:
show
(1,"aa")
(2,"b")
(3,"c")
(4,"d")
(5,"e")
(6,"f")
(7,"g")
(9,"i")
(10,"haskell")
Command:
exit
Bye.