HDBC-mysqlを使うのが難しい

MariaDB + Haskellをしてみるために、HDBC-mysqlを使ってみることにした。

そこで、まずは普通にcabalでインストールしようとしてみるも、

 % uname -a
Linux fg-prime 3.12.8-200.fc19.i686.PAE #1 SMP Thu Jan 16 04:29:09 UTC 2014 i686 i686 i386 GNU/Linux
 % cabal --version
cabal-install version 1.18.0.2
using version 1.18.1.2 of the Cabal library
 % cabal install HDBC-mysql
Resolving dependencies...
In order, the following will be installed:
HDBC-mysql-0.6.6.1
[1 of 1] Compiling Main             ( /tmp/HDBC-mysql-0.6.6.1-7576/HDBC-mysql-0.6.6.1/Setup.lhs, /tmp/HDBC-mysql-0.6.6.1-7576/HDBC-mysql-0.6.6.1/dist/setup/Main.o )

/tmp/HDBC-mysql-0.6.6.1-7576/HDBC-mysql-0.6.6.1/Setup.lhs:30:7:
    Couldn't match type `IO (Maybe FilePath)'
                  with `ProgramSearchPath -> IO (Maybe FilePath)'
    Expected type: IO (Maybe FilePath)
                   -> (Maybe FilePath -> IO (Maybe FilePath))
                   -> ProgramSearchPath
                   -> IO (Maybe FilePath)
      Actual type: IO (Maybe FilePath)
                   -> (Maybe FilePath -> IO (Maybe FilePath)) -> IO (Maybe FilePath)
    In a stmt of a 'do' block:
      mysql_config <- findProgramOnPath "mysql_config" verbosity
    In the expression:
      do { mysql_config <- findProgramOnPath "mysql_config" verbosity;
           mysql_config5 <- findProgramOnPath "mysql_config5" verbosity;
           return (mysql_config `mplus` mysql_config5) }
    In the `programFindLocation' field of a record
Failed to install HDBC-mysql-0.6.6.1
cabal: Error: some packages failed to install:
HDBC-mysql-0.6.6.1 failed during the configure step. The exception was:
ExitFailure 1

といわれた。

まずはコンパイルする

見ての通り、programFindLocationの型が合わないらしい。どうもパッケージが開発されたCabalのバージョンが古いようだ。

というわけで、適当にソースコードを拾ってきて、Distribution.Simple.Programを見ながら、こんな風に書き直してやるとよい。

 % diff HDBC-mysql-0.6.6.1 HDBC-mysql-0.6.6.1.orig
共通のサブディレクトリー: HDBC-mysql-0.6.6.1/Database と HDBC-mysql-0.6.6.1.orig/Database
diff HDBC-mysql-0.6.6.1/Setup.lhs HDBC-mysql-0.6.6.1.orig/Setup.lhs
10d9
< import Distribution.Simple.Program.Find
31,33c30,32
<     programFindLocation = \verbosity searchpath -> do
<       mysql_config  <- findProgramOnSearchPath verbosity searchpath "mysql_config"
<       mysql_config5 <- findProgramOnSearchPath verbosity searchpath "mysql_config5"
---
>     programFindLocation = \verbosity -> do
>       mysql_config  <- findProgramOnPath "mysql_config"  verbosity
>       mysql_config5 <- findProgramOnPath "mysql_config5" verbosity

そして

 % cabal build
Building HDBC-mysql-0.6.6.1...
Preprocessing library HDBC-mysql-0.6.6.1...
In-place registering HDBC-mysql-0.6.6.1...

イエー。

ビルドできたのでインストールを試みる。

 % cabal instal
 : 略
/usr/bin/ld: cannot find -lprobes_mysql

PCのフリーズによりメモが消えたためにエラーメッセージの全文は貼れないが、とにかくlibprobes_mysqlが無いといっている。ところが、そんなライブラリはどこを探してもない。さて困った。

インストールする

でも検索したら、いくつかの例がみつかった。“/usr/bin/ld: cannot find -lprobes_mysql”を参考にして、MariaDBをビルド、インストールし直す。

 % cd maria-db-5.5.35
 % cmake . -DENABLE_DTRACE=0
 : 略
 % make
 : 略
 % sudo make install
 : 略
 % cabal install
Resolving dependencies...
In order, the following will be installed:
HDBC-mysql-0.6.6.1
Configuring HDBC-mysql-0.6.6.1...
Building HDBC-mysql-0.6.6.1...
Preprocessing library HDBC-mysql-0.6.6.1...
In-place registering HDBC-mysql-0.6.6.1...
Installing library in
/home/lab/.cabal/lib/i386-linux-ghc-7.6.3/HDBC-mysql-0.6.6.1
Registering HDBC-mysql-0.6.6.1...
Installed HDBC-mysql-0.6.6.1

MariaDBのビルドは時間がかかる。そこで時間をとられたが、HDBC-mysqlを無事インストールすることができた。

使う

普通にコードを書くと、たぶん高確率で以下のエラーが出てプログラムが強制終了する。

