<commit_before>/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (<EMAIL>)
**
** This file is part of the QtSql module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the either Technology Preview License Agreement or the
** Beta Release License Agreement.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at <EMAIL>.
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qsql_db2.h"
#include <qcoreapplication.h>
#include <qdatetime.h>
#include <qsqlfield.h>
#include <qsqlerror.h>
#include <qsqlindex.h>
#include <qsqlrecord.h>
#include <qstringlist.h>
#include <qvarlengtharray.h>
#include <qvector.h>

#ifndef UNICODE
#define UNICODE
#endif

#if defined(Q_CC_BOR)
// DB2's sqlsystm.h (included through sqlcli1.h) defines the SQL_BIGINT_TYPE
// and SQL_BIGUINT_TYPE to wrong the types for Borland; so do the defines to
// the right type before including the header
#define SQL_BIGINT_TYPE qint64
#define SQL_BIGUINT_TYPE quint64
#endif

#include <sqlcli1.h>

#include <string.h>

QT_BEGIN_NAMESPACE

static const int COLNAMESIZE = 255;
static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };

class QDB2DriverPrivate
{
public:
    QDB2DriverPrivate(): hEnv(0), hDbc(0) {}
    SQLHANDLE hEnv;
    SQLHANDLE hDbc;
    QString user;
};

class QDB2ResultPrivate
{
public:
    QDB2ResultPrivate(const QDB2DriverPrivate* d): dp(d), hStmt(0), precisionPolicy(QSql::HighPrecision)
    {}
    ~QDB2ResultPrivate()
    {
        emptyValueCache();
    }
    void clearValueCache()
    {
        for (int i = 0; i < valueCache.count(); ++i) {
            delete valueCache[i];
            valueCache[i] = NULL;
        }
    }
    void emptyValueCache()
    {
        clearValueCache();
        valueCache.clear();
    }

    const QDB2DriverPrivate* dp;
    SQLHANDLE hStmt;
    QSqlRecord recInf;
    QVector<QVariant*> valueCache;
    QSql::NumericalPrecisionPolicy precisionPolicy;
};

static QString qFromTChar(SQLTCHAR* str)
{
#ifdef UNICODE
    return QString::fromUtf16(str);
#else
    return QString::fromLocal8Bit((const char*) str);
#endif
}

// dangerous!! (but fast). Don't use in functions that
// require out parameters!
static SQLTCHAR* qToTChar(const QString& str)
{
#ifdef UNICODE
    return (SQLTCHAR*)str.utf16();
#else
    return (unsigned char*) str.ascii();
#endif
}

static QString qWarnDB2Handle(int handleType, SQLHANDLE handle)
{
    SQLINTEGER nativeCode;
    SQLSMALLINT msgLen;
    SQLRETURN r = SQL_ERROR;
    SQLTCHAR state[SQL_SQLSTATE_SIZE + 1];
    SQLTCHAR description[SQL_MAX_MESSAGE_LENGTH];
    r = SQLGetDiagRec(handleType,
                       handle,
                       1,
                       (SQLTCHAR*) state,
                       &nativeCode,
                       (SQLTCHAR*) description,
                       SQL_MAX_MESSAGE_LENGTH - 1, /* in bytes, not in characters */
                       &msgLen);
    if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
        return QString(qFromTChar(description));
    return QString();
}

static QString qDB2Warn(const QDB2DriverPrivate* d)
{
    return (qWarnDB2Handle(SQL_HANDLE_ENV, d->hEnv) + QLatin1Char(' ')
             + qWarnDB2Handle(SQL_HANDLE_DBC, d->hDbc));
}

static QString qDB2Warn(const QDB2ResultPrivate* d)
{
    return (qWarnDB2Handle(SQL_HANDLE_ENV, d->dp->hEnv) + QLatin1Char(' ')
             + qWarnDB2Handle(SQL_HANDLE_DBC, d->dp->hDbc)
             + qWarnDB2Handle(SQL_HANDLE_STMT, d->hStmt));
}

static void qSqlWarning(const QString& message, const QDB2DriverPrivate* d)
{
    qWarning("%s\tError: %s", message.toLocal8Bit().constData(),
                              qDB2Warn(d).toLocal8Bit().constData());
}

static void qSqlWarning(const QString& message, const QDB2ResultPrivate* d)
{
    qWarning("%s\tError: %s", message.toLocal8Bit().constData(),
                              qDB2Warn(d).toLocal8Bit().constData());
}

static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
                            const QDB2DriverPrivate* p)
{
    return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type);
}

static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
                            const QDB2ResultPrivate* p)
{
    return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type);
}

static QVariant::Type qDecodeDB2Type(SQLSMALLINT sqltype)
{
    QVariant::Type type = QVariant::Invalid;
    switch (sqltype) {
    case SQL_REAL:
    case SQL_FLOAT:
    case SQL_DOUBLE:
    case SQL_DECIMAL:
    case SQL_NUMERIC:
        type = QVariant::Double;
        break;
    case SQL_SMALLINT:
    case SQL_INTEGER:
    case SQL_BIT:
    case SQL_TINYINT:
        type = QVariant::Int;
        break;
    case SQL_BIGINT:
        type = QVariant::LongLong;
        break;
    case SQL_BLOB:
    case SQL_BINARY:
    case SQL_VARBINARY:
    case SQL_LONGVARBINARY:
    case SQL_CLOB:
    case SQL_DBCLOB:
        type = QVariant::ByteArray;
        break;
    case SQL_DATE:
    case SQL_TYPE_DATE:
        type = QVariant::Date;
        break;
    case SQL_TIME:
    case SQL_TYPE_TIME:
        type = QVariant::Time;
        break;
    case SQL_TIMESTAMP:
    case SQL_TYPE_TIMESTAMP:
        type = QVariant::DateTime;
        break;
    case SQL_WCHAR:
    case SQL_WVARCHAR:
    case SQL_WLONGVARCHAR:
    case SQL_CHAR:
    case SQL_VARCHAR:
    case SQL_LONGVARCHAR:
        type = QVariant::String;
        break;
    default:
        type = QVariant::ByteArray;
        break;
    }
    return type;
}

static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i)
{
    SQLSMALLINT colNameLen;
    SQLSMALLINT colType;
    SQLUINTEGER colSize;
    SQLSMALLINT colScale;
    SQLSMALLINT nullable;
    SQLRETURN r = SQL_ERROR;
    SQLTCHAR colName[COLNAMESIZE];
    r = SQLDescribeCol(d->hStmt,
                        i+1,
                        colName,
                        (SQLSMALLINT) COLNAMESIZE,
                        &colNameLen,
                        &colType,
                        &colSize,
                        &colScale,
                        &nullable);

    if (r != SQL_SUCCESS) {
        qSqlWarning(QString::fromLatin1("qMakeFieldInfo: Unable to describe column %1").arg(i), d);
        return QSqlField();
    }
    QSqlField f(qFromTChar(colName), qDecodeDB2Type(colType));
    // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
    if (nullable == SQL_NO_NULLS)
        f.setRequired(true);
    else if (nullable == SQL_NULLABLE)
        f.setRequired(false);
    // else required is unknown
    f.setLength(colSize == 0 ? -1 : int(colSize));
    f.setPrecision(colScale == 0 ? -1 : int(colScale));
    f.setSqlType(int(colType));
    return f;
}

static int qGetIntData(SQLHANDLE hStmt, int column, bool& isNull)
{
    SQLINTEGER intbuf;
    isNull = false;
    SQLINTEGER lengthIndicator = 0;
    SQLRETURN r = SQLGetData(hStmt,
                              column + 1,
                              SQL_C_SLONG,
                              (SQLPOINTER) &intbuf,
                              0,
                              &lengthIndicator);
    if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) {
        isNull = true;
        return 0;
    }
    return int(intbuf);
}

