Diagnósticos ODBC

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

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

Interprete o código de retorno

Verifique o código de retorno antes de processar a saída ou decidir se tenta novamente. Para SQL_ERROR e SQL_SUCCESS_WITH_INFO, recupere diagnósticos do handle usado pela função. Outros códigos de retorno também podem ter registros diagnósticos quando o contrato de função 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 cada registro de diagnóstico e verifique se a saída está completa, convertida ou truncada.
SQL_ERROR A função falhou. Recupere todos os registros de diagnóstico antes de chamar outra função no mesmo handle.
SQL_INVALID_HANDLE A função detectou um handle nulo ou um handle do tipo errado. O ODBC não fornece registros diagnósticos para esse 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 registros de status dependem da função.

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

Recupere todos os registros diagnósticos

Chame SQLGetDiagRec para registros de status começando no registro número 1. Continue até a função retornar SQL_NO_DATA, ou leia a contagem de registros do registro do cabeçalho 0 chamando SQLGetDiagField com SQL_DIAG_NUMBER. SQLGetDiagRec retorna o SQLSTATE do registro, o código de erro nativo e o texto da mensagem. Use SQLGetDiagField quando precisar de outros campos de cabeçalho ou registro.

Não pare depois do primeiro registro. Um componente ODBC pode adicionar um registro de status com informações sobre sua própria camada enquanto preserva diagnósticos de outro componente. A ordem e o conteúdo do registro podem ajudar a identificar de onde o erro se originou.

Recolha os registros rapidamente. Uma chamada ODBC posterior que usa o mesmo handle substitui 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 retornar SQL_SUCCESS_WITH_INFO quando seu buffer de mensagens estiver muito pequeno. O registro 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 esse código de retorno de SQLGetDiagRec com o código de retorno da função ODBC original.

Para mais informações sobre recuperação diagnóstica, veja Registros diagnósticos, Usando SQLGetDiagRec e SQLGetDiagField, e as referências SQLGetDiagRec e SQLGetDiagField .

Coletar registros diagnósticos em C++

Use este auxiliar em C++11 com um handle ODBC existente. Ele chama SQLGetDiagRecW para coletar SQLSTATE, código de erro nativo e texto de mensagem Unicode na memória sem registrar 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 aceita. Se a mensagem ainda não caber, ela mantém o texto disponível e define message_truncated. Ele continua para o próximo registro, em vez de tratar esse truncamento como o fim da lista diagnóstica. Lança uma exceção se a recuperação falhar ou se o número de registro exceder o intervalo da API SQLSMALLINT.

Cada mensagem contém SQLWCHAR unidades de código sem o terminador nulo. Não os reinterprete como wchar_t, porque os tipos podem ter larguras diferentes. Converta-os para a codificação de texto do seu aplicativo quando precisar processar ou exibir a mensagem.

Chame o auxiliar imediatamente após a operação que retorna 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 wrapper preserva o código de retorno original, incluindo SQL_SUCCESS_WITH_INFO. Aplique o mesmo padrão a outras chamadas ODBC, usando o tipo de handle e handle associados a essa operação. Gerencie outros códigos de retorno de acordo com o contrato dessa função.

A saída omite deliberadamente record.message. Se você precisar de texto de mensagem em um log, oculte dados sensíveis de acordo com a política de dados do seu aplicativo antes de gravá-la. O ajudante não sanitiza as mensagens guardadas na memória.

Leia os campos diagnósticos

Os campos respondem a diferentes perguntas:

Field O que isso te diz
SQLSTATE Um código de cinco caracteres que identifica uma condição diagnóstica padronizada. Sua classe são 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 você não deve assumir que é um número de erro do SQL Server.
Texto da mensagem Uma explicação legível para humanos. Leia com o SQLSTATE e o código de erro nativo; não faça o parsing disso como um contrato estável e legível por máquina.

O Gerenciador de Drivers, o driver e a fonte de dados podem cada um gerar diagnósticos. Um componente que adiciona um registro não deve substituir ou mascarar 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 componentes. Para mais informações sobre as regras de componente, veja Regras de tratamento de diagnóstico. Para mais informações sobre classes e valores SQLSTATE, veja SQLSTATEs.

Use os registros junto 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 todo valor de saída esteja completo. Um aviso de truncamento exige que você verifique o comprimento retornado e decida se recupera os dados restantes. Para mais informações, veja Comprimento dos dados, comprimento do buffer e truncamento.

Colete evidências com segurança

Para um caso de suporte, registre o nome da função, o tipo de handle, o código de retorno e todos os registros de diagnóstico. Inclua as versões do driver e do Driver Manager, sistema operacional, arquitetura e tipo de fonte de dados. Oculte credenciais, tokens de acesso, segredos de strings de conexão, texto de consulta, valores de parâmetros e dados pessoais ou regulados antes de salvar ou compartilhar a saída. Mensagens de diagnóstico e logs de rastreamento podem conter dados de aplicação.

Limite o rastreamento à reprodução do problema mais curta que você precisa. Armazene os logs em um diretório acessível apenas para a conta que executa o aplicativo e use um caminho de log explícito em vez de um padrão compartilhado ou temporário. Pare de rastrear quando a reprodução terminar, e remova o arquivo de rastreamento quando não precisar mais.

Escolha a camada de rastreamento

Diagnósticos e rastreamento de aplicações respondem a diferentes perguntas:

Camada O que ele captura Use-o para
Diagnóstico de aplicativo Códigos de retorno e registros diagnósticos de chamadas ODBC individuais Identifique a função que falhou ou devolveu um aviso e preserve todos os seus campos de diagnóstico.
Rastreamento ODBC Driver Manager Chamadas de API ODBC entre a aplicação, o Gerenciador de Drivers e o driver Veja quais chamadas a aplicação fez e quais códigos de retorno retornaram. Argumentos de rastreamento podem expor valores sensíveis.
Rastreamento específico para o controlador Eventos internos do driver, quando o driver documenta um controle suportado Investigue o comportamento do driver que um rastreamento de API não pode mostrar. Não presuma que um rastreamento do Driver Manager permite esse recurso.

No Windows, o ODBC Driver Manager suporta rastreamento pela aba de Rastreamento no ODBC Data Source Administrator e pelos atributos SQL_ATTR_TRACE e SQL_ATTR_TRACEFILE. O rastreamento é por usuário por padrão. O rastreamento em toda a máquina requer permissões de administrador local e pode capturar chamadas de aplicativos rodando sob outras identidades locais. Prefira rastreamento por usuário e pare assim que a reprodução do problema terminar. Para mais informações, veja Definir opções de rastreamento, Ativando rastreamento e SQLSetConnectAttr.

No Linux e macOS, veja Rastreamento de acesso a dados com o Driver ODBC no Linux e macOS para a configuração do Gerenciador de Drivers unixODBC.

As Diretrizes de Programação existentes para Linux e macOS mantêm uma declaração histórica de que o rastreamento de diagnóstico embutido (BID) não foi verificado antes do Driver ODBC 17.3. Essa observação não descreve um controle de rastreamento específico do driver atual. O procedimento unixODBC captura chamadas de API do Driver Manager, não eventos BID.