Exception: SqlError {seState = "", seNativeError = 2014, seErrorMsg = "Commands out of sync; you can't run this command now"}

これは、MySQL :: MySQL 4.1 リファレンスマニュアル :: A.2.13 クライアントでの Commands out of sync エラーや、haskell - HDBC-mysql "command out of sync" - Stack Overflowによると、mysqlclientが出しているエラーであり、HDBC目線では下記の条件で発生するみたいだ。多分100点の答えではないが。

  • ステートメントがfinishされる前に、次のステートメントを用意してデータを取得しようとした。
  • ステートメントがfinishされる前に、commitしようとした。

また、Database.HDBCによると、finishは、次のとき(など)に実行される。

  • fetchRowして結果の最後の行を返した。
  • fetchAllRowsして得たリストの全要素を全部使い切った。
  • executeを呼んで、かつHDBCさんが必要だと思った。
  • 明示的にステートメントに対してfinishを呼んだ。

だがしかし、Haskellは遅延評価なので、fetchAllRowsして得たリストの全要素を全部使い切るっていつだよ、て感じだし、そんな状況で本当にHDBCさんは必要かどうかわかるのかよ、というわけで、そりゃあ何も考えずにコードを書くとエラーが出る。

そして、Statement won't be finished after all rows are read · Issue #6 · bos/hdbc-mysqlを見ると、どうやら開発者は直せる予定がいまのところ無いとのこと。

私にも無い。

だから、HDBC-mysqlを安全に使うならば自分で、strictバージョンの関数を使ったり(fetchAllRow'とか)、強制正格評価($!)を使ったり、上手に明示的にfinishを挟んでやる必要がある。

この問題に対して、私の知識では、次のような少くともエラーが出ないことだけは保証する程度の関数を作ってやるくらいしかできない。

withQuery :: Connection -> String -> [SqlValue] -> ([[SqlValue]] -> a) -> IO a
withQuery conn sql vals fetch = do
  st <- prepare conn sql
  execute st vals
  rows <- fetchAllRows st
  res <- evaluate $! fetch rows
  finish st
  return res

リストから必要な行を取り出して適当に加工する関数を渡してやって、正格で評価する。それからfinishを呼ぶ。関数の外からは取り出して加工が済んだ値だけしか見えないので、読み過ぎも読まな過ぎも発生しない。

こんな風に使う。

newtype SchoolUniform =
  SchoolUniform { suYearOf :: Int, suStyleOf :: UniformStyle }

instance Convertible SqlValue SchoolUniform where
 ...

printSchoolUniforms conn school max =
  uniforms <- withQuery conn
                "SELECT year, style FROM school_uniforms WHERE school = ?"
                [ toSql school ] $ map fromSql . take max
  case uniforms of
    [] -> putStrLn $ school ++ "では学校指定の制服を採用していません。"
    lst -> forM_ lst $ \uf -> do
      putStrLn $ suYearOf uf ++ "年の制服: "
      putStrLn $ show (suStyleOf uf)

-- 空で書いたので、たぶんどこかコンパイルエラーになる。

これでも動くことは動くんだが、やっぱりmax分は評価されてしまうので無駄が多い。たとえば、上記のような全部の行を表示するような処理じゃなくて、「裾のラインが増えた初めの年の制服を表示したい」だけのときでもmax着分の行がメモリに読まれてさらに変換が動いてしまう。

コードに大げさな変更が生じそうで面倒だったんだが、やはりIterateeとかを当たってみるのか…。

いや、多少の制約はあるけど、検索はwithQueryの引数でやることにすればいいのか。それが当面の答えのような気がしてきた。

One Comment

  1. akisakanakatana
    Posted 2014年2月23日 at 3:57 PM | Permalink

    クエリについてはがんばればなんとかなった気分になれることがわかった。
    しかしよりやばいのは以下のエラーである。

    SqlError {seState = “”, seNativeError = 2050, seErrorMsg = “Row retrieval was canceled by mysql_stmt_close() call”}

    これは検索してみつかるPHPの事例から、ステートメントオブジェクトを再利用することによって起こるらしい。
    のだが、同じプログラムのループ数だけを変えて実行するだけで、エラーが発生したりしなかったりする。

    newStocks 〈- forM wss $ \ws -〉
    (ws,) . listToMaybe 〈$〉 (M.getDayStocksByCode’ conn (S.wsCode ws))

    wssの要素数によってエラーの発生確率が変わる。
    ちなみに同じ要素数でも、成功するときと失敗するときがある。

    わたしわからない なおせるきもちしない だからこのルートあきらめる

2 Trackbacks

  1. By Dwayne Bowe Jersey on 2016年10月10日 at 3:44 PM

    Dwayne Bowe Jersey

    HDBC-mysqlを使うのが難しい – 放置演算子

  2. By Pandora CHARMS on 2016年11月7日 at 12:21 PM

    Pandora CHARMS

    HDBC-mysqlを使うのが難しい – 放置演算子

Post a Comment

Your email is never shared.

引く

PageTop