bool core_sqlsrv_get_odbc_error()

in source/shared/core_util.cpp [260:360]


bool core_sqlsrv_get_odbc_error( _Inout_ sqlsrv_context& ctx, _In_ int record_number, _Inout_ sqlsrv_error_auto_ptr& error, _In_ logging_severity severity, _In_opt_ bool check_warning /* = false */)
{
    SQLHANDLE h = ctx.handle();
    SQLSMALLINT h_type = ctx.handle_type();

    if( h == NULL ) {
        return false;
    }

    SQLRETURN r = SQL_SUCCESS;
    SQLSMALLINT wmessage_len = 0;
    SQLWCHAR wsqlstate[SQL_SQLSTATE_BUFSIZE] = {L'\0'};
    SQLWCHAR wnative_message[SQL_MAX_ERROR_MESSAGE_LENGTH + 1] = {L'\0'};
    SQLSRV_ENCODING enc = ctx.encoding();

    switch( h_type ) {

        case SQL_HANDLE_STMT:
            {
                sqlsrv_stmt* stmt = static_cast<sqlsrv_stmt*>( &ctx );
                if( stmt->current_results != NULL ) {

                    error = stmt->current_results->get_diag_rec( record_number );
                    // don't use the CHECK* macros here since it will trigger reentry into the error handling system
                    if( error == 0 ) {
                        return false;
                    }
                    break;
                }
                // convert the error into the encoding of the context
                if( enc == SQLSRV_ENCODING_DEFAULT ) {
                    enc = stmt->conn->encoding();
                }
            }
        default:
            error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error();
            r = SQLGetDiagRecW( h_type, h, record_number, wsqlstate, &error->native_code, wnative_message,
                                SQL_MAX_ERROR_MESSAGE_LENGTH + 1, &wmessage_len );
            // don't use the CHECK* macros here since it will trigger reentry into the error handling system
            // removed the workaround for Mac users with unixODBC 2.3.4 when connection pooling is enabled (PDO SQLSRV), for two reasons:
            // (1) not recommended to use connection pooling with unixODBC < 2.3.7
            // (2) the problem was not reproducible with unixODBC 2.3.7
            if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
                return false;
            }

            // We need to calculate number of characters
            SQLINTEGER wsqlstate_len = sizeof( wsqlstate ) / sizeof( SQLWCHAR );
            SQLLEN sqlstate_len = 0;

            convert_string_from_utf16(enc, wsqlstate, wsqlstate_len, (char**)&error->sqlstate, sqlstate_len);
            
            SQLLEN message_len = 0;
            if (r == SQL_SUCCESS_WITH_INFO && wmessage_len > SQL_MAX_ERROR_MESSAGE_LENGTH) {
                // note that wmessage_len is the number of characters required for the error message -- 
                // create a new buffer big enough for this lengthy error message
                sqlsrv_malloc_auto_ptr<SQLWCHAR> wnative_message_str;

                SQLSMALLINT expected_len = wmessage_len * sizeof(SQLWCHAR);
                SQLSMALLINT returned_len = 0;

                wnative_message_str = reinterpret_cast<SQLWCHAR*>(sqlsrv_malloc(expected_len));
                memset(wnative_message_str, '\0', expected_len); 

                SQLRETURN rtemp = ::SQLGetDiagFieldW(h_type, h, record_number, SQL_DIAG_MESSAGE_TEXT, wnative_message_str, wmessage_len, &returned_len);
                if (!SQL_SUCCEEDED(rtemp) || returned_len != expected_len) {
                    // something went wrong
                    return false;
                }

                convert_string_from_utf16(enc, wnative_message_str, wmessage_len, (char**)&error->native_message, message_len);
            } else {
                convert_string_from_utf16(enc, wnative_message, wmessage_len, (char**)&error->native_message, message_len);
            }

            if (message_len == 0 && error->native_message == NULL) {
                // something went wrong
                return false;
            }
            break;
    }

    // Only overrides 'severity' if 'check_warning' is true (false by default)
    if (check_warning) {
        // The character string value returned for an SQLSTATE consists of a two-character class value 
        // followed by a three-character subclass value. A class value of "01" indicates a warning.
        // https://docs.microsoft.com/sql/odbc/reference/appendixes/appendix-a-odbc-error-codes?view=sql-server-ver15
        if (error->sqlstate[0] == '0' && error->sqlstate[1] == '1') {
            severity = SEV_WARNING;
        }
    }

    // log the error first
    LOG( severity, "%1!s!: SQLSTATE = %2!s!", ctx.func(), error->sqlstate );
    LOG( severity, "%1!s!: error code = %2!d!", ctx.func(), error->native_code );
    LOG( severity, "%1!s!: message = %2!s!", ctx.func(), error->native_message );

    error->format = false;

    return true;
}