Bug #107307 Inaccurate query result using SQLBindCol for SQL_C_DOUBLE type
Submitted: 16 May 2022 16:42 Modified: 13 Jun 2022 19:49
Reporter: Stephen Lin Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / ODBC Severity:S1 (Critical)
Version:8.0.27 OS:Windows
Assigned to: CPU Architecture:Any

[16 May 2022 16:42] Stephen Lin
Description:
Fetching query result using SQLBindCol() and SQLFetch() returns inaccurate result for column of SQL_C_DOUBLE type.

The following repro steps is only one of examples I have seen with wrong results with SQL_C_DOUBLE type column.  

How to repeat:
Table Calcs with one column num0 (type double):

12.3
-12.3
15.7
-15.7
3.5
-3.5
0
NULL
10

Query using the following code:
    SQLHENV     hEnv = NULL;
    SQLHDBC     hDbc = NULL;
    SQLHSTMT    hStmt = NULL;
    WCHAR*      pwszConnStr = (WCHAR *)L"DRIVER=\"{MySQL ODBC 8.0 Unicode Driver}\";DESC=;DATABASE=TestV1;SERVER=mysql;UID=test;PASSWORD=password;PORT=3306;SOCKET=;OPTION=1048576;STMT=;SSLMODE=disabled";

    // Allocate an environment

    if (SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv) == SQL_ERROR)
    {
        fwprintf(stderr, L"Unable to allocate an environment handle\n");
        exit(-1);
    }

    // Register this as an application that expects 3.x behavior,
    // you must register something if you use AllocHandle

    TRYODBC(hEnv,
            SQL_HANDLE_ENV,
            SQLSetEnvAttr(hEnv,
                SQL_ATTR_ODBC_VERSION,
                (SQLPOINTER)SQL_OV_ODBC3,
                0));

    // Allocate a connection
    TRYODBC(hEnv,
            SQL_HANDLE_ENV,
            SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc));

    if (argc > 1)
    {
        pwszConnStr = *++argv;
    }

    // Connect to the driver.  Use the connection string if supplied
    // on the input, otherwise let the driver manager prompt for input.

    TRYODBC(hDbc,
        SQL_HANDLE_DBC,
        SQLDriverConnect(hDbc,
                         GetDesktopWindow(),
                         pwszConnStr,
                         SQL_NTS,
                         NULL,
                         0,
                         NULL,
                         SQL_DRIVER_COMPLETE));

    fwprintf(stderr, L"Connected!\n");

    RETCODE     RetCode;

    // Execute the query
    TRYODBC(hDbc,
        SQL_HANDLE_DBC,
        SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt));

    SQLWCHAR* query = (SQLWCHAR*)L"SELECT `Calcs`.`num0` FROM `Calcs`";

    TRYODBC(hDbc,
        SQL_HANDLE_DBC,
        SQLPrepare(hStmt, query, SQL_NTS));

    RetCode = SQLExecute(hStmt);
    SQLINTEGER ind = 0;
    double value = 0;
    RetCode = SQLBindCol(hStmt, 1, SQL_C_DOUBLE, (SQLPOINTER)&value, 0, (SQLLEN*)&ind);

    for (int i=0; ; i++) {
        RetCode = SQLFetch(hStmt);
        if (RetCode == SQL_SUCCESS || RetCode == SQL_SUCCESS_WITH_INFO) {
            if (ind <= 0) {
                printf("Record %d: %s\n", i + 1, "NULL");
            }
            else {
                printf("Record %d: %f\n", i + 1, value);
            }
        } else {
            if (RetCode != SQL_NO_DATA) {
                printf("Error\n");
            ;
            } else {
                break;
            }
        }
    }

Actual result:
Record 1: 12.330000  (12.3 expected)
Record 2: -12.300000
Record 3: 15.700000
Record 4: -15.700000
Record 5: 3.500000
Record 6: -3.500000
Record 7: 0.000000
Record 8: NULL
Record 9: 10.000000

