Unicode SQLDriverConnectW(): [unixODBC][Диспетчер драйверов]Имя источника данных не найдено, и драйвер по умолчанию не указан

Ниже приведена полная программа ODBC. Все, что он делает, это пытается открыть соединение с базой данных SQLite, используя полную строку соединения. У меня проблема в том, что когда Unicode включен (используйте SQLDriverConnectW() вместо SQLDriverConnect()), я получаю сообщение об ошибке:

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

Мой файл odbc.ini пуст, а вот содержимое моего файла odbcinst.ini:

[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

В верхней части кода есть #if 1, который может переключать код между Unicode и Non-Unicode (измените его на #if 0, чтобы отключить Unicode). Когда я включаю Unicode, я получаю сообщение об ошибке. Когда я отключаю Unicode, он работает идеально. Любые идеи, почему версия Unicode для подключения не может найти мой 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);
}

Я компилирую на OS X. Я установил sqlite, sqliteodbc и unixodbc через Homebrew. Я использую clang в качестве компилятора. Команда, которую я использую для компиляции и запуска моей программы, находится в комментарии вверху исходного кода.


person user27930    schedule 18.01.2016    source источник
comment
Обратите внимание, что SQLDriverConnectW нуждается в SQLWCHAR в качестве параметров для входных и выходных буферов. Разве вы не получаете никаких предупреждений компилятора при вызове SQLDriverConnectW с аргументом (CHAR*) as для входного и выходного буфера?   -  person erg    schedule 19.01.2016
comment
@erg Существует следующее сопоставление #define CHAR SQLWCHAR, поэтому используется правильный тип. Я могу воспроизвести ту же проблему в Linux с clang 3.7, unixODBC 2.3.4, sqliteodbc 0.9992, sqlite 3.10.1.   -  person mloskot    schedule 19.01.2016
comment
CHAR может сбивать с толку. Для лучшего стиля измените #define CHAR xxx на #define SQLTCHAR xxx (обычно это тип TCHAR, который переключается между char/wchar в зависимости от конфигурации).   -  person Ben    schedule 21.01.2016
comment
Также бросок должен быть ненужным. Я предлагаю вам удалить это. Возникает ошибка, которая предполагает наличие другой проблемы   -  person Ben    schedule 21.01.2016
comment
Приведение необходимо для преобразования строкового литерала формы L"..." в требуемый тип параметра unsigned short *. Без приведения clang++ выдает следующую ошибку: /usr/local/Cellar/unixodbc/2.3.2_1/include/sqlucode.h:257:19: с candidate function not viable: no known conversion from 'const wchar_t [36]' to 'SQLWCHAR *' (aka 'unsigned short *') for 3rd argument из SQLDriverConnectW().   -  person user27930    schedule 23.01.2016
comment
Очень похожий код, также использующий приведение типов, можно найти в MSDN для этого SQLDriverConnectW(): msdn.microsoft.com/en-us/library/ms715433(v=vs.85).aspx Это приведение также требуется при компиляции кода C, когда clang выводит ошибку: error: incompatible pointer types passing 'int [36]' to parameter of type 'SQLWCHAR *' (aka 'unsigned short *') [-Werror,-Wincompatible-pointer-types] for строка кода , L"Driver=SQLite3;Database=nanodbc.db;" // InConnectionString.   -  person user27930    schedule 23.01.2016
comment
@ben: Кроме того, я почти уверен, что TCHAR — это вещь для Windows, а я не использовал Windows с 1998 года. Это просто краткий пример кода, демонстрирующий, почему я обнаружил, что невозможно подключиться к базе данных с помощью SQLDriverConnectW() (возможно, из-за unixODBC или sqliteodbc или какое-то неправильное представление в моем коде, я не уверен). Пожалуйста, будьте уверены, что у меня нет #define CHAR в моем фактическом коде библиотеки. Возможность легко переключаться между включенным и отключенным юникодом в этом примере поучительна. Потому что когда юникод отключен, все работает отлично.   -  person user27930    schedule 23.01.2016


Ответы (3)


Актерский состав скрывал ошибку. API ODBC SQLWCHAR — это UTF16-LE — unsigned short. На Mac wchar_t равно int — 32 бита. Это несовместимо.

Вы должны преобразовать строки широких символов в UTF16-LE или использовать тип строки char16_t. Я считаю, что iconv поставляется с xcode и имеет то, что вам нужно.

person Ben    schedule 23.01.2016

Упоминание Беном UTF-16-LE в его ответе указало мне путь к решению этой проблемы. Изменение строкового литерала с L"..." на u"..." и сохранение приведения решило это для меня. Без приведения вы все равно получите ошибку: no known conversion from 'const char16_t [36]' to 'SQLWCHAR *' (aka 'unsigned short *').

person user27930    schedule 26.01.2016

Вы должны сделать строку подключения параметром, а не жестко запрограммированным значением. Эти преобразования строк неприятны, но в дополнение к опубликованным решениям вы можете упростить код следующим образом.

Это решение только для UNICODE, протестированное в MacOS.

  1. относительно строкового литерала определите макрос
#define WODBC_TEXT(s) u##s

что касается преобразования в std::string в SQLWCHAR * , вы можете просто сделать

используйте std::u16string и выполните в вызове функции как приведение к SQLWCHAR*, так и строку для вызова char*

std::u16string conn;
(SQLWCHAR*)conn.c_str();

полная функция

void wodbc_t::connect(const std::u16string &conn)
{
  SQLHSTMT hstmt;
  SQLWCHAR dsn[1024];
  SQLSMALLINT dsn_size = 0;
  RETCODE rc;
#pragma unused(rc)
  rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
  rc = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_UINTEGER);
  rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);

  rc = SQLDriverConnect(
        hdbc
        , 0
        , (SQLWCHAR*)conn.c_str()
        , SQL_NTS
        , dsn
        , sizeof(dsn) / sizeof(SQLWCHAR)
        , &dsn_size
        , SQL_DRIVER_NOPROMPT
        );

  //statement handle
  if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
  {
    rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);

    //process data

    rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
  }
}

и пример вызова

std::u16string sql;
  sql = WODBC_TEXT("SELECT * FROM [QMSAnalysisResults];");
  wodbc_t wquery;
  wquery.connect(WODBC_TEXT("DRIVER=ODBC Driver 17 for SQL Server;SERVER=192.168.1.237, 1433;UID=SA;PWD=password;"));
  wquery.fetch(sql);
  wquery.disconnect();
person Pedro Vicente    schedule 13.11.2020