Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unicode SQLDriverConnectW(): [unixODBC][Driver Manager]Data source name not found, and no default driver specified

The following is a complete ODBC program. All it does is attempt to open a connection to a SQLite database using a fully qualified connection string. The issue I'm having is that when Unicode is enabled (use SQLDriverConnectW() instead of SQLDriverConnect()) I get the error:

libc++abi.dylib: terminating with uncaught exception of type database_error: connect: IM002: [unixODBC][Driver Manager]Data source name not found, and no default driver specified

My odbc.ini file is empty, and here's the content of my odbcinst.ini file:

[SQLite3]
Description=SQLite ODBC Driver
Driver=/usr/local/Cellar/sqliteodbc/0.9992/lib/libsqlite3odbc-0.9992.dylib
Setup=/usr/local/Cellar/sqliteodbc/0.9992/lib/libsqlite3odbc-0.9992.dylib
Threading=2

At the top of the code there's a #if 1 that can toggle the code between Unicode and Non-Unicode (change it to #if 0 to disable Unicode). When I enable Unicode, I get the error. When I disable Unicode, it works perfectly. Any ideas why the Unicode version of connect can't find my DSN?

/*

Build command:

clang++ -Wall -Werror \
    -std=c++14 -stdlib=libc++ \
    -I/usr/local/Cellar/unixodbc/2.3.2_1/include \
    -L/usr/local/Cellar/unixodbc/2.3.2_1/lib -lodbc \
    unittest.cpp && ./a.out

*/

#include <codecvt>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

#include <sql.h>
#include <sqlext.h>

#if 1
    // Enable Unicode
    #define STRING_LITERAL(s) L ## s
    #define CHAR SQLWCHAR
    #define ODBC_FUNCTION(f) f ## W
#else
    // Enable Non-unicode
    #define STRING_LITERAL(s) s
    #define CHAR SQLCHAR
    #define ODBC_FUNCTION(f) f
#endif

bool success(RETCODE rc);
void convert(const std::wstring& in, std::string& out);
void convert(const std::string& in, std::string& out);
template<typename T, std::size_t N> std::size_t arrlen(T(&)[N]);
std::string recent_error(SQLHANDLE handle, SQLSMALLINT handle_type, long &native, std::string &state);

class database_error : public std::runtime_error
{
public:
    database_error(void* handle, short handle_type, const std::string& info = "");
    const char* what() const noexcept { return message.c_str(); }
    const long native() const noexcept { return native_error; }
    const std::string state() const noexcept { return sql_state; }
private:
    long native_error;
    std::string sql_state;
    std::string message;
};

int main()
{
    RETCODE rc;

    HENV env;
    rc = SQLAllocHandle(
        SQL_HANDLE_ENV
        , SQL_NULL_HANDLE
        , &env);
    if(!success(rc))
        throw database_error(env, SQL_HANDLE_ENV, "env: ");

    rc = SQLSetEnvAttr(
        env
        , SQL_ATTR_ODBC_VERSION
        , (SQLPOINTER)SQL_OV_ODBC3
        , SQL_IS_UINTEGER);
    if(!success(rc))
        throw database_error(env, SQL_HANDLE_ENV, "version: ");

    HDBC conn;
    rc = SQLAllocHandle(
        SQL_HANDLE_DBC
        , env
        , &conn);
    if(!success(rc))
        throw database_error(env, SQL_HANDLE_ENV, "conn: ");

    CHAR dsn[1024];
    SQLSMALLINT dsn_size = 0;
    rc = ODBC_FUNCTION(SQLDriverConnect)(
        conn                                // ConnectionHandle
        , 0                                 // WindowHandle
        , (CHAR*)STRING_LITERAL("Driver=SQLite3;Database=nanodbc.db;") // InConnectionString
        , SQL_NTS                           // StringLength1
        , dsn                               // OutConnectionString
        , sizeof(dsn) / sizeof(CHAR)    // BufferLength
        , &dsn_size                         // StringLength2Ptr
        , SQL_DRIVER_NOPROMPT               // DriverCompletion
    );
    if(!success(rc))
        throw database_error(conn, SQL_HANDLE_DBC, "connect: ");
}

