C++ assign variable value from SQL Server SELECT - c++

I've got a script that does a bunch of SQL inserts but I've been trying to add a section that assigns the returned value of a select query into a variable.
The code I've been using to do the inserts is:
if (SQL_SUCCESS != SQLExecDirect(sqlstatementhandle, (SQLCHAR*)"BULK INSERT mytable FROM 'C:/dir/myfile.csv' WITH (FIRSTROW = 1, FIELDTERMINATOR = ',', ROWTERMINATOR = '\n');", SQL_NTS)) {
show_error(SQL_HANDLE_STMT, sqlstatementhandle);
That all works fine, but I can't figure out how to use the output of a query. The output will be a single int value, which I'd like to assign to an int variable.
Apologies if this is something that's blindingly obvious.
EDIT
Based on the answer below, the following has now worked for me. Thanks!
SQLRETURN retcode;
SQLHSTMT hstmt; // I use my own stmnthndl(sqlstatementhandle) below, this line left in for demonstration
retcode = SQLExecDirect(sqlstatementhandle, (SQLCHAR*)"SELECT count(*) FROM mytable;",SQL_NTS);
SQLINTEGER sCustID;
SQLLEN cbCustID;
if (retcode == SQL_SUCCESS) {
while (TRUE) {
retcode = SQLFetch(sqlstatementhandle);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
// get the first column
SQLGetData(sqlstatementhandle, 1, SQL_C_ULONG, &sCustID, 0, &cbCustID);
//You can now print it
cout << "CustID:" << sCustID;
}
else {
break;
}
}
}

You can use SQLGetData to get the data for a single column in the resultset. Taken from this example, first you execute the SQL query with SQLExecDirect:
SQLRETURN retcode;
SQLHSTMT hstmt;
retcode = SQLExecDirect(hstmt,
(SQLCHAR*)"SELECT CUSTID, NAME, PHONE FROM CUSTOMERS ORDER BY 2, 1, 3",
SQL_NTS);
Then you can get the data, I show a reduced version of the same example:
SQLINTEGER sCustID, cbCustID;
if (retcode == SQL_SUCCESS) {
while (TRUE) {
retcode = SQLFetch(hstmt);
if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO) {
show_error();
}
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){
// get the first column
SQLGetData(hstmt, 1, SQL_C_ULONG, &sCustID, 0, &cbCustID);
//You can now print it
fprintf(out, "CustID: %-5d", sCustID);
} else {
break;
}
}
}
The syntax of SQLGetData is:
SQLRETURN SQLGetData(
SQLHSTMT StatementHandle,
SQLUSMALLINT Col_or_Param_Num,
SQLSMALLINT TargetType,
SQLPOINTER TargetValuePtr,
SQLLEN BufferLength,
SQLLEN * StrLen_or_IndPtr);
With:
StatementHandle: the handle to the executed SQL query.
Col_or_Param_Num: the column number, which starts at 1.
TargetType: the data type, you can probably use SQL_INTEGER, see SQL data types and C data types.
TargetValuePtr: the pointer to the output data.
BufferLength: the length, but is not used for fixed length data such as integers.
StrLen_or_IndPtr: an optional output that can return the length of the data or an error code.
Note: like you commented you might need to cast the SQL query to SQLCHAR * since SQLCHAR is an unsigned char, see the data types.

Related

ODBC - Coding Prepared Statements in C++