static double qGetDoubleData(SQLHANDLE hStmt, int column, bool& isNull)
{
    SQLDOUBLE dblbuf;
    isNull = false;
    SQLINTEGER lengthIndicator = 0;
    SQLRETURN r = SQLGetData(hStmt,
                              column+1,
                              SQL_C_DOUBLE,
                              (SQLPOINTER) &dblbuf,
                              0,
                              &lengthIndicator);
    if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) {
        isNull = true;
        return 0.0;
    }

    return (double) dblbuf;
}

static SQLBIGINT qGetBigIntData(SQLHANDLE hStmt, int column, bool& isNull)
{
    SQLBIGINT lngbuf = Q_INT64_C(0);
    isNull = false;
    SQLINTEGER lengthIndicator = 0;
    SQLRETURN r = SQLGetData(hStmt,
                              column+1,
                              SQL_C_SBIGINT,
                              (SQLPOINTER) &lngbuf,
                              0,
                              &lengthIndicator);
    if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA)
        isNull = true;

    return lngbuf;
}

static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool& isNull)
{
    QString     fieldVal;
    SQLRETURN   r = SQL_ERROR;
    SQLINTEGER  lengthIndicator = 0;

    if (colSize <= 0)
        colSize = 255;
    else if (colSize > 65536) // limit buffer size to 64 KB
        colSize = 65536;
    else
        colSize++; // make sure there is room for more than the 0 termination
    SQLTCHAR* buf = new SQLTCHAR[colSize];

    while (true) {
        r = SQLGetData(hStmt,
                        column+1,
#ifdef UNICODE
                        SQL_C_WCHAR,
#else
                        SQL_C_CHAR,
#endif
                        (SQLPOINTER)buf,
                        colSize * sizeof(SQLTCHAR),
                        &lengthIndicator);
        if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
            if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
                fieldVal.clear();
                isNull = true;
                break;
            }
            fieldVal += qFromTChar(buf);
        } else if (r == SQL_NO_DATA) {
            break;
        } else {
            qWarning("qGetStringData: Error while fetching data (%d)", r);
            fieldVal.clear();
            break;
        }
    }
    delete[] buf;
    return fieldVal;
}

static QByteArray qGetBinaryData(SQLHANDLE hStmt, int column, SQLINTEGER& lengthIndicator, bool& isNull)
{
    QByteArray fieldVal;
    SQLSMALLINT colNameLen;
    SQLSMALLINT colType;
    SQLUINTEGER colSize;
    SQLSMALLINT colScale;
    SQLSMALLINT nullable;
    SQLRETURN r = SQL_ERROR;

    SQLTCHAR colName[COLNAMESIZE];
    r = SQLDescribeCol(hStmt,
                        column+1,
                        colName,
                        COLNAMESIZE,
                        &colNameLen,
                        &colType,
                        &colSize,
                        &colScale,
                        &nullable);
    if (r != SQL_SUCCESS)
        qWarning("qGetBinaryData: Unable to describe column %d", column);
    // SQLDescribeCol may return 0 if size cannot be determined
    if (!colSize)
        colSize = 255;
    else if (colSize > 65536) // read the field in 64 KB chunks
        colSize = 65536;
    char * buf = new char[colSize];
    while (true) {
        r = SQLGetData(hStmt,
                        column+1,
                        colType == SQL_DBCLOB ? SQL_C_CHAR : SQL_C_BINARY,
                        (SQLPOINTER) buf,
                        colSize,
                        &lengthIndicator);
        if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
            if (lengthIndicator == SQL_NULL_DATA) {
                isNull = true;
                break;
            } else {
                int rSize;
                r == SQL_SUCCESS ? rSize = lengthIndicator : rSize = colSize;
                if (lengthIndicator == SQL_NO_TOTAL) // size cannot be determined
                    rSize = colSize;
                fieldVal.append(QByteArray(buf, rSize));
                if (r == SQL_SUCCESS) // the whole field was read in one chunk
                    break;
            }
        } else {
            break;
        }
    }
    delete [] buf;
    return fieldVal;
}

static void qSplitTableQualifier(const QString & qualifier, QString * catalog,
                                  QString * schema, QString * table)
{
    if (!catalog || !schema || !table)
        return;
    QStringList l = qualifier.split(QLatin1Char('.'));
    if (l.count() > 3)
        return; // can't possibly be a valid table qualifier
    int i = 0, n = l.count();
    if (n == 1) {
        *table = qualifier;
    } else {
        for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) {
            if (n == 3) {
                if (i == 0)
                    *catalog = *it;
                else if (i == 1)
                    *schema = *it;
                else if (i == 2)
                    *table = *it;
            } else if (n == 2) {
                if (i == 0)
                    *schema = *it;
                else if (i == 1)
                    *table = *it;
            }
            i++;
        }
    }
}

// creates a QSqlField from a valid hStmt generated
// by SQLColumns. The hStmt has to point to a valid position.
static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt)
{
    bool isNull;
    int type = qGetIntData(hStmt, 4, isNull);
    QSqlField f(qGetStringData(hStmt, 3, -1, isNull), qDecodeDB2Type(type));
    int required = qGetIntData(hStmt, 10, isNull); // nullable-flag
    // required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
    if (required == SQL_NO_NULLS)
        f.setRequired(true);
    else if (required == SQL_NULLABLE)
        f.setRequired(false);
    // else we don't know.
    f.setLength(qGetIntData(hStmt, 6, isNull)); // column size
    f.setPrecision(qGetIntData(hStmt, 8, isNull)); // precision
    f.setSqlType(type);
    return f;
}

static bool qMakeStatement(QDB2ResultPrivate* d, bool forwardOnly, bool setForwardOnly = true)
{
    SQLRETURN r;
    if (!d->hStmt) {
        r = SQLAllocHandle(SQL_HANDLE_STMT,
                            d->dp->hDbc,
                            &d->hStmt);
        if (r != SQL_SUCCESS) {
            qSqlWarning(QLatin1String("QDB2Result::reset: Unable to allocate statement handle"), d);
            return false;
        }
    } else {
        r = SQLFreeStmt(d->hStmt, SQL_CLOSE);
        if (r != SQL_SUCCESS) {
            qSqlWarning(QLatin1String("QDB2Result::reset: Unable to close statement handle"), d);
            return false;
        }
    }

    if (!setForwardOnly)
        return true;

    if (forwardOnly) {
        r = SQLSetStmtAttr(d->hStmt,
                            SQL_ATTR_CURSOR_TYPE,
                            (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY,
                            SQL_IS_UINTEGER);
    } else {
        r = SQLSetStmtAttr(d->hStmt,
                            SQL_ATTR_CURSOR_TYPE,
                            (SQLPOINTER) SQL_CURSOR_STATIC,
                            SQL_IS_UINTEGER);
    }
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        qSqlWarning(QString::fromLatin1("QDB2Result::reset: Unable to set %1 attribute.").arg(
                     forwardOnly ? QLatin1String("SQL_CURSOR_FORWARD_ONLY")
                                 : QLatin1String("SQL_CURSOR_STATIC")), d);
        return false;
    }
    return true;
}

QVariant QDB2Result::handle() const
{
    return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt);
}

/************************************/

QDB2Result::QDB2Result(const QDB2Driver* dr, const QDB2DriverPrivate* dp)
    : QSqlResult(dr)
{
    d = new QDB2ResultPrivate(dp);
}

QDB2Result::~QDB2Result()
{
    if (d->hStmt) {
        SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
        if (r != SQL_SUCCESS)
            qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ")
                        + QString::number(r), d);
    }
    delete d;
}

