Unicode SQLDriverConnectW():[unixODBC][Driver Manager]未找到数据源名称,并且未指定默认驱动程序
Unicode SQLDriverConnectW(): [unixODBC][Driver Manager]Data source name not found, and no default driver specified
下面是一个完整的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 和非 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] = '[=12=]';
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 [=12=] into error messages for unknown reasons
using std::replace;
replace(status.begin(), status.end(), '[=12=]', ' ');
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 上编译。我已经通过 Homebrew 安装了 sqlite
、sqliteodbc
和 unixodbc
。我使用 clang 作为我的编译器。我用来编译的命令和运行我的程序在源代码顶部的注释中。
演员表掩盖了错误。 ODBC API SQLWCHAR
是 UTF16-LE - unsigned short
。在 Mac 上,wchar_t
是 int
- 32 位。这些是不兼容的。
您必须将宽字符串转换为 UTF16-LE,或使用 char16_t
字符串类型。我相信 iconv
与 xcode 一起提供并且有您需要的东西。
为我指明了解决此问题的途径。将字符串文字从 L"..."
更改为 u"..."
并 keeping 转换为我解决了这个问题。如果没有转换,您仍然会收到错误消息:no known conversion from 'const char16_t [36]' to 'SQLWCHAR *' (aka 'unsigned short *')
.
您应该使连接字符串成为参数,而不是硬编码值。
这些字符串转换很讨厌,但除了已发布的解决方案之外,
你可以像这样简化代码
这是在 MacOS 中测试过的仅 UNICODE 解决方案
- 关于字符串文字定义宏
#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);
}
}
和调用示例[=14=]
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();
下面是一个完整的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 和非 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] = '[=12=]';
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 [=12=] into error messages for unknown reasons
using std::replace;
replace(status.begin(), status.end(), '[=12=]', ' ');
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 上编译。我已经通过 Homebrew 安装了 sqlite
、sqliteodbc
和 unixodbc
。我使用 clang 作为我的编译器。我用来编译的命令和运行我的程序在源代码顶部的注释中。
演员表掩盖了错误。 ODBC API SQLWCHAR
是 UTF16-LE - unsigned short
。在 Mac 上,wchar_t
是 int
- 32 位。这些是不兼容的。
您必须将宽字符串转换为 UTF16-LE,或使用 char16_t
字符串类型。我相信 iconv
与 xcode 一起提供并且有您需要的东西。
L"..."
更改为 u"..."
并 keeping 转换为我解决了这个问题。如果没有转换,您仍然会收到错误消息:no known conversion from 'const char16_t [36]' to 'SQLWCHAR *' (aka 'unsigned short *')
.
您应该使连接字符串成为参数,而不是硬编码值。 这些字符串转换很讨厌,但除了已发布的解决方案之外, 你可以像这样简化代码
这是在 MacOS 中测试过的仅 UNICODE 解决方案
- 关于字符串文字定义宏
#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);
}
}
和调用示例[=14=]
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();