Haskell で PostgreSQL を扱う:「遅延読み込み」

前回は connectPostgreSQL でコネクションを確立して quickQuery'で読み出しましたが、今回は prepare を使った読み出しです。
SQLの「PREPARE で SQL を準備し、EXECUTE によりパラメータのみの実行」に相当します。

--  ghc --make -o sel -LC:\PostgreSQL\8.4\lib select.hs
main = do
  conn <- connectPostgreSQL "host=localhost port=5433 dbname=test" 
  stmt <- prepare conn "SELECT id,code from address"
  execute stmt []
  -- fetchAllRowsAL:この関数を変えると出力されるデータの型が変わる。
  results <- fetchAllRowsAL stmt
  -- この場合、テーブルの件数にかかわらず、読み出されるのは2行。
  print $ take 2 results
  disconnect conn
-- アポストロフィーのない関数は遅延読み込み
results <- fetchAllRowsAL stmt

-- fetchAllRows stmt のとき
[[SqlInteger 1,SqlInteger 1],[SqlInteger 2,SqlInteger 5]]

-- sFetchAllRows' stmt
[[Just "1",Just "1"],[Just "2",Just "5"]]

-- fetchAllRowsAL が返すのはカラム名とデータのタプル。
[[("id",SqlInteger 1),("code",SqlInteger 1)],[("id",SqlInteger 2),("code",SqlInteger 5)]]
  • fetchAllRowsAL :カラム名とデータのタプルを返します。
  • fetchAllRows :SqlValue のリストを返します。
  • 頭に s のついた関数は Maybe 型 を返します。
sFetchAllRows  :: Statement -> IO [[Maybe String]]
sFetchAllRows' :: Statement -> IO [[Maybe String]]
sFetchRow      :: Statement -> IO (Maybe [Maybe String])
sRun           :: (IConnection conn) => conn -> String -> [Maybe String] -> IO Integer

「遅延読み込み」
同名の関数で末尾にアポストロフィーのあるものとないものがあります。アポストロフィーのないものは遅延読み込みを行い、アポストロフィーのあるものは正格読み込みを行うものです。(foldl と foldl' の違いのようなもの)

  • results1 <- fetchAllRowsAL stmt -- この時点で読み込みは行なわれない
  • results2 <- fetchAllRowsAL' stmt -- この時点で全部読み込む。

results1 はプログラム上は全データが読み込まれたリストですが、実際は空のリスト。実際にデータが必要なときにDBから読み込まれます。

fetchAllRows  :: Statement -> IO [[SqlValue]]
fetchAllRows' :: Statement -> IO [[SqlValue]]

fetchAllRowsAL  :: Statement -> IO [[(String, SqlValue)]]
fetchAllRowsAL' :: Statement -> IO [[(String, SqlValue)]]

fetchAllRowsMap  :: Statement -> IO [Data.Map.Map String SqlValue]
fetchAllRowsMap' :: Statement -> IO [Data.Map.Map String SqlValue]

fetchRowAL  :: Statement -> IO (Maybe [(String, SqlValue)])
fetchRowMap :: Statement -> IO (Maybe (Data.Map.Map String SqlValue))

quickQuery  :: (IConnection conn) => conn -> String -> [SqlValue] -> IO [[SqlValue]]
quickQuery' :: (IConnection conn) => conn -> String -> [SqlValue] -> IO [[SqlValue]]
>conn <- connectPostgreSQL "host=localhost port=5433 dbname=daname" 
>stmt <- prepare conn "SELECT * from address"
>execute stmt []

> getTables conn             -- => ["address","area"]
> proxiedClientName conn     -- => "postgresql"
> dbServerVer conn           -- => "80405"
> dbTransactionSupport conn  -- => True

> getColumnNames stmt -- => ["id","code","name","kana","post","uptime","memo"]
> originalQuery stmt  -- => "SELECT * from address"

> results <- fetchAllRowsAL stmt
> mapM_ (putStrLn.concat.(map (\(name,dat)->(name++": "++ ( ucs4ToSjis (fromSql dat)::String)++" ")))) results
id: 1 code: 1 name: 住所 kana:  post: 272-0001 uptime: 2010-12-18 15:28:04.597 +0900 memo:
id: 2 code: 5 name: 市川 kana:  post: 272-0002 uptime: 2010-12-18 15:28:04.604 +0900 memo:
(snip)
id: 31 code: 84 name: 千葉 kana:  post: 272-1234 uptime: 2010-12-18 15:28:04.705 +0900 memo:

-- 二度目の fetchAllRowsAL は空のリストを返す。
> results <- fetchAllRowsAL stmt
-- => []
  • 巨大なテーブルを1件ずつ遅延読み込みすることが出来ます。
>conn <- connectPostgreSQL "host=localhost port=5433 dbname=daname" 
>stmt <- prepare conn "SELECT * from address"
>execute stmt []
-- => 0
-- fetchRow を実行する度に1件ずつ読み込みます。
> fetchRow stmt  -- => 
Just [SqlInteger 1,SqlInteger 1,SqlByteString "272-0001",SqlZonedTime 2010-12-18 15:28:04.597 +0900]

> fetchRow stmt -- => 
Just [SqlInteger 2,SqlInteger 5,SqlByteString "272-0002",SqlZonedTime 2010-12-18 15:28:04.604 +0900]

> fetchRow stmt  -- => 
Just [SqlInteger 3,SqlInteger 12,SqlByteString "272-1234",SqlZonedTime 2010-12-18 15:28:04.617 +0900,]
(snip)
-- データが空になるとNothingを返します。
> fetchRow stmt -- => Nothing

> disconnect conn

これは SQL で DECLARE を宣言し、FETCH で1行ずつ取り出したときと同じ動作です。

>psql -p 5433 dbname
psql (8.4.5)
"help" でヘルプを表示します.

-- トランザクション開始。
dbname=# BEGIN WORK;
BEGIN

-- カーソル設定。
dbname=# DECLARE stmt SCROLL CURSOR FOR SELECT id, code, post, uptime from jusho_mst;
DECLARE CURSOR

-- stmt カーソルから最初の1行を取り出す。
dbname=# FETCH FORWARD 1 FROM stmt;
 id | code |   post   |           uptime
----+------+----------+----------------------------
  1 |    1 | 272-0035 | 2010-12-18 15:28:04.597+09
(1 行)

dbname=# FETCH FORWARD 10 FROM stmt;
 id | code |   post   |           uptime
----+------+----------+----------------------------
  2 |    5 | 272-0035 | 2010-12-18 15:28:04.604+09
  3 |   12 | 272-0031 | 2010-12-18 15:28:04.617+09
  4 |   31 | 272-0824 | 2010-12-18 15:28:04.619+09
  5 |   32 | 272-0824 | 2010-12-18 15:28:04.621+09
  6 |   33 | 272-0824 | 2010-12-18 15:28:04.622+09
  7 |   34 | 272-0824 | 2010-12-18 15:28:04.624+09
  8 |   35 | 272-0824 | 2010-12-18 15:28:04.626+09
  9 |   36 | 272-0824 | 2010-12-18 15:28:04.634+09
 10 |   41 | 272-0034 | 2010-12-18 15:28:04.636+09
 11 |   42 | 272-0034 | 2010-12-18 15:28:04.638+09
(10 行)

-- データがなくなると
dbname=# FETCH FORWARD 1 FROM stmt;
 id | code | post | uptime
----+------+------+--------
(0 行)

-- カーソルを閉じる。
dbname=# CLOSE stmt;
CLOSE CURSOR
-- トランザクションを終了。
dbname=# COMMIT WORK;
COMMIT