I'm using Microsoft's ODBC driver to connect a C++/Linux application to a SQL Server database running remotely, and when I try to connect to the database, the call fails with SQL_INVALID_HANDLE. Reading through their documentation, I find this:
SQL_INVALID_HANDLE Function failed due to an invalid environment, connection, statement, or descriptor handle. This indicates a programming error. No additional information is available from SQLGetDiagRec or SQLGetDiagField. This code is returned only when the handle is a null pointer or is the wrong type, such as when a statement handle is passed for an argument that requires a connection handle.
Fair enough, but at no point in the creation of the handles and environment prior to the connect statement do I get any errors. Also, for the second argument, their documentation says I can pass in a null pointer if there is no desktop window (as is the case on this linux console application). Here is a MVCE, adapted from Microsoft's example program:
#include "sql.h"
#include "sqlext.h"
#include "msodbcsql.h"
#include <iostream>
#include <string>
int main(int, char**)
{
using std::cerr;
using std::endl;
SQLHENV henv;
SQLHDBC hdbc;
HWND dhandle = nullptr; // no desktop handle in linux
SQLHSTMT hstmt;
SQLRETURN retcode;
SQLCHAR OutConnStr[255];
SQLSMALLINT OutConnStrLen;
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLAllocHandle (environment) failed " << retcode << endl;
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER*)SQL_OV_ODBC3, 0);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLSetEnvAttr failed " << retcode << endl;
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLAllocHandle (connection) failed " << retcode << endl;
retcode = SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLSetConnectAttr failed " << retcode << endl;
std::string dsn = "DRIVER={ODBC Driver 17 for SQL Server};SERVER=*.*.*,1433;DATABASE=***;UID=***;PWD=***";
retcode = SQLDriverConnect(hdbc, dhandle, (SQLCHAR*)dsn.c_str(), dsn.length(), OutConnStr, 255, &OutConnStrLen, SQL_DRIVER_PROMPT);
if (!(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO))
cerr << "SQLDriverConnect failed " << retcode << endl;
// cleanup code redacted for brevity
return 0;
}
The program outputs SQLDriverConnect failed -2, which is SQL_INVALID_HANDLE. I'm stumped. hdbc is clearly the right type, and examining it in the debugger shows me it is not null.
It may be worth noting that the exact same connection string works in a python program using pyodbc. It seems that the C++ program isn't even getting as far as looking at that string, though. It just doesn't like the handle I'm sending into the connect call.
Microsoft's documentation clearly says they provide no additional information. If anyone can provide any direction on how to diagnose/debug this, I'd appreciate it greatly.
This application uses gcc 4.9.1 on Centos 7.
After two weeks of digging, this turned out to be some kind of versioning problem.
Eventually, this program will be doing some BCP uploads via Microsoft's extensions in libmsodbcsql.so. It turns out that library also has implementations of many of the SQL* functions, which are the ones that are failing in this test program. When I change the order of the link so that libodbc.so is before the MSFT extensions library so that the loader finds those implementations first, the program works fine.
I'm curious why this is, and it probably points to something else I'm doing wrong that may bite me down the road. But for now, at least, I am able to get connected to the database and do basic queries and updates.
Thanks to those who helped.
In Unix does the handle not have to be a non-null value for dialogs to be displayed.
For any handle in SQL Server, it has to be allocated before used!
So the order is Environment, Connection and Statement.
Example:
SQLHENV hEnv = nullptr;
SQLHDBC hDbc = nullptr;
SQLHSTMT hStmt = NULL;
Allocations
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
Below is sample code that may help you.
Basics fast,
Create table in your sql server database and insert some data
create table test (id int, name nvarchar(128));
Insert some data
insert into test (id,name) values (1, 'Awesome Name');
C++ Code to query items in the table
#include <iostream>
#include <string>
#include <sql.h>
#include <sqlext.h>
int main(int argc, char **argv) {
SQLHENV hEnv = nullptr;
SQLHDBC hDbc = nullptr;
SQLHSTMT hStmt = NULL;
/**
* Allocate environment handle
*/
SQLRETURN allocReturn = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
//Set environment
SQLRETURN setEnvReturn = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, 0);
//Allocate connection handle
SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);
SQLCHAR *connection_string = (SQLCHAR *)
"DRIVER={ODBC Driver 17 for SQL Server};SERVER=localhost,1433;DATABASE=database;UID=sa;PWD=password";
//Connect to database
SQLRETURN connReturn = SQLDriverConnect(hDbc, NULL, connection_string, SQL_NTS, NULL, 0, NULL,
SQL_DRIVER_COMPLETE);
//Allocate Statement Handle
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
//Create statement
SQLCHAR *query = (SQLCHAR *) "SELECT * FROM TEST;";
SQLRETURN sqlPrepareResponse = SQLPrepare(hStmt, query, SQL_NTS); //strlen(reinterpret_cast<const char *>(query))
//Bind columns
SQLCHAR personName[20];
SQLLEN personNameIndex;
SQLRETURN bindNameResponse = SQLBindCol(hStmt, 2, SQL_C_CHAR, personName, sizeof(personName),
&personNameIndex);
SQLINTEGER personId;
SQLLEN personIdIndex;
SQLRETURN personIdBindResponse = SQLBindCol(hStmt, 1, SQL_INTEGER, &personId, 0, &personIdIndex);
SQLRETURN execResponse = SQLExecute(hStmt);
SQLRETURN fetchResponse;
while ((fetchResponse = SQLFetch(hStmt)) != SQL_NO_DATA) {
std::cout << "ID: [" << personId << "] :" << personName << std::endl;
}
/* Free the statement handle. */
SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
/* Disconnect from the database. */
SQLDisconnect(hDbc);
/* Free the connection handle. */
SQLFreeHandle(SQL_HANDLE_DBC, hDbc);
/* Free the environment handle. */
SQLFreeHandle(SQL_HANDLE_ENV, hEnv);
return EXIT_SUCCESS;
}
I have exactly the same error using similar code (that was working in Ubuntu 18.04, but not with a update to 20.04)
cat /etc/odbcinst.ini
[ODBC Driver 17 for SQL Server]
Description=Microsoft ODBC Driver 17 for SQL Server
Driver=/opt/microsoft/msodbcsql17/lib64/libmsodbcsql-17.5.so.2.1
UsageCount=1
using this connection string
DRIVER=ODBC Driver 17 for SQL Server;SERVER=127.0.0.1, 1433;UID=SA;PWD=password;DATABASE=my_database;
this is my library link order
if(UNIX)
find_program(LSB_RELEASE_EXEC lsb_release)
execute_process(COMMAND ${LSB_RELEASE_EXEC} -is OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "Building in " ${LSB_RELEASE_ID_SHORT})
if("${LSB_RELEASE_ID_SHORT}" STREQUAL "Ubuntu")
message(STATUS "Linking with SQL-Server library")
set(lib_dep ${lib_dep} msodbcsql-17)
endif()
set(lib_dep ${lib_dep} pthread odbc dl)
endif()
As noted in the solution above, changing the link order fixed the problem
set(lib_dep ${lib_dep} pthread odbc dl msodbcsql-17)
Related
In short very new to all of this, so I'm not even sure if this is possible,anyway, I need to be able to make code, that will stand between programs like power bi and database. For example Power bi should be able to use my custom driver to connect to any database and when I he request data from database I need to mask all of the data before power bi receive it. So far the only guide I got was to look for custom ODBC and how to implement that. Not even sure how all of those work.
My understanding is that I need to make some c++ library, that implements some ODBC functions,API and then I need to through registry register this dll and power bi should then be able to see my driver.
My question is how would I start doing that, is there some examples code, that do just the minimal stuff, so I can see the concept on how I should do that?
EDIT:
I made some progress and now I have concrete question.
I made data source (DSN) on windows, linked with postgres driver and now I'm trying to connect to that dsn through c++ code.
But I m having trouble with that.
This is my code
enter code here
HENV hEnv = NULL; // for allocating memory usingSQLAllocEnv
HDBC hDBC = NULL; // connection handler
HSTMT hStmt = NULL; // statement handler
const char* szDSN = "PostgreSQL30"; // DataSourceName (config in windows
control panel)
const char* szUID = "postgres"; //username of the database
const char* szPasswd = "postgres"; //password of the database
RETCODE retcode;
int rcod = 1;
int i, j, no = 2;
int main()
{
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);
std::cout << retcode << std::endl;
SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
retcode = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDBC);
std::cout << retcode << std::endl;
retcode = SQLConnectA(hDBC,(SQLCHAR*)szDSN, SQL_NTS, (SQLCHAR*)szUID, SQL_NTS, (SQLCHAR*)szPasswd, SQL_NTS);
std::cout << (SQLCHAR*)szUID << std::endl;
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
{
// connected!!
std::cout << "Hello World!\n SUCCESSS COME ON PLEASE";
}
else {
std::cout << retcode << std::endl;
}
SQLFreeHandle(SQL_HANDLE_ENV, &hEnv);
SQLFreeHandle(SQL_HANDLE_DBC, &hDBC);
}
But SQLConnect returns SQL_ERROR.
Question is how would I connect to a data source name with c++ code ?
Can I use an ODBC statement handle again (i.e. is it valid) after a command using it, for example SQLExecute, fails? (does not return SQL_SUCCESS or SQL_SUCCESS_WITH_INFO)
And is this possibly DBMS/driver-specific?
I couldn't find anything about this on the ODBC Programmer's reference page.
I do not find any authoritative answers in the doc neither. But I would say: Yes - you can, except the error code returned is SQL_INVALID_HANDLE:
Reasoning:
None of the docs of the functions that require a statement-handle as argument mention anything about a handle being invalidated in case of error. All that matters is the return code. So if its not explicitly forbidden, it should work.
In the case you get an SQL_ERROR returned, you can use the same statement handle to fetch more information about that error. So the statement handle still has a valid context.
3: We are using the same statement again and again, even in case of SQL_ERROR returned. And we did not have any problems so far. Well, but mostly we do not get any Errors..
Update, after comment about "Statement has been terminated": Yes, you can re-use the same statement-handle. The error is just indicating that the currently running statement has been terminated by the server. See the following sample, which produces such an error and then uses the same statement again to do a successful insert:
#include <windows.h>
#include <tchar.h>
#include <iostream>
#include <sql.h>
#include <sqlext.h>
#include <sqlucode.h>
void printErr(SQLHANDLE handle, SQLSMALLINT handleType)
{
SQLSMALLINT recNr = 1;
SQLRETURN ret = SQL_SUCCESS;
while (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
{
SQLWCHAR errMsg[SQL_MAX_MESSAGE_LENGTH + 1];
SQLWCHAR sqlState[5 + 1];
errMsg[0] = 0;
SQLINTEGER nativeError;
SQLSMALLINT cb = 0;
ret = SQLGetDiagRec(handleType, handle, recNr, sqlState, &nativeError, errMsg, SQL_MAX_MESSAGE_LENGTH + 1, &cb);
if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
{
std::wcerr << L"ERROR; native: " << nativeError << L"; state: " << sqlState << L"; msg: " << errMsg << std::endl;
}
++recNr;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
// connect to db
SQLRETURN nResult = 0;
SQLHANDLE handleEnv = 0;
nResult = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, (SQLHANDLE*)&handleEnv);
nResult = SQLSetEnvAttr(handleEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3_80, SQL_IS_INTEGER);
SQLHANDLE handleDBC = 0;
nResult = SQLAllocHandle(SQL_HANDLE_DBC, handleEnv, (SQLHANDLE*)&handleDBC);
SQLWCHAR strConnect[256] = L"Driver={SQL Server};Server=.\\INSTANCE;Database=Test;Trusted_Connection=yes;";
SQLWCHAR strConnectOut[1024] = { 0 };
SQLSMALLINT nNumOut = 0;
nResult = SQLDriverConnect(handleDBC, NULL, (SQLWCHAR*)strConnect, SQL_NTS, (SQLWCHAR*)strConnectOut, sizeof(strConnectOut), &nNumOut, SQL_DRIVER_NOPROMPT);
if (!SQL_SUCCEEDED(nResult))
printErr(handleDBC, SQL_HANDLE_DBC);
SQLHSTMT handleStatement = SQL_NULL_HSTMT;
nResult = SQLAllocHandle(SQL_HANDLE_STMT, handleDBC, (SQLHANDLE*)&handleStatement);
if (!SQL_SUCCEEDED(nResult))
printErr(handleDBC, SQL_HANDLE_DBC);
// try to drop table Wallet, ignore if it exists
nResult = SQLExecDirect(handleStatement, L"DROP TABLE Wallet", SQL_NTS);
// create table Wallet
nResult = SQLExecDirect(handleStatement, L"CREATE TABLE Wallet (WalletID int NOT NULL, Name nvarchar(5) NOT NULL)", SQL_NTS);
if (!SQL_SUCCEEDED(nResult))
printErr(handleStatement, SQL_HANDLE_STMT);
// Create a query that fails with data truncation and statement got terminated error:
nResult = SQLExecDirect(handleStatement, L"INSERT INTO Wallet (WalletID, Name) VALUES (1, 'SomethingTooLong')", SQL_NTS);
if (!SQL_SUCCEEDED(nResult))
printErr(handleStatement, SQL_HANDLE_STMT);
// and now run a query on the same statement and check in the db: Has been inserted just fine
nResult = SQLExecDirect(handleStatement, L"INSERT INTO Wallet (WalletID, Name) VALUES (2, 'Fan')", SQL_NTS);
if (!SQL_SUCCEEDED(nResult))
printErr(handleStatement, SQL_HANDLE_STMT);
// actually we should now free all handles properly...
return 0;
}
The output of this program is:
ERROR; native: 8152; state: 22001; msg: [Microsoft][ODBC SQL Server
Driver][SQL Server]String or binary data would be truncated. ERROR;
native: 3621; state: 01000; msg: [Microsoft][ODBC SQL Server
Driver][SQL Server]The statement has been terminated.
But the last insert query that works without error, using the statement, has been executed successfully: Check in your database and see that the row has been inserted (and that the SQLExecDirect did not return any error).
See here for SQL_INVALID_HANDLE: https://msdn.microsoft.com/en-us/library/ms716219(v=vs.85).aspx
Greeting guys , I have been looking on the internet on how to connect C++ code with SQLEXPRESS DATABASE, I have read some threads about how to connect but yet... I CAN NOT follow any of them... neither they are working for me.
I dont want to use MFC or any kind of C++ methods for DB , I want to do it via raw coding.
problem:
the Database connectable via Visual studio and via Microsoft SQL
Server Studio , I can add or drop tables as I LIKE, I believe the DB
as it is, is working fine
the C++ code is here " I am Connecting to a local DB "
I got this code from one of the threads and was not able to figure out how to do the SQLDriverConnect,
I am very noob in C++... adding SQLEXPRESS on top of that...
edit:
I dont know what that 3055 in the connect code is...
thank you in advance for help
Data base information:
UDL:
this what I got from the udl
( Provider=SQLOLEDB.1;Integrated Security=SSPI;
Persist Security Info=False;User ID=sa;Initial Catalog=Holpa;Data Source=AMH )
Microsoft server:
Server type: Database Engine
Server name: AMH
Authen: SQL server Authen
Login: sa
password : amh999
VisualStudio:
Data Source=AMH;Initial Catalog=Holpa;User ID=sa;Password=***********
.NET Framework Data Provider for SQL Server
Open
Microsoft SQL Server
Owner sa
running on local machine.
#include <iostream>
#include <windows.h>
#include <sqltypes.h>
#include <sql.h>
#include <sqlext.h>
using namespace std;
SQLHANDLE sqlenvhandle = SQL_NULL_HANDLE;
SQLHANDLE sqlconnectionhandle = SQL_NULL_HANDLE;
SQLHANDLE sqlstatementhandle = SQL_NULL_HANDLE;
SQLRETURN retcode;
void show_error(RETCODE rc, SQLHENV hEnv, SQLHDBC hDbc,
SQLHSTMT hStmt, const char *action)
{
SQLWCHAR szMessage[256];
SQLWCHAR szState[6];
SDWORD sdwNative;
SWORD swMsgLen;
SQLError(hEnv, hDbc, hStmt, szState, &sdwNative, szMessage,
sizeof(szMessage), &swMsgLen);
wcout << "wcout MESSAGE: " << szMessage << "\n SQLSTATE " << szState << endl;
printf("Error %d performing %s\n SQLState=%s\nSQL message=%s\n",rc, action, szState, szMessage);
}
void CloseSQL()
{
SQLFreeHandle(SQL_HANDLE_STMT, sqlstatementhandle);
SQLDisconnect(sqlconnectionhandle);
SQLFreeHandle(SQL_HANDLE_DBC, sqlconnectionhandle);
SQLFreeHandle(SQL_HANDLE_ENV, sqlenvhandle);
}
int main()
{
if (SQL_SUCCESS != SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &sqlenvhandle))
{
printf("huh \n");
CloseSQL();
goto END;
}
if (SQL_SUCCESS != SQLSetEnvAttr(sqlenvhandle, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0))
{
printf("huh \n");
CloseSQL();
goto END;
}
if (SQL_SUCCESS != SQLAllocHandle(SQL_HANDLE_DBC, sqlenvhandle, &sqlconnectionhandle))
{
printf("huh \n");
CloseSQL();
goto END;
}
printf("Driver Initialised\n");
SQLWCHAR retconstring[1024];
printf("about to Driver Conneect\n");
retcode = SQLDriverConnect(sqlconnectionhandle,
NULL,
(SQLWCHAR*)"DRIVER={SQL Server};SERVER=AMH, 3055;DATABASE=Holpa;UID=sa;PWD=amh999;",
SQL_NTS,
retconstring,
1024,
NULL,
SQL_DRIVER_NOPROMPT);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
{
printf("Connection made\n");
}
else
{
show_error(retcode, sqlenvhandle, sqlconnectionhandle, sqlstatmenthandle, "Connecting.");
}
END:
printf("\n");
printf("Program End, press enter key to exit!");
getchar(); // waits for input
return 0;
}
ERRORS:
the error codes are not constants... they keep changing ... example I got the following:
Message: 0022E1EC
SQLSTATE: 0022e9f4
re-run the program
Message: 009fdc4c
SQLSTATE: 009fe454
rerun the code
Message: 00aadd90
SQLSTATE: 00aae598
Firstly, you need a diagonostic function that works:
void show_error(RETCODE rc, SQLHENV hEnv, SQLHDBC hDbc,
SQLHSTMT hStmt, const char *action)
{
char szMessage[256];
char szState[6];
SDWORD sdwNative;
SWORD swMsgLen;
SQLError(hEnv, hDbc, hStmt, szState, &sdwNative, szMessage,
sizeof(szMessage), &swMsgLen);
printf("Error %d performing %s\n"
"SQLState=%s\nSQL message=%s\n",
rc, action, szState, szMessage);
}
Then call it if anything goes wrong, e.g.:
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
{
printf("Connection made\n");
}
else
{
show_error(retcode, sqlenvhandle, sqlconnectionhandle, sqlstatmenthandle, "Connecting.");
}
We create and cache ODBC connections created using SQLDriverConnect; we've found circumstances where the connection becomes lost... prepared statements stop working, etc.
I didn't see an obvious function to test a connection/statement is valid, or to reset/re-create a connection, which seems like it would be a fairly common pattern. Can anyone suggest how this is best implemented?
This answer is along the same lines, is that the correct solution here too? If so is there any neat way to 're-boot' an existing statement to use the new connection? And if a connection is discovered to be dead does it still need to be freed?
In my mind there has always been some confusion over whether SQL_ATTR_CONNECTION_DEAD works to test if a connection is still alive before you make a call which requires it to be alive. This is based on the usage of many ODBC drivers, including the ones I wrote and I suspect they may all implement it differently.
In the post you refer to Nick's answer (SQL_ATTR_CONNECTION_DEAD) does not work with many drivers I have access to:
#include <stdio.h>
#include <sql.h>
#include <sqlext.h>
#include <stdlib.h>
static void extract_error(
char *fn,
SQLHANDLE handle,
SQLSMALLINT type);
main() {
SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;
SQLCHAR outstr[1024];
SQLSMALLINT outstr_len;
SQLRETURN ret;
SQLINTEGER dead;
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
ret = SQLDriverConnect(hdbc, (void *)0, "DSN=xx;UID=xx;PWD=xx", SQL_NTS,
outstr, sizeof(outstr), &outstr_len,
SQL_DRIVER_COMPLETE);
if (!SQL_SUCCEEDED(ret)) {
extract_error("SQLDriverConnect", hdbc, SQL_HANDLE_DBC);
exit(1);
}
SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
ret = SQLPrepare(hstmt, "select 123", SQL_NTS);
if (!SQL_SUCCEEDED(ret)) {
extract_error("SQLPrepare", hstmt, SQL_HANDLE_STMT);
exit(1);
}
ret = SQLExecute(hstmt);
if (!SQL_SUCCEEDED(ret)) {
extract_error("SQLExecute", hstmt, SQL_HANDLE_STMT);
exit(1);
}
sleep(120);
SQLGetConnectAttr(hdbc, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL);
printf ("dead=%ld\n", dead);
ret = SQLExecute(hstmt);
if (!SQL_SUCCEEDED(ret)) {
extract_error("SQLExecute", hstmt, SQL_HANDLE_STMT);
}
SQLGetConnectAttr(hdbc, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL);
printf ("dead=%ld\n", dead);
}
static void extract_error(
char *fn,
SQLHANDLE handle,
SQLSMALLINT type)
{
SQLINTEGER i = 0, native;
SQLCHAR state[ 7 ];
SQLCHAR text[256];
SQLSMALLINT len;
int ret;
fprintf(stderr,
"\n"
"The driver reported the following diagnostics whilst running "
"%s\n\n",
fn);
do
{
ret = SQLGetDiagRec(type, handle, ++i, state, &native, text,
sizeof(text), &len );
if (SQL_SUCCEEDED(ret))
printf( "%s:%ld:%ld:%s\n", state, i, native, text );
}
while( ret == SQL_SUCCESS );
}
which outputs with a number of drivers:
dead=0
# At this point I took the server down
The driver reported the following diagnostics whilst running SQLExecute
08S01:1:0:[SQL Server Driver 10.0][SQL Server]Communication link failure: short write
dead=0
The other option specified in the post you refer to is SQL_COPT_SS_CONNECTION_DEAD but that is specific to MS SQL Server.
Since I presume you don't intend for any statement handles to fail ordinarily can't you just run the stmt and if it fails assume the connection is dead and reconnect and reprepare?
If a connection does die you do still need to call SQLFreeHandle for the various handles.
I'm attempting to connect to a MySQL server using C++ with the MySQL ODBC 5.1 Driver on Visual C++ 2008 Express Edition.
I'm following these instructions from MSDN:
SQLConnect
SQLGetData
SQLFetch
The only difference is that I have to convert all the SQLCHAR to SQLWCHAR, to match the function params, hopefully that doesn't affect the connection string.
Every time I connect I get SQL_ERROR as the return value.
So I'm assuming there's something wrong with the connection string or the connection statement.
I've tried
DNS=TestConnection; UID=user; PSW=password
and
SERVER=localhost; DRIVER={MySQL ODBC 5.1 Driver}; PORT=3306; UID=user; PSW=password; DATABASE=dbo;
and other similar connection strings.
The DNS that's called TestConnection has the same info as the latter connection string.
The schema is dbo, and have one table called testfire with the following column specs:
TEST_ID( INT(11), PRIMARY, AUTO INCREMENT)
TEST_STRING( VARCHAR(50) )
TEST_INTEGER( INT(11) )
TEST_FLOAT( FLOAT )
TEST_DATE( DATETIME )
With 3 rows:
ID STRING INT FLOAT DATE
------------------------------------------------------
| 1 | Test 1 | 1 | 0.1 | 2001-01-01 00:00:00 |
| 2 | Test 2 | 2 | 0.2 | 2002-01-01 00:00:00 |
| 3 | Test 3 | 3 | 0.3 | 2003-01-01 00:00:00 |
------------------------------------------------------
I've attempted to retrieve the data using an Excel connection, mostly to see if the driver works. Excel successfully retrieved the data without problem, so the DNS named TestConnection is valid, and so are the credentials.
What am I doing wrong?
What should I change?
Is it the conversion to MYSQLWCHAR * that messes up the connection string?
Is there a different, perhaps better and more efficient approach? (except perhaps class encapsulation, that's what I'm going to do after the test is successful)
Oh, and the compiler doesn't give any errors or warnings, the code is compiled and runs without any problems.
So, here's the test code, which returns "Query execution error":
#include <iostream>
#include <windows.h>
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
using namespace std;
int main(){
SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;
SQLRETURN retcode;
HWND desktopHandle = GetDesktopWindow();
SQLWCHAR OutConnStr[255];
SQLSMALLINT OutConnStrLen;
SQLWCHAR szDNS[2048] ={0};
// Allocate environment handle
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
// Set the ODBC version environment attribute
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
// Allocate connection handle
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
// Set login timeout to 5 seconds
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
// Connect to data source
retcode = SQLDriverConnect(
hdbc,
desktopHandle,
(SQLWCHAR*)"driver=MySQL Server",
_countof("driver=MySQL Server"),
OutConnStr,
255,
&OutConnStrLen,
SQL_DRIVER_PROMPT );
// Allocate statement handle
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
// Process data
retcode = SQLExecDirect(hstmt, (SQLWCHAR*)"SELECT TEST_STRING, TEST_INTEGER, TEST_FLOAT FROM dbo.testfire", SQL_NTS);
if (retcode == SQL_SUCCESS) {
SQLINTEGER sTestInt, cbTestStr, cbTestInt, cbTestFloat;
SQLFLOAT dTestFloat;
SQLCHAR szTestStr[200];
while (TRUE) {
cout<<"Inside loop";
retcode = SQLFetch(hstmt);
if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO) {
cout<<"An error occurred";
}
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){
SQLGetData(hstmt, 1, SQL_C_CHAR, szTestStr, 200, &cbTestStr);
SQLGetData(hstmt, 2, SQL_C_ULONG, &sTestInt, 0, &cbTestInt);
SQLGetData(hstmt, 3, SQL_C_FLOAT, &dTestFloat, 0,&cbTestFloat);
/* Print the row of data */
cout<<szTestStr<<endl;
cout<<sTestInt<<endl;
cout<<dTestFloat<<endl;
} else {
break;
}
}
}else{
cout<<"Query execution error."<<endl;
SQLWCHAR SqlState[6], Msg[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER NativeError;
SQLSMALLINT i, MsgLen;
SQLRETURN rc2;
// Get the status records.
i = 1;
while ((rc2 = SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, i, SqlState, &NativeError,
Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) {
cout<<SqlState<<endl;
cout<<NativeError<<endl;
cout<<Msg<<endl;
cout<<MsgLen<<endl;
i++;
}
}
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
SQLDisconnect(hdbc);
}else{
cout<<"Connection error."<<endl;
}
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
}
}
SQLFreeHandle(SQL_HANDLE_ENV, henv);
}
system("pause");
return 0;
}
UPDATE
After updating the code (and post) using the correct arguments for the SQLDriverConnect from the documentation provided by Mat (see comments below), the connection works. How can I do the same thing without having to prompt for the DNS name? Put window handle as null and...?
Now it fails at the SQLExecDirect(hstmt, (SQLWCHAR*)"SELECT TEST_STRING, TEST_INTEGER, TEST_FLOAT FROM dbo.testfire", SQL_NTS), but the query is correct, so, what's the problem?
The exact error message returned is:
Sql State: 42000
Native Error: 1064
Message:
Message Length: 211
42000: Syntax error or access violation
*StatementText contained an SQL statement that was not preparable or contained a syntax error.
The user did not have permission to execute the SQL statement contained in *StatementText.
So... what does that mean?
How can I not have permission?
How can that generate a syntax error, it's clearly a valid query?
With a little help from Mat, I was able to figure out what the problem was, but since he didn't give it in a form of an answer, I'll have to answer it so it can be shared for those who have the same problem, and also to mark as answered.
So, my problem was that I couldn't connect to the database. As Mat suggested, I should use the extended error info, known as SQLGetDiagRec and also fix the arguments according to the documentation. Took me a moment to learn how the SQLGetDiagRec function works, but once I managed to convert the wchar_t to char * I was able to see the error it was generating.
The connection attempt gave me the error Data source not found and no default driver specified. That gave me a clue, indicating I either wrote the incorrect connection string or that the text string was somehow misinterpreted or mangled.
Doing some searching on the net gave me the insight that the string was misinterpreted, and to fix it I had to make it a literal string. Surely enough, putting an L in front of the string solved it!
retcode = SQLDriverConnect(hdbc, 0,
(SQLWCHAR*)L"DSN=TestConnection;SERVER=localhost;UID=user;PWD=password;DRIVER=MySQL Server;",
_countof(L"DSN=TestConnection;SERVER=localhost;UID=user;PWD=password;DRIVER=MySQL Server;"),
OutConnStr, 255, &OutConnStrLen, SQL_DRIVER_COMPLETE);
At the same time, I learned how to get rid of the prompt, which was quite easy to figure out after correcting the initial problem. Specify null for the window handle, set driver completion to SQL_DRIVER_COMPLETE and make sure you add all the information needed in the connection string.
So, the next problem I had with the query with SQLExecDirect was giving an error saying Syntax error or access violation. The problem was obviously the same as with the connection string. Surely enough
retcode = SQLExecDirect(hstmt, (SQLWCHAR*)L"SELECT TEST_STRING, TEST_INTEGER, TEST_FLOAT FROM dbo.testfire", SQL_NTS);
Worked like a charm.
Here's the code in its entirety, fully functional:
#include <iostream>
#include <windows.h>
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
#include <string>
using namespace std;
int main(){
SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;
SQLRETURN retcode;
SQLWCHAR OutConnStr[255];
SQLSMALLINT OutConnStrLen;
// Allocate environment handle
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
// Set the ODBC version environment attribute
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
// Allocate connection handle
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
// Set login timeout to 5 seconds
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
// Connect to data source
retcode = SQLDriverConnect(
hdbc,
0,
(SQLWCHAR*)L"DSN=TestConnection;SERVER=localhost;UID=root;PWD=never140;DRIVER=MySQL Server;",
_countof(L"DSN=TestConnection;SERVER=localhost;UID=root;PWD=never140;DRIVER=MySQL Server;"),
OutConnStr,
255,
&OutConnStrLen,
SQL_DRIVER_COMPLETE );
// Allocate statement handle
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
// Process data
retcode = SQLExecDirect(hstmt, (SQLWCHAR*)L"SELECT TEST_STRING, TEST_INTEGER, TEST_FLOAT FROM dbo.testfire", SQL_NTS);
if (retcode == SQL_SUCCESS) {
SQLINTEGER sTestInt, cbTestStr, cbTestInt, cbTestFloat, iCount = 1;
SQLFLOAT dTestFloat;
SQLCHAR szTestStr[200];
while (TRUE) {
retcode = SQLFetch(hstmt);
if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO) {
cout<<"An error occurred";
}
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){
SQLGetData(hstmt, 1, SQL_C_CHAR, szTestStr, 200, &cbTestStr);
SQLGetData(hstmt, 2, SQL_C_ULONG, &sTestInt, 0, &cbTestInt);
SQLGetData(hstmt, 3, SQL_C_DOUBLE, &dTestFloat, 0,&cbTestFloat);
/* Print the row of data */
cout<<"Row "<<iCount<<":"<<endl;
cout<<szTestStr<<endl;
cout<<sTestInt<<endl;
cout<<dTestFloat<<endl;
iCount++;
} else {
break;
}
}
}else{
cout<<"Query execution error."<<endl;
}
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
SQLDisconnect(hdbc);
}else{
cout<<"Connection error"<<endl;
}
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
}
}
SQLFreeHandle(SQL_HANDLE_ENV, henv);
}
system("pause");
return 0;
}
Just goes to show, even the tiniest thing can make everything fail.
Thank you Mat for your help.
change (SQLWCHAR*) to L. this works fine for me