bool QDB2Result::reset (const QString& query)
{
    setActive(false);
    setAt(QSql::BeforeFirstRow);
    SQLRETURN r;

    d->recInf.clear();
    d->emptyValueCache();

    if (!qMakeStatement(d, isForwardOnly()))
        return false;

    r = SQLExecDirect(d->hStmt,
                       qToTChar(query),
                       (SQLINTEGER) query.length());
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                "Unable to execute statement"), QSqlError::StatementError, d));
        return false;
    }
    SQLSMALLINT count;
    r = SQLNumResultCols(d->hStmt, &count);
    if (count) {
        setSelect(true);
        for (int i = 0; i < count; ++i) {
            d->recInf.append(qMakeFieldInfo(d, i));
        }
    } else {
        setSelect(false);
    }
    d->valueCache.resize(count, NULL);
    setActive(true);
    return true;
}

bool QDB2Result::prepare(const QString& query)
{
    setActive(false);
    setAt(QSql::BeforeFirstRow);
    SQLRETURN r;

    d->recInf.clear();
    d->emptyValueCache();

    if (!qMakeStatement(d, isForwardOnly()))
        return false;

    r = SQLPrepare(d->hStmt,
                    qToTChar(query),
                    (SQLINTEGER) query.length());

    if (r != SQL_SUCCESS) {
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                     "Unable to prepare statement"), QSqlError::StatementError, d));
        return false;
    }
    return true;
}

bool QDB2Result::exec()
{
    QList<QByteArray> tmpStorage; // holds temporary ptrs
    QVarLengthArray<SQLINTEGER, 32> indicators(boundValues().count());

    memset(indicators.data(), 0, indicators.size() * sizeof(SQLINTEGER));
    setActive(false);
    setAt(QSql::BeforeFirstRow);
    SQLRETURN r;

    d->recInf.clear();
    d->emptyValueCache();

    if (!qMakeStatement(d, isForwardOnly(), false))
        return false;


    QVector<QVariant> &values = boundValues();
    int i;
    for (i = 0; i < values.count(); ++i) {
        // bind parameters - only positional binding allowed
        SQLINTEGER *ind = &indicators[i];
        if (values.at(i).isNull())
            *ind = SQL_NULL_DATA;
        if (bindValueType(i) & QSql::Out)
            values[i].detach();

        switch (values.at(i).type()) {
            case QVariant::Date: {
                QByteArray ba;
                ba.resize(sizeof(DATE_STRUCT));
                DATE_STRUCT *dt = (DATE_STRUCT *)ba.constData();
                QDate qdt = values.at(i).toDate();
                dt->year = qdt.year();
                dt->month = qdt.month();
                dt->day = qdt.day();
                r = SQLBindParameter(d->hStmt,
                                     i + 1,
                                     qParamType[(QFlag)(bindValueType(i)) & 3],
                                     SQL_C_DATE,
                                     SQL_DATE,
                                     0,
                                     0,
                                     (void *) dt,
                                     0,
                                     *ind == SQL_NULL_DATA ? ind : NULL);
                tmpStorage.append(ba);
                break; }
            case QVariant::Time: {
                QByteArray ba;
                ba.resize(sizeof(TIME_STRUCT));
                TIME_STRUCT *dt = (TIME_STRUCT *)ba.constData();
                QTime qdt = values.at(i).toTime();
                dt->hour = qdt.hour();
                dt->minute = qdt.minute();
                dt->second = qdt.second();
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_TIME,
                                      SQL_TIME,
                                      0,
                                      0,
                                      (void *) dt,
                                      0,
                                      *ind == SQL_NULL_DATA ? ind : NULL);
                tmpStorage.append(ba);
                break; }
            case QVariant::DateTime: {
                QByteArray ba;
                ba.resize(sizeof(TIMESTAMP_STRUCT));
                TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)ba.constData();
                QDateTime qdt = values.at(i).toDateTime();
                dt->year = qdt.date().year();
                dt->month = qdt.date().month();
                dt->day = qdt.date().day();
                dt->hour = qdt.time().hour();
                dt->minute = qdt.time().minute();
                dt->second = qdt.time().second();
                dt->fraction = qdt.time().msec() * 1000000;
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_TIMESTAMP,
                                      SQL_TIMESTAMP,
                                      0,
                                      0,
                                      (void *) dt,
                                      0,
                                      *ind == SQL_NULL_DATA ? ind : NULL);
                tmpStorage.append(ba);
                break; }
            case QVariant::Int:
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_SLONG,
                                      SQL_INTEGER,
                                      0,
                                      0,
                                      (void *)values.at(i).constData(),
                                      0,
                                      *ind == SQL_NULL_DATA ? ind : NULL);
                break;
            case QVariant::Double:
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_DOUBLE,
                                      SQL_DOUBLE,
                                      0,
                                      0,
                                      (void *)values.at(i).constData(),
                                      0,
                                      *ind == SQL_NULL_DATA ? ind : NULL);
                break;
            case QVariant::ByteArray: {
                int len = values.at(i).toByteArray().size();
                if (*ind != SQL_NULL_DATA)
                    *ind = len;
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_BINARY,
                                      SQL_LONGVARBINARY,
                                      len,
                                      0,
                                      (void *)values.at(i).toByteArray().constData(),
                                      len,
                                      ind);
                break; }
            case QVariant::String:
#ifdef UNICODE
            {
                QString str(values.at(i).toString());
                if (*ind != SQL_NULL_DATA)
                    *ind = str.length() * sizeof(QChar);
                if (bindValueType(i) & QSql::Out) {
                    QByteArray ba((char*)str.utf16(), str.capacity() * sizeof(QChar));
                    r = SQLBindParameter(d->hStmt,
                                        i + 1,
                                        qParamType[(QFlag)(bindValueType(i)) & 3],
                                        SQL_C_WCHAR,
                                        SQL_WVARCHAR,
                                        str.length(),
                                        0,
                                        (void *)ba.constData(),
                                        ba.size(),
                                        ind);
                    tmpStorage.append(ba);
                } else {
                    void *data = (void*)str.utf16();
                    int len = str.length();
                    r = SQLBindParameter(d->hStmt,
                                        i + 1,
                                        qParamType[(QFlag)(bindValueType(i)) & 3],
                                        SQL_C_WCHAR,
                                        SQL_WVARCHAR,
                                        len,
                                        0,
                                        data,
                                        len * sizeof(QChar),
                                        ind);
                }
                break;
            }
#endif
            // fall through
            default: {
                QByteArray ba = values.at(i).toString().toAscii();
                int len = ba.length() + 1;
                if (*ind != SQL_NULL_DATA)
                    *ind = ba.length();
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_CHAR,
                                      SQL_VARCHAR,
                                      len,
                                      0,
                                      (void *) ba.constData(),
                                      len,
                                      ind);
                tmpStorage.append(ba);
                break; }
        }
        if (r != SQL_SUCCESS) {
            qWarning("QDB2Result::exec: unable to bind variable: %s",
                     qDB2Warn(d).toLocal8Bit().constData());
            setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                    "Unable to bind variable"), QSqlError::StatementError, d));
            return false;
        }
    }

    r = SQLExecute(d->hStmt);
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        qWarning("QDB2Result::exec: Unable to execute statement: %s",
                 qDB2Warn(d).toLocal8Bit().constData());
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                "Unable to execute statement"), QSqlError::StatementError, d));
        return false;
    }
    SQLSMALLINT count;
    r = SQLNumResultCols(d->hStmt, &count);
    if (count) {
        setSelect(true);
        for (int i = 0; i < count; ++i) {
            d->recInf.append(qMakeFieldInfo(d, i));
        }
    } else {
        setSelect(false);
    }
    setActive(true);
    d->valueCache.resize(count, NULL);

    //get out parameters
    if (!hasOutValues())
        return true;

    for (i = 0; i < values.count(); ++i) {
        switch (values[i].type()) {
            case QVariant::Date: {
                DATE_STRUCT ds = *((DATE_STRUCT *)tmpStorage.takeFirst().constData());
                values[i] = QVariant(QDate(ds.year, ds.month, ds.day));
                break; }
            case QVariant::Time: {
                TIME_STRUCT dt = *((TIME_STRUCT *)tmpStorage.takeFirst().constData());
                values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second));
                break; }
            case QVariant::DateTime: {
                TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT *)tmpStorage.takeFirst().constData());
                values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day),
                              QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000)));
                break; }
            case QVariant::Int:
            case QVariant::Double:
            case QVariant::ByteArray:
                break;
            case QVariant::String:
#ifdef UNICODE
                if (bindValueType(i) & QSql::Out)
                    values[i] = QString::fromUtf16((ushort*)tmpStorage.takeFirst().constData());
                break;
#endif
                // fall through
            default: {
                values[i] = QString::fromAscii(tmpStorage.takeFirst().constData());
                break; }
        }
        if (indicators[i] == SQL_NULL_DATA)
            values[i] = QVariant(values[i].type());
    }
    return true;
}

bool QDB2Result::fetch(int i)
{
    if (isForwardOnly() && i < at())
        return false;
    if (i == at())
        return true;
    d->clearValueCache();
    int actualIdx = i + 1;
    if (actualIdx <= 0) {
        setAt(QSql::BeforeFirstRow);
        return false;
    }
    SQLRETURN r;
    if (isForwardOnly()) {
        bool ok = true;
        while (ok && i > at())
            ok = fetchNext();
        return ok;
    } else {
        r = SQLFetchScroll(d->hStmt,
                            SQL_FETCH_ABSOLUTE,
                            actualIdx);
    }
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                "Unable to fetch record %1").arg(i), QSqlError::StatementError, d));
        return false;
    }
    setAt(i);
    return true;
}

bool QDB2Result::fetchNext()
{
    SQLRETURN r;
    d->clearValueCache();
    r = SQLFetchScroll(d->hStmt,
                       SQL_FETCH_NEXT,
                       0);
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        if (r != SQL_NO_DATA)
            setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                    "Unable to fetch next"), QSqlError::StatementError, d));
        return false;
    }
    setAt(at() + 1);
    return true;
}

bool QDB2Result::fetchFirst()
{
    if (isForwardOnly() && at() != QSql::BeforeFirstRow)
        return false;
    if (isForwardOnly())
        return fetchNext();
    d->clearValueCache();
    SQLRETURN r;
    r = SQLFetchScroll(d->hStmt,
                       SQL_FETCH_FIRST,
                       0);
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to fetch first"),
                                QSqlError::StatementError, d));
        return false;
    }
    setAt(0);
    return true;
}

bool QDB2Result::fetchLast()
{
    d->clearValueCache();

    int i = at();
    if (i == QSql::AfterLastRow) {
        if (isForwardOnly()) {
            return false;
        } else {
            if (!fetch(0))
                return false;
            i = at();
        }
    }

    while (fetchNext())
        ++i;

    if (i == QSql::BeforeFirstRow) {
        setAt(QSql::AfterLastRow);
        return false;
    }

    if (!isForwardOnly())
        return fetch(i);

    setAt(i);
    return true;
}


QVariant QDB2Result::data(int field)
{
    if (field >= d->recInf.count()) {
        qWarning("QDB2Result::data: column %d out of range", field);
        return QVariant();
    }
    SQLRETURN r = 0;
    SQLINTEGER lengthIndicator = 0;
    bool isNull = false;
    const QSqlField info = d->recInf.field(field);

    if (!info.isValid() || field >= d->valueCache.size())
        return QVariant();

    if (d->valueCache[field])
        return *d->valueCache[field];


    QVariant* v = 0;
    switch (info.type()) {
        case QVariant::LongLong:
            v = new QVariant((qint64) qGetBigIntData(d->hStmt, field, isNull));
            break;
        case QVariant::Int:
            v = new QVariant(qGetIntData(d->hStmt, field, isNull));
            break;
        case QVariant::Date: {
            DATE_STRUCT dbuf;
            r = SQLGetData(d->hStmt,
                            field + 1,
                            SQL_C_DATE,
                            (SQLPOINTER) &dbuf,
                            0,
                            &lengthIndicator);
            if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
                v = new QVariant(QDate(dbuf.year, dbuf.month, dbuf.day));
            } else {
                v = new QVariant(QDate());
                isNull = true;
            }
            break; }
        case QVariant::Time: {
            TIME_STRUCT tbuf;
            r = SQLGetData(d->hStmt,
                            field + 1,
                            SQL_C_TIME,
                            (SQLPOINTER) &tbuf,
                            0,
                            &lengthIndicator);
            if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
                v = new QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second));
            } else {
                v = new QVariant(QTime());
                isNull = true;
            }
            break; }
        case QVariant::DateTime: {
            TIMESTAMP_STRUCT dtbuf;
            r = SQLGetData(d->hStmt,
                            field + 1,
                            SQL_C_TIMESTAMP,
                            (SQLPOINTER) &dtbuf,
                            0,
                            &lengthIndicator);
            if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
                v = new QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day),
                                             QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000)));
            } else {
                v = new QVariant(QDateTime());
                isNull = true;
            }
            break; }
        case QVariant::ByteArray:
            v = new QVariant(qGetBinaryData(d->hStmt, field, lengthIndicator, isNull));
            break;
        case QVariant::Double:
            {
            QString value=qGetStringData(d->hStmt, field, info.length() + 1, isNull);
            bool ok=false;
            switch(d->precisionPolicy) {
                case QSql::LowPrecisionInt32:
                    v = new QVariant(value.toInt(&ok));
                    break;
                case QSql::LowPrecisionInt64:
                    v = new QVariant(value.toLongLong(&ok));
                    break;
                case QSql::LowPrecisionDouble:
                    v = new QVariant(value.toDouble(&ok));
                    break;
                case QSql::HighPrecision:
                default:
                    // length + 1 for the comma
                    v = new QVariant(qGetStringData(d->hStmt, field, info.length() + 1, isNull));
                    ok = true;
                    break;
            }
            if(!ok)
                v = new QVariant();
            break;
            }
        case QVariant::String:
        default:
            v = new QVariant(qGetStringData(d->hStmt, field, info.length(), isNull));
            break;
    }
    if (isNull)
        *v = QVariant(info.type());
    d->valueCache[field] = v;
    return *v;
}

bool QDB2Result::isNull(int i)
{
    if (i >= d->valueCache.size())
        return true;

    if (d->valueCache[i])
        return d->valueCache[i]->isNull();
    return data(i).isNull();
}

int QDB2Result::numRowsAffected()
{
    SQLINTEGER affectedRowCount = 0;
    SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount);
    if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
        return affectedRowCount;
    else
        qSqlWarning(QLatin1String("QDB2Result::numRowsAffected: Unable to count affected rows"), d);
    return -1;
}

int QDB2Result::size()
{
    return -1;
}

QSqlRecord QDB2Result::record() const
{
    if (isActive())
        return d->recInf;
    return QSqlRecord();
}

bool QDB2Result::nextResult()
{
    setActive(false);
    setAt(QSql::BeforeFirstRow);
    d->recInf.clear();
    d->emptyValueCache();
    setSelect(false);

    SQLRETURN r = SQLMoreResults(d->hStmt);
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        if (r != SQL_NO_DATA) {
            setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
                "Unable to fetch last"), QSqlError::ConnectionError, d));
        }
        return false;
    }

    SQLSMALLINT fieldCount;
    r = SQLNumResultCols(d->hStmt, &fieldCount);
    setSelect(fieldCount > 0);
    for (int i = 0; i < fieldCount; ++i)
        d->recInf.append(qMakeFieldInfo(d, i));

    d->valueCache.resize(fieldCount, NULL);<commit_msg>Missed these changes from the last commit.<commit_after>/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Qt Software Information (<EMAIL>)
**
** This file is part of the QtSql module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the either Technology Preview License Agreement or the
** Beta Release License Agreement.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at <EMAIL>.
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qsql_db2.h"
#include <qcoreapplication.h>
#include <qdatetime.h>
#include <qsqlfield.h>
#include <qsqlerror.h>
#include <qsqlindex.h>
#include <qsqlrecord.h>
#include <qstringlist.h>
#include <qvarlengtharray.h>
#include <qvector.h>
#include <QDebug>

