Yesod と HDBC-mysql と haskell-relational-record を一緒に使っているのだが、MySQL サーバーに「Commands out of sync; you can’t run this command now」って言われてぐぬぬってなって、最近デバッグをがんばっている。とりあえず途中経過をメモしておく。
そもそも MySQL プロトコルの呼び出し順とか知らないので苦戦していた。
この辺でエラーが出ている。結合をしなければエラーは出ない。
import Import hiding (on)
import Database.Relational.Query (on)
import qualified Model.Table.Account as Account
import qualified Model.Table.Tweet as Tweet
import qualified Model.Table.User as User
getTweetR :: AccountIdParam -> TweetIdParam -> Handler Html
getTweetR accountIdParam tweetIdParam = do
user <- requireAuth
p <- runRelational $ do
accounts <- runQuery Account.selectAccount accountIdParam
ts <- flip runQuery () $ relationalQuery $ relation $ do
t <- query Tweet.tweet
u <- query User.user
on $ t ! Tweet.userId' .=. u ! User.id'
wheres $ t ! Tweet.id' .=. value tweetIdParam
return $ (,) |$| t |*| u
return (accounts, ts)
case p of
([account@(Account _ _ _ _)], [(tweet@(Tweet _ _ _ _ _), tweetUser@(User _ _ _ _))]) -> do
form <- generateFormPost commentForm
defaultLayout $ do
headerWidget $ Just user
tweetWidget account user tweet form
_ -> notFound
とりあえず HDBC-mysql は FFI で libmysqlclient を呼んでるので C のレベルでどの関数を呼んでいるのかログに出してみる。コードはこんな感じ。めっちゃ地道にログを挟んでいった。一部引用すると下記。
mysql_stmt_prepare :: Ptr MYSQL_STMT -> CString -> CInt -> IO CInt
mysql_stmt_prepare mysql a1 a2 = do
s1 <- peekCString a1
hPutStrLn stderr $ "mysql_stmt_prepare " ++ (show mysql) ++ " " ++ (show s1) ++ " " ++ (show a2)
cr <- mysql_stmt_prepare_ mysql a1 a2
hPutStrLn stderr $ "\t→ " ++ (show cr)
return cr
そのログ出力が下記(別ページで開く)。同じ接続・同じステートメントに同じ色を塗った。
どうも mysql_stmt_execute
した後に mysql_stmt_fetch
せずに mysql_stmt_prepare
するとダメっぽい。C で確認してみると下記のコードのコメントアウトしてるところのありなしでエラーが出たり出なかったりするのでおそらく合ってる。
MYSQL_BIND result;
char display_name[100];
unsigned long display_name_length;
my_bool display_name_error;
memset(&result, 0, 1);
result.buffer = display_name;
result.buffer_length = sizeof(display_name) * sizeof(display_name[0]);
result.is_null = NULL;
result.length = &display_name_length;
result.error = &display_name_error;
{
my_bool error = mysql_stmt_bind_result(stmt, &result);
if (error) {
fprintf(stderr, "mysql_stmt_bind_result\n");
mysql_close(mysql);
exit(1);
}
}
{
my_ulonglong rows = mysql_stmt_affected_rows(stmt);
printf("mysql_stmt_affected_rows: %ld\n", (long)rows);
}
{
MYSQL_STMT *stmt2 = mysql_stmt_init(mysql);
if (stmt == NULL) {
fprintf(stderr, "mysql_stmt_init: error\n");
mysql_close(mysql);
exit(1);
}
{
const char * const stmt_str = "SELECT `email` FROM `user` WHERE `id` = ?";
int error = mysql_stmt_prepare(stmt2, stmt_str, strlen(stmt_str));
if (error) {
fprintf(stderr, "mysql_stmt_prepare: error\n");
fprintf(stderr, "error message: %s\n", mysql_stmt_error(stmt2));
mysql_close(mysql);
exit(1);
}
}
}
この FFI 呼び出しがどういう経緯で呼ばれてるかを探すのはこれから。
接続を使い回すようにしたのがダメなのかもしれない。
解決しました。(2017.03.17)
正格版の runQuery'
があったのですね。