Hi all,
Plugging away at learning how to develop ODBC SQL driver and data source stuff, but I seem to have run into a bit of a snag. I'm currently working with prepared statements on the a database with the following statement:
select * from TEST1 where NAME = ? and LOCATION__LATITUDE__S = ?
In English, find all records from TEST1 with to-be-specified name and latitudinal coordinate. I'm able to do the above with the ODBCTest app, so I know I can connect to the data source and query it with parameterized queries. Here's what I have for code for my problematic function:
void ExecPreparedStatement(const char* stmt) {
HSTMT hstmt;
SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
RETCODE rc = SQLPrepare(hstmt, (WCHAR*)stmt, SQL_NTS);
SQLSMALLINT numParams;
rc = SQLNumParams(hstmt, &numParams);
WCHAR* param1 = (WCHAR*)L"Jacob";
SQLFLOAT param2 = 40.0;
rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 80, 0, (SQLPOINTER)param1, 300, NULL);
rc = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_FLOAT, SQL_FLOAT, 0, 0, &param2, 300, NULL);
rc = SQLExecute(hstmt); /* <fails here> */
SQLSMALLINT numCols;
SQLNumResultCols(hstmt, &numCols);
DisplayRecords(hstmt, numCols);
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
This should give me the same results from the test app: 3 records of 13 attributes (3 rows, 13 columns). Instead, it fails on execution. For ease of reading, I've removed all of my RetCode handling from the code, but I do have it in there to check that the statements are completed and to handle it gracefully if they fail. There must be something I'm misunderstanding here - it also shows me that the number of parameters from the statement is 0 (with the numParams variable); I theorize that this is because it should be placed after the Execute call, but I can't test that right now because I never get to that point in execution.
Any ideas? Banging my head on a brick wall here; MSDN and other online sources are proving less than informative on this.
Clarification on the main question: Does anybody have any idea why the Execute function is failing?
ANSWER FOUND
The issue was malformed SQL through misused casting. Instead of passing in a const char* string to the function, I pass in a WCHAR*-casted string instead, and inside the function I use WCHAR*. The functional code now looks like this:
HSTMT hstmt;
SQLAllocStmt(hdbc, &hstmt);
TryODBC(hstmt, SQL_HANDLE_STMT, SQLPrepare(hstmt, stmt, SQL_NTS));
// Prepare passes test - we return 0.
WCHAR* param1 = (WCHAR*)L"Jacob";
TryODBC(hstmt, SQL_HANDLE_STMT,
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_WCHAR,
SQL_WVARCHAR, 80, 0, (SQLPOINTER)param1, 300, NULL)
);
TryODBC(hstmt, SQL_HANDLE_STMT, SQLExecute(hstmt));
SQLSMALLINT numCols;
TryODBC(hstmt, SQL_HANDLE_STMT, SQLNumResultCols(hstmt, &numCols));
DisplayRecords(hstmt, numCols);
SQLFreeStmt(hstmt, SQL_CLOSE);
Where TryODBC() is a function as follows:
bool disconnectOnError = false;
if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_ERROR) {
if (!Success(rc)) {
disconnectOnError = true;
}
SQLWCHAR state[6], errorMsg[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER nativeError;
SQLSMALLINT i = 1, msgLen;
while ((rc = SQLGetDiagRec(handleType, handle, i, state,
&nativeError, errorMsg, sizeof(errorMsg), &msgLen)) != SQL_NO_DATA)
{
ShowMessage(nativeError, errorMsg);
i++;
}
}
if (disconnectOnError) {
Disconnect(-1);
}
Massive thanks to #erg for directing me towards the SQLGetDiagRec() function.
You are passing a wide string but specifying SQL_C_CHAR as the parameter type, this should be SQL_C_WCHAR.
300 as your BufferLength parameter makes no sense, for the string argument pass the correct number of bytes, for param2 just pass 0 (BufferLength is ignored for non-character or binary-string data).
And you really need to check the error code after every ODBC call, and if it's an error dump the diagnostics. Either that or turn on the trace connection attribute and look at the results.
Answer noted in original question.

How do I setup an ODBC connection to perform multiple querys(SQLExecDirect) in c++?

I have the following code (using ODBC to connect to an SQL database):
The connection is OK and also the first SQL_ExecuteQuery(), but the second and third SQL_ExecuteQuery() will return with an error (returncode -1 for SQLExecDirect).
I assume, that the "statement handle hstmt" will be overwritten after the first execution. But how can I avoid this? Thank you so much.
SQLHENV henv = SQL_NULL_HENV;
SQLHDBC hdbc = SQL_NULL_HDBC;
SQLHDBC hstmt= SQL_NULL_HSTMT;
SQLRETURN retcode = SQL_SUCCESS;
//Connect function
int SQL_Connect()
{
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, (SQLPOINTER*)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);
retcode = SQLDriverConnect( hdbc,
NULL,
#ifdef IPC
(SQLWCHAR *)L"DSN=TEST;Description=ODK;UID=FFF;PWD=XXX;Trusted_Connection=No;DATABASE=DDD;",
#else
(SQLWCHAR *)L"DSN=ODKSQL64;Description=ODK;UID=auto;PWD=Visu_KDbos;Trusted_Connection=No;DATABASE=Giesserei_BKO;",
#endif
SQL_NTS,
OutConnStr,
255,
&OutConnStrLen,
SQL_DRIVER_NOPROMPT);
// Allocate statement handle
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
{
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
cout<<"Verbindung OK"<<std::endl;
}
}
}
}
return retcode;
}
//Disonnect function
int SQL_Disconnect ()
{
SQLFreeHandle(SQL_HANDLE_STMT, hstmt );
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return 1;
}
//Query function
int SQL_ExecuteQuery()
{
short rc;
char material[50];
SQLINTEGER strlenmaterial;
//prepare query
std::wstring SQL_Statement = L"SELECT blablabla";
rc = SQLExecDirect(hstmt, const_cast<SQLWCHAR*>(SQL_Statement.c_str()), SQL_NTS);
if (rc==SQL_SUCCESS || rc==SQL_SUCCESS_WITH_INFO) {
SQLBindCol(hstmt, 1, SQL_C_CHAR, &material, (SQLINTEGER) sizeof(material), &strlenmaterial);
while (1) {
rc = SQLFetch(hstmt);
if (rc==SQL_SUCCESS || rc==SQL_SUCCESS_WITH_INFO) {
rc = 1;
}else {
break;
}
}
} else {
//no data found
rc = 3;
}
return rc;
}
int main()
{
short rc;
rc = SQL_Connect();
rc = SQL_ExecuteQuery();
rc = SQL_ExecuteQuery();
rc = SQL_ExecuteQuery();
rc = SQL_Disconnect();
return 0;
}
You can re-use a HSTMT handle, but before running a new query, you need to close the pending cursor. As you are binding the columns using SQLBindCol, you also need to unbind the columns, before binding them again.
In your SQL_ExecuteQuery(), before returning from the function call:
SQLFreeStmt(hstmt, SQL_UNBIND)
SQLFreeStmt(hstmt, SQL_CLOSE)
Now you are ready to execute another query, bind again and fetch the result.
Note that you could also change the logic of your program, and bind only once, and then skip the unbind-step: If you know that you are always interested in the result of the same column, you could bind the column before executing the query. You can then execute the query, read the result, call SQLFreeStmt with the SQL_CLOSE option and start over with executing the query.
See here for more details:
https://learn.microsoft.com/en-us/sql/odbc/reference/syntax/sqlfreestmt-function