#ifndef UNICODE
#define UNICODE
#endif

#if defined(Q_CC_BOR)
// DB2's sqlsystm.h (included through sqlcli1.h) defines the SQL_BIGINT_TYPE
// and SQL_BIGUINT_TYPE to wrong the types for Borland; so do the defines to
// the right type before including the header
#define SQL_BIGINT_TYPE qint64
#define SQL_BIGUINT_TYPE quint64
#endif

#include <sqlcli1.h>

#include <string.h>

QT_BEGIN_NAMESPACE

static const int COLNAMESIZE = 255;
static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };

class QDB2DriverPrivate
{
public:
    QDB2DriverPrivate(): hEnv(0), hDbc(0) {}
    SQLHANDLE hEnv;
    SQLHANDLE hDbc;
    QString user;
};

class QDB2ResultPrivate
{
public:
    QDB2ResultPrivate(const QDB2DriverPrivate* d): dp(d), hStmt(0), precisionPolicy(QSql::HighPrecision)
    {}
    ~QDB2ResultPrivate()
    {
        emptyValueCache();
    }
    void clearValueCache()
    {
        for (int i = 0; i < valueCache.count(); ++i) {
            delete valueCache[i];
            valueCache[i] = NULL;
        }
    }
    void emptyValueCache()
    {
        clearValueCache();
        valueCache.clear();
    }

    const QDB2DriverPrivate* dp;
    SQLHANDLE hStmt;
    QSqlRecord recInf;
    QVector<QVariant*> valueCache;
    QSql::NumericalPrecisionPolicy precisionPolicy;
};

static QString qFromTChar(SQLTCHAR* str)
{
#ifdef UNICODE
    return QString::fromUtf16(str);
#else
    return QString::fromLocal8Bit((const char*) str);
#endif
}

// dangerous!! (but fast). Don't use in functions that
// require out parameters!
static SQLTCHAR* qToTChar(const QString& str)
{
#ifdef UNICODE
    return (SQLTCHAR*)str.utf16();
#else
    return (unsigned char*) str.ascii();
#endif
}

static QString qWarnDB2Handle(int handleType, SQLHANDLE handle)
{
    SQLINTEGER nativeCode;
    SQLSMALLINT msgLen;
    SQLRETURN r = SQL_ERROR;
    SQLTCHAR state[SQL_SQLSTATE_SIZE + 1];
    SQLTCHAR description[SQL_MAX_MESSAGE_LENGTH];
    r = SQLGetDiagRec(handleType,
                       handle,
                       1,
                       (SQLTCHAR*) state,
                       &nativeCode,
                       (SQLTCHAR*) description,
                       SQL_MAX_MESSAGE_LENGTH - 1, /* in bytes, not in characters */
                       &msgLen);
    if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
        return QString(qFromTChar(description));
    return QString();
}

static QString qDB2Warn(const QDB2DriverPrivate* d)
{
    return (qWarnDB2Handle(SQL_HANDLE_ENV, d->hEnv) + QLatin1Char(' ')
             + qWarnDB2Handle(SQL_HANDLE_DBC, d->hDbc));
}

static QString qDB2Warn(const QDB2ResultPrivate* d)
{
    return (qWarnDB2Handle(SQL_HANDLE_ENV, d->dp->hEnv) + QLatin1Char(' ')
             + qWarnDB2Handle(SQL_HANDLE_DBC, d->dp->hDbc)
             + qWarnDB2Handle(SQL_HANDLE_STMT, d->hStmt));
}

static void qSqlWarning(const QString& message, const QDB2DriverPrivate* d)
{
    qWarning("%s\tError: %s", message.toLocal8Bit().constData(),
                              qDB2Warn(d).toLocal8Bit().constData());
}

static void qSqlWarning(const QString& message, const QDB2ResultPrivate* d)
{
    qWarning("%s\tError: %s", message.toLocal8Bit().constData(),
                              qDB2Warn(d).toLocal8Bit().constData());
}

static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
                            const QDB2DriverPrivate* p)
{
    return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type);
}

static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
                            const QDB2ResultPrivate* p)
{
    return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type);
}

static QVariant::Type qDecodeDB2Type(SQLSMALLINT sqltype)
{
    QVariant::Type type = QVariant::Invalid;
    switch (sqltype) {
    case SQL_REAL:
    case SQL_FLOAT:
    case SQL_DOUBLE:
    case SQL_DECIMAL:
    case SQL_NUMERIC:
        type = QVariant::Double;
        break;
    case SQL_SMALLINT:
    case SQL_INTEGER:
    case SQL_BIT:
    case SQL_TINYINT:
        type = QVariant::Int;
        break;
    case SQL_BIGINT:
        type = QVariant::LongLong;
        break;
    case SQL_BLOB:
    case SQL_BINARY:
    case SQL_VARBINARY:
    case SQL_LONGVARBINARY:
    case SQL_CLOB:
    case SQL_DBCLOB:
        type = QVariant::ByteArray;
        break;
    case SQL_DATE:
    case SQL_TYPE_DATE:
        type = QVariant::Date;
        break;
    case SQL_TIME:
    case SQL_TYPE_TIME:
        type = QVariant::Time;
        break;
    case SQL_TIMESTAMP:
    case SQL_TYPE_TIMESTAMP:
        type = QVariant::DateTime;
        break;
    case SQL_WCHAR:
    case SQL_WVARCHAR:
    case SQL_WLONGVARCHAR:
    case SQL_CHAR:
    case SQL_VARCHAR:
    case SQL_LONGVARCHAR:
        type = QVariant::String;
        break;
    default:
        type = QVariant::ByteArray;
        break;
    }
    return type;
}

static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i)
{
    SQLSMALLINT colNameLen;
    SQLSMALLINT colType;
    SQLUINTEGER colSize;
    SQLSMALLINT colScale;
    SQLSMALLINT nullable;
    SQLRETURN r = SQL_ERROR;
    SQLTCHAR colName[COLNAMESIZE];
    r = SQLDescribeCol(d->hStmt,
                        i+1,
                        colName,
                        (SQLSMALLINT) COLNAMESIZE,
                        &colNameLen,
                        &colType,
                        &colSize,
                        &colScale,
                        &nullable);

    if (r != SQL_SUCCESS) {
        qSqlWarning(QString::fromLatin1("qMakeFieldInfo: Unable to describe column %1").arg(i), d);
        return QSqlField();
    }
    QSqlField f(qFromTChar(colName), qDecodeDB2Type(colType));
    // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
    if (nullable == SQL_NO_NULLS)
        f.setRequired(true);
    else if (nullable == SQL_NULLABLE)
        f.setRequired(false);
    // else required is unknown
    f.setLength(colSize == 0 ? -1 : int(colSize));
    f.setPrecision(colScale == 0 ? -1 : int(colScale));
    f.setSqlType(int(colType));
    return f;
}

static int qGetIntData(SQLHANDLE hStmt, int column, bool& isNull)
{
    SQLINTEGER intbuf;
    isNull = false;
    SQLINTEGER lengthIndicator = 0;
    SQLRETURN r = SQLGetData(hStmt,
                              column + 1,
                              SQL_C_SLONG,
                              (SQLPOINTER) &intbuf,
                              0,
                              &lengthIndicator);
    if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) {
        isNull = true;
        return 0;
    }
    return int(intbuf);
}

static double qGetDoubleData(SQLHANDLE hStmt, int column, bool& isNull)
{
    SQLDOUBLE dblbuf;
    isNull = false;
    SQLINTEGER lengthIndicator = 0;
    SQLRETURN r = SQLGetData(hStmt,
                              column+1,
                              SQL_C_DOUBLE,
                              (SQLPOINTER) &dblbuf,
                              0,
                              &lengthIndicator);
    if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) {
        isNull = true;
        return 0.0;
    }

    return (double) dblbuf;
}

