PostgreSQL『function sequence error(HY010/S1010)』の原因と対処【psqlODBC/ODBC経由】

PostgreSQL『function sequence error(HY010/S1010)』の原因と対処【psqlODBC/ODBC経由】

PostgreSQLサーバー自身のエラーではなく、ODBCドライバ層(psqlODBCなど)が「関数呼び出しの順序が不正」と判断したときに返されるHY010/S1010系エラーの実態を整理。典型パターン、切り分けSQL/ドライバ設定、アプリ側の正しい呼び出し順、サンプルコード(C ODBC / Python pyodbc / PHP PDO-ODBC)を収録。再発防止のチェックリストとDSN/接続文字列での推奨オプション付き。

エラーの意味と代表メッセージ

・ODBCの「関数シーケンス(呼び出し順)違反」。サーバーログに何も出ないケースが多い

SQLSTATE=HY010  [Driver Manager] Function sequence error
S1010          [Driver Manager] Function sequence error
HY010          (SQLFetch / SQLGetData / SQLExecute / SQLMoreResults などの直後に発生)

発生条件の早見表(まずここを疑う)

・前の結果セットを閉じずに同じHSTMT/同じ接続で新しいSQLを実行

・SQLExecuteの前にSQLFetch/SQLGetDataを呼ぶなど、APIの順序が逆

・複数スレッドで同一接続/同一ステートメントを共有(競合)

・非同期実行や複数結果セット処理の途中に別操作を割り込ませる

・SELECT * の大規模結果を取得中に別のクエリを同じハンドルで実行

・psqlODBCのメタデータ取得時の「早期実行(premature execution)」とアプリの呼び出し設計が衝突

・DSN/接続文字列のカーソル設定(UseDeclareFetch、ServerSidePrepare、Disallow Premature 等)がアプリの期待と不整合

切り分け:サーバー側状態とカーソルの確認

・サーバー側は正常なことが多いので、まずはアプリ/ドライバの順序とカーソル状態を点検

-- 参考:サーバー側の通常カーソル(psqlでの概念確認)
BEGIN;
DECLARE c CURSOR FOR SELECT generate_series(1,3);
FETCH ALL FROM c;  -- 終了させることが重要(ODBCでも「結果を読み切る/閉じる」)
CLOSE c;
COMMIT;

・ODBCでは「結果を読み切る」「カーソルを閉じる(SQLCloseCursor/SQLFreeStmt(SQL_CLOSE))」が次操作の前提

ドライバ(psqlODBC)設定のポイント

・大量フェッチや結果セットを流し読みするなら UseDeclareFetch=1 を検討(サーバーサイドカーソルで逐次取得)

・メタデータ参照時の早期実行を抑止したいなら Disallow Premature=1(古いバージョンでは有効)

・ServerSidePrepare/Parse Statements も合わせて見直す(アプリのメタデータ照会パターンと相性をとる)

・DSNでは「詳細設定」から、接続文字列ならキー=値を追加

Driver=PostgreSQL Unicode(x64);Server=db.example.com;Port=5432;Database=app;
Uid=app;Pwd=***;UseDeclareFetch=1;Fetch=1000;ServerSidePrepare=1;
# (古い環境で必要な場合)DisallowPremature=1

正しい呼び出し順(C/ODBCの最小サンプル)

// 1) SQLAllocHandle(SQL_HANDLE_STMT)
// 2) SQLPrepare または 直接 SQLExecDirect
// 3) (SELECTなら)SQLExecute → SQLFetch/SQLGetData で最後まで読む
// 4) SQLCloseCursor(または SQLFreeStmt(SQL_CLOSE))で結果を確実に閉じる
// 5) 次の SQL を実行(別の結果を重ねない)
// 6) 終了時に SQLFreeHandle

// 典型的なNG:Fetchが終わっていないのに別のSQLExecuteを同じHSTMTで実行 → HY010

Python(pyodbc)でのNG/OK例

# NG:結果を読み切らずに同じカーソルで別クエリを実行
cur.execute("SELECT * FROM big_table")
row = cur.fetchone()         # まだ残りがある
cur.execute("UPDATE t SET x=1 WHERE id=1")  # HY010 を誘発することがある