SQLExecute always returning "[Microsoft][SQL Server Native Client 10.0]String data, right truncation" in parameters more than 8k sized

When I execute the SQLExecute function it always returns me "[Microsoft][SQL Server Native Client 10.0]String data, right truncation" when my parameter has more than 8k bytes. I will paste the code below.
What I'm trying to do: to store a XML file in a column declared as varbinary(max) through a Stored Procedure via ODBC drivers (Visual C++ 2008) in a SQL Server 2008 R2.
The SP converts from varchar to varbinary calling SET #XML_FILE_BIN = CONVERT(VARBINARY(MAX), #XML_FILE)
It works fine if I try it pasting the whole XML int the SQL Server Management Studio.
I think something is wrong the binding in SQLBindParameter.
The code:
char* cXmlBuf; it contains my buffer
retcode = SQLBindParameter(
hstmt, //StatementHandle
1, //ParameterNumber
SQL_PARAM_INPUT, //InputOutputType
SQL_C_CHAR, //ValueType
SQL_CHAR, //ParameterType
SQL_DESC_LENGTH, //ColumnSize
0, //DecimalDigits
cXmlBuf, //ParameterValuePtr
bufLenght, //BufferLength
&cbXml //StrLen_or_IndPtr
);
if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
return;
SWORD id = 0;
SQLINTEGER cbId = 0;
retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_OUTPUT, SQL_C_SSHORT, SQL_INTEGER, 0, 0, &id, 0, &cbId);
if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
return;
retcode = SQLPrepare(hstmt, (SQLCHAR*)"{CALL MY_STORE_PROC(?, ?)}", SQL_NTS);
if (retcode != SQL_SUCCESS && retcode != SQL_SUCCESS_WITH_INFO)
return;
retcode = SQLFreeStmt(hstmt, SQL_CLOSE); // Clear any cursor state
retcode = SQLExecute(hstmt);
//in this part retcode is -1 and "[Microsoft][SQL Server Native Client
//10.0]String data, right truncation" is returned if my XML buffer
//has more than 8k
Found it!
I've declared SQLINTEGER cbXml = SQL_NTS; instead SQLLEN cbXml = 0;
Thanks.

ODBC SQLParamData return data

I am trying to insert o blob in a database. I have succeed to enter data i two columns.
I am having problem whit SQLParamData .It returns an error when it should return SQL_NEED_DATA(i will post code)
When i run SQLGetDiagRec it returns S1010 with error text Function sequence error .
I search this error on the internet and i learned that it could be related to a parameter from SQLBindParameter .
// Bind the parameter marker.
retCode = retcode = SQLBindParameter(hstmt, // hstmt
1, // ipar
SQL_PARAM_INPUT, // fParamType
SQL_C_BINARY, // fCType
SQL_LONGVARBINARY, // FSqlType
lbytes, // cbColDef
0, // ibScale
&pParmID, // rgbValue
0, // cbValueMax
&cbTextSize); // pcbValue
SqlError(hstmt,SQL_HANDLE_STMT,_T("WriteBlob"), _T("CTLSqlConnection"), _T("SQLBindParameter"));
if(retCode != SQL_SUCCESS)
{
delete pData;
if(!EndTransaction(FALSE))
return ERR_ENDTRANSACTION_FAILED;
else
return -3;
}
//SQLExec
retcode = retCode = SQLExecDirect(hstmt,(SQLTCHAR*)szSqlStat, SQL_NTS);
SQLRETURN ret;
SQLCHAR* SQLState;
SQLINTEGER NativeError;
SQLSMALLINT errmsglen;
SQLCHAR errmsg[255];
SQLCHAR errstate[50];
retCode = SQLParamData(hstmt, &pParmID);
SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, 1, (SQLCHAR*)errstate, &NativeError, (SQLCHAR*)errmsg, sizeof(errmsg), &errmsglen);
if(retCode == SQL_NEED_DATA)
{
// Put final batch.
SQLPutData(hstmt, pData, lbytes);
}
else
{
delete pData;
If this part of the code is not relevant enough i will post more.
Hope you can help me .Thanks .
Your logic looks wrong here. It is when SQLExecute (SQLExecDirect) returns SQL_NEED_DATA you call SQLParamData and it tells you will parameter you need to supply data for with SQLPutData. If you call SQLParamData when SQLExecute did not return SQL_NEED_DATA I can well imagine it is a function sequence error. I could probably dig you out an example from somewhere if you still need it.

Connecting to a MySQL server using C++

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