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.
SQLDriverConnectW
needsSQLWCHAR
as parameters for in- and out-buffers. Dont you get any compiler warnings from calling SQLDriverConnectW with a(CHAR*)
as argument for the in and out buffer? – Hexachlorophene#define CHAR SQLWCHAR
, so correct type is used. I can reproduce the same problem on Linux with clang 3.7, unixODBC 2.3.4, sqliteodbc 0.9992, sqlite 3.10.1. – SchiltCHAR
might be confusing. For better style, change your#define CHAR xxx
to#define SQLTCHAR xxx
(TCHAR is usually the type which switches between char/wchar depending on config). – BabcockL"..."
to the parameter type required,unsigned short *
. Without the cast, clang++ produces the following error:/usr/local/Cellar/unixodbc/2.3.2_1/include/sqlucode.h:257:19:
withcandidate function not viable: no known conversion from 'const wchar_t [36]' to 'SQLWCHAR *' (aka 'unsigned short *') for 3rd argument
ofSQLDriverConnectW()
. – BicentenarySQLDriverConnectW()
: msdn.microsoft.com/en-us/library/ms715433(v=vs.85).aspx This cast is also required if compiling C code, with clang outputting the error:error: incompatible pointer types passing 'int [36]' to parameter of type 'SQLWCHAR *' (aka 'unsigned short *') [-Werror,-Wincompatible-pointer-types]
for the line of code, L"Driver=SQLite3;Database=nanodbc.db;" // InConnectionString
. – BicentenarySQLDriverConnectW()
(perhaps due to unixODBC or sqliteodbc or some misconception in my code, I'm not sure). Please rest assured I don't have any#define CHAR
in my actual library code. Being able to easily toggle between unicode enabled and unicode disabled, for this example, is instructive. Because when unicode is disabled, things work perfectly. – Bicentenary