ひょんなことから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" }
connect
とdefaultConnectInfo
の情報を調べてみましょう。
$ 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}
ここまで見れば明らかですがdefaultConnectInfoはConnectInfo型でこれはMySQLに接続するために必要な情報を格納するレコードです。connectはこれを引数にとってMySQLと接続するための関数です。defaultConnectInfo
のままではtest
のDBを見に行ってしまうのでレコード構文を使って先ほど作ったDBを見に行くように変えています。
defaultConnectInfo { connectDatabase = "mysql_test" }
次の行は実際にデータを挿入しているところです
execute conn "INSERT INTO user VALUES (?, ?)" (1 :: Int, "a" :: String)
mysql-simpleにはqueryとexecuteの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.