static SQLBIGINT qGetBigIntData(SQLHANDLE hStmt, int column, bool& isNull)
{
    SQLBIGINT lngbuf = Q_INT64_C(0);
    isNull = false;
    SQLINTEGER lengthIndicator = 0;
    SQLRETURN r = SQLGetData(hStmt,
                              column+1,
                              SQL_C_SBIGINT,
                              (SQLPOINTER) &lngbuf,
                              0,
                              &lengthIndicator);
    if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA)
        isNull = true;

    return lngbuf;
}

static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool& isNull)
{
    QString     fieldVal;
    SQLRETURN   r = SQL_ERROR;
    SQLINTEGER  lengthIndicator = 0;

    if (colSize <= 0)
        colSize = 255;
    else if (colSize > 65536) // limit buffer size to 64 KB
        colSize = 65536;
    else
        colSize++; // make sure there is room for more than the 0 termination
    SQLTCHAR* buf = new SQLTCHAR[colSize];

    while (true) {
        r = SQLGetData(hStmt,
                        column+1,
#ifdef UNICODE
                        SQL_C_WCHAR,
#else
                        SQL_C_CHAR,
#endif
                        (SQLPOINTER)buf,
                        colSize * sizeof(SQLTCHAR),
                        &lengthIndicator);
        if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
            if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
                fieldVal.clear();
                isNull = true;
                break;
            }
            fieldVal += qFromTChar(buf);
        } else if (r == SQL_NO_DATA) {
            break;
        } else {
            qWarning("qGetStringData: Error while fetching data (%d)", r);
            fieldVal.clear();
            break;
        }
    }
    delete[] buf;
    return fieldVal;
}

static QByteArray qGetBinaryData(SQLHANDLE hStmt, int column, SQLINTEGER& lengthIndicator, bool& isNull)
{
    QByteArray fieldVal;
    SQLSMALLINT colNameLen;
    SQLSMALLINT colType;
    SQLUINTEGER colSize;
    SQLSMALLINT colScale;
    SQLSMALLINT nullable;
    SQLRETURN r = SQL_ERROR;

    SQLTCHAR colName[COLNAMESIZE];
    r = SQLDescribeCol(hStmt,
                        column+1,
                        colName,
                        COLNAMESIZE,
                        &colNameLen,
                        &colType,
                        &colSize,
                        &colScale,
                        &nullable);
    if (r != SQL_SUCCESS)
        qWarning("qGetBinaryData: Unable to describe column %d", column);
    // SQLDescribeCol may return 0 if size cannot be determined
    if (!colSize)
        colSize = 255;
    else if (colSize > 65536) // read the field in 64 KB chunks
        colSize = 65536;
    char * buf = new char[colSize];
    while (true) {
        r = SQLGetData(hStmt,
                        column+1,
                        colType == SQL_DBCLOB ? SQL_C_CHAR : SQL_C_BINARY,
                        (SQLPOINTER) buf,
                        colSize,
                        &lengthIndicator);
        if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
            if (lengthIndicator == SQL_NULL_DATA) {
                isNull = true;
                break;
            } else {
                int rSize;
                r == SQL_SUCCESS ? rSize = lengthIndicator : rSize = colSize;
                if (lengthIndicator == SQL_NO_TOTAL) // size cannot be determined
                    rSize = colSize;
                fieldVal.append(QByteArray(buf, rSize));
                if (r == SQL_SUCCESS) // the whole field was read in one chunk
                    break;
            }
        } else {
            break;
        }
    }
    delete [] buf;
    return fieldVal;
}

static void qSplitTableQualifier(const QString & qualifier, QString * catalog,
                                  QString * schema, QString * table)
{
    if (!catalog || !schema || !table)
        return;
    QStringList l = qualifier.split(QLatin1Char('.'));
    if (l.count() > 3)
        return; // can't possibly be a valid table qualifier
    int i = 0, n = l.count();
    if (n == 1) {
        *table = qualifier;
    } else {
        for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) {
            if (n == 3) {
                if (i == 0)
                    *catalog = *it;
                else if (i == 1)
                    *schema = *it;
                else if (i == 2)
                    *table = *it;
            } else if (n == 2) {
                if (i == 0)
                    *schema = *it;
                else if (i == 1)
                    *table = *it;
            }
            i++;
        }
    }
}

// creates a QSqlField from a valid hStmt generated
// by SQLColumns. The hStmt has to point to a valid position.
static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt)
{
    bool isNull;
    int type = qGetIntData(hStmt, 4, isNull);
    QSqlField f(qGetStringData(hStmt, 3, -1, isNull), qDecodeDB2Type(type));
    int required = qGetIntData(hStmt, 10, isNull); // nullable-flag
    // required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
    if (required == SQL_NO_NULLS)
        f.setRequired(true);
    else if (required == SQL_NULLABLE)
        f.setRequired(false);
    // else we don't know.
    f.setLength(qGetIntData(hStmt, 6, isNull)); // column size
    f.setPrecision(qGetIntData(hStmt, 8, isNull)); // precision
    f.setSqlType(type);
    return f;
}

static bool qMakeStatement(QDB2ResultPrivate* d, bool forwardOnly, bool setForwardOnly = true)
{
    SQLRETURN r;
    if (!d->hStmt) {
        r = SQLAllocHandle(SQL_HANDLE_STMT,
                            d->dp->hDbc,
                            &d->hStmt);
        if (r != SQL_SUCCESS) {
            qSqlWarning(QLatin1String("QDB2Result::reset: Unable to allocate statement handle"), d);
            return false;
        }
    } else {
        r = SQLFreeStmt(d->hStmt, SQL_CLOSE);
        if (r != SQL_SUCCESS) {
            qSqlWarning(QLatin1String("QDB2Result::reset: Unable to close statement handle"), d);
            return false;
        }
    }

    if (!setForwardOnly)
        return true;

    if (forwardOnly) {
        r = SQLSetStmtAttr(d->hStmt,
                            SQL_ATTR_CURSOR_TYPE,
                            (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY,
                            SQL_IS_UINTEGER);
    } else {
        r = SQLSetStmtAttr(d->hStmt,
                            SQL_ATTR_CURSOR_TYPE,
                            (SQLPOINTER) SQL_CURSOR_STATIC,
                            SQL_IS_UINTEGER);
    }
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        qSqlWarning(QString::fromLatin1("QDB2Result::reset: Unable to set %1 attribute.").arg(
                     forwardOnly ? QLatin1String("SQL_CURSOR_FORWARD_ONLY")
                                 : QLatin1String("SQL_CURSOR_STATIC")), d);
        return false;
    }
    return true;
}

QVariant QDB2Result::handle() const
{
    return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt);
}

/************************************/

QDB2Result::QDB2Result(const QDB2Driver* dr, const QDB2DriverPrivate* dp)
    : QSqlResult(dr)
{
    d = new QDB2ResultPrivate(dp);
}

QDB2Result::~QDB2Result()
{
    if (d->hStmt) {
        SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
        if (r != SQL_SUCCESS)
            qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ")
                        + QString::number(r), d);
    }
    delete d;
}

bool QDB2Result::reset (const QString& query)
{
    setActive(false);
    setAt(QSql::BeforeFirstRow);
    SQLRETURN r;

    d->recInf.clear();
    d->emptyValueCache();

    if (!qMakeStatement(d, isForwardOnly()))
        return false;

    r = SQLExecDirect(d->hStmt,
                       qToTChar(query),
                       (SQLINTEGER) query.length());
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                "Unable to execute statement"), QSqlError::StatementError, d));
        return false;
    }
    SQLSMALLINT count;
    r = SQLNumResultCols(d->hStmt, &count);
    if (count) {
        setSelect(true);
        for (int i = 0; i < count; ++i) {
            d->recInf.append(qMakeFieldInfo(d, i));
        }
    } else {
        setSelect(false);
    }
    d->valueCache.resize(count);
    d->valueCache.fill(NULL);
    setActive(true);
    return true;
}