bool success(RETCODE rc)
{
    return rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO;
}

void convert(const std::wstring& in, std::string& out)
{
    out = std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(in);
}

void convert(const std::string& in, std::string& out)
{
    out = in;
}

template<typename T, std::size_t N>
std::size_t arrlen(T(&)[N])
{
    return N;
}

std::string recent_error(
    SQLHANDLE handle
    , SQLSMALLINT handle_type
    , long &native
    , std::string &state)
{
    std::wstring result;
    std::string rvalue;
    std::vector<CHAR> sql_message(SQL_MAX_MESSAGE_LENGTH);
    sql_message[0] = '\0';

    SQLINTEGER i = 1;
    SQLINTEGER native_error;
    SQLSMALLINT total_bytes;
    CHAR sql_state[6];
    RETCODE rc;

    do
    {
        rc = ODBC_FUNCTION(SQLGetDiagRec)(
            handle_type
            , handle
            , (SQLSMALLINT)i
            , sql_state
            , &native_error
            , 0
            , 0
            , &total_bytes);

        if(success(rc) && total_bytes > 0)
            sql_message.resize(total_bytes + 1);

        if(rc == SQL_NO_DATA)
            break;

        rc = ODBC_FUNCTION(SQLGetDiagRec)(
            handle_type
            , handle
            , (SQLSMALLINT)i
            , sql_state
            , &native_error
            , sql_message.data()
            , (SQLSMALLINT)sql_message.size()
            , &total_bytes);

        if(!success(rc)) {
            convert(result, rvalue);
            return rvalue;
        }

        if(!result.empty())
            result += ' ';

        result += std::wstring(sql_message.begin(), sql_message.end());
        i++;

        // NOTE: unixODBC using PostgreSQL and SQLite drivers crash if you call SQLGetDiagRec()
        // more than once. So as a (terrible but the best possible) workaround just exit
        // this loop early on non-Windows systems.
        #ifndef _MSC_VER
            break;
        #endif
    } while(rc != SQL_NO_DATA);

    convert(result, rvalue);
    state = std::string(&sql_state[0], &sql_state[arrlen(sql_state) - 1]);
    native = native_error;
    std::string status = state;
    status += ": ";
    status += rvalue;

    // some drivers insert \0 into error messages for unknown reasons
    using std::replace;
    replace(status.begin(), status.end(), '\0', ' ');

    return status;
}

database_error::database_error(void* handle, short handle_type, const std::string& info)
: std::runtime_error(info), native_error(0), sql_state("00000")
{
    message = std::string(std::runtime_error::what()) + recent_error(handle, handle_type, native_error, sql_state);
}

I am compiling on OS X. I've installed sqlite, sqliteodbc, and unixodbc via Homebrew. I'm using clang as my compiler. The command I'm using to compile and run my program is in the comment at the top of the source code.

like image 309
user27930 Avatar asked Jan 18 '16 18:01

user27930


2 Answers

Ben's mention of UTF-16-LE in his answer pointed me down the path to resolving this issue. Changing the string literal from L"..." to u"..." and keeping the cast solved it for me. Without the cast you still get the error: no known conversion from 'const char16_t [36]' to 'SQLWCHAR *' (aka 'unsigned short *').

like image 148
user27930 Avatar answered Oct 28 '22 15:10

user27930


The cast was masking the error. The ODBC API SQLWCHAR is UTF16-LE - unsigned short. On the Mac, wchar_t is int - 32 bits. These are incompatible.

You must convert your wide character strings to UTF16-LE, or use a char16_t string type. I believe iconv is shipped with xcode and has what you need.

like image 39
Ben Avatar answered Oct 28 '22 15:10

Ben