Diagnósticos ODBC

Use esta orientação com o Driver ODBC 17 ou 18 da Microsoft. Descreve o contrato de diagnóstico ODBC; não define mapeamentos nativos de erro específicos de versão.

Uma função ODBC devolve um código que descreve o resultado dessa chamada. Os registos diagnósticos fornecem mais detalhes. A interface de programação de aplicações (API) pode comunicar registos do Driver Manager, do driver, da fonte de dados ou de mais do que um componente. Não trate o código de retorno, SQLSTATE, código de erro nativo e mensagem como intercambiáveis.

Interpretar o código de retorno

Verifique o código de retorno antes de processar a saída ou decidir se deve tentar novamente. Para SQL_ERROR e SQL_SUCCESS_WITH_INFO, recupere diagnósticos a partir do manipulador usado pela função. Outros códigos de retorno também podem ter registos de diagnóstico quando o contrato de função os permite.

Código de retorno Significado e resposta
SQL_SUCCESS A função foi concluída com êxito. Verifique o contrato de saída da função.
SQL_SUCCESS_WITH_INFO A função foi concluída com informações de aviso. Recupere todos os registos de diagnóstico e verifique se a saída está completa, convertida ou truncada.
SQL_ERROR A função falhou. Recupere todos os registos de diagnóstico antes de chamar outra função no mesmo handle.
SQL_INVALID_HANDLE A função detetou um handle nulo ou um handle do tipo errado. O ODBC não fornece registos de diagnóstico para este código de retorno. Verifique a referência da função para requisitos adicionais de identificadores.
SQL_NO_DATA Não há mais dados disponíveis para a operação. Verifique a referência da função porque o significado e quaisquer registos de estado dependem da função.

Para mais informações sobre o contrato completo de códigos de retorno, consulte códigos de retorno ODBC.

Recuperar todos os registos de diagnóstico

Chame SQLGetDiagRec registos de estado a partir do registo número 1. Continue até a função devolver SQL_NO_DATA, ou leia a contagem de registos do registo do cabeçalho 0 chamando SQLGetDiagField com SQL_DIAG_NUMBER. SQLGetDiagRec devolve o SQLSTATE do registo, o código de erro nativo e o texto da mensagem. Usa SQLGetDiagField quando precisares de outros campos de cabeçalho ou registo.

Não pares depois do primeiro registo. Um componente ODBC pode adicionar um registo de estado com informação sobre a sua própria camada, preservando os diagnósticos de outro componente. A ordem e o conteúdo do registo podem ajudar a identificar onde se originou um erro.

Recolha os registos rapidamente. Uma chamada ODBC posterior que utiliza o mesmo handle substitui a sua área de diagnóstico, exceto chamadas para SQLGetDiagRec, SQLGetDiagField, e a função legada SQLError . Use SQLGetDiagRec e SQLGetDiagField em código novo.

SQLGetDiagRec pode regressar SQL_SUCCESS_WITH_INFO quando o seu buffer de mensagens for demasiado pequeno. O registo foi devolvido, mas o texto da mensagem foi truncado. Verifique o comprimento do texto reportado e recupere a mensagem com um buffer maior se precisar do texto completo. Não confunda este código de retorno de SQLGetDiagRec com o código de retorno da função ODBC original.

Para mais informações sobre recuperação de diagnósticos, consulte Registos de diagnóstico, Utilizar SQLGetDiagRec e SQLGetDiagField e as referências SQLGetDiagRec e SQLGetDiagField.

Recolher registos de diagnóstico em C++

Use este auxiliar em C++11 com um handle ODBC existente. Chama SQLGetDiagRecW para recolher SQLSTATE, código de erro nativo e texto de mensagens Unicode na memória sem registar as mensagens.

#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
        });
    }
}

O ajudante tenta novamente uma mensagem truncada com o maior comprimento positivo do buffer que SQLGetDiagRecW aceite. Se a mensagem ainda não couber, mantém o texto disponível e define message_truncated. Continua para o registo seguinte em vez de tratar esse truncamento como o fim da lista de diagnóstico. Lança se a recuperação falhar ou se o número do registo exceder o intervalo da SQLSMALLINT API.

Cada mensagem contém SQLWCHAR unidades de código sem o terminador nulo. Não os reinterpretes como wchar_t, porque os tipos podem ter larguras diferentes. Converta-os para a codificação de texto da sua aplicação quando precisar de processar ou mostrar a mensagem.

Chame imediatamente a função auxiliar após a operação devolver um erro ou aviso. Este fragmento assume que statement é um handle de instrução válido e preparado e que a execução assíncrona está desativada:

#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;
}

