4

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:

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?

ShadowScripter
  • 7,314
  • 4
  • 36
  • 54
  • 1
    The arguments you pass to SQLDriverConnect don't match the docs ([here](http://msdn.microsoft.com/en-us/library/windows/desktop/ms715433(v=vs.85).aspx)). Also get the extended error info as mentioned in that documentation to know what the problem is. (And `s/DNS/DSN/` in your post.) – Mat Oct 22 '11 at 15:18
  • Ok, using the arguments from those docs worked, the connection was successful, but the data retrieval doesn't work. Could you by any chance whip up a working example? – ShadowScripter Oct 22 '11 at 15:45
  • "Doesn't work" is not a problem description. Edit your question with the relevant (updated) code, and the exact error message and/or behavior you are getting. – Mat Oct 22 '11 at 15:47
  • @Mat the code has been updated and update notes have been added to the bottom of the post. – ShadowScripter Oct 22 '11 at 16:07
  • Assuming you got the `Sql State` and others from SqlGetDiagRec, why aren't you printing the returned strings? – Mat Oct 22 '11 at 16:12
  • That's the thing, the returned strings are returned as gibberish. Probably because printing an SQLWCHAR isn't the same as printing a char *, yeah? I added the SQLGetDiagRec code to the original code, see if you can shed some light on that. :) – ShadowScripter Oct 22 '11 at 16:16
  • Why are you insisting on SQLWCHAR when you're calling the ANSI functions? – Mat Oct 22 '11 at 16:25
  • All the functions are overridden in `` so `SQLGetDiagRec` is actually `SQLGetDiagRecW`. – ShadowScripter Oct 22 '11 at 16:36
  • Can I ask you retrieve data from the column `Date`, cause I have tried something like you did, but I cannot get proper time, please see [here](http://stackoverflow.com/questions/40738263/how-to-get-a-field-with-the-type-of-timestamp-in-mysql-through-odbc-using-cpp-in) – QM.py Nov 22 '16 at 12:16

2 Answers2

7

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.

ShadowScripter
  • 7,314
  • 4
  • 36
  • 54
  • This was very useful. It would be nice if you had posted a nominal DSN to go with the code, as the code relies upon that to function. – Jiminion Jun 09 '14 at 15:13
  • It works~~Thank you~~I still have a question how can I get the `Date` field in this example. Because I have tried a lot [see here](http://stackoverflow.com/questions/40738263/how-to-get-a-field-with-the-type-of-timestamp-in-mysql-through-odbc-using-cpp-in), but it seems I get the wrong time. – QM.py Nov 22 '16 at 12:13
  • 1
    @QM.py you should be using timestamp structs for output. If you read the [MSDN](https://msdn.microsoft.com/en-us/library/bb677267.aspx) you can see how the driver maps to different structures depending on the server type. I found an example on how to use it [here](http://stackoverflow.com/questions/29273293/how-to-get-sql-server-datetime-field-in-odbc-native-client) – ShadowScripter Nov 22 '16 at 16:10
  • @ShadowScripter Thanks for your help! I am a pythoner, and cpp drives me crazy...Copied and imitate code is what I am doing now. And I am finding the code for select the data by timestamp now.... – QM.py Nov 23 '16 at 02:03
  • Is it possible to get more error information when things goes wrong? – Damian Dec 03 '16 at 09:27
0

change (SQLWCHAR*) to L. this works fine for me

littlecodefarmer758
  • 968
  • 2
  • 10
  • 23