# OK:結果を読み切る or 閉じる or 別カーソル/別接続を使う
cur.execute("SELECT * FROM big_table")
for _ in cur:                 # すべて消費
    pass
# もしくは cur.fetchall() で読み切る/cur.close() で明示クローズ
cur.execute("UPDATE t SET x=1 WHERE id=1")

PHP(PDO-ODBC)でのNG/OK例

// NG:バッファ非使用で結果を残したまま次のクエリ
$stmt = $pdo->query("SELECT * FROM big_table");  // 結果が未消費
$pdo->exec("DELETE FROM t WHERE id=1");          // HY010 になる場合

// OK:結果を消費/閉じる or 別ステートメントを使う
$stmt = $pdo->query("SELECT * FROM big_table");
$stmt->fetchAll();   // 全件を読み切る(大きい場合は逐次fetch + 破棄)
$stmt->closeCursor(); // カーソルを閉じる
$pdo->exec("DELETE FROM t WHERE id=1");

マルチスレッド/並列処理の注意

・1接続を複数スレッドで共有しない(各スレッドで接続/カーソルを分離)

・接続プールでも「1コネクション=1スレッド占有」を守る

・実装言語に応じて「複数アクティブ結果」を前提にしない(MARS相当はODBC+PostgreSQLでは基本不可)

「メタデータ→早期実行」との衝突を避ける

・アプリがSQLNumResultCols/DescribeColで列情報を先に知りたがる場合、ドライバが内部的にクエリを早期実行することがある

・メタデータ呼び出し直後に別操作を混ぜない、またはドライバ設定(UseDeclareFetch/ServerSidePrepare/Disallow Premature)で挙動を調整

DSN/接続文字列のテンプレート(Windows/UnixODBC)

# Windows: ファイルDSN/システムDSNの例
[ODBC]
DRIVER=PostgreSQL Unicode(x64)
SERVER=127.0.0.1
PORT=5432
DATABASE=app
UID=app
PWD=***
UseDeclareFetch=1
Fetch=1000
ServerSidePrepare=1

# UnixODBC: odbc.ini の例
[pg-app]
Driver=PostgreSQL Unicode
Servername=127.0.0.1
Port=5432
Database=app
Username=app
Password=***
UseDeclareFetch=1
Fetch=1000

ODBCトレースでの実地調査

・Windows:ODBCデータソースアドミニストレーターの「トレース」を有効化→再現→SQL.logを読む

・UnixODBC:odbcinst.ini / isql + TraceFile設定でAPIシーケンスを確認

・HY010直前のAPI(SQLFetch/SQLGetData/SQLExecute/SQLMoreResults など)の並びを精査

再現→解消の通し例(疑似コード)

// 再現:結果未クローズのまま別SELECT
stmt = SQLAllocHandle(STMT)
SQLExecDirect(stmt, "SELECT * FROM big_table")
SQLFetch(stmt)                   // 1行だけ取得、まだ開いている
SQLExecDirect(stmt, "SELECT 1")  // ← ここで HY010
// 解消
SQLCloseCursor(stmt)             // 先に閉じる
SQLExecDirect(stmt, "SELECT 1")  // 正常実行

チェックリスト(上から順に実施)

・同じステートメント/接続で結果を開きっぱなしにしていないか

・fetchを最後まで実施 or closeCursor/SQLCloseCursor 済みか

・メタデータ(列情報取得)の直後に別操作を挟んでいないか

・スレッド毎に接続を分けているか(共有禁止)

・UseDeclareFetch/ServerSidePrepare/Disallow Premature の設定適合

・ドライバ/アプリのバージョン差異・既知不具合の有無(リリースノート参照)

まとめ:最短復旧の行動手順

・1) 直前の結果を必ず読み切る or 明示的にカーソルを閉じる

・2) 同じハンドルでの同時実行をやめ、必要なら別カーソル/別接続を確保

・3) DSN/接続文字列で UseDeclareFetch=1 を有効化し逐次フェッチに切替

・4) メタデータ照会と実行の間に状態遷移を挟まない設計へ修正

・5) トレースでAPIシーケンスを確認し、再発箇所をコードレビュー