bool QDB2Result::prepare(const QString& query)
{
    setActive(false);
    setAt(QSql::BeforeFirstRow);
    SQLRETURN r;

    d->recInf.clear();
    d->emptyValueCache();

    if (!qMakeStatement(d, isForwardOnly()))
        return false;

    r = SQLPrepare(d->hStmt,
                    qToTChar(query),
                    (SQLINTEGER) query.length());

    if (r != SQL_SUCCESS) {
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                     "Unable to prepare statement"), QSqlError::StatementError, d));
        return false;
    }
    return true;
}

bool QDB2Result::exec()
{
    QList<QByteArray> tmpStorage; // holds temporary ptrs
    QVarLengthArray<SQLINTEGER, 32> indicators(boundValues().count());

    memset(indicators.data(), 0, indicators.size() * sizeof(SQLINTEGER));
    setActive(false);
    setAt(QSql::BeforeFirstRow);
    SQLRETURN r;

    d->recInf.clear();
    d->emptyValueCache();

    if (!qMakeStatement(d, isForwardOnly(), false))
        return false;


    QVector<QVariant> &values = boundValues();
    int i;
    for (i = 0; i < values.count(); ++i) {
        // bind parameters - only positional binding allowed
        SQLINTEGER *ind = &indicators[i];
        if (values.at(i).isNull())
            *ind = SQL_NULL_DATA;
        if (bindValueType(i) & QSql::Out)
            values[i].detach();

        switch (values.at(i).type()) {
            case QVariant::Date: {
                QByteArray ba;
                ba.resize(sizeof(DATE_STRUCT));
                DATE_STRUCT *dt = (DATE_STRUCT *)ba.constData();
                QDate qdt = values.at(i).toDate();
                dt->year = qdt.year();
                dt->month = qdt.month();
                dt->day = qdt.day();
                r = SQLBindParameter(d->hStmt,
                                     i + 1,
                                     qParamType[(QFlag)(bindValueType(i)) & 3],
                                     SQL_C_DATE,
                                     SQL_DATE,
                                     0,
                                     0,
                                     (void *) dt,
                                     0,
                                     *ind == SQL_NULL_DATA ? ind : NULL);
                tmpStorage.append(ba);
                break; }
            case QVariant::Time: {
                QByteArray ba;
                ba.resize(sizeof(TIME_STRUCT));
                TIME_STRUCT *dt = (TIME_STRUCT *)ba.constData();
                QTime qdt = values.at(i).toTime();
                dt->hour = qdt.hour();
                dt->minute = qdt.minute();
                dt->second = qdt.second();
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_TIME,
                                      SQL_TIME,
                                      0,
                                      0,
                                      (void *) dt,
                                      0,
                                      *ind == SQL_NULL_DATA ? ind : NULL);
                tmpStorage.append(ba);
                break; }
            case QVariant::DateTime: {
                QByteArray ba;
                ba.resize(sizeof(TIMESTAMP_STRUCT));
                TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)ba.constData();
                QDateTime qdt = values.at(i).toDateTime();
                dt->year = qdt.date().year();
                dt->month = qdt.date().month();
                dt->day = qdt.date().day();
                dt->hour = qdt.time().hour();
                dt->minute = qdt.time().minute();
                dt->second = qdt.time().second();
                dt->fraction = qdt.time().msec() * 1000000;
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_TIMESTAMP,
                                      SQL_TIMESTAMP,
                                      0,
                                      0,
                                      (void *) dt,
                                      0,
                                      *ind == SQL_NULL_DATA ? ind : NULL);
                tmpStorage.append(ba);
                break; }
            case QVariant::Int:
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_SLONG,
                                      SQL_INTEGER,
                                      0,
                                      0,
                                      (void *)values.at(i).constData(),
                                      0,
                                      *ind == SQL_NULL_DATA ? ind : NULL);
                break;
            case QVariant::Double:
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_DOUBLE,
                                      SQL_DOUBLE,
                                      0,
                                      0,
                                      (void *)values.at(i).constData(),
                                      0,
                                      *ind == SQL_NULL_DATA ? ind : NULL);
                break;
            case QVariant::ByteArray: {
                int len = values.at(i).toByteArray().size();
                if (*ind != SQL_NULL_DATA)
                    *ind = len;
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_BINARY,
                                      SQL_LONGVARBINARY,
                                      len,
                                      0,
                                      (void *)values.at(i).toByteArray().constData(),
                                      len,
                                      ind);
                break; }
            case QVariant::String:
#ifdef UNICODE
            {
                QString str(values.at(i).toString());
                if (*ind != SQL_NULL_DATA)
                    *ind = str.length() * sizeof(QChar);
                if (bindValueType(i) & QSql::Out) {
                    QByteArray ba((char*)str.utf16(), str.capacity() * sizeof(QChar));
                    r = SQLBindParameter(d->hStmt,
                                        i + 1,
                                        qParamType[(QFlag)(bindValueType(i)) & 3],
                                        SQL_C_WCHAR,
                                        SQL_WVARCHAR,
                                        str.length(),
                                        0,
                                        (void *)ba.constData(),
                                        ba.size(),
                                        ind);
                    tmpStorage.append(ba);
                } else {
                    void *data = (void*)str.utf16();
                    int len = str.length();
                    r = SQLBindParameter(d->hStmt,
                                        i + 1,
                                        qParamType[(QFlag)(bindValueType(i)) & 3],
                                        SQL_C_WCHAR,
                                        SQL_WVARCHAR,
                                        len,
                                        0,
                                        data,
                                        len * sizeof(QChar),
                                        ind);
                }
                break;
            }
#endif
            // fall through
            default: {
                QByteArray ba = values.at(i).toString().toAscii();
                int len = ba.length() + 1;
                if (*ind != SQL_NULL_DATA)
                    *ind = ba.length();
                r = SQLBindParameter(d->hStmt,
                                      i + 1,
                                      qParamType[(QFlag)(bindValueType(i)) & 3],
                                      SQL_C_CHAR,
                                      SQL_VARCHAR,
                                      len,
                                      0,
                                      (void *) ba.constData(),
                                      len,
                                      ind);
                tmpStorage.append(ba);
                break; }
        }
        if (r != SQL_SUCCESS) {
            qWarning("QDB2Result::exec: unable to bind variable: %s",
                     qDB2Warn(d).toLocal8Bit().constData());
            setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                    "Unable to bind variable"), QSqlError::StatementError, d));
            return false;
        }
    }

    r = SQLExecute(d->hStmt);
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        qWarning("QDB2Result::exec: Unable to execute statement: %s",
                 qDB2Warn(d).toLocal8Bit().constData());
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                "Unable to execute statement"), QSqlError::StatementError, d));
        return false;
    }
    SQLSMALLINT count;
    r = SQLNumResultCols(d->hStmt, &count);
    if (count) {
        setSelect(true);
        for (int i = 0; i < count; ++i) {
            d->recInf.append(qMakeFieldInfo(d, i));
        }
    } else {
        setSelect(false);
    }
    setActive(true);
    d->valueCache.resize(count);
    d->valueCache.fill(NULL);

    //get out parameters
    if (!hasOutValues())
        return true;

    for (i = 0; i < values.count(); ++i) {
        switch (values[i].type()) {
            case QVariant::Date: {
                DATE_STRUCT ds = *((DATE_STRUCT *)tmpStorage.takeFirst().constData());
                values[i] = QVariant(QDate(ds.year, ds.month, ds.day));
                break; }
            case QVariant::Time: {
                TIME_STRUCT dt = *((TIME_STRUCT *)tmpStorage.takeFirst().constData());
                values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second));
                break; }
            case QVariant::DateTime: {
                TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT *)tmpStorage.takeFirst().constData());
                values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day),
                              QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000)));
                break; }
            case QVariant::Int:
            case QVariant::Double:
            case QVariant::ByteArray:
                break;
            case QVariant::String:
