ODBC診断

このガイダンスをMicrosoft ODBCドライバー17または18で使用してください。 これはODBCの診断仕様を説明しています。バージョン固有のネイティブエラーマッピングは定義していません。

ODBC関数は、その呼び出しの結果を記述するコードを返します。 診断記録はより詳細を提供します。 アプリケーションプログラミングインターフェース(API)は、ドライバーマネージャー、ドライバー、データソース、または複数のコンポーネントからのレコードを返すことができます。 リターンコード、SQLSTATE、ネイティブエラーコード、メッセージを同義扱いしないでください。

リターンコードを解釈してください

出力処理や再試行の判断前にリターンコードを確認してください。 SQL_ERRORおよびSQL_SUCCESS_WITH_INFOについては、その関数で使用したハンドルから診断データを取得します。 機能契約が許可する場合、他のリターンコードも診断レコードを持つことができます。

リターン コード 意味とレスポンス
SQL_SUCCESS 関数は正常に完了しました。 関数の出力仕様を確認してください。
SQL_SUCCESS_WITH_INFO この関数は警告情報を伴って完了しました。 すべての診断レコードを取得し、出力が完全、変換済み、または切断されているかを確認します。
SQL_ERROR 関数の実行に失敗しました。 同じハンドル上の別の関数を呼び出す前に、すべての診断レコードを取得してください。
SQL_INVALID_HANDLE 関数はnullハンドルや誤ったタイプのハンドルを検出しました。 ODBCはこのリターンコードの診断記録を提供していません。 追加のハンドル要件については関数参照を確認してください。
SQL_NO_DATA この操作に関するデータはこれ以上利用できません。 関数ごとに意味や状態レコードが異なるため、関数のリファレンスを確認してください。

完全な戻りコード仕様の詳細については、ODBC の戻りコードをご覧ください。

すべての診断記録を取得

記録番号1から始まるステータス記録については SQLGetDiagRec にお電話ください。 関数がSQL_NO_DATAを戻すまで続けるか、SQLGetDiagFieldを指定してSQL_DIAG_NUMBERを呼び出し、ヘッダーレコード0からレコードカウントを読み取る。 SQLGetDiagRec レコードのSQLSTATE、ネイティブエラーコード、メッセージテキストを返します。 別のヘッダーまたはレコード フィールドが必要な場合は SQLGetDiagField を使いましょう。

最初のレコードで止まらないでください。 ODBCコンポーネントは、自身のレイヤーに関する情報を含むステータスレコードを追加しつつ、他のコンポーネントからの診断情報を保持できます。 レコードの順序や内容は、エラーの発生源を特定するのに役立ちます。

記録は速やかに収集してください。 同じハンドルを使用する後のODBCコールは、SQLGetDiagRec、SQLGetDiagField、およびレガシー SQLError 関数の呼び出しを除き、その診断領域を置き換えます。 新しいコードで SQLGetDiagRec と SQLGetDiagField を使いましょう。

SQLGetDiagRec のメッセージ バッファが小さすぎる場合、SQL_SUCCESS_WITH_INFO を返すことがあります。 レコードは返されましたが、メッセージ テキストは切り詰められていました。 報告されたテキストの長さを確認し、全文が必要な場合はより大きなバッファでメッセージを取り出してください。 SQLGetDiagRecのこのリターンコードと元のODBC関数のリターンコードを混同しないでください。

診断検索に関する詳細は、「診断レコード」、「SQLGetDiagRec および SQLGetDiagField の使用」、および「SQLGetDiagRec」と「SQLGetDiagField」のリファレンスを参照してください。

C++で診断記録を収集

既存のODBCハンドルでこのC++11ヘルパーを使ってください。 SQLGetDiagRecWを呼び出して、メッセージをログに記録せずにSQLSTATE、ネイティブエラーコード、Unicodeメッセージのテキストをメモリに収集します。

#ifdef _WIN32
#include <windows.h>
#endif
#include <sql.h>
#include <sqlext.h>
#include <algorithm>
#include <limits>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

