Pergunta

Using the Informix 64 bit driver with unixODBC (with both the default of 2.2.14 that comes with Redhat and also with 2.3.1) and running into problem when a column has a NULL value. unixODBC is using SQLLEN for the (last) StrLen_or_Ind argument whereas the Informix 64 bit driver appears to use SQLINTEGER.

So when testing if the return value is SQL_NULL_DATA the test does not work.. since the value -1 (which SQL_NULL_DATA is defined as) as 32 bit integer is 4294967296 when treated as 64 bit integer.

I found this problem when using the driver with PHP ODBC as it was causing a segfault. Does anyone know of a workaround? I thought of having a custom compiled unixODBC and PHP that works with the Informix ODBC driver, but then it won't work correctly with the other ODBC drivers :(

Below is a simple C program I wrote to test this.

#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>

typedef struct odbc_connection {
    SQLHENV henv;
    SQLHDBC hdbc;
} odbc_connection;

typedef struct odbc_result_value {
    char name[32];
    char *value;
    SQLLEN vallen;
    SQLLEN coltype;
} odbc_result_value;

typedef struct odbc_result {
    SQLHSTMT stmt;
    odbc_result_value *values;
    SQLSMALLINT numcols;
    odbc_connection *conn;
} odbc_result;

int print_error (SQLHENV    henv,
                 SQLHDBC    hdbc,
                 SQLHSTMT   hstmt)
{
    SQLCHAR     buffer[SQL_MAX_MESSAGE_LENGTH + 1];
    SQLCHAR     sqlstate[SQL_SQLSTATE_SIZE + 1];
    SQLINTEGER  sqlcode;
    SQLSMALLINT length;


    while ( SQLError(henv, hdbc, hstmt, sqlstate, &sqlcode, buffer,
                     SQL_MAX_MESSAGE_LENGTH + 1, &length) == SQL_SUCCESS )
    {
        printf("\n **** ERROR *****\n");
        printf("         SQLSTATE: %s\n", sqlstate);
        printf("Native Error Code: %ld\n", sqlcode);
        printf("%s \n", buffer);
    };

    return ( SQL_ERROR);
}

int terminate(SQLHENV henv,
              SQLHDBC hdbc)
{
    SQLRETURN   rc;

    rc = SQLDisconnect (hdbc);               /* disconnect from database  */
    if (rc != SQL_SUCCESS )
        print_error (henv, hdbc, SQL_NULL_HSTMT);
    rc = SQLFreeConnect (hdbc);              /* free connection handle    */
    if (rc != SQL_SUCCESS )
        print_error (henv, hdbc, SQL_NULL_HSTMT);
    rc = SQLFreeEnv (henv);                  /* free environment handle   */
    if (rc != SQL_SUCCESS )
        print_error (henv, hdbc, SQL_NULL_HSTMT);

    return(rc);
}

int check_error (SQLHENV    henv,
                 SQLHDBC    hdbc,
                 SQLHSTMT   hstmt,
                 SQLRETURN  frc)
{
    SQLRETURN   rc;

    print_error(henv, hdbc, hstmt);

    switch (frc){
    case SQL_SUCCESS : break;
    case SQL_ERROR :
    case SQL_INVALID_HANDLE:
        printf("\n ** FATAL ERROR, Attempting to rollback transaction**\n");
        rc = SQLTransact(henv, hdbc, SQL_ROLLBACK);
        if (rc != SQL_SUCCESS)
            printf("Rollback Failed, Exiting application\n");
        else
            printf("Rollback Successful, Exiting application\n");
        terminate(henv, hdbc);
        exit(frc);
        break;
    case SQL_SUCCESS_WITH_INFO :
        printf("\n ** Warning Message, application continuing\n");
        break;
    case SQL_NO_DATA_FOUND :
        printf("\n ** No Data Found ** \n");
        break;
    default :
        printf("\n ** Invalid Return Code ** \n");
        printf(" ** Attempting to rollback transaction **\n");
        SQLTransact(henv, hdbc, SQL_ROLLBACK);
        terminate(henv, hdbc);
        exit(frc);
        break;
    }
    return(SQL_SUCCESS);

}