Another example is ROUND(-9.31, 0) returns -92, when -9 is expected.
[16 May 2022 16:43] Stephen Lin
Update buy title
[17 May 2022 13:02] Bogdan Degtyariov
Thank you for reporting an issue in Connector/ODBC.

First and most important is this:

SQLINTEGER ind = 0;
RetCode = SQLBindCol(hStmt, 1, SQL_C_DOUBLE, (SQLPOINTER)&value, 0, (SQLLEN*)&ind);

The above code will most likely cause buffer overrun and all sorts of unpredictable behavior because a 32-bit SQLINTEGER variable ind is used as 64-bit SQLLEN buffer.

I need to check how it works with the correct buffer when `ind` variable is declared as 
SQLLEN ind = 0;
[17 May 2022 13:12] Bogdan Degtyariov
I was able to repeat the problem even with the correct index buffer.
The bug is verified.

The test code is as this:

DECLARE_TEST(t_bug107307)
{
  SQLCHAR buff[128];
  SQLRETURN rc;
  double value = 0;
  SQLLEN ind = 0;

  DECLARE_BASIC_HANDLES(henv1, hdbc1, hstmt1);
  alloc_basic_handles_with_opt(&henv1, &hdbc1, &hstmt1, NULL, NULL,
    NULL, NULL, "OPTION=1048576");

  ok_sql(hstmt1, "DROP TABLE IF EXISTS t_bug107307");
  ok_sql(hstmt1, "CREATE TABLE t_bug107307 (num0 DOUBLE)");

  ok_sql(hstmt1, "INSERT INTO t_bug107307 VALUES (12.3), (-12.3), (15.7),"
    "(-15.7), (3.5), (-3.5), (0), (NULL), (10)");

  ok_stmt(hstmt1, SQLPrepare(hstmt1, "SELECT * FROM t_bug107307", SQL_NTS));
  ok_stmt(hstmt, SQLExecute(hstmt1));

  ok_stmt(hstmt1, SQLBindCol(hstmt1, 1, SQL_C_DOUBLE, (SQLPOINTER)&value, 0, &ind));

  for (int i = 0; ; i++) {
    rc = SQLFetch(hstmt1);
    if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
    {
      if (ind <= 0) {
        printf("Record %d: %s\n", i + 1, "NULL");
      }
      else {
        printf("Record %d: %f\n", i + 1, value);
      }
    } else {
      if (rc != SQL_NO_DATA) {
        printf("Error\n");
      } else {
        break;
      }
    }
  }

  ok_sql(hstmt1, "DROP TABLE IF EXISTS t_bug107307");

  free_basic_handles(&henv1, &hdbc1, &hstmt1);

  return OK;
}

The output is

Record 1: 12.330000
Record 2: -12.300000
Record 3: 15.700000
Record 4: -15.700000
Record 5: 3.500000
Record 6: -3.500000
Record 7: 0.000000
Record 8: NULL
Record 9: 10.000000
[17 May 2022 20:15] Stephen Lin
Bogdan,

This defect and another defect I filed before were found when we run Tableau TDVT. Are you interested in running TDVT to test new MySQL ODBC driver releases? TDVT is publicly available on Github.  I can help you set up if you are interested. 

https://tableau.github.io/connector-plugin-sdk/docs/tdvt

Thanks,
Stephen
[18 May 2022 12:24] Bogdan Degtyariov
Posted by developer:
 
A workaround for this problem: do not use option NO_CACHE=1
However, the incorrect handling of the result with NO_CACHE=1 must be fixed.
[3 Jun 2022 13:14] Bogdan Degtyariov
Posted by developer:
 
The problem is fixed. The patch is now pushed into the release branch. Now the type SQL_C_DOUBLE is returned correctly with NO_CACHE=1.
[13 Jun 2022 19:49] Philip Olson
Posted by developer:
 
Fixed as of the upcoming MySQL Connector/ODBC 8.0.30 release, and here's the proposed changelog entry from the documentation team:

The SQL_C_DOUBLE type could return as an inaccurate result with NO_CACHE enabled.

Thank you for the bug report.