struct DiagnosticRecord {
    std::string sqlstate;
    SQLINTEGER native_error;
    std::vector<SQLWCHAR> message;
    bool message_truncated;
};

std::vector<DiagnosticRecord> ReadDiagnostics(SQLSMALLINT type, SQLHANDLE handle) {
    const SQLSMALLINT limit = (std::numeric_limits<SQLSMALLINT>::max)();
    std::vector<DiagnosticRecord> records;
    for (SQLINTEGER number = 1; ; ++number) {
        if (number > limit) {
            throw std::runtime_error("Diagnostic record number exceeds SQLSMALLINT.");
        }
        SQLWCHAR state[6] = {};
        SQLINTEGER native = 0;
        std::vector<SQLWCHAR> message(256);
        auto read = [&]() {
            return SQLGetDiagRecW(type, handle, static_cast<SQLSMALLINT>(number),
                state, &native, message.data(),
                static_cast<SQLSMALLINT>(message.size()), nullptr);
        };

        SQLRETURN result = read();
        if (result == SQL_NO_DATA) {
            return records;
        }
        if (result == SQL_SUCCESS_WITH_INFO) {
            message.resize(limit);
            result = read(); // Retrieve the same record, not the next one.
        }
        if (result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
            throw std::runtime_error("SQLGetDiagRecW couldn't retrieve a diagnostic record.");
        }
        message.erase(std::find(message.begin(), message.end(), SQLWCHAR{0}), message.end());
        records.push_back({
            std::string(state, state + 5),
            native,
            std::move(message),
            result == SQL_SUCCESS_WITH_INFO
        });
    }
}

ヘルパーは、SQLGetDiagRecW が受け入れる最大の正のバッファ長を用いて、切り詰められたメッセージの再送信を試みます。 それでもメッセージが収まらない場合は、利用可能なテキストを保持したまま、message_truncated を設定します。 その切り詰め部分を診断リストの終わりとして扱うのではなく、次のレコードへと進みます。 取得に失敗したり、レコード番号がAPIの SQLSMALLINT 範囲を超えたりすると、例外がスローされます。

各メッセージには、null 終端文字を除いて SQLWCHAR コード単位が含まれています。 型によって幅が異なる可能性があるため、それらを wchar_t として再解釈しないでください。 メッセージを処理したり表示したりするときに、アプリケーションのテキストエンコーディングに変換してください。

エラーや警告が返された操作の直後にヘルパーを呼び出してください。 このフラグメントは、statement が有効なプリペアドステートメントハンドルであり、非同期実行が無効であることを前提としています。

#include <iostream>

SQLRETURN ExecuteWithDiagnostics(SQLHSTMT statement) {
    SQLRETURN result = SQLExecute(statement);
    if (result == SQL_ERROR || result == SQL_SUCCESS_WITH_INFO) {
        const auto records = ReadDiagnostics(SQL_HANDLE_STMT, statement);
        for (const auto& record : records) {
            std::cerr << "SQLExecute: SQLSTATE=" << record.sqlstate
                      << " native_error=" << record.native_error
                      << " message_truncated=" << record.message_truncated << '\n';
        }
    } else if (result == SQL_INVALID_HANDLE) {
        throw std::runtime_error("SQLExecute returned SQL_INVALID_HANDLE.");
    }
    return result;
}

ラッパーは元のリターンコード( SQL_SUCCESS_WITH_INFOを含む)を保持します。 同じパターンを他のODBCコールにも適用し、その操作に関連付けられたハンドルタイプとハンドルを使用します。 その関数の仕様に従って他の戻りコードも扱いましょう。

出力は意図的にrecord.messageを省略しています。 ログにメッセージ本文を記録する必要がある場合は、書き込む前に、アプリケーションのデータ ポリシーに従ってその内容を伏せてください。 ヘルパーはメモリに保存されたメッセージをサニタイズしません。

診断フィールドを読んでみてください

各フィールドは、それぞれ異なる質問に答えるものです:

フィールド それがあなたに伝えるもの
SQLSTATE 標準化された診断状態を特定する5文字コード。 そのクラスは最初の2文字です。
ネイティブエラーコード データソースまたはODBCコンポーネント固有の値です。 これは普遍的なエラー番号ではなく、SQL Serverのエラー番号だと決めつけるべきではありません。
メッセージ テキスト 人間が読める説明。 SQLSTATE やネイティブのエラーコードと併せて読み解いてください。安定した機械可読な契約として解釈しないでください。

ドライバーマネージャー、ドライバー、データソースはそれぞれ診断を生成できます。 レコードを追加するコンポーネントは、他のコンポーネントのエラーや警告を置き換えたり隠したりしてはなりません。 ネイティブのエラー コードは、コンポーネントによって生成された診断情報においては利用できない場合や、有用な意味を持たない場合があります。 コンポーネントルールの詳細については、 診断処理ルールを参照してください。 SQLSTATEのクラスや値についての詳細はSQLSTATEsを参照してください。

レコードと関数のリターンコードおよび操作を組み合わせて使用してください。 例えば、 SQL_SUCCESS_WITH_INFO は関数が完了したことを意味しますが、すべての出力値が完全であることを保証するわけではありません。 切り捨ての警告が表示された場合は、返された長さを確認し、残りのデータを取得するかどうかを判断する必要があります。 詳細については、データの長さ、バッファの長さ、切り捨てを参照してください。

証拠を安全に収集する

サポートケースでは、関数名、ハンドルタイプ、リターンコード、すべての診断レコードを記録します。 ドライバーおよびドライバーマネージャーのバージョン、オペレーティングシステム、アーキテクチャ、データソースの種類を含めてください。 出力を保存または共有する前に、資格情報、アクセストークン、接続文字列の秘密情報、クエリ文、パラメーター値、個人情報や規制対象データをマスキングしてください。 診断メッセージやトレースログにはアプリケーションデータを含めることができます。

トレースは必要な最小限の再現に制限してください。 ログはアプリケーションを実行しているアカウントのみがアクセスできるディレクトリに保存し、共有や一時的なデフォルトではなく明示的なログパスを使用します。 複製が終了したらトレースをやめ、不要になったらトレースファイルを削除してください。

トレースレイヤーを選択します

アプリケーション診断とトレーシングは異なる疑問に答えます:

レイヤー キャプチャされる内容 次の目的に使用します
アプリケーション診断 個々のODBC呼び出しからのリターン コードおよび診断レコード 失敗または警告を返した関数を特定し、そのすべての診断フィールドを保持します。
ODBCドライバーマネージャートレース アプリケーション、ドライバーマネージャー、ドライバー間のODBC API呼び出し アプリケーションが行った呼び出しと、返ってきた戻りコードを確認しましょう。 トレース引数は機密値を露呈させることがあります。
ドライバー固有のトレース 内部ドライバーイベント: ドライバーがサポートされているコントロールをドキュメントする場合 APIトレースでは表示できないドライバーの挙動を調査しましょう。 ドライバーマネージャーのトレースでこの機能が有効になるとは限りません。

Windowsでは、ODBCドライバーマネージャーはODBCデータソース管理者のトレーシングタブおよびSQL_ATTR_TRACEおよびSQL_ATTR_TRACEFILE属性を通じたトレースをサポートしています。 トレースはデフォルトでユーザー単位です。 マシン全体のトレースはローカル管理者の権限を必要とし、他のローカルアイデンティティで動作するアプリケーションからの呼び出しをキャプチャできます。 ユーザーごとのトレースを好み、再現が終了したらすぐに停止しましょう。 詳細については、「ト レースオプションの設定」「 トレースの有効化」「 SQLSetConnectAttr」をご覧ください。

LinuxおよびmacOSでは、unixODBC Driver Manager の構成については、Linux および macOS での ODBC ドライバーによるデータ アクセス トレースを参照してください。

既存のLinuxおよびmacOS プログラミングガイドライン には、組み込み診断(BID)トレーシングがODBCドライバー17.3以前に検証されていなかったという歴史的な記述が残っています。 その注釈は現在のドライバー固有のトレースコントロールについては説明していません。 unixODBC手続きはDriver ManagerのAPI呼び出しをキャプチャし、BIDイベントはキャプチャしません。