odbc_connection* odbc_connect(char *dsn, char *user, char* password)
{
    SQLRETURN rc;
    odbc_connection *conn;

    conn = (odbc_connection *) malloc(sizeof(odbc_connection));

    // Allocate environment handle
    rc = SQLAllocEnv(&conn->henv);
    if (rc != SQL_SUCCESS) {
        printf("Unable to allocate environment\n");
        check_error(conn->henv, conn->hdbc, SQL_NULL_HSTMT, rc);
    }

    // Allocate connection handle
    rc = SQLAllocConnect(conn->henv, &conn->hdbc);
    if (rc != SQL_SUCCESS) {
        printf("Unable to allocate connection handle\n");
        check_error(conn->henv, conn->hdbc, SQL_NULL_HSTMT, rc);
    }

    // Connect to database
rc = SQLConnect(conn->hdbc, dsn, SQL_NTS, user, SQL_NTS, password,
SQL_NTS);
    if (rc != SQL_SUCCESS) {
        printf("Unable to connect\n");
        check_error(conn->henv, conn->hdbc, SQL_NULL_HSTMT, rc);
    }

    return conn;
}

odbc_result* odbc_query(odbc_connection *conn, char *sql)
{
    SQLRETURN rc;
    odbc_result *result;
    int i;
    SQLSMALLINT colnamelen; /* Not used */
    SQLLEN displaysize;

    result = (odbc_result *) malloc(sizeof(odbc_result));
    result->conn = conn;

    rc = SQLAllocStmt(conn->hdbc, &(result->stmt));
    if (rc != SQL_SUCCESS) {
        printf("Unable to allocate statement\n");
        check_error(conn->henv, conn->hdbc, SQL_NULL_HSTMT, rc);
    }

    rc = SQLExecDirect(result->stmt, sql, SQL_NTS);
    if (rc != SQL_SUCCESS) {
        printf("Unable to execute statement\n");
        check_error(conn->henv, conn->hdbc, SQL_NULL_HSTMT, rc);
    }

    SQLNumResultCols(result->stmt, &(result->numcols));

    if (result->numcols > 0) {
        // Bind columns
        result->values = (odbc_result_value *) malloc(sizeof(odbc_result_value) * result->numcols);

        for (i = 0; i < result->numcols; i++) {
            rc = SQLColAttributes(result->stmt, (SQLUSMALLINT)(i+1),
                SQL_COLUMN_NAME, result->values[i].name,
                sizeof(result->values[i].name), &colnamelen, 0);
            rc = SQLColAttributes(result->stmt, (SQLUSMALLINT)(i+1), 
                SQL_COLUMN_TYPE, NULL, 0, NULL, &result->values[i].coltype);
            rc = SQLColAttributes(result->stmt, (SQLUSMALLINT)(i+1),
                SQL_COLUMN_DISPLAY_SIZE, NULL, 0, NULL, &displaysize);
            result->values[i].value = (char *) malloc(sizeof(char) * (displaysize + 1));
            rc = SQLBindCol(result->stmt, (SQLUSMALLINT)(i+1), SQL_C_CHAR,
                result->values[i].value, displaysize + 1, &result->values[i].vallen);
        }
    }

    return result;
}

int odbc_print_row(odbc_result *result) {
    SQLRETURN rc;
    int i;

    rc = SQLFetch(result->stmt);
    if (rc != SQL_SUCCESS) {
        return 0;
    }

    for (i = 0; i < result->numcols; i++) {
        /* BUG: the 64 bit informix driver here is has returned a 32 bit -1
        integer but is stored in a 64 integer field */
        if (result->values[i].vallen == SQL_NULL_DATA) {
            printf("NULL;");
        } else {
            printf("\"");
            printf("%s", result->values[i].value);
            printf("\";");
        }
    }
    printf("\n");
    return 1;
}

int main(int argc, char *argv[])
{
    odbc_connection* conn;
    odbc_result *result;

    conn = odbc_connect("authlive", "auth", "xxx");
    result = odbc_query(conn, argv[1]);

    while (odbc_print_row(result));

    SQLFreeStmt(result->stmt, SQL_CLOSE);
    free(result);
    terminate(conn->henv, conn->hdbc);
    free(conn);
    return 0;
}
Foi útil?

Solução

This is a known problem in the CSDK 3.70 and earlier versions of the Informix ODBC driver.

Yesterday (2013-03-26), IBM released IBM Informix 12.10.xC1 and the companion IBM Informix ClientSDK 4.10.xC1. The version of ODBC in there should have the correct 64-bit types for SQLLEN and SQLULEN.

This means that if you get the upgraded, you should be OK. It does mean that any code needs to be recompiled with the new version of ODBC. It also means that some (commercial) ODBC driver managers which work around the oddity (bug) in Informix 3.70 and earlier will need to be rebuilt, or configured to use a standard 64-bit driver interface instead of the buggy Informix interface when working with the new 4.10 drivers.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top