src/sql/drivers/odbc/qsql_odbc.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sql/drivers/odbc/qsql_odbc.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,2432 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** 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 Technology Preview License Agreement accompanying
+** this package.
+**
+** 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.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_odbc.h"
+#include <qsqlrecord.h>
+
+#if defined (Q_OS_WIN32)
+#include <qt_windows.h>
+#endif
+#include <qcoreapplication.h>
+#include <qvariant.h>
+#include <qdatetime.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qstringlist.h>
+#include <qvarlengtharray.h>
+#include <qvector.h>
+#include <QDebug>
+#include <QSqlQuery>
+
+QT_BEGIN_NAMESPACE
+
+// undefine this to prevent initial check of the ODBC driver
+#define ODBC_CHECK_DRIVER
+
+#if defined(Q_ODBC_VERSION_2)
+//crude hack to get non-unicode capable driver managers to work
+# undef UNICODE
+# define SQLTCHAR SQLCHAR
+# define SQL_C_WCHAR SQL_C_CHAR
+#endif
+
+// newer platform SDKs use SQLLEN instead of SQLINTEGER
+#if defined(WIN32) && (_MSC_VER < 1300)
+# define QSQLLEN SQLINTEGER
+# define QSQLULEN SQLUINTEGER
+#else
+# define QSQLLEN SQLLEN
+# define QSQLULEN SQLULEN
+#endif
+
+
+static const int COLNAMESIZE = 256;
+//Map Qt parameter types to ODBC types
+static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
+
+class QODBCDriverPrivate
+{
+public:
+    enum DefaultCase{Lower, Mixed, Upper, Sensitive};
+    QODBCDriverPrivate()
+    : hEnv(0), hDbc(0), useSchema(false), disconnectCount(0), isMySqlServer(false),
+           isMSSqlServer(false), hasSQLFetchScroll(true), hasMultiResultSets(false),
+           isQuoteInitialized(false), quote(QLatin1Char('"'))
+    {
+        unicode = false;
+    }
+
+    SQLHANDLE hEnv;
+    SQLHANDLE hDbc;
+
+    uint unicode :1;
+    uint useSchema :1;
+    int disconnectCount;
+    bool isMySqlServer;
+    bool isMSSqlServer;
+    bool hasSQLFetchScroll;
+    bool hasMultiResultSets;
+
+    bool checkDriver() const;
+    void checkUnicode();
+    void checkSqlServer();
+    void checkHasSQLFetchScroll();
+    void checkHasMultiResults();
+    void checkSchemaUsage();
+    bool setConnectionOptions(const QString& connOpts);
+    void splitTableQualifier(const QString &qualifier, QString &catalog,
+                             QString &schema, QString &table);
+    DefaultCase defaultCase() const;
+    QString adjustCase(const QString&) const;
+    QChar quoteChar();
+private:
+    bool isQuoteInitialized;
+    QChar quote;
+};
+
+class QODBCPrivate
+{
+public:
+    QODBCPrivate(QODBCDriverPrivate *dpp)
+    : hStmt(0), useSchema(false), hasSQLFetchScroll(true), driverPrivate(dpp), userForwardOnly(false)
+    {
+        unicode = false;
+    }
+
+    inline void clearValues()
+    { fieldCache.fill(QVariant()); fieldCacheIdx = 0; }
+
+    SQLHANDLE dpEnv() const { return driverPrivate ? driverPrivate->hEnv : 0;}
+    SQLHANDLE dpDbc() const { return driverPrivate ? driverPrivate->hDbc : 0;}
+    SQLHANDLE hStmt;
+
+    uint unicode :1;
+    uint useSchema :1;
+
+    QSqlRecord rInf;
+    QVector<QVariant> fieldCache;
+    int fieldCacheIdx;
+    int disconnectCount;
+    bool hasSQLFetchScroll;
+    QODBCDriverPrivate *driverPrivate;
+    bool userForwardOnly;
+
+    bool isStmtHandleValid(const QSqlDriver *driver);
+    void updateStmtHandleState(const QSqlDriver *driver);
+};
+
+bool QODBCPrivate::isStmtHandleValid(const QSqlDriver *driver)
+{
+    const QODBCDriver *odbcdriver = static_cast<const QODBCDriver*> (driver);
+    return disconnectCount == odbcdriver->d->disconnectCount;
+}
+
+void QODBCPrivate::updateStmtHandleState(const QSqlDriver *driver)
+{
+    const QODBCDriver *odbcdriver = static_cast<const QODBCDriver*> (driver);
+    disconnectCount = odbcdriver->d->disconnectCount;
+}
+
+static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = 0)
+{
+    SQLINTEGER nativeCode_ = 0;
+    SQLSMALLINT msgLen = 0;
+    SQLRETURN r = SQL_NO_DATA;
+    SQLTCHAR state_[SQL_SQLSTATE_SIZE+1];
+    SQLTCHAR description_[SQL_MAX_MESSAGE_LENGTH];
+    QString result;
+    int i = 1;
+
+    description_[0] = 0;
+    do {
+        r = SQLGetDiagRec(handleType,
+                            handle,
+                            i,
+                            (SQLTCHAR*)state_,
+                            &nativeCode_,
+                            (SQLTCHAR*)description_,
+                            SQL_MAX_MESSAGE_LENGTH, /* in bytes, not in characters */
+                            &msgLen);
+        if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+            if (nativeCode)
+                *nativeCode = nativeCode_;
+            QString tmpstore;
+#ifdef UNICODE
+            tmpstore = QString((const QChar*)description_, msgLen);
+#else
+            tmpstore = QString::fromLocal8Bit((const char*)description_, msgLen);
+#endif
+            if(result != tmpstore) {
+                if(!result.isEmpty())
+                    result += QLatin1Char(' ');
+                result += tmpstore;
+            }
+        } else if (r == SQL_ERROR || r == SQL_INVALID_HANDLE) {
+            return result;
+        }
+        ++i;
+    } while (r != SQL_NO_DATA);
+    return result;
+}
+
+static QString qODBCWarn(const QODBCPrivate* odbc, int *nativeCode = 0)
+{
+    return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->dpEnv()) + QLatin1Char(' ')
+             + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->dpDbc()) + QLatin1Char(' ')
+             + qWarnODBCHandle(SQL_HANDLE_STMT, odbc->hStmt, nativeCode));
+}
+
+static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = 0)
+{
+    return (qWarnODBCHandle(SQL_HANDLE_ENV, odbc->hEnv) + QLatin1Char(' ')
+             + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->hDbc, nativeCode));
+}
+
+static void qSqlWarning(const QString& message, const QODBCPrivate* odbc)
+{
+    qWarning() << message << "\tError:" << qODBCWarn(odbc);
+}
+
+static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc)
+{
+    qWarning() << message << "\tError:" << qODBCWarn(odbc);
+}
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCPrivate* p)
+{
+    int nativeCode = -1;
+    QString message = qODBCWarn(p, &nativeCode);
+    return QSqlError(QLatin1String("QODBC3: ") + err, message, type, nativeCode);
+}
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
+                            const QODBCDriverPrivate* p)
+{
+    int nativeCode = -1;
+    QString message = qODBCWarn(p, &nativeCode);
+    return QSqlError(QLatin1String("QODBC3: ") + err, qODBCWarn(p), type, nativeCode);
+}
+
+template<class T>
+static QVariant::Type qDecodeODBCType(SQLSMALLINT sqltype, const T* p, bool isSigned = true)
+{
+    Q_UNUSED(p);
+    QVariant::Type type = QVariant::Invalid;
+    switch (sqltype) {
+    case SQL_DECIMAL:
+    case SQL_NUMERIC:
+    case SQL_REAL:
+    case SQL_FLOAT:
+    case SQL_DOUBLE:
+        type = QVariant::Double;
+        break;
+    case SQL_SMALLINT:
+    case SQL_INTEGER:
+    case SQL_BIT:
+        type = isSigned ? QVariant::Int : QVariant::UInt;
+        break;
+    case SQL_TINYINT:
+        type = QVariant::UInt;
+        break;
+    case SQL_BIGINT:
+        type = isSigned ? QVariant::LongLong : QVariant::ULongLong;
+        break;
+    case SQL_BINARY:
+    case SQL_VARBINARY:
+    case SQL_LONGVARBINARY:
+        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;
+#ifndef Q_ODBC_VERSION_2
+    case SQL_WCHAR:
+    case SQL_WVARCHAR:
+    case SQL_WLONGVARCHAR:
+        type = QVariant::String;
+        break;
+#endif
+    case SQL_CHAR:
+    case SQL_VARCHAR:
+    case SQL_GUID:
+    case SQL_LONGVARCHAR:
+        type = QVariant::String;
+        break;
+    default:
+        type = QVariant::ByteArray;
+        break;
+    }
+    return type;
+}
+
+static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode = false)
+{
+    QString fieldVal;
+    SQLRETURN r = SQL_ERROR;
+    QSQLLEN lengthIndicator = 0;
+
+    // NB! colSize must be a multiple of 2 for unicode enabled DBs
+    if (colSize <= 0) {
+        colSize = 256;
+    } 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
+        if (unicode) {
+            colSize *= 2; // a tiny bit faster, since it saves a SQLGetData() call
+        }
+    }
+    QVarLengthArray<char> buf(colSize);
+    while (true) {
+        r = SQLGetData(hStmt,
+                        column+1,
+                        unicode ? SQL_C_WCHAR : SQL_C_CHAR,
+                        (SQLPOINTER)buf.data(),
+                        colSize,
+                        &lengthIndicator);
+        if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+            if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
+                fieldVal.clear();
+                break;
+            }
+            // if SQL_SUCCESS_WITH_INFO is returned, indicating that
+            // more data can be fetched, the length indicator does NOT
+            // contain the number of bytes returned - it contains the
+            // total number of bytes that CAN be fetched
+            // colSize-1: remove 0 termination when there is more data to fetch
+            int rSize = (r == SQL_SUCCESS_WITH_INFO) ? (unicode ? colSize-2 : colSize-1) : lengthIndicator;
+            if (unicode) {
+                fieldVal += QString((const QChar*) buf.constData(), rSize / 2);
+            } else {
+                fieldVal += QString::fromAscii(buf.constData(), rSize);
+            }
+            memset(buf.data(), 0, colSize);
+            if (lengthIndicator < colSize) {
+                // workaround for Drivermanagers that don't return SQL_NO_DATA
+                break;
+            }
+        } else if (r == SQL_NO_DATA) {
+            break;
+        } else {
+            qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
+            fieldVal.clear();
+            break;
+        }
+    }
+    return fieldVal;
+}
+
+static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
+{
+    QByteArray fieldVal;
+    SQLSMALLINT colNameLen;
+    SQLSMALLINT colType;
+    QSQLULEN colSize;
+    SQLSMALLINT colScale;
+    SQLSMALLINT nullable;
+    QSQLLEN lengthIndicator = 0;
+    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" << 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;
+    fieldVal.resize(colSize);
+    ulong read = 0;
+    while (true) {
+        r = SQLGetData(hStmt,
+                        column+1,
+                        SQL_C_BINARY,
+                        (SQLPOINTER)(fieldVal.constData() + read),
+                        colSize,
+                        &lengthIndicator);
+        if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+            break;
+        if (lengthIndicator == SQL_NULL_DATA)
+            return QVariant(QVariant::ByteArray);
+        if (lengthIndicator > QSQLLEN(colSize) || lengthIndicator == SQL_NO_TOTAL) {
+            read += colSize;
+            colSize = 65536;
+        } else {
+            read += lengthIndicator;
+        }
+        if (r == SQL_SUCCESS) { // the whole field was read in one chunk
+            fieldVal.resize(read);
+            break;
+        }
+        fieldVal.resize(fieldVal.size() + colSize);
+    }
+    return fieldVal;
+}
+
+static QVariant qGetIntData(SQLHANDLE hStmt, int column, bool isSigned = true)
+{
+    SQLINTEGER intbuf = 0;
+    QSQLLEN lengthIndicator = 0;
+    SQLRETURN r = SQLGetData(hStmt,
+                              column+1,
+                              isSigned ? SQL_C_SLONG : SQL_C_ULONG,
+                              (SQLPOINTER)&intbuf,
+                              sizeof(intbuf),
+                              &lengthIndicator);
+    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+        return QVariant(QVariant::Invalid);
+    if (lengthIndicator == SQL_NULL_DATA)
+        return QVariant(QVariant::Int);
+    if (isSigned)
+        return int(intbuf);
+    else
+        return uint(intbuf);
+}
+
+static QVariant qGetDoubleData(SQLHANDLE hStmt, int column)
+{
+    SQLDOUBLE dblbuf;
+    QSQLLEN lengthIndicator = 0;
+    SQLRETURN r = SQLGetData(hStmt,
+                              column+1,
+                              SQL_C_DOUBLE,
+                              (SQLPOINTER) &dblbuf,
+                              0,
+                              &lengthIndicator);
+    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+        return QVariant(QVariant::Invalid);
+    }
+    if(lengthIndicator == SQL_NULL_DATA)
+        return QVariant(QVariant::Double);
+
+    return (double) dblbuf;
+}
+
+
+static QVariant qGetBigIntData(SQLHANDLE hStmt, int column, bool isSigned = true)
+{
+    SQLBIGINT lngbuf = 0;
+    QSQLLEN lengthIndicator = 0;
+    SQLRETURN r = SQLGetData(hStmt,
+                              column+1,
+                              isSigned ? SQL_C_SBIGINT : SQL_C_UBIGINT,
+                              (SQLPOINTER) &lngbuf,
+                              sizeof(lngbuf),
+                              &lengthIndicator);
+    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+        return QVariant(QVariant::Invalid);
+    if (lengthIndicator == SQL_NULL_DATA)
+        return QVariant(QVariant::LongLong);
+
+    if (isSigned)
+        return qint64(lngbuf);
+    else
+        return quint64(lngbuf);
+}
+
+// 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, const QODBCDriverPrivate* p)
+{
+    QString fname = qGetStringData(hStmt, 3, -1, p->unicode);
+    int type = qGetIntData(hStmt, 4).toInt(); // column type
+    QSqlField f(fname, qDecodeODBCType(type, p));
+    int required = qGetIntData(hStmt, 10).toInt(); // 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
+    QVariant var = qGetIntData(hStmt, 6);
+    f.setLength(var.isNull() ? -1 : var.toInt()); // column size
+    var = qGetIntData(hStmt, 8).toInt();
+    f.setPrecision(var.isNull() ? -1 : var.toInt()); // precision
+    f.setSqlType(type);
+    return f;
+}
+
+static QSqlField qMakeFieldInfo(const QODBCPrivate* p, int i )
+{
+    SQLSMALLINT colNameLen;
+    SQLSMALLINT colType;
+    QSQLULEN colSize;
+    SQLSMALLINT colScale;
+    SQLSMALLINT nullable;
+    SQLRETURN r = SQL_ERROR;
+    SQLTCHAR colName[COLNAMESIZE];
+    r = SQLDescribeCol(p->hStmt,
+                        i+1,
+                        colName,
+                        (SQLSMALLINT)COLNAMESIZE,
+                        &colNameLen,
+                        &colType,
+                        &colSize,
+                        &colScale,
+                        &nullable);
+
+    if (r != SQL_SUCCESS) {
+        qSqlWarning(QString::fromLatin1("qMakeField: Unable to describe column %1").arg(i), p);
+        return QSqlField();
+    }
+
+    QSQLLEN unsignedFlag = SQL_FALSE;
+    r = SQLColAttribute (p->hStmt,
+                         i + 1,
+                         SQL_DESC_UNSIGNED,
+                         0,
+                         0,
+                         0,
+                         &unsignedFlag);
+    if (r != SQL_SUCCESS) {
+        qSqlWarning(QString::fromLatin1("qMakeField: Unable to get column attributes for column %1").arg(i), p);
+    }
+
+#ifdef UNICODE
+    QString qColName((const QChar*)colName, colNameLen);
+#else
+    QString qColName = QString::fromLocal8Bit((const char*)colName);
+#endif
+    // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
+    int required = -1;
+    if (nullable == SQL_NO_NULLS) {
+        required = 1;
+    } else if (nullable == SQL_NULLABLE) {
+        required = 0;
+    }
+    QVariant::Type type = qDecodeODBCType(colType, p, unsignedFlag == SQL_FALSE);
+    QSqlField f(qColName, type);
+    f.setSqlType(colType);
+    f.setLength(colSize == 0 ? -1 : int(colSize));
+    f.setPrecision(colScale == 0 ? -1 : int(colScale));
+    if (nullable == SQL_NO_NULLS)
+        f.setRequired(true);
+    else if (nullable == SQL_NULLABLE)
+        f.setRequired(false);
+    // else we don't know
+    return f;
+}
+
+static int qGetODBCVersion(const QString &connOpts)
+{
+#ifndef Q_ODBC_VERSION_2
+    if (connOpts.contains(QLatin1String("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"), Qt::CaseInsensitive))
+        return SQL_OV_ODBC3;
+#endif
+    return SQL_OV_ODBC2;
+}
+
+QChar QODBCDriverPrivate::quoteChar()
+{
+    if (!isQuoteInitialized) {
+        char driverResponse[4];
+        SQLSMALLINT length;
+        int r = SQLGetInfo(hDbc,
+                SQL_IDENTIFIER_QUOTE_CHAR,
+                &driverResponse,
+                sizeof(driverResponse),
+                &length);
+        if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+            quote = QLatin1Char(driverResponse[0]);
+        } else {
+            quote = QLatin1Char('"');
+        }
+        isQuoteInitialized = true;
+    }
+    return quote;
+}
+
+
+bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts)
+{
+    // Set any connection attributes
+    const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
+    SQLRETURN r = SQL_SUCCESS;
+    for (int i = 0; i < opts.count(); ++i) {
+        const QString tmp(opts.at(i));
+        int idx;
+        if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) {
+            qWarning() << "QODBCDriver::open: Illegal connect option value '" << tmp << '\'';
+            continue;
+        }
+        const QString opt(tmp.left(idx));
+        const QString val(tmp.mid(idx + 1).simplified());
+        SQLUINTEGER v = 0;
+
+        r = SQL_SUCCESS;
+        if (opt.toUpper() == QLatin1String("SQL_ATTR_ACCESS_MODE")) {
+            if (val.toUpper() == QLatin1String("SQL_MODE_READ_ONLY")) {
+                v = SQL_MODE_READ_ONLY;
+            } else if (val.toUpper() == QLatin1String("SQL_MODE_READ_WRITE")) {
+                v = SQL_MODE_READ_WRITE;
+            } else {
+                qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+                continue;
+            }
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) v, 0);
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_TIMEOUT")) {
+            v = val.toUInt();
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) v, 0);
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) {
+            v = val.toUInt();
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) v, 0);
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CURRENT_CATALOG")) {
+            val.utf16(); // 0 terminate
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG,
+#ifdef UNICODE
+                                    (SQLWCHAR*) val.unicode(),
+#else
+                                    (SQLCHAR*) val.toLatin1().constData(),
+#endif
+                                    SQL_NTS);
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_METADATA_ID")) {
+            if (val.toUpper() == QLatin1String("SQL_TRUE")) {
+                v = SQL_TRUE;
+            } else if (val.toUpper() == QLatin1String("SQL_FALSE")) {
+                v = SQL_FALSE;
+            } else {
+                qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+                continue;
+            }
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_METADATA_ID, (SQLPOINTER) v, 0);
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_PACKET_SIZE")) {
+            v = val.toUInt();
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) v, 0);
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACEFILE")) {
+            val.utf16(); // 0 terminate
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE,
+#ifdef UNICODE
+                                    (SQLWCHAR*) val.unicode(),
+#else
+                                    (SQLCHAR*) val.toLatin1().constData(),
+#endif
+                                    SQL_NTS);
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACE")) {
+            if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_OFF")) {
+                v = SQL_OPT_TRACE_OFF;
+            } else if (val.toUpper() == QLatin1String("SQL_OPT_TRACE_ON")) {
+                v = SQL_OPT_TRACE_ON;
+            } else {
+                qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+                continue;
+            }
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACE, (SQLPOINTER) v, 0);
+#ifndef Q_ODBC_VERSION_2
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_POOLING")) {
+            if (val == QLatin1String("SQL_CP_OFF"))
+                v = SQL_CP_OFF;
+            else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_DRIVER"))
+                v = SQL_CP_ONE_PER_DRIVER;
+            else if (val.toUpper() == QLatin1String("SQL_CP_ONE_PER_HENV"))
+                v = SQL_CP_ONE_PER_HENV;
+            else if (val.toUpper() == QLatin1String("SQL_CP_DEFAULT"))
+                v = SQL_CP_DEFAULT;
+            else {
+                qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+                continue;
+            }
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER)v, 0);
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CP_MATCH")) {
+            if (val.toUpper() == QLatin1String("SQL_CP_STRICT_MATCH"))
+                v = SQL_CP_STRICT_MATCH;
+            else if (val.toUpper() == QLatin1String("SQL_CP_RELAXED_MATCH"))
+                v = SQL_CP_RELAXED_MATCH;
+            else if (val.toUpper() == QLatin1String("SQL_CP_MATCH_DEFAULT"))
+                v = SQL_CP_MATCH_DEFAULT;
+            else {
+                qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+                continue;
+            }
+            r = SQLSetConnectAttr(hDbc, SQL_ATTR_CP_MATCH, (SQLPOINTER)v, 0);
+#endif
+        } else if (opt.toUpper() == QLatin1String("SQL_ATTR_ODBC_VERSION")) {
+            // Already handled in QODBCDriver::open()
+            continue;
+        } else {
+                qWarning() << "QODBCDriver::open: Unknown connection attribute '" << opt << '\'';
+        }
+        if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+            qSqlWarning(QString::fromLatin1("QODBCDriver::open: Unable to set connection attribute'%1'").arg(
+                        opt), this);
+    }
+    return true;
+}
+
+void QODBCDriverPrivate::splitTableQualifier(const QString & qualifier, QString &catalog,
+                                       QString &schema, QString &table)
+{
+    if (!useSchema) {
+        table = qualifier;
+        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++;
+        }
+    }
+}
+
+QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const
+{
+    DefaultCase ret;
+    SQLUSMALLINT casing;
+    int r = SQLGetInfo(hDbc,
+            SQL_IDENTIFIER_CASE,
+            &casing,
+            sizeof(casing),
+            NULL);
+    if ( r != SQL_SUCCESS)
+        ret = Mixed;//arbitrary case if driver cannot be queried
+    else {
+        switch (casing) {
+            case (SQL_IC_UPPER):
+                ret = Upper;
+                break;
+            case (SQL_IC_LOWER):
+                ret = Lower;
+                break;
+            case (SQL_IC_SENSITIVE):
+                ret = Sensitive;
+                break;
+            case (SQL_IC_MIXED):
+            default:
+                ret = Mixed;
+                break;
+        }
+    }
+    return ret;
+}
+
+/*
+   Adjust the casing of an identifier to match what the
+   database engine would have done to it.
+*/
+QString QODBCDriverPrivate::adjustCase(const QString &identifier) const
+{
+    QString ret = identifier;
+    switch(defaultCase()) {
+        case (Lower):
+            ret = identifier.toLower();
+            break;
+        case (Upper):
+            ret = identifier.toUpper();
+            break;
+        case(Mixed):
+        case(Sensitive):
+        default:
+            ret = identifier;
+    }
+    return ret;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+QODBCResult::QODBCResult(const QODBCDriver * db, QODBCDriverPrivate* p)
+: QSqlResult(db)
+{
+    d = new QODBCPrivate(p);
+    d->unicode = p->unicode;
+    d->useSchema = p->useSchema;
+    d->disconnectCount = p->disconnectCount;
+    d->hasSQLFetchScroll = p->hasSQLFetchScroll;
+}
+
+QODBCResult::~QODBCResult()
+{
+    if (d->hStmt && d->isStmtHandleValid(driver()) && driver()->isOpen()) {
+        SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
+        if (r != SQL_SUCCESS)
+            qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ")
+                         + QString::number(r), d);
+    }
+
+    delete d;
+}
+
+bool QODBCResult::reset (const QString& query)
+{
+    setActive(false);
+    setAt(QSql::BeforeFirstRow);
+    d->rInf.clear();
+    d->fieldCache.clear();
+    d->fieldCacheIdx = 0;
+
+    // Always reallocate the statement handle - the statement attributes
+    // are not reset if SQLFreeStmt() is called which causes some problems.
+    SQLRETURN r;
+    if (d->hStmt && d->isStmtHandleValid(driver())) {
+        r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
+        if (r != SQL_SUCCESS) {
+            qSqlWarning(QLatin1String("QODBCResult::reset: Unable to free statement handle"), d);
+            return false;
+        }
+    }
+    r  = SQLAllocHandle(SQL_HANDLE_STMT,
+                         d->dpDbc(),
+                         &d->hStmt);
+    if (r != SQL_SUCCESS) {
+        qSqlWarning(QLatin1String("QODBCResult::reset: Unable to allocate statement handle"), d);
+        return false;
+    }
+
+    d->updateStmtHandleState(driver());
+
+    if (d->userForwardOnly) {
+        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) {
+        setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+            "QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
+            "Please check your ODBC driver configuration"), QSqlError::StatementError, d));
+        return false;
+    }
+
+#ifdef UNICODE
+    r = SQLExecDirect(d->hStmt,
+                       (SQLWCHAR*) query.unicode(),
+                       (SQLINTEGER) query.length());
+#else
+    QByteArray query8 = query.toLocal8Bit();
+    r = SQLExecDirect(d->hStmt,
+                       (SQLCHAR*) query8.constData(),
+                       (SQLINTEGER) query8.length());
+#endif
+    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+        setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                     "Unable to execute statement"), QSqlError::StatementError, d));
+        return false;
+    }
+
+    SQLINTEGER isScrollable, bufferLength;
+    r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, &bufferLength);
+    if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+        QSqlResult::setForwardOnly(isScrollable==SQL_NONSCROLLABLE);
+
+    SQLSMALLINT count;
+    SQLNumResultCols(d->hStmt, &count);
+    if (count) {
+        setSelect(true);
+        for (int i = 0; i < count; ++i) {
+            d->rInf.append(qMakeFieldInfo(d, i));
+        }
+        d->fieldCache.resize(count);
+    } else {
+        setSelect(false);
+    }
+    setActive(true);
+
+    return true;
+}
+
+bool QODBCResult::fetch(int i)
+{
+    if (!driver()->isOpen())
+        return false;
+
+    if (isForwardOnly() && i < at())
+        return false;
+    if (i == at())
+        return true;
+    d->clearValues();
+    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) {
+        if (r != SQL_NO_DATA)
+            setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                "Unable to fetch"), QSqlError::ConnectionError, d));
+        return false;
+    }
+    setAt(i);
+    return true;
+}
+
+bool QODBCResult::fetchNext()
+{
+    SQLRETURN r;
+    d->clearValues();
+
+    if (d->hasSQLFetchScroll)
+        r = SQLFetchScroll(d->hStmt,
+                           SQL_FETCH_NEXT,
+                           0);
+    else
+        r = SQLFetch(d->hStmt);
+
+    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+        if (r != SQL_NO_DATA)
+            setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                "Unable to fetch next"), QSqlError::ConnectionError, d));
+        return false;
+    }
+    setAt(at() + 1);
+    return true;
+}
+
+bool QODBCResult::fetchFirst()
+{
+    if (isForwardOnly() && at() != QSql::BeforeFirstRow)
+        return false;
+    SQLRETURN r;
+    d->clearValues();
+    if (isForwardOnly()) {
+        return fetchNext();
+    }
+    r = SQLFetchScroll(d->hStmt,
+                       SQL_FETCH_FIRST,
+                       0);
+    if (r != SQL_SUCCESS) {
+        if (r != SQL_NO_DATA)
+            setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                "Unable to fetch first"), QSqlError::ConnectionError, d));
+        return false;
+    }
+    setAt(0);
+    return true;
+}
+
+bool QODBCResult::fetchPrevious()
+{
+    if (isForwardOnly())
+        return false;
+    SQLRETURN r;
+    d->clearValues();
+    r = SQLFetchScroll(d->hStmt,
+                       SQL_FETCH_PRIOR,
+                       0);
+    if (r != SQL_SUCCESS) {
+        if (r != SQL_NO_DATA)
+            setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                "Unable to fetch previous"), QSqlError::ConnectionError, d));
+        return false;
+    }
+    setAt(at() - 1);
+    return true;
+}
+
+bool QODBCResult::fetchLast()
+{
+    SQLRETURN r;
+    d->clearValues();
+
+    if (isForwardOnly()) {
+        // cannot seek to last row in forwardOnly mode, so we have to use brute force
+        int i = at();
+        if (i == QSql::AfterLastRow)
+            return false;
+        if (i == QSql::BeforeFirstRow)
+            i = 0;
+        while (fetchNext())
+            ++i;
+        setAt(i);
+        return true;
+    }
+
+    r = SQLFetchScroll(d->hStmt,
+                       SQL_FETCH_LAST,
+                       0);
+    if (r != SQL_SUCCESS) {
+        if (r != SQL_NO_DATA)
+            setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                "Unable to fetch last"), QSqlError::ConnectionError, d));
+        return false;
+    }
+    SQLINTEGER currRow;
+    r = SQLGetStmtAttr(d->hStmt,
+                        SQL_ROW_NUMBER,
+                        &currRow,
+                        SQL_IS_INTEGER,
+                        0);
+    if (r != SQL_SUCCESS)
+        return false;
+    setAt(currRow-1);
+    return true;
+}
+
+QVariant QODBCResult::data(int field)
+{
+    if (field >= d->rInf.count() || field < 0) {
+        qWarning() << "QODBCResult::data: column" << field << "out of range";
+        return QVariant();
+    }
+    if (field < d->fieldCacheIdx)
+        return d->fieldCache.at(field);
+
+    SQLRETURN r(0);
+    QSQLLEN lengthIndicator = 0;
+
+    for (int i = d->fieldCacheIdx; i <= field; ++i) {
+        // some servers do not support fetching column n after we already
+        // fetched column n+1, so cache all previous columns here
+        const QSqlField info = d->rInf.field(i);
+        switch (info.type()) {
+        case QVariant::LongLong:
+            d->fieldCache[i] = qGetBigIntData(d->hStmt, i);
+        break;
+        case QVariant::ULongLong:
+            d->fieldCache[i] = qGetBigIntData(d->hStmt, i, false);
+            break;
+        case QVariant::Int:
+            d->fieldCache[i] = qGetIntData(d->hStmt, i);
+        break;
+        case QVariant::UInt:
+            d->fieldCache[i] = qGetIntData(d->hStmt, i, false);
+            break;
+        case QVariant::Date:
+            DATE_STRUCT dbuf;
+            r = SQLGetData(d->hStmt,
+                            i + 1,
+                            SQL_C_DATE,
+                            (SQLPOINTER)&dbuf,
+                            0,
+                            &lengthIndicator);
+            if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
+                d->fieldCache[i] = QVariant(QDate(dbuf.year, dbuf.month, dbuf.day));
+            else
+                d->fieldCache[i] = QVariant(QVariant::Date);
+        break;
+        case QVariant::Time:
+            TIME_STRUCT tbuf;
+            r = SQLGetData(d->hStmt,
+                            i + 1,
+                            SQL_C_TIME,
+                            (SQLPOINTER)&tbuf,
+                            0,
+                            &lengthIndicator);
+            if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
+                d->fieldCache[i] = QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second));
+            else
+                d->fieldCache[i] = QVariant(QVariant::Time);
+        break;
+        case QVariant::DateTime:
+            TIMESTAMP_STRUCT dtbuf;
+            r = SQLGetData(d->hStmt,
+                            i + 1,
+                            SQL_C_TIMESTAMP,
+                            (SQLPOINTER)&dtbuf,
+                            0,
+                            &lengthIndicator);
+            if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
+                d->fieldCache[i] = QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day),
+                       QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000)));
+            else
+                d->fieldCache[i] = QVariant(QVariant::DateTime);
+            break;
+        case QVariant::ByteArray:
+            d->fieldCache[i] = qGetBinaryData(d->hStmt, i);
+            break;
+        case QVariant::String:
+            d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), d->unicode);
+            break;
+        case QVariant::Double:
+            switch(numericalPrecisionPolicy()) {
+                case QSql::LowPrecisionInt32:
+                    d->fieldCache[i] = qGetIntData(d->hStmt, i);
+                    break;
+                case QSql::LowPrecisionInt64:
+                    d->fieldCache[i] = qGetBigIntData(d->hStmt, i);
+                    break;
+                case QSql::LowPrecisionDouble:
+                    d->fieldCache[i] = qGetDoubleData(d->hStmt, i);
+                    break;
+                case QSql::HighPrecision:
+                    d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), false);
+                    break;
+            }
+            break;
+        default:
+            d->fieldCache[i] = QVariant(qGetStringData(d->hStmt, i, info.length(), false));
+            break;
+        }
+        d->fieldCacheIdx = field + 1;
+    }
+    return d->fieldCache[field];
+}
+
+bool QODBCResult::isNull(int field)
+{
+    if (field < 0 || field > d->fieldCache.size())
+        return true;
+    if (field <= d->fieldCacheIdx) {
+        // since there is no good way to find out whether the value is NULL
+        // without fetching the field we'll fetch it here.
+        // (data() also sets the NULL flag)
+        data(field);
+    }
+    return d->fieldCache.at(field).isNull();
+}
+
+int QODBCResult::size()
+{
+    return -1;
+}
+
+int QODBCResult::numRowsAffected()
+{
+    QSQLLEN affectedRowCount = 0;
+    SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount);
+    if (r == SQL_SUCCESS)
+        return affectedRowCount;
+    else
+        qSqlWarning(QLatin1String("QODBCResult::numRowsAffected: Unable to count affected rows"), d);
+    return -1;
+}
+
+bool QODBCResult::prepare(const QString& query)
+{
+    setActive(false);
+    setAt(QSql::BeforeFirstRow);
+    SQLRETURN r;
+
+    d->rInf.clear();
+    if (d->hStmt && d->isStmtHandleValid(driver())) {
+        r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
+        if (r != SQL_SUCCESS) {
+            qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to close statement"), d);
+            return false;
+        }
+    }
+    r  = SQLAllocHandle(SQL_HANDLE_STMT,
+                         d->dpDbc(),
+                         &d->hStmt);
+    if (r != SQL_SUCCESS) {
+        qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to allocate statement handle"), d);
+        return false;
+    }
+
+    d->updateStmtHandleState(driver());
+
+    if (d->userForwardOnly) {
+        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) {
+        setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+            "QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
+            "Please check your ODBC driver configuration"), QSqlError::StatementError, d));
+        return false;
+    }
+
+#ifdef UNICODE
+    r = SQLPrepare(d->hStmt,
+                    (SQLWCHAR*) query.unicode(),
+                    (SQLINTEGER) query.length());
+#else
+    QByteArray query8 = query.toLocal8Bit();
+    r = SQLPrepare(d->hStmt,
+                    (SQLCHAR*) query8.constData(),
+                    (SQLINTEGER) query8.length());
+#endif
+
+    if (r != SQL_SUCCESS) {
+        setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                     "Unable to prepare statement"), QSqlError::StatementError, d));
+        return false;
+    }
+    return true;
+}
+
+bool QODBCResult::exec()
+{
+    setActive(false);
+    setAt(QSql::BeforeFirstRow);
+    d->rInf.clear();
+    d->fieldCache.clear();
+    d->fieldCacheIdx = 0;
+
+    if (!d->hStmt) {
+        qSqlWarning(QLatin1String("QODBCResult::exec: No statement handle available"), d);
+        return false;
+    }
+
+    if (isSelect())
+        SQLCloseCursor(d->hStmt);
+
+    QList<QByteArray> tmpStorage; // holds temporary buffers
+    QVarLengthArray<QSQLLEN, 32> indicators(boundValues().count());
+    memset(indicators.data(), 0, indicators.size() * sizeof(QSQLLEN));
+
+    // bind parameters - only positional binding allowed
+    QVector<QVariant>& values = boundValues();
+    int i;
+    SQLRETURN r;
+    for (i = 0; i < values.count(); ++i) {
+        if (bindValueType(i) & QSql::Out)
+            values[i].detach();
+        const QVariant &val = values.at(i);
+        QSQLLEN *ind = &indicators[i];
+        if (val.isNull())
+            *ind = SQL_NULL_DATA;
+        switch (val.type()) {
+            case QVariant::Date: {
+                QByteArray ba;
+                ba.resize(sizeof(DATE_STRUCT));
+                DATE_STRUCT *dt = (DATE_STRUCT *)ba.constData();
+                QDate qdt = val.toDate();
+                dt->year = qdt.year();
+                dt->month = qdt.month();
+                dt->day = qdt.day();
+                r = SQLBindParameter(d->hStmt,
+                                      i + 1,
+                                      qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                      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 = val.toTime();
+                dt->hour = qdt.hour();
+                dt->minute = qdt.minute();
+                dt->second = qdt.second();
+                r = SQLBindParameter(d->hStmt,
+                                      i + 1,
+                                      qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                      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 = val.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)) & QSql::InOut],
+                                      SQL_C_TIMESTAMP,
+                                      SQL_TIMESTAMP,
+                                      19,
+                                      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)) & QSql::InOut],
+                                      SQL_C_SLONG,
+                                      SQL_INTEGER,
+                                      0,
+                                      0,
+                                      (void *) val.constData(),
+                                      0,
+                                      *ind == SQL_NULL_DATA ? ind : NULL);
+                break;
+            case QVariant::UInt:
+                r = SQLBindParameter(d->hStmt,
+                                      i + 1,
+                                      qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                      SQL_C_ULONG,
+                                      SQL_NUMERIC,
+                                      15,
+                                      0,
+                                      (void *) val.constData(),
+                                      0,
+                                      *ind == SQL_NULL_DATA ? ind : NULL);
+                break;
+            case QVariant::Double:
+                r = SQLBindParameter(d->hStmt,
+                                      i + 1,
+                                      qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                      SQL_C_DOUBLE,
+                                      SQL_DOUBLE,
+                                      0,
+                                      0,
+                                      (void *) val.constData(),
+                                      0,
+                                      *ind == SQL_NULL_DATA ? ind : NULL);
+                break;
+            case QVariant::LongLong:
+                r = SQLBindParameter(d->hStmt,
+                                      i + 1,
+                                      qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                      SQL_C_SBIGINT,
+                                      SQL_BIGINT,
+                                      0,
+                                      0,
+                                      (void *) val.constData(),
+                                      0,
+                                      *ind == SQL_NULL_DATA ? ind : NULL);
+                break;
+            case QVariant::ULongLong:
+                r = SQLBindParameter(d->hStmt,
+                                      i + 1,
+                                      qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                      SQL_C_UBIGINT,
+                                      SQL_BIGINT,
+                                      0,
+                                      0,
+                                      (void *) val.constData(),
+                                      0,
+                                      *ind == SQL_NULL_DATA ? ind : NULL);
+                break;
+            case QVariant::ByteArray:
+                if (*ind != SQL_NULL_DATA) {
+                    *ind = val.toByteArray().size();
+                }
+                r = SQLBindParameter(d->hStmt,
+                                      i + 1,
+                                      qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                      SQL_C_BINARY,
+                                      SQL_LONGVARBINARY,
+                                      val.toByteArray().size(),
+                                      0,
+                                      (void *) val.toByteArray().constData(),
+                                      val.toByteArray().size(),
+                                      ind);
+                break;
+            case QVariant::Bool:
+                r = SQLBindParameter(d->hStmt,
+                                      i + 1,
+                                      qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                      SQL_C_BIT,
+                                      SQL_BIT,
+                                      0,
+                                      0,
+                                      (void *) val.constData(),
+                                      0,
+                                      *ind == SQL_NULL_DATA ? ind : NULL);
+                break;
+            case QVariant::String:
+#ifndef Q_ODBC_VERSION_2
+                if (d->unicode) {
+                    QString str = val.toString();
+                    str.utf16();
+                    if (*ind != SQL_NULL_DATA)
+                        *ind = str.length() * sizeof(QChar);
+                    int strSize = str.length() * sizeof(QChar);
+
+                    if (bindValueType(i) & QSql::Out) {
+                        QByteArray ba((char*)str.constData(), str.capacity() * sizeof(QChar));
+                        r = SQLBindParameter(d->hStmt,
+                                            i + 1,
+                                            qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                            SQL_C_WCHAR,
+                                            strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
+                                            0, // god knows... don't change this!
+                                            0,
+                                            (void *)ba.constData(),
+                                            ba.size(),
+                                            ind);
+                        tmpStorage.append(ba);
+                        break;
+                    }
+
+                    r = SQLBindParameter(d->hStmt,
+                                          i + 1,
+                                          qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                          SQL_C_WCHAR,
+                                          strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
+                                          strSize,
+                                          0,
+                                          (void *)str.constData(),
+                                          strSize,
+                                          ind);
+                    break;
+                }
+                else
+#endif
+                {
+                    QByteArray str = val.toString().toAscii();
+                    if (*ind != SQL_NULL_DATA)
+                        *ind = str.length();
+                    int strSize = str.length();
+
+                    r = SQLBindParameter(d->hStmt,
+                                          i + 1,
+                                          qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                          SQL_C_CHAR,
+                                          strSize > 254 ? SQL_LONGVARCHAR : SQL_VARCHAR,
+                                          strSize,
+                                          0,
+                                          (void *)str.constData(),
+                                          strSize,
+                                          ind);
+                    tmpStorage.append(str);
+                    break;
+                }
+            // fall through
+            default: {
+                QByteArray ba = val.toByteArray();
+                if (*ind != SQL_NULL_DATA)
+                    *ind = ba.size();
+                r = SQLBindParameter(d->hStmt,
+                                      i + 1,
+                                      qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+                                      SQL_C_BINARY,
+                                      SQL_VARBINARY,
+                                      ba.length() + 1,
+                                      0,
+                                      (void *) ba.constData(),
+                                      ba.length() + 1,
+                                      ind);
+                tmpStorage.append(ba);
+                break; }
+        }
+        if (r != SQL_SUCCESS) {
+            qWarning() << "QODBCResult::exec: unable to bind variable:" << qODBCWarn(d);
+            setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                         "Unable to bind variable"), QSqlError::StatementError, d));
+            return false;
+        }
+    }
+    r = SQLExecute(d->hStmt);
+    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+        qWarning() << "QODBCResult::exec: Unable to execute statement:" << qODBCWarn(d);
+        setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                     "Unable to execute statement"), QSqlError::StatementError, d));
+        return false;
+    }
+
+    SQLINTEGER isScrollable, bufferLength;
+    r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, &bufferLength);
+    if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+        QSqlResult::setForwardOnly(isScrollable==SQL_NONSCROLLABLE);
+
+    SQLSMALLINT count;
+    SQLNumResultCols(d->hStmt, &count);
+    if (count) {
+        setSelect(true);
+        for (int i = 0; i < count; ++i) {
+            d->rInf.append(qMakeFieldInfo(d, i));
+        }
+        d->fieldCache.resize(count);
+    } else {
+        setSelect(false);
+    }
+    setActive(true);
+
+
+    //get out parameters
+    if (!hasOutValues())
+        return true;
+
+    for (i = 0; i < values.count(); ++i) {
+        switch (values.at(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::Bool:
+            case QVariant::Int:
+            case QVariant::UInt:
+            case QVariant::Double:
+            case QVariant::ByteArray:
+            case QVariant::LongLong:
+            case QVariant::ULongLong:
+                //nothing to do
+                break;
+            case QVariant::String:
+                if (d->unicode) {
+                    if (bindValueType(i) & QSql::Out)
+                        values[i] = QString::fromUtf16((ushort*)tmpStorage.takeFirst().constData());
+                    break;
+                }
+                // fall through
+            default: {
+                QByteArray ba = tmpStorage.takeFirst();
+                if (bindValueType(i) & QSql::Out)
+                    values[i] = QString::fromAscii(ba.constData());
+                break; }
+        }
+        if (indicators[i] == SQL_NULL_DATA)
+            values[i] = QVariant(values[i].type());
+    }
+    return true;
+}
+
+QSqlRecord QODBCResult::record() const
+{
+    if (!isActive() || !isSelect())
+        return QSqlRecord();
+    return d->rInf;
+}
+
+QVariant QODBCResult::handle() const
+{
+    return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt);
+}
+
+bool QODBCResult::nextResult()
+{
+    setActive(false);
+    setAt(QSql::BeforeFirstRow);
+    d->rInf.clear();
+    d->fieldCache.clear();
+    d->fieldCacheIdx = 0;
+    setSelect(false);
+
+    SQLRETURN r = SQLMoreResults(d->hStmt);
+    if (r != SQL_SUCCESS) {
+        if (r == SQL_SUCCESS_WITH_INFO) {
+            int nativeCode = -1;
+            QString message = qODBCWarn(d, &nativeCode);
+            qWarning() << "QODBCResult::nextResult():" << message;
+        } else {
+            if (r != SQL_NO_DATA)
+                setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+                    "Unable to fetch last"), QSqlError::ConnectionError, d));
+            return false;
+        }
+    }
+
+    SQLSMALLINT count;
+    SQLNumResultCols(d->hStmt, &count);
+    if (count) {
+        setSelect(true);
+        for (int i = 0; i < count; ++i) {
+            d->rInf.append(qMakeFieldInfo(d, i));
+        }
+        d->fieldCache.resize(count);
+    } else {
+        setSelect(false);
+    }
+    setActive(true);
+
+    return true;
+}
+
+void QODBCResult::virtual_hook(int id, void *data)
+{
+    switch (id) {
+    case QSqlResult::DetachFromResultSet:
+        if (d->hStmt)
+            SQLCloseCursor(d->hStmt);
+        break;
+    case QSqlResult::NextResult:
+        Q_ASSERT(data);
+        *static_cast<bool*>(data) = nextResult();
+        break;
+    default:
+        QSqlResult::virtual_hook(id, data);
+    }
+}
+
+void QODBCResult::setForwardOnly(bool forward)
+{
+    d->userForwardOnly = forward;
+    QSqlResult::setForwardOnly(forward);
+}
+
+////////////////////////////////////////
+
+
+QODBCDriver::QODBCDriver(QObject *parent)
+    : QSqlDriver(parent)
+{
+    init();
+}
+
+QODBCDriver::QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject * parent)
+    : QSqlDriver(parent)
+{
+    init();
+    d->hEnv = env;
+    d->hDbc = con;
+    if (env && con) {
+        setOpen(true);
+        setOpenError(false);
+    }
+}
+
+void QODBCDriver::init()
+{
+    d = new QODBCDriverPrivate();
+}
+
+QODBCDriver::~QODBCDriver()
+{
+    cleanup();
+    delete d;
+}
+
+bool QODBCDriver::hasFeature(DriverFeature f) const
+{
+    switch (f) {
+    case Transactions: {
+        if (!d->hDbc)
+            return false;
+        SQLUSMALLINT txn;
+        SQLSMALLINT t;
+        int r = SQLGetInfo(d->hDbc,
+                        (SQLUSMALLINT)SQL_TXN_CAPABLE,
+                        &txn,
+                        sizeof(txn),
+                        &t);
+        if (r != SQL_SUCCESS || txn == SQL_TC_NONE)
+            return false;
+        else
+            return true;
+    }
+    case Unicode:
+        return d->unicode;
+    case PreparedQueries:
+    case PositionalPlaceholders:
+    case FinishQuery:
+    case LowPrecisionNumbers:
+        return true;
+    case QuerySize:
+    case NamedPlaceholders:
+    case LastInsertId:
+    case BatchOperations:
+    case SimpleLocking:
+    case EventNotifications:
+        return false;
+    case MultipleResultSets:
+        return d->hasMultiResultSets;
+    case BLOB: {
+        if(d->isMySqlServer)
+            return true;
+        else
+            return false;
+    }
+    }
+    return false;
+}
+
+bool QODBCDriver::open(const QString & db,
+                        const QString & user,
+                        const QString & password,
+                        const QString &,
+                        int,
+                        const QString& connOpts)
+{
+    if (isOpen())
+      close();
+    SQLRETURN r;
+    r = SQLAllocHandle(SQL_HANDLE_ENV,
+                        SQL_NULL_HANDLE,
+                        &d->hEnv);
+    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+        qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate environment"), d);
+        setOpenError(true);
+        return false;
+    }
+    r = SQLSetEnvAttr(d->hEnv,
+                       SQL_ATTR_ODBC_VERSION,
+                       (SQLPOINTER)qGetODBCVersion(connOpts),
+                       SQL_IS_UINTEGER);
+    r = SQLAllocHandle(SQL_HANDLE_DBC,
+                        d->hEnv,
+                        &d->hDbc);
+    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+        qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate connection"), d);
+        setOpenError(true);
+        return false;
+    }
+
+    if (!d->setConnectionOptions(connOpts))
+        return false;
+
+    // Create the connection string
+    QString connQStr;
+    // support the "DRIVER={SQL SERVER};SERVER=blah" syntax
+    if (db.contains(QLatin1String(".dsn"), Qt::CaseInsensitive))
+        connQStr = QLatin1String("FILEDSN=") + db;
+    else if (db.contains(QLatin1String("DRIVER="), Qt::CaseInsensitive)
+            || db.contains(QLatin1String("SERVER="), Qt::CaseInsensitive))
+        connQStr = db;
+    else
+        connQStr = QLatin1String("DSN=") + db;
+
+    if (!user.isEmpty())
+        connQStr += QLatin1String(";UID=") + user;
+    if (!password.isEmpty())
+        connQStr += QLatin1String(";PWD=") + password;
+
+    SQLSMALLINT cb;
+    SQLTCHAR connOut[1024];
+    r = SQLDriverConnect(d->hDbc,
+                          NULL,
+#ifdef UNICODE
+                          (SQLWCHAR*)connQStr.unicode(),
+#else
+                          (SQLCHAR*)connQStr.toLatin1().constData(),
+#endif
+                          (SQLSMALLINT)connQStr.length(),
+                          connOut,
+                          1024,
+                          &cb,
+                          SQL_DRIVER_NOPROMPT);
+    if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+        setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
+        setOpenError(true);
+        return false;
+    }
+
+    if (!d->checkDriver()) {
+        setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all "
+                     "functionality required"), QSqlError::ConnectionError, d));
+        setOpenError(true);
+        return false;
+    }
+
+    d->checkUnicode();
+    d->checkSchemaUsage();
+    d->checkSqlServer();
+    d->checkHasSQLFetchScroll();
+    d->checkHasMultiResults();
+    setOpen(true);
+    setOpenError(false);
+    if(d->isMSSqlServer) {
+        QSqlQuery i(createResult());
+        i.exec(QLatin1String("SET QUOTED_IDENTIFIER ON"));
+    }
+    return true;
+}
+
+void QODBCDriver::close()
+{
+    cleanup();
+    setOpen(false);
+    setOpenError(false);
+}
+
+void QODBCDriver::cleanup()
+{
+    SQLRETURN r;
+    if (!d)
+        return;
+
+    if(d->hDbc) {
+        // Open statements/descriptors handles are automatically cleaned up by SQLDisconnect
+        if (isOpen()) {
+            r = SQLDisconnect(d->hDbc);
+            if (r != SQL_SUCCESS)
+                qSqlWarning(QLatin1String("QODBCDriver::disconnect: Unable to disconnect datasource"), d);
+            else
+                d->disconnectCount++;
+        }
+
+        r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc);
+        if (r != SQL_SUCCESS)
+            qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free connection handle"), d);
+        d->hDbc = 0;
+    }
+
+    if (d->hEnv) {
+        r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv);
+        if (r != SQL_SUCCESS)
+            qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free environment handle"), d);
+        d->hEnv = 0;
+    }
+}
+
+// checks whether the server can return char, varchar and longvarchar
+// as two byte unicode characters
+void QODBCDriverPrivate::checkUnicode()
+{
+#if defined(Q_ODBC_VERSION_2)
+    unicode = false;
+    return;
+#endif
+
+    SQLRETURN   r;
+    SQLUINTEGER fFunc;
+
+    unicode = false;
+    r = SQLGetInfo(hDbc,
+                    SQL_CONVERT_CHAR,
+                    (SQLPOINTER)&fFunc,
+                    sizeof(fFunc),
+                    NULL);
+    if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WCHAR)) {
+        unicode = true;
+    }
+
+    r = SQLGetInfo(hDbc,
+                    SQL_CONVERT_VARCHAR,
+                    (SQLPOINTER)&fFunc,
+                    sizeof(fFunc),
+                    NULL);
+    if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WVARCHAR)) {
+        unicode = true;
+    }
+
+    r = SQLGetInfo(hDbc,
+                    SQL_CONVERT_LONGVARCHAR,
+                    (SQLPOINTER)&fFunc,
+                    sizeof(fFunc),
+                    NULL);
+    if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WLONGVARCHAR)) {
+        unicode = true;
+    }
+}
+
+bool QODBCDriverPrivate::checkDriver() const
+{
+#ifdef ODBC_CHECK_DRIVER
+    static const SQLUSMALLINT reqFunc[] = {
+                SQL_API_SQLDESCRIBECOL, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS,
+                SQL_API_SQLGETSTMTATTR, SQL_API_SQLGETDIAGREC, SQL_API_SQLEXECDIRECT,
+                SQL_API_SQLGETINFO, SQL_API_SQLTABLES, 0
+    };
+
+    // these functions are optional
+    static const SQLUSMALLINT optFunc[] = {
+        SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT, 0
+    };
+
+    SQLRETURN r;
+    SQLUSMALLINT sup;
+
+    int i;
+    // check the required functions
+    for (i = 0; reqFunc[i] != 0; ++i) {
+
+        r = SQLGetFunctions(hDbc, reqFunc[i], &sup);
+
+        if (r != SQL_SUCCESS) {
+            qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this);
+            return false;
+        }
+        if (sup == SQL_FALSE) {
+            qWarning () << "QODBCDriver::open: Warning - Driver doesn't support all needed functionality (" << reqFunc[i] <<
+                    ").\nPlease look at the Qt SQL Module Driver documentation for more information.";
+            return false;
+        }
+    }
+
+    // these functions are optional and just generate a warning
+    for (i = 0; optFunc[i] != 0; ++i) {
+
+        r = SQLGetFunctions(hDbc, optFunc[i], &sup);
+
+        if (r != SQL_SUCCESS) {
+            qSqlWarning(QLatin1String("QODBCDriver::checkDriver: Cannot get list of supported functions"), this);
+            return false;
+        }
+        if (sup == SQL_FALSE) {
+            qWarning() << "QODBCDriver::checkDriver: Warning - Driver doesn't support some non-critical functions (" << optFunc[i] << ')';
+            return true;
+        }
+    }
+#endif //ODBC_CHECK_DRIVER
+
+    return true;
+}
+
+void QODBCDriverPrivate::checkSchemaUsage()
+{
+    SQLRETURN   r;
+    SQLUINTEGER val;
+
+    r = SQLGetInfo(hDbc,
+                   SQL_SCHEMA_USAGE,
+                   (SQLPOINTER) &val,
+                   sizeof(val),
+                   NULL);
+    if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+        useSchema = (val != 0);
+}
+
+void QODBCDriverPrivate::checkSqlServer()
+{
+    SQLRETURN   r;
+    char serverString[200];
+    SQLSMALLINT t;
+
+    r = SQLGetInfo(hDbc,
+                   SQL_DBMS_NAME,
+                   serverString,
+                   sizeof(serverString),
+                   &t);
+    if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+        QString serverType;
+#ifdef UNICODE
+        serverType = QString(reinterpret_cast<const QChar*>(serverString), t/sizeof(QChar));
+#else
+        serverType = QString::fromLocal8Bit(serverString, t);
+#endif
+        isMySqlServer = serverType.contains(QLatin1String("mysql"), Qt::CaseInsensitive);
+        isMSSqlServer = serverType.contains(QLatin1String("Microsoft SQL Server"), Qt::CaseInsensitive);
+    }
+}
+
+void QODBCDriverPrivate::checkHasSQLFetchScroll()
+{
+    SQLUSMALLINT sup;
+    SQLRETURN r = SQLGetFunctions(hDbc, SQL_API_SQLFETCHSCROLL, &sup);
+    if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || sup != SQL_TRUE) {
+        hasSQLFetchScroll = false;
+        qWarning() << "QODBCDriver::checkHasSQLFetchScroll: Warning - Driver doesn't support scrollable result sets, use forward only mode for queries";
+    }
+}
+
+void QODBCDriverPrivate::checkHasMultiResults()
+{
+    char driverResponse[4];
+    SQLSMALLINT length;
+    SQLRETURN r = SQLGetInfo(hDbc,
+                             SQL_MULT_RESULT_SETS,
+                             driverResponse,
+                             sizeof(driverResponse),
+                             &length);
+    if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+#ifdef UNICODE
+        hasMultiResultSets = QString(reinterpret_cast<const QChar*>(driverResponse), length/sizeof(QChar)).startsWith(QLatin1Char('Y'));
+#else
+        hasMultiResultSets = QString::fromLocal8Bit(driverResponse, length).startsWith(QLatin1Char('Y'));
+#endif
+}
+
+QSqlResult *QODBCDriver::createResult() const
+{
+    return new QODBCResult(this, d);
+}
+
+bool QODBCDriver::beginTransaction()
+{
+    if (!isOpen()) {
+        qWarning() << "QODBCDriver::beginTransaction: Database not open";
+        return false;
+    }
+    SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
+    SQLRETURN r  = SQLSetConnectAttr(d->hDbc,
+                                      SQL_ATTR_AUTOCOMMIT,
+                                      (SQLPOINTER)ac,
+                                      sizeof(ac));
+    if (r != SQL_SUCCESS) {
+        setLastError(qMakeError(tr("Unable to disable autocommit"),
+                     QSqlError::TransactionError, d));
+        return false;
+    }
+    return true;
+}
+
+bool QODBCDriver::commitTransaction()
+{
+    if (!isOpen()) {
+        qWarning() << "QODBCDriver::commitTransaction: Database not open";
+        return false;
+    }
+    SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
+                              d->hDbc,
+                              SQL_COMMIT);
+    if (r != SQL_SUCCESS) {
+        setLastError(qMakeError(tr("Unable to commit transaction"),
+                     QSqlError::TransactionError, d));
+        return false;
+    }
+    return endTrans();
+}
+
+bool QODBCDriver::rollbackTransaction()
+{
+    if (!isOpen()) {
+        qWarning() << "QODBCDriver::rollbackTransaction: Database not open";
+        return false;
+    }
+    SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
+                              d->hDbc,
+                              SQL_ROLLBACK);
+    if (r != SQL_SUCCESS) {
+        setLastError(qMakeError(tr("Unable to rollback transaction"),
+                     QSqlError::TransactionError, d));
+        return false;
+    }
+    return endTrans();
+}
+
+bool QODBCDriver::endTrans()
+{
+    SQLUINTEGER ac(SQL_AUTOCOMMIT_ON);
+    SQLRETURN r  = SQLSetConnectAttr(d->hDbc,
+                                      SQL_ATTR_AUTOCOMMIT,
+                                      (SQLPOINTER)ac,
+                                      sizeof(ac));
+    if (r != SQL_SUCCESS) {
+        setLastError(qMakeError(tr("Unable to enable autocommit"), QSqlError::TransactionError, d));
+        return false;
+    }
+    return true;
+}
+
+QStringList QODBCDriver::tables(QSql::TableType type) const
+{
+    QStringList tl;
+    if (!isOpen())
+        return tl;
+    SQLHANDLE hStmt;
+
+    SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
+                                  d->hDbc,
+                                  &hStmt);
+    if (r != SQL_SUCCESS) {
+        qSqlWarning(QLatin1String("QODBCDriver::tables: Unable to allocate handle"), d);
+        return tl;
+    }
+    r = SQLSetStmtAttr(hStmt,
+                        SQL_ATTR_CURSOR_TYPE,
+                        (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+                        SQL_IS_UINTEGER);
+    QStringList tableType;
+    if (type & QSql::Tables)
+        tableType += QLatin1String("TABLE");
+    if (type & QSql::Views)
+        tableType += QLatin1String("VIEW");
+    if (type & QSql::SystemTables)
+        tableType += QLatin1String("SYSTEM TABLE");
+    if (tableType.isEmpty())
+        return tl;
+
+    QString joinedTableTypeString = tableType.join(QLatin1String(","));
+
+    r = SQLTables(hStmt,
+                   NULL,
+                   0,
+                   NULL,
+                   0,
+                   NULL,
+                   0,
+#ifdef UNICODE
+                   (SQLWCHAR*)joinedTableTypeString.unicode(),
+#else
+                   (SQLCHAR*)joinedTableTypeString.toLatin1().constData(),
+#endif
+                   joinedTableTypeString.length() /* characters, not bytes */);
+
+    if (r != SQL_SUCCESS)
+        qSqlWarning(QLatin1String("QODBCDriver::tables Unable to execute table list"), d);
+
+    if (d->hasSQLFetchScroll)
+        r = SQLFetchScroll(hStmt,
+                           SQL_FETCH_NEXT,
+                           0);
+    else
+        r = SQLFetch(hStmt);
+
+    while (r == SQL_SUCCESS) {
+        QString fieldVal = qGetStringData(hStmt, 2, -1, false);
+        tl.append(fieldVal);
+
+        if (d->hasSQLFetchScroll)
+            r = SQLFetchScroll(hStmt,
+                               SQL_FETCH_NEXT,
+                               0);
+        else
+            r = SQLFetch(hStmt);
+    }
+
+    r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+    if (r!= SQL_SUCCESS)
+        qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d);
+    return tl;
+}
+
+QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const
+{
+    QSqlIndex index(tablename);
+    if (!isOpen())
+        return index;
+    bool usingSpecialColumns = false;
+    QSqlRecord rec = record(tablename);
+
+    SQLHANDLE hStmt;
+    SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
+                                  d->hDbc,
+                                  &hStmt);
+    if (r != SQL_SUCCESS) {
+        qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to list primary key"), d);
+        return index;
+    }
+    QString catalog, schema, table;
+    d->splitTableQualifier(tablename, catalog, schema, table);
+
+    if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
+        catalog = stripDelimiters(catalog, QSqlDriver::TableName);
+    else
+        catalog = d->adjustCase(catalog);
+
+    if (isIdentifierEscaped(schema, QSqlDriver::TableName))
+        schema = stripDelimiters(schema, QSqlDriver::TableName);
+    else
+        schema = d->adjustCase(schema);
+
+    if (isIdentifierEscaped(table, QSqlDriver::TableName))
+        table = stripDelimiters(table, QSqlDriver::TableName);
+    else
+        table = d->adjustCase(table);
+
+    r = SQLSetStmtAttr(hStmt,
+                        SQL_ATTR_CURSOR_TYPE,
+                        (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+                        SQL_IS_UINTEGER);
+    r = SQLPrimaryKeys(hStmt,
+#ifdef UNICODE
+                        catalog.length() == 0 ? NULL : (SQLWCHAR*)catalog.unicode(),
+#else
+                        catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toLatin1().constData(),
+#endif
+                        catalog.length(),
+#ifdef UNICODE
+                        schema.length() == 0 ? NULL : (SQLWCHAR*)schema.unicode(),
+#else
+                        schema.length() == 0 ? NULL : (SQLCHAR*)schema.toLatin1().constData(),
+#endif
+                        schema.length(),
+#ifdef UNICODE
+                        (SQLWCHAR*)table.unicode(),
+#else
+                        (SQLCHAR*)table.toLatin1().constData(),
+#endif
+                        table.length() /* in characters, not in bytes */);
+
+    // if the SQLPrimaryKeys() call does not succeed (e.g the driver
+    // does not support it) - try an alternative method to get hold of
+    // the primary index (e.g MS Access and FoxPro)
+    if (r != SQL_SUCCESS) {
+            r = SQLSpecialColumns(hStmt,
+                        SQL_BEST_ROWID,
+#ifdef UNICODE
+                        catalog.length() == 0 ? NULL : (SQLWCHAR*)catalog.unicode(),
+#else
+                        catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toLatin1().constData(),
+#endif
+                        catalog.length(),
+#ifdef UNICODE
+                        schema.length() == 0 ? NULL : (SQLWCHAR*)schema.unicode(),
+#else
+                        schema.length() == 0 ? NULL : (SQLCHAR*)schema.toLatin1().constData(),
+#endif
+                        schema.length(),
+#ifdef UNICODE
+                        (SQLWCHAR*)table.unicode(),
+#else
+                        (SQLCHAR*)table.toLatin1().constData(),
+#endif
+                        table.length(),
+                        SQL_SCOPE_CURROW,
+                        SQL_NULLABLE);
+
+            if (r != SQL_SUCCESS) {
+                qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to execute primary key list"), d);
+            } else {
+                usingSpecialColumns = true;
+            }
+    }
+
+    if (d->hasSQLFetchScroll)
+        r = SQLFetchScroll(hStmt,
+                           SQL_FETCH_NEXT,
+                           0);
+    else
+        r = SQLFetch(hStmt);
+
+    int fakeId = 0;
+    QString cName, idxName;
+    // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
+    while (r == SQL_SUCCESS) {
+        if (usingSpecialColumns) {
+            cName = qGetStringData(hStmt, 1, -1, d->unicode); // column name
+            idxName = QString::number(fakeId++); // invent a fake index name
+        } else {
+            cName = qGetStringData(hStmt, 3, -1, d->unicode); // column name
+            idxName = qGetStringData(hStmt, 5, -1, d->unicode); // pk index name
+        }
+        index.append(rec.field(cName));
+        index.setName(idxName);
+
+        if (d->hasSQLFetchScroll)
+            r = SQLFetchScroll(hStmt,
+                               SQL_FETCH_NEXT,
+                               0);
+        else
+            r = SQLFetch(hStmt);
+
+    }
+    r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+    if (r!= SQL_SUCCESS)
+        qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d);
+    return index;
+}
+
+QSqlRecord QODBCDriver::record(const QString& tablename) const
+{
+    QSqlRecord fil;
+    if (!isOpen())
+        return fil;
+
+    SQLHANDLE hStmt;
+    QString catalog, schema, table;
+    d->splitTableQualifier(tablename, catalog, schema, table);
+
+    if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
+        catalog = stripDelimiters(catalog, QSqlDriver::TableName);
+    else
+        catalog = d->adjustCase(catalog);
+
+    if (isIdentifierEscaped(schema, QSqlDriver::TableName))
+        schema = stripDelimiters(schema, QSqlDriver::TableName);
+    else
+        schema = d->adjustCase(schema);
+
+    if (isIdentifierEscaped(table, QSqlDriver::TableName))
+        table = stripDelimiters(table, QSqlDriver::TableName);
+    else
+        table = d->adjustCase(table);
+
+    SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
+                                  d->hDbc,
+                                  &hStmt);
+    if (r != SQL_SUCCESS) {
+        qSqlWarning(QLatin1String("QODBCDriver::record: Unable to allocate handle"), d);
+        return fil;
+    }
+    r = SQLSetStmtAttr(hStmt,
+                        SQL_ATTR_CURSOR_TYPE,
+                        (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+                        SQL_IS_UINTEGER);
+    r =  SQLColumns(hStmt,
+#ifdef UNICODE
+                     catalog.length() == 0 ? NULL : (SQLWCHAR*)catalog.unicode(),
+#else
+                     catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toLatin1().constData(),
+#endif
+                     catalog.length(),
+#ifdef UNICODE
+                     schema.length() == 0 ? NULL : (SQLWCHAR*)schema.unicode(),
+#else
+                     schema.length() == 0 ? NULL : (SQLCHAR*)schema.toLatin1().constData(),
+#endif
+                     schema.length(),
+#ifdef UNICODE
+                     (SQLWCHAR*)table.unicode(),
+#else
+                     (SQLCHAR*)table.toLatin1().constData(),
+#endif
+                     table.length(),
+                     NULL,
+                     0);
+    if (r != SQL_SUCCESS)
+        qSqlWarning(QLatin1String("QODBCDriver::record: Unable to execute column list"), d);
+
+    if (d->hasSQLFetchScroll)
+        r = SQLFetchScroll(hStmt,
+                           SQL_FETCH_NEXT,
+                           0);
+    else
+        r = SQLFetch(hStmt);
+
+    // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
+    while (r == SQL_SUCCESS) {
+
+        fil.append(qMakeFieldInfo(hStmt, d));
+
+        if (d->hasSQLFetchScroll)
+            r = SQLFetchScroll(hStmt,
+                               SQL_FETCH_NEXT,
+                               0);
+        else
+            r = SQLFetch(hStmt);
+    }
+
+    r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+    if (r!= SQL_SUCCESS)
+        qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") + QString::number(r), d);
+
+    return fil;
+}
+
+QString QODBCDriver::formatValue(const QSqlField &field,
+                                 bool trimStrings) const
+{
+    QString r;
+    if (field.isNull()) {
+        r = QLatin1String("NULL");
+    } else if (field.type() == QVariant::DateTime) {
+        // Use an escape sequence for the datetime fields
+        if (field.value().toDateTime().isValid()){
+            QDate dt = field.value().toDateTime().date();
+            QTime tm = field.value().toDateTime().time();
+            // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10
+            r = QLatin1String("{ ts '") +
+                QString::number(dt.year()) + QLatin1Char('-') +
+                QString::number(dt.month()).rightJustified(2, QLatin1Char('0'), true) +
+                QLatin1Char('-') +
+                QString::number(dt.day()).rightJustified(2, QLatin1Char('0'), true) +
+                QLatin1Char(' ') +
+                tm.toString() +
+                QLatin1String("' }");
+        } else
+            r = QLatin1String("NULL");
+    } else if (field.type() == QVariant::ByteArray) {
+        QByteArray ba = field.value().toByteArray();
+        QString res;
+        static const char hexchars[] = "0123456789abcdef";
+        for (int i = 0; i < ba.size(); ++i) {
+            uchar s = (uchar) ba[i];
+            res += QLatin1Char(hexchars[s >> 4]);
+            res += QLatin1Char(hexchars[s & 0x0f]);
+        }
+        r = QLatin1String("0x") + res;
+    } else {
+        r = QSqlDriver::formatValue(field, trimStrings);
+    }
+    return r;
+}
+
+QVariant QODBCDriver::handle() const
+{
+    return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc);
+}
+
+QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
+{
+    QChar quote = d->quoteChar();
+    QString res = identifier;
+    if(!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) {
+        res.replace(quote, QString(quote)+QString(quote));
+        res.prepend(quote).append(quote);
+        res.replace(QLatin1Char('.'), QString(quote)+QLatin1Char('.')+QString(quote));
+    }
+    return res;
+}
+
+bool QODBCDriver::isIdentifierEscapedImplementation(const QString &identifier, IdentifierType) const
+{
+    QChar quote = d->quoteChar();
+    return identifier.size() > 2
+        && identifier.startsWith(quote) //left delimited
+        && identifier.endsWith(quote); //right delimited
+}
+
+QT_END_NAMESPACE