#ifdef UNICODE
                if (bindValueType(i) & QSql::Out)
                    values[i] = QString::fromUtf16((ushort*)tmpStorage.takeFirst().constData());
                break;
#endif
                // fall through
            default: {
                values[i] = QString::fromAscii(tmpStorage.takeFirst().constData());
                break; }
        }
        if (indicators[i] == SQL_NULL_DATA)
            values[i] = QVariant(values[i].type());
    }
    return true;
}

bool QDB2Result::fetch(int i)
{
    if (isForwardOnly() && i < at())
        return false;
    if (i == at())
        return true;
    d->clearValueCache();
    int actualIdx = i + 1;
    if (actualIdx <= 0) {
        setAt(QSql::BeforeFirstRow);
        return false;
    }
    SQLRETURN r;
    if (isForwardOnly()) {
        bool ok = true;
        while (ok && i > at())
            ok = fetchNext();
        return ok;
    } else {
        r = SQLFetchScroll(d->hStmt,
                            SQL_FETCH_ABSOLUTE,
                            actualIdx);
    }
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                "Unable to fetch record %1").arg(i), QSqlError::StatementError, d));
        return false;
    }
    setAt(i);
    return true;
}

bool QDB2Result::fetchNext()
{
    SQLRETURN r;
    d->clearValueCache();
    r = SQLFetchScroll(d->hStmt,
                       SQL_FETCH_NEXT,
                       0);
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        if (r != SQL_NO_DATA)
            setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
                                    "Unable to fetch next"), QSqlError::StatementError, d));
        return false;
    }
    setAt(at() + 1);
    return true;
}

bool QDB2Result::fetchFirst()
{
    if (isForwardOnly() && at() != QSql::BeforeFirstRow)
        return false;
    if (isForwardOnly())
        return fetchNext();
    d->clearValueCache();
    SQLRETURN r;
    r = SQLFetchScroll(d->hStmt,
                       SQL_FETCH_FIRST,
                       0);
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to fetch first"),
                                QSqlError::StatementError, d));
        return false;
    }
    setAt(0);
    return true;
}

bool QDB2Result::fetchLast()
{
    d->clearValueCache();

    int i = at();
    if (i == QSql::AfterLastRow) {
        if (isForwardOnly()) {
            return false;
        } else {
            if (!fetch(0))
                return false;
            i = at();
        }
    }

    while (fetchNext())
        ++i;

    if (i == QSql::BeforeFirstRow) {
        setAt(QSql::AfterLastRow);
        return false;
    }

    if (!isForwardOnly())
        return fetch(i);

    setAt(i);
    return true;
}


QVariant QDB2Result::data(int field)
{
    if (field >= d->recInf.count()) {
        qWarning("QDB2Result::data: column %d out of range", field);
        return QVariant();
    }
    SQLRETURN r = 0;
    SQLINTEGER lengthIndicator = 0;
    bool isNull = false;
    const QSqlField info = d->recInf.field(field);

    if (!info.isValid() || field >= d->valueCache.size())
        return QVariant();

    if (d->valueCache[field])
        return *d->valueCache[field];


    QVariant* v = 0;
    switch (info.type()) {
        case QVariant::LongLong:
            v = new QVariant((qint64) qGetBigIntData(d->hStmt, field, isNull));
            break;
        case QVariant::Int:
            v = new QVariant(qGetIntData(d->hStmt, field, isNull));
            break;
        case QVariant::Date: {
            DATE_STRUCT dbuf;
            r = SQLGetData(d->hStmt,
                            field + 1,
                            SQL_C_DATE,
                            (SQLPOINTER) &dbuf,
                            0,
                            &lengthIndicator);
            if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
                v = new QVariant(QDate(dbuf.year, dbuf.month, dbuf.day));
            } else {
                v = new QVariant(QDate());
                isNull = true;
            }
            break; }
        case QVariant::Time: {
            TIME_STRUCT tbuf;
            r = SQLGetData(d->hStmt,
                            field + 1,
                            SQL_C_TIME,
                            (SQLPOINTER) &tbuf,
                            0,
                            &lengthIndicator);
            if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
                v = new QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second));
            } else {
                v = new QVariant(QTime());
                isNull = true;
            }
            break; }
        case QVariant::DateTime: {
            TIMESTAMP_STRUCT dtbuf;
            r = SQLGetData(d->hStmt,
                            field + 1,
                            SQL_C_TIMESTAMP,
                            (SQLPOINTER) &dtbuf,
                            0,
                            &lengthIndicator);
            if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
                v = new QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day),
                                             QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000)));
            } else {
                v = new QVariant(QDateTime());
                isNull = true;
            }
            break; }
        case QVariant::ByteArray:
            v = new QVariant(qGetBinaryData(d->hStmt, field, lengthIndicator, isNull));
            break;
        case QVariant::Double:
            {
            QString value=qGetStringData(d->hStmt, field, info.length() + 1, isNull);
            bool ok=false;
            switch(d->precisionPolicy) {
                case QSql::LowPrecisionInt32:
                    v = new QVariant(value.toInt(&ok));
                    break;
                case QSql::LowPrecisionInt64:
                    v = new QVariant(value.toLongLong(&ok));
                    break;
                case QSql::LowPrecisionDouble:
                    v = new QVariant(value.toDouble(&ok));
                    break;
                case QSql::HighPrecision:
                default:
                    // length + 1 for the comma
                    v = new QVariant(qGetStringData(d->hStmt, field, info.length() + 1, isNull));
                    ok = true;
                    break;
            }
            if(!ok)
                v = new QVariant();
            break;
            }
        case QVariant::String:
        default:
            v = new QVariant(qGetStringData(d->hStmt, field, info.length(), isNull));
            break;
    }
    if (isNull)
        *v = QVariant(info.type());
    d->valueCache[field] = v;
    return *v;
}

bool QDB2Result::isNull(int i)
{
    if (i >= d->valueCache.size())
        return true;

    if (d->valueCache[i])
        return d->valueCache[i]->isNull();
    return data(i).isNull();
}

int QDB2Result::numRowsAffected()
{
    SQLINTEGER affectedRowCount = 0;
    SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount);
    if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
        return affectedRowCount;
    else
        qSqlWarning(QLatin1String("QDB2Result::numRowsAffected: Unable to count affected rows"), d);
    return -1;
}

int QDB2Result::size()
{
    return -1;
}

QSqlRecord QDB2Result::record() const
{
    if (isActive())
        return d->recInf;
    return QSqlRecord();
}

bool QDB2Result::nextResult()
{
    setActive(false);
    setAt(QSql::BeforeFirstRow);
    d->recInf.clear();
    d->emptyValueCache();
    setSelect(false);

    SQLRETURN r = SQLMoreResults(d->hStmt);
    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
        if (r != SQL_NO_DATA) {
            setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
                "Unable to fetch last"), QSqlError::ConnectionError, d));
        }
        return false;
    }

    SQLSMALLINT fieldCount;
    r = SQLNumResultCols(d->hStmt, &fieldCount);
    setSelect(fieldCount > 0);
    for (int i = 0; i < fieldCount; ++i)
        d->recInf.append(qMakeFieldInfo(d, i));

    d->valueCache.resize(fieldCount);
    d->valueCache.fill(NULL);
{'fraction_non_alphanumeric': 0.11182684533513593, 'fraction_numerical': 0.008164671888875635, 'mean_word_length': 1.504581064931616, 'pattern_counts': {'":': 0, '<': 58, '<?xml version=': 0, '>': 239, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 190, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7465284', 'n_tokens_mistral': 21976, 'n_tokens_neox': 21235, 'n_words': 4837}