O invólucro preserva o código de retorno original, incluindo SQL_SUCCESS_WITH_INFO. Aplicar o mesmo padrão a outras chamadas ODBC, usando o tipo de handle e handle associados a essa operação. Trate de outros códigos de retorno de acordo com o contrato dessa função.

A saída omite record.message deliberadamente. Se precisares de texto de mensagem num registo, anonimiza-o de acordo com a política de dados da tua aplicação antes de o escrever. O helper não sanitiza as mensagens guardadas na memória.

Leia os campos de diagnóstico

Os campos respondem a diferentes questões:

Field O que isto lhe diz
SQLSTATE Um código de cinco caracteres que identifica uma condição diagnóstica padronizada. A sua classe é os dois primeiros caracteres.
Código de erro nativo Um valor específico para a fonte de dados ou componente ODBC. Não é um número de erro universal, e não deve assumir que se trata de um número de erro do SQL Server.
Texto da mensagem Uma explicação legível para humanos. Leia-o juntamente com o SQLSTATE e código de erro nativo; não o analises como um contrato estável e legível por máquina.

O Gestor de Drivers, o driver e a fonte de dados podem gerar diagnósticos cada um. Um componente que adiciona um registo não deve substituir ou mascarar um erro ou aviso de outro componente. Um código de erro nativo pode não estar disponível ou não ter significado útil para um diagnóstico gerado por um componente. Para mais informações sobre as regras de componentes, consulte Regras de tratamento de diagnóstico. Para mais informações sobre classes e valores SQLSTATE, consulte SQLSTATEs.

Utilize os registos juntamente com o código de retorno da função e a operação. Por exemplo, SQL_SUCCESS_WITH_INFO significa que a função foi concluída, mas não garante que todos os valores de saída estejam completos. Um aviso de truncamento exige que verifique o comprimento devolvido e decida se recupera os dados restantes. Para mais informações, veja Comprimento dos dados, comprimento do buffer e truncagem.

Recolha provas em segurança

Para um caso de suporte, regista o nome da função, o tipo de handle, o código de retorno e todos os registos de diagnóstico. Inclua as versões do driver e do Driver Manager, sistema operativo, arquitetura e tipo de fonte de dados. Oculte credenciais, tokens de acesso, segredos de cadeias de ligação, texto de consulta, valores de parâmetros e dados pessoais ou regulados antes de guardar ou partilhar a saída. Mensagens de diagnóstico e registos de rastreio podem conter dados de aplicação.

Limite o rastreio à reprodução mais curta de que precisa. Armazene os registos num diretório acessível apenas à conta que executa a aplicação, e utilize um caminho de registo explícito em vez de um padrão partilhado ou temporário. Pára de rastrear quando a reprodução terminar, depois remove o ficheiro de rastreio quando já não precisares dele.

Escolha a camada de rastreio

O diagnóstico e o rastreio de aplicações respondem a diferentes questões:

Camada O que capta Usa-o para
Diagnóstico de aplicativos Códigos de retorno e registos de diagnóstico de chamadas individuais ODBC Identifique a função que falhou ou devolveu um aviso e preserve todos os seus campos de diagnóstico.
Rastreio ODBC Driver Manager Chamadas de API ODBC entre a aplicação, o Gestor de Drivers e o driver Veja que chamadas a aplicação fez e que códigos de retorno foram obtidos. Argumentos de rastreio podem expor valores sensíveis.
Rasto específico do driver Eventos internos do driver, quando o driver documenta um controlo suportado Investigue o comportamento do driver que um rastreio da API não consegue mostrar. Não assumas que um rastreio do Driver Manager permite esta funcionalidade.

No Windows, o ODBC Driver Manager suporta o rastreio através do separador de Rastreio no ODBC Data Source Administrator e dos atributos SQL_ATTR_TRACE e SQL_ATTR_TRACEFILE. O rastreamento é por utilizador por defeito. O rastreamento a nível de máquina requer permissões de administrador local e pode captar chamadas de aplicações a correr sob outras identidades locais. Prefira o rastreio por utilizador e pare assim que a reprodução do problema terminar. Para mais informações, consulte Definir opções de rastreamento, Ativar o rastreio e SQLSetConnectAttr.

No Linux e macOS, consulte Rastreio de acesso a dados com o Driver ODBC no Linux e macOS para a configuração do Gestor de Drivers unixODBC.

As Diretrizes de Programação existentes para Linux e macOS mantêm uma declaração histórica de que o rastreio de diagnósticos incorporado (BID) não foi verificado antes do ODBC Driver 17.3. Essa nota não descreve um controlo de rastreio específico do driver atual. O procedimento unixODBC capta chamadas de API do Gestor de Drivers, não eventos BID.