Bug #5778 SQLNumResultCols() creates an extra "blank" row for INSERT with params
Submitted: 28 Sep 2004 2:32 Modified: 2 Feb 2005 16:48
Reporter: Jeff Martin Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / ODBC Severity:S2 (Serious)
Version:3.51.07 OS:Solaris (Solaris 8)
Assigned to: Peter Harvey CPU Architecture:Any

[28 Sep 2004 2:32] Jeff Martin
Description:

It's easiest to describe with some pseudo-code

    SQLCHAR* insert = "INSERT INTO ThreeCol VALUES (?, ?, ?)"
    SQLPrepare(hStmt, insert, SQL_NTS);
    SQLNumResultCols(hStmt, &i);

The SQLNumResultCols has an unexpected side-effect:  An extra "blank" row is inserted into the ThreeCol tables.  Even though you never bind params nor invoke SQLExecute, you still have an extra "blank" row.  This extra "blank" row has default values for the columns (e.g. zero for integers, null for null-allowed columns, etc).

The above example might look a bit silly at first.  After all, why would a program ever invoke SQLNumResultCols for an INSERT?  The value will always be zero.  

However, we are using a library to put a friendlier C++ interface on top of ODBC.  Our library must call SQLNumResultCols after every SQLPrepare to handle any kind of statement.  FYI, our library is similar in concept to odbc++ (http://libodbcxx.sourceforge.net).

If the INSERT does not have any bound params, the SQLNumResultCols does not insert an extra blank row (it works correctly).

FYI, I'm pretty sure that bug #4264 has the same underlying problem with SQLNumResultCols.  Bug #4264 is about an extra blank row inserted using the Perl DBI with MyODBC.  I'll bet that the Perl DBI for ODBC also does an SQLNumResultCols after every SQLPrepare.

We are using unixODBC v2.2.8, but that's not the problem.  If we link directly to MyODBC, SQLNumResultCols still inserts the extra blank row.

How to repeat:

Create a table with three columns like
  CREATE TABLE ThreeCol (colA INTEGER, colB VARCHAR(10), colC DATETIME)

Compile and run the sample program below.

Run a select on ThreeCol and you will see a blank row like the one below, even though the program never even ran SQLExecute:
  +------------+-----------+--------------------+
  | colA       | colB      | colC               |
  +------------+-----------+--------------------+
  | 0          | NULL      | 0000-00-00 00:00:00|
  +------------+-----------+--------------------+

Here's the complete sample program.  This should compile with any C++ compiler (we're using Sun's C++ compiler):

/*
CC -I${ODBC_HOME}/include -L${ODBC_HOME}/lib -lodbc \
   -g -o myOdbcBug myOdbcBug.cc
*/
#include <sql.h>
#include <sqlext.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void CheckRC(SQLRETURN rc, char* msg, char* filename, int lineno);
#define CHECKRC(FUNCSHN,MSG) CheckRC(FUNCSHN, MSG, __FILE__, __LINE__)

SQLHENV     hEnv = SQL_NULL_HENV;
SQLHDBC     hDbc = SQL_NULL_HDBC;
SQLHSTMT    hStmt = SQL_NULL_HSTMT;
SQLCHAR*    connString;
SQLCHAR     connOut[255]; /* buffer for connection output */
SQLSMALLINT szConnOut; /* num bytes returned in connOut */
short       numResultCol;

//-------- main() -----------
int main(int argc, char** argv)
{
    if (argc < 2 || strlen(argv[1]) == 0)
    {
        fprintf(stderr,"ERROR: ODBC connect string not specified!\n");
        return 1;
    }
    connString = (SQLCHAR *)argv[1];

    //---- SETUP -------------------------------------------

    if (SQLAllocEnv(&hEnv) != SQL_SUCCESS)
    {
        fprintf(stderr, "Unable to allocate SQLHENV\n");
        return(1);
    }

    CHECKRC( SQLAllocConnect(hEnv, &hDbc), "SQLAllocConnect()" );

    CHECKRC( SQLDriverConnect(hDbc, NULL, connString, SQL_NTS, connOut, 
                              255, &szConnOut, SQL_DRIVER_NOPROMPT),
             "SQLDriverConnect()" );

    //---- STATEMENTS --------------------------------------

    CHECKRC( SQLAllocStmt(hDbc, &hStmt), "SQLAllocStmt()" );

    SQLCHAR* sqlStmt = (SQLCHAR*) "INSERT INTO ThreeCol VALUES (?, ?, ?)";

    CHECKRC( SQLPrepare(hStmt, sqlStmt, SQL_NTS), "SQLPrepare()" );

    CHECKRC( SQLNumResultCols(hStmt,&numResultCol), "SQLNumResultCols()");

    /****
    * At this point, even though we have not actually bound any params nor
    * have we even run SQLExecute(), you will see a "blank" row in the
    * database!
    *****/

    //---- TEAR DOWN -------------------------------------------

    CHECKRC( SQLFreeStmt(hStmt, SQL_DROP), "SQLFreeStmt()" );

    CHECKRC( SQLDisconnect(hDbc), "SQLDisconnect()" );

    CHECKRC( SQLFreeConnect(hDbc), "SQLFreeConnect()" );

    CHECKRC( SQLFreeEnv(hEnv), "SQLFreeEnv()" );

    return 0;
}

//---- Helper function -------------
void CheckRC(SQLRETURN rc,
                     char* msg,
                     char* filename, int lineno)
{
#define MSG_LNG 512

    SQLCHAR     szSqlState[MSG_LNG];
    SQLINTEGER  pfNativeError;
    SQLCHAR     szErrorMsg[MSG_LNG];
    SQLSMALLINT pcbErrorMsg;
    SQLRETURN   ret = SQL_SUCCESS;

    bool error = false;

    if (rc != SQL_SUCCESS && rc != SQL_NO_DATA_FOUND)
    {
        if (rc != SQL_SUCCESS_WITH_INFO)
        {
            // An error, not a warning
            fprintf(stderr, "\nERROR (%s:%d): %s\n",
                    filename, lineno, msg);
            error = true;
        }

        // Find the reason for the err/warning
        while (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)
        {
            ret = SQLError(hEnv, hDbc, hStmt,
                           szSqlState, &pfNativeError,
                           szErrorMsg, MSG_LNG,
                           &pcbErrorMsg);
            switch (ret)
            {
                case SQL_SUCCESS:  // This means SQLError() returned normally
                    if (rc == SQL_SUCCESS_WITH_INFO)
                    {
                        fprintf(stderr, "\nINFO: (%s:%d): %s\n",
                                filename, lineno, msg);
                    }
                    fprintf(stderr, 
                            "*** %s\n*** ODBC Code: %s, "
                            "Driver Code: %ld\n\n",
                            szErrorMsg, szSqlState, pfNativeError);
                    break;
                case SQL_NO_DATA_FOUND: // from SQLError()
                    break;
                default:
                    fprintf(stderr,
                            "*** SQLError() failed with code=%d\n\n", ret);
                    break;
            }
        }
    }
    if (error) exit(1);
}
[28 Sep 2004 2:33] Jeff Martin
Complete sample code

Attachment: myOdbcBug.cc (text/plain), 3.89 KiB.

[3 Nov 2004 18:30] Peter Harvey
revised test

Attachment: test.cpp (text/plain), 4.25 KiB.

[3 Nov 2004 22:28] Peter Harvey
rev 3

Attachment: test.cpp (text/plain), 4.30 KiB.

[2 Feb 2005 16:48] Dean Ellis
Thank you for your bug report. This issue has already been fixed
in the latest released version of that product, which you can download at 
http://www.mysql.com/downloads/