summaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/sqldrivers/db2/db2.pro11
-rw-r--r--src/plugins/sqldrivers/db2/main.cpp2
-rw-r--r--src/plugins/sqldrivers/db2/qsql_db2.cpp1700
-rw-r--r--src/plugins/sqldrivers/db2/qsql_db2_p.h103
-rw-r--r--src/plugins/sqldrivers/ibase/ibase.pro13
-rw-r--r--src/plugins/sqldrivers/ibase/main.cpp2
-rw-r--r--src/plugins/sqldrivers/ibase/qsql_ibase.cpp1948
-rw-r--r--src/plugins/sqldrivers/ibase/qsql_ibase_p.h114
-rw-r--r--src/plugins/sqldrivers/mysql/main.cpp2
-rw-r--r--src/plugins/sqldrivers/mysql/mysql.pro19
-rw-r--r--src/plugins/sqldrivers/mysql/qsql_mysql.cpp1665
-rw-r--r--src/plugins/sqldrivers/mysql/qsql_mysql_p.h110
-rw-r--r--src/plugins/sqldrivers/oci/main.cpp2
-rw-r--r--src/plugins/sqldrivers/oci/oci.pro12
-rw-r--r--src/plugins/sqldrivers/oci/qsql_oci.cpp2725
-rw-r--r--src/plugins/sqldrivers/oci/qsql_oci_p.h106
-rw-r--r--src/plugins/sqldrivers/odbc/main.cpp2
-rw-r--r--src/plugins/sqldrivers/odbc/odbc.pro15
-rw-r--r--src/plugins/sqldrivers/odbc/qsql_odbc.cpp2639
-rw-r--r--src/plugins/sqldrivers/odbc/qsql_odbc_p.h127
-rw-r--r--src/plugins/sqldrivers/psql/main.cpp2
-rw-r--r--src/plugins/sqldrivers/psql/psql.pro13
-rw-r--r--src/plugins/sqldrivers/psql/qsql_psql.cpp1508
-rw-r--r--src/plugins/sqldrivers/psql/qsql_psql_p.h128
-rw-r--r--src/plugins/sqldrivers/sqldrivers.pro18
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp828
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h100
-rw-r--r--src/plugins/sqldrivers/sqlite/smain.cpp2
-rw-r--r--src/plugins/sqldrivers/sqlite/sqlite.pro12
-rw-r--r--src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp615
-rw-r--r--src/plugins/sqldrivers/sqlite2/qsql_sqlite2_p.h109
-rw-r--r--src/plugins/sqldrivers/sqlite2/smain.cpp2
-rw-r--r--src/plugins/sqldrivers/sqlite2/sqlite2.pro7
-rw-r--r--src/plugins/sqldrivers/tds/main.cpp2
-rw-r--r--src/plugins/sqldrivers/tds/qsql_tds.cpp881
-rw-r--r--src/plugins/sqldrivers/tds/qsql_tds_p.h120
-rw-r--r--src/plugins/sqldrivers/tds/tds.pro13
37 files changed, 15641 insertions, 36 deletions
diff --git a/src/plugins/sqldrivers/db2/db2.pro b/src/plugins/sqldrivers/db2/db2.pro
index 2365c5bc0e..31822ef8dc 100644
--- a/src/plugins/sqldrivers/db2/db2.pro
+++ b/src/plugins/sqldrivers/db2/db2.pro
@@ -1,8 +1,15 @@
TARGET = qsqldb2
-SOURCES = main.cpp
+HEADERS += $$PWD/qsql_db2_p.h
+SOURCES += $$PWD/qsql_db2.cpp $$PWD/main.cpp
+
+unix {
+ !contains(LIBS, .*db2.*):LIBS += -ldb2
+} else {
+ !contains(LIBS, .*db2.*):LIBS += -ldb2cli
+}
+
OTHER_FILES += db2.json
-include(../../../sql/drivers/db2/qsql_db2.pri)
PLUGIN_CLASS_NAME = QDB2DriverPlugin
include(../qsqldriverbase.pri)
diff --git a/src/plugins/sqldrivers/db2/main.cpp b/src/plugins/sqldrivers/db2/main.cpp
index 3869d7798d..97338b8eef 100644
--- a/src/plugins/sqldrivers/db2/main.cpp
+++ b/src/plugins/sqldrivers/db2/main.cpp
@@ -39,7 +39,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
-#include "../../../sql/drivers/db2/qsql_db2_p.h"
+#include "qsql_db2_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/sqldrivers/db2/qsql_db2.cpp b/src/plugins/sqldrivers/db2/qsql_db2.cpp
new file mode 100644
index 0000000000..4ccc3aca9e
--- /dev/null
+++ b/src/plugins/sqldrivers/db2/qsql_db2.cpp
@@ -0,0 +1,1700 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_db2_p.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>
+#include <QtSql/private/qsqldriver_p.h>
+#include <QtSql/private/qsqlresult_p.h>
+
+#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
+
+#define UNICODE
+
+#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 QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QDB2Driver)
+
+public:
+ QDB2DriverPrivate() : QSqlDriverPrivate(), hEnv(0), hDbc(0) { dbmsType = QSqlDriver::DB2; }
+ SQLHANDLE hEnv;
+ SQLHANDLE hDbc;
+ QString user;
+};
+
+class QDB2ResultPrivate;
+
+class QDB2Result: public QSqlResult
+{
+ Q_DECLARE_PRIVATE(QDB2Result)
+
+public:
+ QDB2Result(const QDB2Driver *drv);
+ ~QDB2Result();
+ bool prepare(const QString &query) Q_DECL_OVERRIDE;
+ bool exec() Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+
+protected:
+ QVariant data(int field) Q_DECL_OVERRIDE;
+ bool reset(const QString &query) Q_DECL_OVERRIDE;
+ bool fetch(int i) Q_DECL_OVERRIDE;
+ bool fetchNext() Q_DECL_OVERRIDE;
+ bool fetchFirst() Q_DECL_OVERRIDE;
+ bool fetchLast() Q_DECL_OVERRIDE;
+ bool isNull(int i) Q_DECL_OVERRIDE;
+ int size() Q_DECL_OVERRIDE;
+ int numRowsAffected() Q_DECL_OVERRIDE;
+ QSqlRecord record() const Q_DECL_OVERRIDE;
+ void virtual_hook(int id, void *data) Q_DECL_OVERRIDE;
+ void detachFromResultSet() Q_DECL_OVERRIDE;
+ bool nextResult() Q_DECL_OVERRIDE;
+};
+
+class QDB2ResultPrivate: public QSqlResultPrivate
+{
+ Q_DECLARE_PUBLIC(QDB2Result)
+
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QDB2Driver)
+ QDB2ResultPrivate(QDB2Result *q, const QDB2Driver *drv)
+ : QSqlResultPrivate(q, drv),
+ hStmt(0)
+ {}
+ ~QDB2ResultPrivate()
+ {
+ emptyValueCache();
+ }
+ void clearValueCache()
+ {
+ for (int i = 0; i < valueCache.count(); ++i) {
+ delete valueCache[i];
+ valueCache[i] = NULL;
+ }
+ }
+ void emptyValueCache()
+ {
+ clearValueCache();
+ valueCache.clear();
+ }
+
+ SQLHANDLE hStmt;
+ QSqlRecord recInf;
+ QVector<QVariant*> valueCache;
+};
+
+static QString qFromTChar(SQLTCHAR* str)
+{
+ return QString((const QChar *)str);
+}
+
+// dangerous!! (but fast). Don't use in functions that
+// require out parameters!
+static SQLTCHAR* qToTChar(const QString& str)
+{
+ return (SQLTCHAR*)str.utf16();
+}
+
+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->drv_d_func()->hEnv) + QLatin1Char(' ')
+ + qWarnDB2Handle(SQL_HANDLE_DBC, d->drv_d_func()->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,
+ SQL_C_WCHAR,
+ (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->drv_d_func()->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
+{
+ Q_D(const QDB2Result);
+ return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt);
+}
+
+/************************************/
+
+QDB2Result::QDB2Result(const QDB2Driver *drv)
+ : QSqlResult(*new QDB2ResultPrivate(this, drv))
+{
+}
+
+QDB2Result::~QDB2Result()
+{
+ Q_D(const 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);
+ }
+}
+
+bool QDB2Result::reset (const QString& query)
+{
+ Q_D(QDB2Result);
+ 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 = 0;
+ 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)
+{
+ Q_D(QDB2Result);
+ 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()
+{
+ Q_D(QDB2Result);
+ 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[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[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[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[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[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[bindValueType(i) & 3],
+ SQL_C_BINARY,
+ SQL_LONGVARBINARY,
+ len,
+ 0,
+ (void *)values.at(i).toByteArray().constData(),
+ len,
+ ind);
+ break; }
+ case QVariant::String:
+ {
+ 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[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[bindValueType(i) & 3],
+ SQL_C_WCHAR,
+ SQL_WVARCHAR,
+ len,
+ 0,
+ data,
+ len * sizeof(QChar),
+ ind);
+ }
+ break;
+ }
+ default: {
+ QByteArray ba = values.at(i).toString().toLatin1();
+ int len = ba.length() + 1;
+ if (*ind != SQL_NULL_DATA)
+ *ind = ba.length();
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[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 = 0;
+ 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:
+ if (bindValueType(i) & QSql::Out)
+ values[i] = QString((const QChar *)tmpStorage.takeFirst().constData());
+ break;
+ default: {
+ values[i] = QString::fromLatin1(tmpStorage.takeFirst().constData());
+ break; }
+ }
+ if (indicators[i] == SQL_NULL_DATA)
+ values[i] = QVariant(values[i].type());
+ }
+ return true;
+}
+
+bool QDB2Result::fetch(int i)
+{
+ Q_D(QDB2Result);
+ 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 && r != SQL_NO_DATA) {
+ setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
+ "Unable to fetch record %1").arg(i), QSqlError::StatementError, d));
+ return false;
+ }
+ else if (r == SQL_NO_DATA)
+ return false;
+ setAt(i);
+ return true;
+}
+
+bool QDB2Result::fetchNext()
+{
+ Q_D(QDB2Result);
+ 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()
+{
+ Q_D(QDB2Result);
+ 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) {
+ if(r!= SQL_NO_DATA)
+ setLastError(qMakeError(QCoreApplication::translate("QDB2Result", "Unable to fetch first"),
+ QSqlError::StatementError, d));
+ return false;
+ }
+ setAt(0);
+ return true;
+}
+
+bool QDB2Result::fetchLast()
+{
+ Q_D(QDB2Result);
+ 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)
+{
+ Q_D(QDB2Result);
+ 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:
+ {
+ switch(numericalPrecisionPolicy()) {
+ case QSql::LowPrecisionInt32:
+ v = new QVariant(qGetIntData(d->hStmt, field, isNull));
+ break;
+ case QSql::LowPrecisionInt64:
+ v = new QVariant((qint64) qGetBigIntData(d->hStmt, field, isNull));
+ break;
+ case QSql::LowPrecisionDouble:
+ v = new QVariant(qGetDoubleData(d->hStmt, field, isNull));
+ break;
+ case QSql::HighPrecision:
+ default:
+ // length + 1 for the comma
+ v = new QVariant(qGetStringData(d->hStmt, field, info.length() + 1, isNull));
+ break;
+ }
+ 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)
+{
+ Q_D(const QDB2Result);
+ if (i >= d->valueCache.size())
+ return true;
+
+ if (d->valueCache[i])
+ return d->valueCache[i]->isNull();
+ return data(i).isNull();
+}
+
+int QDB2Result::numRowsAffected()
+{
+ Q_D(const QDB2Result);
+ 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
+{
+ Q_D(const QDB2Result);
+ if (isActive())
+ return d->recInf;
+ return QSqlRecord();
+}
+
+bool QDB2Result::nextResult()
+{
+ Q_D(QDB2Result);
+ 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 = 0;
+ 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);
+ setActive(true);
+
+ return true;
+}
+
+void QDB2Result::virtual_hook(int id, void *data)
+{
+ QSqlResult::virtual_hook(id, data);
+}
+
+void QDB2Result::detachFromResultSet()
+{
+ Q_D(QDB2Result);
+ if (d->hStmt)
+ SQLCloseCursor(d->hStmt);
+}
+
+/************************************/
+
+QDB2Driver::QDB2Driver(QObject* parent)
+ : QSqlDriver(*new QDB2DriverPrivate, parent)
+{
+}
+
+QDB2Driver::QDB2Driver(Qt::HANDLE env, Qt::HANDLE con, QObject* parent)
+ : QSqlDriver(*new QDB2DriverPrivate, parent)
+{
+ Q_D(QDB2Driver);
+ d->hEnv = reinterpret_cast<intptr_t>(env);
+ d->hDbc = reinterpret_cast<intptr_t>(con);
+ if (env && con) {
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+QDB2Driver::~QDB2Driver()
+{
+ close();
+}
+
+bool QDB2Driver::open(const QString& db, const QString& user, const QString& password, const QString& host, int port,
+ const QString& connOpts)
+{
+ Q_D(QDB2Driver);
+ 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("QDB2Driver::open: Unable to allocate environment"), d);
+ setOpenError(true);
+ return false;
+ }
+
+ r = SQLAllocHandle(SQL_HANDLE_DBC,
+ d->hEnv,
+ &d->hDbc);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ qSqlWarning(QLatin1String("QDB2Driver::open: Unable to allocate connection"), d);
+ setOpenError(true);
+ return false;
+ }
+
+ QString protocol;
+ // Set connection attributes
+ const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
+ for (int i = 0; i < opts.count(); ++i) {
+ const QString tmp(opts.at(i));
+ int idx;
+ if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) {
+ qWarning("QDB2Driver::open: Illegal connect option value '%s'",
+ tmp.toLocal8Bit().constData());
+ continue;
+ }
+
+ const QString opt(tmp.left(idx));
+ const QString val(tmp.mid(idx + 1).simplified());
+
+ SQLUINTEGER v = 0;
+ r = SQL_SUCCESS;
+ if (opt == QLatin1String("SQL_ATTR_ACCESS_MODE")) {
+ if (val == QLatin1String("SQL_MODE_READ_ONLY")) {
+ v = SQL_MODE_READ_ONLY;
+ } else if (val == QLatin1String("SQL_MODE_READ_WRITE")) {
+ v = SQL_MODE_READ_WRITE;
+ } else {
+ qWarning("QDB2Driver::open: Unknown option value '%s'",
+ tmp.toLocal8Bit().constData());
+ continue;
+ }
+ r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_ACCESS_MODE, reinterpret_cast<SQLPOINTER>(v), 0);
+ } else if (opt == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) {
+ v = val.toUInt();
+ r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_LOGIN_TIMEOUT, reinterpret_cast<SQLPOINTER>(v), 0);
+ } else if (opt.compare(QLatin1String("PROTOCOL"), Qt::CaseInsensitive) == 0) {
+ protocol = tmp;
+ }
+ else {
+ qWarning("QDB2Driver::open: Unknown connection attribute '%s'",
+ tmp.toLocal8Bit().constData());
+ }
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ qSqlWarning(QString::fromLatin1("QDB2Driver::open: "
+ "Unable to set connection attribute '%1'").arg(opt), d);
+ }
+
+ if (protocol.isEmpty())
+ protocol = QLatin1String("PROTOCOL=TCPIP");
+
+ if (port < 0 )
+ port = 50000;
+
+ QString connQStr;
+ connQStr = protocol + QLatin1String(";DATABASE=") + db + QLatin1String(";HOSTNAME=") + host
+ + QLatin1String(";PORT=") + QString::number(port) + QLatin1String(";UID=") + user
+ + QLatin1String(";PWD=") + password;
+
+
+ SQLTCHAR connOut[SQL_MAX_OPTION_STRING_LENGTH];
+ SQLSMALLINT cb;
+
+ r = SQLDriverConnect(d->hDbc,
+ NULL,
+ qToTChar(connQStr),
+ (SQLSMALLINT) connQStr.length(),
+ connOut,
+ SQL_MAX_OPTION_STRING_LENGTH,
+ &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;
+ }
+
+ d->user = user;
+ setOpen(true);
+ setOpenError(false);
+ return true;
+}
+
+void QDB2Driver::close()
+{
+ Q_D(QDB2Driver);
+ SQLRETURN r;
+ 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("QDB2Driver::close: Unable to disconnect datasource"), d);
+ }
+ r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc);
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QDB2Driver::close: 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("QDB2Driver::close: Unable to free environment handle"), d);
+ d->hEnv = 0;
+ }
+ setOpen(false);
+ setOpenError(false);
+}
+
+QSqlResult *QDB2Driver::createResult() const
+{
+ return new QDB2Result(this);
+}
+
+QSqlRecord QDB2Driver::record(const QString& tableName) const
+{
+ Q_D(const QDB2Driver);
+ QSqlRecord fil;
+ if (!isOpen())
+ return fil;
+
+ SQLHANDLE hStmt;
+ QString catalog, schema, table;
+ qSplitTableQualifier(tableName, &catalog, &schema, &table);
+ if (schema.isEmpty())
+ schema = d->user;
+
+ if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
+ catalog = stripDelimiters(catalog, QSqlDriver::TableName);
+ else
+ catalog = catalog.toUpper();
+
+ if (isIdentifierEscaped(schema, QSqlDriver::TableName))
+ schema = stripDelimiters(schema, QSqlDriver::TableName);
+ else
+ schema = schema.toUpper();
+
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ else
+ table = table.toUpper();
+
+ SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
+ d->hDbc,
+ &hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QDB2Driver::record: Unable to allocate handle"), d);
+ return fil;
+ }
+
+ r = SQLSetStmtAttr(hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+
+
+ //Aside: szSchemaName and szTableName parameters of SQLColumns
+ //are case sensitive search patterns, so no escaping is used.
+ r = SQLColumns(hStmt,
+ NULL,
+ 0,
+ qToTChar(schema),
+ schema.length(),
+ qToTChar(table),
+ table.length(),
+ NULL,
+ 0);
+
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QDB2Driver::record: Unable to execute column list"), d);
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ while (r == SQL_SUCCESS) {
+ fil.append(qMakeFieldInfo(hStmt));
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ }
+
+ r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ")
+ + QString::number(r), d);
+
+ return fil;
+}
+
+QStringList QDB2Driver::tables(QSql::TableType type) const
+{
+ Q_D(const QDB2Driver);
+ QStringList tl;
+ if (!isOpen())
+ return tl;
+
+ SQLHANDLE hStmt;
+
+ SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
+ d->hDbc,
+ &hStmt);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to allocate handle"), d);
+ return tl;
+ }
+ r = SQLSetStmtAttr(hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+
+ QString 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;
+ tableType.chop(1);
+
+ r = SQLTables(hStmt,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ qToTChar(tableType),
+ tableType.length());
+
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to execute table list"), d);
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ while (r == SQL_SUCCESS) {
+ bool isNull;
+ QString fieldVal = qGetStringData(hStmt, 2, -1, isNull);
+ QString userVal = qGetStringData(hStmt, 1, -1, isNull);
+ QString user = d->user;
+ if ( isIdentifierEscaped(user, QSqlDriver::TableName))
+ user = stripDelimiters(user, QSqlDriver::TableName);
+ else
+ user = user.toUpper();
+
+ if (userVal != user)
+ fieldVal = userVal + QLatin1Char('.') + fieldVal;
+ tl.append(fieldVal);
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ }
+
+ r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QDB2Driver::tables: Unable to free statement handle ")
+ + QString::number(r), d);
+ return tl;
+}
+
+QSqlIndex QDB2Driver::primaryIndex(const QString& tablename) const
+{
+ Q_D(const QDB2Driver);
+ QSqlIndex index(tablename);
+ if (!isOpen())
+ return index;
+ QSqlRecord rec = record(tablename);
+
+ SQLHANDLE hStmt;
+ SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
+ d->hDbc,
+ &hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QDB2Driver::primaryIndex: Unable to list primary key"), d);
+ return index;
+ }
+ QString catalog, schema, table;
+ qSplitTableQualifier(tablename, &catalog, &schema, &table);
+
+ if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
+ catalog = stripDelimiters(catalog, QSqlDriver::TableName);
+ else
+ catalog = catalog.toUpper();
+
+ if (isIdentifierEscaped(schema, QSqlDriver::TableName))
+ schema = stripDelimiters(schema, QSqlDriver::TableName);
+ else
+ schema = schema.toUpper();
+
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ else
+ table = table.toUpper();
+
+ r = SQLSetStmtAttr(hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+
+ r = SQLPrimaryKeys(hStmt,
+ NULL,
+ 0,
+ qToTChar(schema),
+ schema.length(),
+ qToTChar(table),
+ table.length());
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+
+ bool isNull;
+ QString cName, idxName;
+ // Store all fields in a StringList because the driver can't detail fields in this FETCH loop
+ while (r == SQL_SUCCESS) {
+ cName = qGetStringData(hStmt, 3, -1, isNull); // column name
+ idxName = qGetStringData(hStmt, 5, -1, isNull); // pk index name
+ index.append(rec.field(cName));
+ index.setName(idxName);
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ }
+ r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ if (r!= SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ")
+ + QString::number(r), d);
+ return index;
+}
+
+bool QDB2Driver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case QuerySize:
+ case NamedPlaceholders:
+ case BatchOperations:
+ case LastInsertId:
+ case SimpleLocking:
+ case EventNotifications:
+ case CancelQuery:
+ return false;
+ case BLOB:
+ case Transactions:
+ case MultipleResultSets:
+ case PreparedQueries:
+ case PositionalPlaceholders:
+ case LowPrecisionNumbers:
+ case FinishQuery:
+ return true;
+ case Unicode:
+ return true;
+ }
+ return false;
+}
+
+bool QDB2Driver::beginTransaction()
+{
+ if (!isOpen()) {
+ qWarning("QDB2Driver::beginTransaction: Database not open");
+ return false;
+ }
+ return setAutoCommit(false);
+}
+
+bool QDB2Driver::commitTransaction()
+{
+ Q_D(QDB2Driver);
+ if (!isOpen()) {
+ qWarning("QDB2Driver::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 setAutoCommit(true);
+}
+
+bool QDB2Driver::rollbackTransaction()
+{
+ Q_D(QDB2Driver);
+ if (!isOpen()) {
+ qWarning("QDB2Driver::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 setAutoCommit(true);
+}
+
+bool QDB2Driver::setAutoCommit(bool autoCommit)
+{
+ Q_D(QDB2Driver);
+ SQLUINTEGER ac = autoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF;
+ SQLRETURN r = SQLSetConnectAttr(d->hDbc,
+ SQL_ATTR_AUTOCOMMIT,
+ reinterpret_cast<SQLPOINTER>(ac),
+ sizeof(ac));
+ if (r != SQL_SUCCESS) {
+ setLastError(qMakeError(tr("Unable to set autocommit"),
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+QString QDB2Driver::formatValue(const QSqlField &field, bool trimStrings) const
+{
+ if (field.isNull())
+ return QLatin1String("NULL");
+
+ switch (field.type()) {
+ case 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
+ return QLatin1Char('\'') + QString::number(dt.year()) + QLatin1Char('-')
+ + QString::number(dt.month()) + QLatin1Char('-')
+ + QString::number(dt.day()) + QLatin1Char('-')
+ + QString::number(tm.hour()) + QLatin1Char('.')
+ + QString::number(tm.minute()).rightJustified(2, QLatin1Char('0'), true)
+ + QLatin1Char('.')
+ + QString::number(tm.second()).rightJustified(2, QLatin1Char('0'), true)
+ + QLatin1Char('.')
+ + QString::number(tm.msec() * 1000).rightJustified(6, QLatin1Char('0'), true)
+ + QLatin1Char('\'');
+ } else {
+ return QLatin1String("NULL");
+ }
+ }
+ case QVariant::ByteArray: {
+ QByteArray ba = field.value().toByteArray();
+ QString res = QString::fromLatin1("BLOB(X'");
+ 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]);
+ }
+ res += QLatin1String("')");
+ return res;
+ }
+ default:
+ return QSqlDriver::formatValue(field, trimStrings);
+ }
+}
+
+QVariant QDB2Driver::handle() const
+{
+ Q_D(const QDB2Driver);
+ return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc);
+}
+
+QString QDB2Driver::escapeIdentifier(const QString &identifier, IdentifierType) const
+{
+ QString res = identifier;
+ if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) {
+ res.replace(QLatin1Char('"'), QLatin1String("\"\""));
+ res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
+ res.replace(QLatin1Char('.'), QLatin1String("\".\""));
+ }
+ return res;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/sqldrivers/db2/qsql_db2_p.h b/src/plugins/sqldrivers/db2/qsql_db2_p.h
new file mode 100644
index 0000000000..fa6d739479
--- /dev/null
+++ b/src/plugins/sqldrivers/db2/qsql_db2_p.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_DB2_H
+#define QSQL_DB2_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_DB2
+#else
+#define Q_EXPORT_SQLDRIVER_DB2 Q_SQL_EXPORT
+#endif
+
+#include <QtSql/qsqldriver.h>
+
+QT_BEGIN_NAMESPACE
+
+class QDB2DriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_DB2 QDB2Driver : public QSqlDriver
+{
+ Q_DECLARE_PRIVATE(QDB2Driver)
+ Q_OBJECT
+ friend class QDB2ResultPrivate;
+
+public:
+ explicit QDB2Driver(QObject* parent = 0);
+ QDB2Driver(Qt::HANDLE env, Qt::HANDLE con, QObject* parent = 0);
+ ~QDB2Driver();
+ bool hasFeature(DriverFeature) const Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ QSqlRecord record(const QString &tableName) const Q_DECL_OVERRIDE;
+ QStringList tables(QSql::TableType type) const Q_DECL_OVERRIDE;
+ QSqlResult *createResult() const Q_DECL_OVERRIDE;
+ QSqlIndex primaryIndex(const QString &tablename) const Q_DECL_OVERRIDE;
+ bool beginTransaction() Q_DECL_OVERRIDE;
+ bool commitTransaction() Q_DECL_OVERRIDE;
+ bool rollbackTransaction() Q_DECL_OVERRIDE;
+ QString formatValue(const QSqlField &field, bool trimStrings) const Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+ bool open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port,
+ const QString& connOpts) Q_DECL_OVERRIDE;
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE;
+
+private:
+ bool setAutoCommit(bool autoCommit);
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_DB2_H
diff --git a/src/plugins/sqldrivers/ibase/ibase.pro b/src/plugins/sqldrivers/ibase/ibase.pro
index 1f29597a2b..8237245183 100644
--- a/src/plugins/sqldrivers/ibase/ibase.pro
+++ b/src/plugins/sqldrivers/ibase/ibase.pro
@@ -1,8 +1,17 @@
TARGET = qsqlibase
-SOURCES = main.cpp
+HEADERS += $$PWD/qsql_ibase_p.h
+SOURCES += $$PWD/qsql_ibase.cpp $$PWD/main.cpp
+
+unix {
+ !contains(LIBS, .*gds.*):!contains(LIBS, .*libfb.*):LIBS += -lgds
+} else {
+ !contains(LIBS, .*gds.*):!contains(LIBS, .*fbclient.*) {
+ LIBS += -lgds32_ms
+ }
+}
+
OTHER_FILES += ibase.json
-include(../../../sql/drivers/ibase/qsql_ibase.pri)
PLUGIN_CLASS_NAME = QIBaseDriverPlugin
include(../qsqldriverbase.pri)
diff --git a/src/plugins/sqldrivers/ibase/main.cpp b/src/plugins/sqldrivers/ibase/main.cpp
index c6f0b785de..8d462afcdc 100644
--- a/src/plugins/sqldrivers/ibase/main.cpp
+++ b/src/plugins/sqldrivers/ibase/main.cpp
@@ -39,7 +39,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
-#include "../../../sql/drivers/ibase/qsql_ibase_p.h"
+#include "qsql_ibase_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
new file mode 100644
index 0000000000..6fd91b6b76
--- /dev/null
+++ b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
@@ -0,0 +1,1948 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_ibase_p.h"
+#include <qcoreapplication.h>
+#include <qdatetime.h>
+#include <qvariant.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlquery.h>
+#include <QtSql/private/qsqlcachedresult_p.h>
+#include <QtSql/private/qsqldriver_p.h>
+#include <qlist.h>
+#include <qvector.h>
+#include <qtextcodec.h>
+#include <qmutex.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <math.h>
+#include <qdebug.h>
+#include <QVarLengthArray>
+
+QT_BEGIN_NAMESPACE
+
+#define FBVERSION SQL_DIALECT_V6
+
+#ifndef SQLDA_CURRENT_VERSION
+#define SQLDA_CURRENT_VERSION SQLDA_VERSION1
+#endif
+
+enum { QIBaseChunkSize = SHRT_MAX / 2 };
+
+#if defined(FB_API_VER) && FB_API_VER >= 20
+static bool getIBaseError(QString& msg, const ISC_STATUS* status, ISC_LONG &sqlcode, QTextCodec *tc)
+#else
+static bool getIBaseError(QString& msg, ISC_STATUS* status, ISC_LONG &sqlcode, QTextCodec *tc)
+#endif
+{
+ if (status[0] != 1 || status[1] <= 0)
+ return false;
+
+ msg.clear();
+ sqlcode = isc_sqlcode(status);
+ char buf[512];
+#if defined(FB_API_VER) && FB_API_VER >= 20
+ while(fb_interpret(buf, 512, &status)) {
+#else
+ while(isc_interprete(buf, &status)) {
+#endif
+ if(!msg.isEmpty())
+ msg += QLatin1String(" - ");
+ if (tc)
+ msg += tc->toUnicode(buf);
+ else
+ msg += QString::fromUtf8(buf);
+ }
+ return true;
+}
+
+static void createDA(XSQLDA *&sqlda)
+{
+ sqlda = (XSQLDA *) malloc(XSQLDA_LENGTH(1));
+ if (sqlda == (XSQLDA*)0) return;
+ sqlda->sqln = 1;
+ sqlda->sqld = 0;
+ sqlda->version = SQLDA_CURRENT_VERSION;
+ sqlda->sqlvar[0].sqlind = 0;
+ sqlda->sqlvar[0].sqldata = 0;
+}
+
+static void enlargeDA(XSQLDA *&sqlda, int n)
+{
+ if (sqlda != (XSQLDA*)0)
+ free(sqlda);
+ sqlda = (XSQLDA *) malloc(XSQLDA_LENGTH(n));
+ if (sqlda == (XSQLDA*)0) return;
+ sqlda->sqln = n;
+ sqlda->version = SQLDA_CURRENT_VERSION;
+}
+
+static void initDA(XSQLDA *sqlda)
+{
+ for (int i = 0; i < sqlda->sqld; ++i) {
+ switch (sqlda->sqlvar[i].sqltype & ~1) {
+ case SQL_INT64:
+ case SQL_LONG:
+ case SQL_SHORT:
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ case SQL_TIMESTAMP:
+ case SQL_TYPE_TIME:
+ case SQL_TYPE_DATE:
+ case SQL_TEXT:
+ case SQL_BLOB:
+ sqlda->sqlvar[i].sqldata = new char[sqlda->sqlvar[i].sqllen];
+ break;
+ case SQL_ARRAY:
+ sqlda->sqlvar[i].sqldata = new char[sizeof(ISC_QUAD)];
+ memset(sqlda->sqlvar[i].sqldata, 0, sizeof(ISC_QUAD));
+ break;
+ case SQL_VARYING:
+ sqlda->sqlvar[i].sqldata = new char[sqlda->sqlvar[i].sqllen + sizeof(short)];
+ break;
+ default:
+ // not supported - do not bind.
+ sqlda->sqlvar[i].sqldata = 0;
+ break;
+ }
+ if (sqlda->sqlvar[i].sqltype & 1) {
+ sqlda->sqlvar[i].sqlind = new short[1];
+ *(sqlda->sqlvar[i].sqlind) = 0;
+ } else {
+ sqlda->sqlvar[i].sqlind = 0;
+ }
+ }
+}
+
+static void delDA(XSQLDA *&sqlda)
+{
+ if (!sqlda)
+ return;
+ for (int i = 0; i < sqlda->sqld; ++i) {
+ delete [] sqlda->sqlvar[i].sqlind;
+ delete [] sqlda->sqlvar[i].sqldata;
+ }
+ free(sqlda);
+ sqlda = 0;
+}
+
+static QVariant::Type qIBaseTypeName(int iType, bool hasScale)
+{
+ switch (iType) {
+ case blr_varying:
+ case blr_varying2:
+ case blr_text:
+ case blr_cstring:
+ case blr_cstring2:
+ return QVariant::String;
+ case blr_sql_time:
+ return QVariant::Time;
+ case blr_sql_date:
+ return QVariant::Date;
+ case blr_timestamp:
+ return QVariant::DateTime;
+ case blr_blob:
+ return QVariant::ByteArray;
+ case blr_quad:
+ case blr_short:
+ case blr_long:
+ return (hasScale ? QVariant::Double : QVariant::Int);
+ case blr_int64:
+ return (hasScale ? QVariant::Double : QVariant::LongLong);
+ case blr_float:
+ case blr_d_float:
+ case blr_double:
+ return QVariant::Double;
+ }
+ qWarning("qIBaseTypeName: unknown datatype: %d", iType);
+ return QVariant::Invalid;
+}
+
+static QVariant::Type qIBaseTypeName2(int iType, bool hasScale)
+{
+ switch(iType & ~1) {
+ case SQL_VARYING:
+ case SQL_TEXT:
+ return QVariant::String;
+ case SQL_LONG:
+ case SQL_SHORT:
+ return (hasScale ? QVariant::Double : QVariant::Int);
+ case SQL_INT64:
+ return (hasScale ? QVariant::Double : QVariant::LongLong);
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ return QVariant::Double;
+ case SQL_TIMESTAMP:
+ return QVariant::DateTime;
+ case SQL_TYPE_TIME:
+ return QVariant::Time;
+ case SQL_TYPE_DATE:
+ return QVariant::Date;
+ case SQL_ARRAY:
+ return QVariant::List;
+ case SQL_BLOB:
+ return QVariant::ByteArray;
+ default:
+ return QVariant::Invalid;
+ }
+}
+
+static ISC_TIMESTAMP toTimeStamp(const QDateTime &dt)
+{
+ static const QTime midnight(0, 0, 0, 0);
+ static const QDate basedate(1858, 11, 17);
+ ISC_TIMESTAMP ts;
+ ts.timestamp_time = midnight.msecsTo(dt.time()) * 10;
+ ts.timestamp_date = basedate.daysTo(dt.date());
+ return ts;
+}
+
+static QDateTime fromTimeStamp(char *buffer)
+{
+ static const QDate bd(1858, 11, 17);
+ QTime t(0, 0);
+ QDate d;
+
+ // have to demangle the structure ourselves because isc_decode_time
+ // strips the msecs
+ t = t.addMSecs(int(((ISC_TIMESTAMP*)buffer)->timestamp_time / 10));
+ d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date));
+
+ return QDateTime(d, t);
+}
+
+static ISC_TIME toTime(const QTime &t)
+{
+ static const QTime midnight(0, 0, 0, 0);
+ return (ISC_TIME)midnight.msecsTo(t) * 10;
+}
+
+static QTime fromTime(char *buffer)
+{
+ QTime t(0, 0);
+ // have to demangle the structure ourselves because isc_decode_time
+ // strips the msecs
+ t = t.addMSecs(int((*(ISC_TIME*)buffer) / 10));
+
+ return t;
+}
+
+static ISC_DATE toDate(const QDate &t)
+{
+ static const QDate basedate(1858, 11, 17);
+ ISC_DATE date;
+
+ date = basedate.daysTo(t);
+ return date;
+}
+
+static QDate fromDate(char *buffer)
+{
+ static const QDate bd(1858, 11, 17);
+ QDate d;
+
+ // have to demangle the structure ourselves because isc_decode_time
+ // strips the msecs
+ d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date));
+
+ return d;
+}
+
+static QByteArray encodeString(QTextCodec *tc, const QString &str)
+{
+ if (tc)
+ return tc->fromUnicode(str);
+ return str.toUtf8();
+}
+
+struct QIBaseEventBuffer {
+#if defined(FB_API_VER) && FB_API_VER >= 20
+ ISC_UCHAR *eventBuffer;
+ ISC_UCHAR *resultBuffer;
+#else
+ char *eventBuffer;
+ char *resultBuffer;
+#endif
+ ISC_LONG bufferLength;
+ ISC_LONG eventId;
+
+ enum QIBaseSubscriptionState { Starting, Subscribed, Finished };
+ QIBaseSubscriptionState subscriptionState;
+};
+
+class QIBaseDriverPrivate : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QIBaseDriver)
+public:
+ QIBaseDriverPrivate() : QSqlDriverPrivate(), ibase(0), trans(0), tc(0) { dbmsType = QSqlDriver::Interbase; }
+
+ bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError)
+ {
+ Q_Q(QIBaseDriver);
+ QString imsg;
+ ISC_LONG sqlcode;
+ if (!getIBaseError(imsg, status, sqlcode, tc))
+ return false;
+
+ q->setLastError(QSqlError(QCoreApplication::translate("QIBaseDriver", msg),
+ imsg, typ, int(sqlcode)));
+ return true;
+ }
+
+public:
+ isc_db_handle ibase;
+ isc_tr_handle trans;
+ QTextCodec *tc;
+ ISC_STATUS status[20];
+ QMap<QString, QIBaseEventBuffer*> eventBuffers;
+};
+
+typedef QMap<void *, QIBaseDriver *> QIBaseBufferDriverMap;
+Q_GLOBAL_STATIC(QIBaseBufferDriverMap, qBufferDriverMap)
+Q_GLOBAL_STATIC(QMutex, qMutex);
+
+static void qFreeEventBuffer(QIBaseEventBuffer* eBuffer)
+{
+ qMutex()->lock();
+ qBufferDriverMap()->remove(reinterpret_cast<void *>(eBuffer->resultBuffer));
+ qMutex()->unlock();
+ delete eBuffer;
+}
+
+class QIBaseResultPrivate;
+
+class QIBaseResult : public QSqlCachedResult
+{
+ Q_DECLARE_PRIVATE(QIBaseResult)
+
+public:
+ explicit QIBaseResult(const QIBaseDriver* db);
+
+ bool prepare(const QString &query) Q_DECL_OVERRIDE;
+ bool exec() Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+
+protected:
+ bool gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx) Q_DECL_OVERRIDE;
+ bool reset (const QString &query) Q_DECL_OVERRIDE;
+ int size() Q_DECL_OVERRIDE;
+ int numRowsAffected() Q_DECL_OVERRIDE;
+ QSqlRecord record() const Q_DECL_OVERRIDE;
+};
+
+class QIBaseResultPrivate: public QSqlCachedResultPrivate
+{
+ Q_DECLARE_PUBLIC(QIBaseResult)
+
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QIBaseDriver)
+
+ QIBaseResultPrivate(QIBaseResult *q, const QIBaseDriver *drv);
+ ~QIBaseResultPrivate() { cleanup(); }
+
+ void cleanup();
+ bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError)
+ {
+ Q_Q(QIBaseResult);
+ QString imsg;
+ ISC_LONG sqlcode;
+ if (!getIBaseError(imsg, status, sqlcode, tc))
+ return false;
+
+ q->setLastError(QSqlError(QCoreApplication::translate("QIBaseResult", msg),
+ imsg, typ, int(sqlcode)));
+ return true;
+ }
+
+ bool transaction();
+ bool commit();
+
+ bool isSelect();
+ QVariant fetchBlob(ISC_QUAD *bId);
+ bool writeBlob(int i, const QByteArray &ba);
+ QVariant fetchArray(int pos, ISC_QUAD *arr);
+ bool writeArray(int i, const QList<QVariant> &list);
+
+public:
+ ISC_STATUS status[20];
+ isc_tr_handle trans;
+ //indicator whether we have a local transaction or a transaction on driver level
+ bool localTransaction;
+ isc_stmt_handle stmt;
+ isc_db_handle ibase;
+ XSQLDA *sqlda; // output sqlda
+ XSQLDA *inda; // input parameters
+ int queryType;
+ QTextCodec *tc;
+};
+
+
+QIBaseResultPrivate::QIBaseResultPrivate(QIBaseResult *q, const QIBaseDriver *drv)
+ : QSqlCachedResultPrivate(q, drv),
+ trans(0),
+ localTransaction(!drv_d_func()->ibase),
+ stmt(0),
+ ibase(drv_d_func()->ibase),
+ sqlda(0),
+ inda(0),
+ queryType(-1),
+ tc(drv_d_func()->tc)
+{
+}
+
+void QIBaseResultPrivate::cleanup()
+{
+ Q_Q(QIBaseResult);
+ commit();
+ if (!localTransaction)
+ trans = 0;
+
+ if (stmt) {
+ isc_dsql_free_statement(status, &stmt, DSQL_drop);
+ stmt = 0;
+ }
+
+ delDA(sqlda);
+ delDA(inda);
+
+ queryType = -1;
+ q->cleanup();
+}
+
+bool QIBaseResultPrivate::writeBlob(int i, const QByteArray &ba)
+{
+ isc_blob_handle handle = 0;
+ ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[i].sqldata;
+ isc_create_blob2(status, &ibase, &trans, &handle, bId, 0, 0);
+ if (!isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to create BLOB"),
+ QSqlError::StatementError)) {
+ int i = 0;
+ while (i < ba.size()) {
+ isc_put_segment(status, &handle, qMin(ba.size() - i, int(QIBaseChunkSize)),
+ const_cast<char*>(ba.data()) + i);
+ if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to write BLOB")))
+ return false;
+ i += qMin(ba.size() - i, int(QIBaseChunkSize));
+ }
+ }
+ isc_close_blob(status, &handle);
+ return true;
+}
+
+QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId)
+{
+ isc_blob_handle handle = 0;
+
+ isc_open_blob2(status, &ibase, &trans, &handle, bId, 0, 0);
+ if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to open BLOB"),
+ QSqlError::StatementError))
+ return QVariant();
+
+ unsigned short len = 0;
+ QByteArray ba;
+ int chunkSize = QIBaseChunkSize;
+ ba.resize(chunkSize);
+ int read = 0;
+ while (isc_get_segment(status, &handle, &len, chunkSize, ba.data() + read) == 0 || status[1] == isc_segment) {
+ read += len;
+ ba.resize(read + chunkSize);
+ }
+ ba.resize(read);
+
+ bool isErr = (status[1] == isc_segstr_eof ? false :
+ isError(QT_TRANSLATE_NOOP("QIBaseResult",
+ "Unable to read BLOB"),
+ QSqlError::StatementError));
+
+ isc_close_blob(status, &handle);
+
+ if (isErr)
+ return QVariant();
+
+ ba.resize(read);
+ return ba;
+}
+
+template<typename T>
+static QList<QVariant> toList(char** buf, int count, T* = 0)
+{
+ QList<QVariant> res;
+ for (int i = 0; i < count; ++i) {
+ res.append(*(T*)(*buf));
+ *buf += sizeof(T);
+ }
+ return res;
+}
+/* char** ? seems like bad influence from oracle ... */
+template<>
+QList<QVariant> toList<long>(char** buf, int count, long*)
+{
+ QList<QVariant> res;
+ for (int i = 0; i < count; ++i) {
+ if (sizeof(int) == sizeof(long))
+ res.append(int((*(long*)(*buf))));
+ else
+ res.append((qint64)(*(long*)(*buf)));
+ *buf += sizeof(long);
+ }
+ return res;
+}
+
+static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim,
+ short* numElements, ISC_ARRAY_DESC *arrayDesc,
+ QTextCodec *tc)
+{
+ const short dim = arrayDesc->array_desc_dimensions - 1;
+ const unsigned char dataType = arrayDesc->array_desc_dtype;
+ QList<QVariant> valList;
+ unsigned short strLen = arrayDesc->array_desc_length;
+
+ if (curDim != dim) {
+ for(int i = 0; i < numElements[curDim]; ++i)
+ buffer = readArrayBuffer(list, buffer, curDim + 1, numElements,
+ arrayDesc, tc);
+ } else {
+ switch(dataType) {
+ case blr_varying:
+ case blr_varying2:
+ strLen += 2; // for the two terminating null values
+ case blr_text:
+ case blr_text2: {
+ int o;
+ for (int i = 0; i < numElements[dim]; ++i) {
+ for(o = 0; o < strLen && buffer[o]!=0; ++o )
+ ;
+
+ if (tc)
+ valList.append(tc->toUnicode(buffer, o));
+ else
+ valList.append(QString::fromUtf8(buffer, o));
+
+ buffer += strLen;
+ }
+ break; }
+ case blr_long:
+ valList = toList<long>(&buffer, numElements[dim], static_cast<long *>(0));
+ break;
+ case blr_short:
+ valList = toList<short>(&buffer, numElements[dim]);
+ break;
+ case blr_int64:
+ valList = toList<qint64>(&buffer, numElements[dim]);
+ break;
+ case blr_float:
+ valList = toList<float>(&buffer, numElements[dim]);
+ break;
+ case blr_double:
+ valList = toList<double>(&buffer, numElements[dim]);
+ break;
+ case blr_timestamp:
+ for(int i = 0; i < numElements[dim]; ++i) {
+ valList.append(fromTimeStamp(buffer));
+ buffer += sizeof(ISC_TIMESTAMP);
+ }
+ break;
+ case blr_sql_time:
+ for(int i = 0; i < numElements[dim]; ++i) {
+ valList.append(fromTime(buffer));
+ buffer += sizeof(ISC_TIME);
+ }
+ break;
+ case blr_sql_date:
+ for(int i = 0; i < numElements[dim]; ++i) {
+ valList.append(fromDate(buffer));
+ buffer += sizeof(ISC_DATE);
+ }
+ break;
+ }
+ }
+ if (dim > 0)
+ list.append(valList);
+ else
+ list += valList;
+ return buffer;
+}
+
+QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr)
+{
+ QList<QVariant> list;
+ ISC_ARRAY_DESC desc;
+
+ if (!arr)
+ return list;
+
+ QByteArray relname(sqlda->sqlvar[pos].relname, sqlda->sqlvar[pos].relname_length);
+ QByteArray sqlname(sqlda->sqlvar[pos].aliasname, sqlda->sqlvar[pos].aliasname_length);
+
+ isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc);
+ if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"),
+ QSqlError::StatementError))
+ return list;
+
+
+ int arraySize = 1, subArraySize;
+ short dimensions = desc.array_desc_dimensions;
+ QVarLengthArray<short> numElements(dimensions);
+
+ for(int i = 0; i < dimensions; ++i) {
+ subArraySize = (desc.array_desc_bounds[i].array_bound_upper -
+ desc.array_desc_bounds[i].array_bound_lower + 1);
+ numElements[i] = subArraySize;
+ arraySize = subArraySize * arraySize;
+ }
+
+ ISC_LONG bufLen;
+ QByteArray ba;
+ /* varying arrayelements are stored with 2 trailing null bytes
+ indicating the length of the string
+ */
+ if (desc.array_desc_dtype == blr_varying
+ || desc.array_desc_dtype == blr_varying2) {
+ desc.array_desc_length += 2;
+ bufLen = desc.array_desc_length * arraySize * sizeof(short);
+ } else {
+ bufLen = desc.array_desc_length * arraySize;
+ }
+
+
+ ba.resize(int(bufLen));
+ isc_array_get_slice(status, &ibase, &trans, arr, &desc, ba.data(), &bufLen);
+ if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get array data"),
+ QSqlError::StatementError))
+ return list;
+
+ readArrayBuffer(list, ba.data(), 0, numElements.data(), &desc, tc);
+
+ return QVariant(list);
+}
+
+template<typename T>
+static char* fillList(char *buffer, const QList<QVariant> &list, T* = 0)
+{
+ for (int i = 0; i < list.size(); ++i) {
+ T val;
+ val = qvariant_cast<T>(list.at(i));
+ memcpy(buffer, &val, sizeof(T));
+ buffer += sizeof(T);
+ }
+ return buffer;
+}
+
+template<>
+char* fillList<float>(char *buffer, const QList<QVariant> &list, float*)
+{
+ for (int i = 0; i < list.size(); ++i) {
+ double val;
+ float val2 = 0;
+ val = qvariant_cast<double>(list.at(i));
+ val2 = (float)val;
+ memcpy(buffer, &val2, sizeof(float));
+ buffer += sizeof(float);
+ }
+ return buffer;
+}
+
+static char* qFillBufferWithString(char *buffer, const QString& string,
+ short buflen, bool varying, bool array,
+ QTextCodec *tc)
+{
+ QByteArray str = encodeString(tc, string); // keep a copy of the string alive in this scope
+ if (varying) {
+ short tmpBuflen = buflen;
+ if (str.length() < buflen)
+ buflen = str.length();
+ if (array) { // interbase stores varying arrayelements different than normal varying elements
+ memcpy(buffer, str.data(), buflen);
+ memset(buffer + buflen, 0, tmpBuflen - buflen);
+ } else {
+ *(short*)buffer = buflen; // first two bytes is the length
+ memcpy(buffer + sizeof(short), str.data(), buflen);
+ }
+ buffer += tmpBuflen;
+ } else {
+ str = str.leftJustified(buflen, ' ', true);
+ memcpy(buffer, str.data(), buflen);
+ buffer += buflen;
+ }
+ return buffer;
+}
+
+static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
+ QVariant::Type type, short curDim, ISC_ARRAY_DESC *arrayDesc,
+ QString& error, QTextCodec *tc)
+{
+ int i;
+ ISC_ARRAY_BOUND *bounds = arrayDesc->array_desc_bounds;
+ short dim = arrayDesc->array_desc_dimensions - 1;
+
+ int elements = (bounds[curDim].array_bound_upper -
+ bounds[curDim].array_bound_lower + 1);
+
+ if (list.size() != elements) { // size mismatch
+ error = QLatin1String("Expected size: %1. Supplied size: %2");
+ error = QLatin1String("Array size mismatch. Fieldname: %1 ")
+ + error.arg(elements).arg(list.size());
+ return 0;
+ }
+
+ if (curDim != dim) {
+ for(i = 0; i < list.size(); ++i) {
+
+ if (list.at(i).type() != QVariant::List) { // dimensions mismatch
+ error = QLatin1String("Array dimensons mismatch. Fieldname: %1");
+ return 0;
+ }
+
+ buffer = createArrayBuffer(buffer, list.at(i).toList(), type, curDim + 1,
+ arrayDesc, error, tc);
+ if (!buffer)
+ return 0;
+ }
+ } else {
+ switch(type) {
+ case QVariant::Int:
+ case QVariant::UInt:
+ if (arrayDesc->array_desc_dtype == blr_short)
+ buffer = fillList<short>(buffer, list);
+ else
+ buffer = fillList<int>(buffer, list);
+ break;
+ case QVariant::Double:
+ if (arrayDesc->array_desc_dtype == blr_float)
+ buffer = fillList<float>(buffer, list, static_cast<float *>(0));
+ else
+ buffer = fillList<double>(buffer, list);
+ break;
+ case QVariant::LongLong:
+ buffer = fillList<qint64>(buffer, list);
+ break;
+ case QVariant::ULongLong:
+ buffer = fillList<quint64>(buffer, list);
+ break;
+ case QVariant::String:
+ for (i = 0; i < list.size(); ++i)
+ buffer = qFillBufferWithString(buffer, list.at(i).toString(),
+ arrayDesc->array_desc_length,
+ arrayDesc->array_desc_dtype == blr_varying,
+ true, tc);
+ break;
+ case QVariant::Date:
+ for (i = 0; i < list.size(); ++i) {
+ *((ISC_DATE*)buffer) = toDate(list.at(i).toDate());
+ buffer += sizeof(ISC_DATE);
+ }
+ break;
+ case QVariant::Time:
+ for (i = 0; i < list.size(); ++i) {
+ *((ISC_TIME*)buffer) = toTime(list.at(i).toTime());
+ buffer += sizeof(ISC_TIME);
+ }
+ break;
+
+ case QVariant::DateTime:
+ for (i = 0; i < list.size(); ++i) {
+ *((ISC_TIMESTAMP*)buffer) = toTimeStamp(list.at(i).toDateTime());
+ buffer += sizeof(ISC_TIMESTAMP);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return buffer;
+}
+
+bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list)
+{
+ Q_Q(QIBaseResult);
+ QString error;
+ ISC_QUAD *arrayId = (ISC_QUAD*) inda->sqlvar[column].sqldata;
+ ISC_ARRAY_DESC desc;
+
+ QByteArray relname(inda->sqlvar[column].relname, inda->sqlvar[column].relname_length);
+ QByteArray sqlname(inda->sqlvar[column].aliasname, inda->sqlvar[column].aliasname_length);
+
+ isc_array_lookup_bounds(status, &ibase, &trans, relname.data(), sqlname.data(), &desc);
+ if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not find array"),
+ QSqlError::StatementError))
+ return false;
+
+ short arraySize = 1;
+ ISC_LONG bufLen;
+ QList<QVariant> subList = list;
+
+ short dimensions = desc.array_desc_dimensions;
+ for(int i = 0; i < dimensions; ++i) {
+ arraySize *= (desc.array_desc_bounds[i].array_bound_upper -
+ desc.array_desc_bounds[i].array_bound_lower + 1);
+ }
+
+ /* varying arrayelements are stored with 2 trailing null bytes
+ indicating the length of the string
+ */
+ if (desc.array_desc_dtype == blr_varying ||
+ desc.array_desc_dtype == blr_varying2)
+ desc.array_desc_length += 2;
+
+ bufLen = desc.array_desc_length * arraySize;
+ QByteArray ba;
+ ba.resize(int(bufLen));
+
+ if (list.size() > arraySize) {
+ error = QLatin1String("Array size missmatch: size of %1 is %2, size of provided list is %3");
+ error = error.arg(QLatin1String(sqlname)).arg(arraySize).arg(list.size());
+ q->setLastError(QSqlError(error, QLatin1String(""), QSqlError::StatementError));
+ return false;
+ }
+
+ if (!createArrayBuffer(ba.data(), list,
+ qIBaseTypeName(desc.array_desc_dtype, inda->sqlvar[column].sqlscale < 0),
+ 0, &desc, error, tc)) {
+ q->setLastError(QSqlError(error.arg(QLatin1String(sqlname)), QLatin1String(""),
+ QSqlError::StatementError));
+ return false;
+ }
+
+ /* readjust the buffer size*/
+ if (desc.array_desc_dtype == blr_varying
+ || desc.array_desc_dtype == blr_varying2)
+ desc.array_desc_length -= 2;
+
+ isc_array_put_slice(status, &ibase, &trans, arrayId, &desc, ba.data(), &bufLen);
+ return true;
+}
+
+
+bool QIBaseResultPrivate::isSelect()
+{
+ char acBuffer[9];
+ char qType = isc_info_sql_stmt_type;
+ isc_dsql_sql_info(status, &stmt, 1, &qType, sizeof(acBuffer), acBuffer);
+ if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get query info"),
+ QSqlError::StatementError))
+ return false;
+ int iLength = isc_vax_integer(&acBuffer[1], 2);
+ queryType = isc_vax_integer(&acBuffer[3], iLength);
+ return (queryType == isc_info_sql_stmt_select || queryType == isc_info_sql_stmt_exec_procedure);
+}
+
+bool QIBaseResultPrivate::transaction()
+{
+ if (trans)
+ return true;
+ if (drv_d_func()->trans) {
+ localTransaction = false;
+ trans = drv_d_func()->trans;
+ return true;
+ }
+ localTransaction = true;
+
+ isc_start_transaction(status, &trans, 1, &ibase, 0, NULL);
+ if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not start transaction"),
+ QSqlError::TransactionError))
+ return false;
+
+ return true;
+}
+
+// does nothing if the transaction is on the
+// driver level
+bool QIBaseResultPrivate::commit()
+{
+ if (!trans)
+ return false;
+ // don't commit driver's transaction, the driver will do it for us
+ if (!localTransaction)
+ return true;
+
+ isc_commit_transaction(status, &trans);
+ trans = 0;
+ return !isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to commit transaction"),
+ QSqlError::TransactionError);
+}
+
+//////////
+
+QIBaseResult::QIBaseResult(const QIBaseDriver *db)
+ : QSqlCachedResult(*new QIBaseResultPrivate(this, db))
+{
+}
+
+bool QIBaseResult::prepare(const QString& query)
+{
+ Q_D(QIBaseResult);
+// qDebug("prepare: %s", qPrintable(query));
+ if (!driver() || !driver()->isOpen() || driver()->isOpenError())
+ return false;
+ d->cleanup();
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+
+ createDA(d->sqlda);
+ if (d->sqlda == (XSQLDA*)0) {
+ qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory";
+ return false;
+ }
+
+ createDA(d->inda);
+ if (d->inda == (XSQLDA*)0){
+ qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory";
+ return false;
+ }
+
+ if (!d->transaction())
+ return false;
+
+ isc_dsql_allocate_statement(d->status, &d->ibase, &d->stmt);
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not allocate statement"),
+ QSqlError::StatementError))
+ return false;
+ isc_dsql_prepare(d->status, &d->trans, &d->stmt, 0,
+ const_cast<char*>(encodeString(d->tc, query).constData()), FBVERSION, d->sqlda);
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not prepare statement"),
+ QSqlError::StatementError))
+ return false;
+
+ isc_dsql_describe_bind(d->status, &d->stmt, FBVERSION, d->inda);
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult",
+ "Could not describe input statement"), QSqlError::StatementError))
+ return false;
+ if (d->inda->sqld > d->inda->sqln) {
+ enlargeDA(d->inda, d->inda->sqld);
+ if (d->inda == (XSQLDA*)0) {
+ qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory";
+ return false;
+ }
+
+ isc_dsql_describe_bind(d->status, &d->stmt, FBVERSION, d->inda);
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult",
+ "Could not describe input statement"), QSqlError::StatementError))
+ return false;
+ }
+ initDA(d->inda);
+ if (d->sqlda->sqld > d->sqlda->sqln) {
+ // need more field descriptors
+ enlargeDA(d->sqlda, d->sqlda->sqld);
+ if (d->sqlda == (XSQLDA*)0) {
+ qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory";
+ return false;
+ }
+
+ isc_dsql_describe(d->status, &d->stmt, FBVERSION, d->sqlda);
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not describe statement"),
+ QSqlError::StatementError))
+ return false;
+ }
+ initDA(d->sqlda);
+
+ setSelect(d->isSelect());
+ if (!isSelect()) {
+ free(d->sqlda);
+ d->sqlda = 0;
+ }
+
+ return true;
+}
+
+
+bool QIBaseResult::exec()
+{
+ Q_D(QIBaseResult);
+ bool ok = true;
+
+ if (!d->trans)
+ d->transaction();
+
+ if (!driver() || !driver()->isOpen() || driver()->isOpenError())
+ return false;
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+
+ if (d->inda) {
+ QVector<QVariant>& values = boundValues();
+ int i;
+ if (values.count() > d->inda->sqld) {
+ qWarning("QIBaseResult::exec: Parameter mismatch, expected %d, got %d parameters",
+ d->inda->sqld, values.count());
+ return false;
+ }
+ int para = 0;
+ for (i = 0; i < values.count(); ++i) {
+ para = i;
+ if (!d->inda->sqlvar[para].sqldata)
+ // skip unknown datatypes
+ continue;
+ const QVariant val(values[i]);
+ if (d->inda->sqlvar[para].sqltype & 1) {
+ if (val.isNull()) {
+ // set null indicator
+ *(d->inda->sqlvar[para].sqlind) = -1;
+ // and set the value to 0, otherwise it would count as empty string.
+ // it seems to be working with just setting sqlind to -1
+ //*((char*)d->inda->sqlvar[para].sqldata) = 0;
+ continue;
+ }
+ // a value of 0 means non-null.
+ *(d->inda->sqlvar[para].sqlind) = 0;
+ }
+ switch(d->inda->sqlvar[para].sqltype & ~1) {
+ case SQL_INT64:
+ if (d->inda->sqlvar[para].sqlscale < 0)
+ *((qint64*)d->inda->sqlvar[para].sqldata) =
+ (qint64)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1));
+ else
+ *((qint64*)d->inda->sqlvar[para].sqldata) = val.toLongLong();
+ break;
+ case SQL_LONG:
+ if (d->inda->sqlvar[para].sqllen == 4) {
+ if (d->inda->sqlvar[para].sqlscale < 0)
+ *((qint32*)d->inda->sqlvar[para].sqldata) =
+ (qint32)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1));
+ else
+ *((qint32*)d->inda->sqlvar[para].sqldata) = (qint32)val.toInt();
+ } else {
+ *((qint64*)d->inda->sqlvar[para].sqldata) = val.toLongLong();
+ }
+ break;
+ case SQL_SHORT:
+ if (d->inda->sqlvar[para].sqlscale < 0)
+ *((short*)d->inda->sqlvar[para].sqldata) =
+ (short)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1));
+ else
+ *((short*)d->inda->sqlvar[para].sqldata) = (short)val.toInt();
+ break;
+ case SQL_FLOAT:
+ *((float*)d->inda->sqlvar[para].sqldata) = (float)val.toDouble();
+ break;
+ case SQL_DOUBLE:
+ *((double*)d->inda->sqlvar[para].sqldata) = val.toDouble();
+ break;
+ case SQL_TIMESTAMP:
+ *((ISC_TIMESTAMP*)d->inda->sqlvar[para].sqldata) = toTimeStamp(val.toDateTime());
+ break;
+ case SQL_TYPE_TIME:
+ *((ISC_TIME*)d->inda->sqlvar[para].sqldata) = toTime(val.toTime());
+ break;
+ case SQL_TYPE_DATE:
+ *((ISC_DATE*)d->inda->sqlvar[para].sqldata) = toDate(val.toDate());
+ break;
+ case SQL_VARYING:
+ case SQL_TEXT:
+ qFillBufferWithString(d->inda->sqlvar[para].sqldata, val.toString(),
+ d->inda->sqlvar[para].sqllen,
+ (d->inda->sqlvar[para].sqltype & ~1) == SQL_VARYING, false, d->tc);
+ break;
+ case SQL_BLOB:
+ ok &= d->writeBlob(para, val.toByteArray());
+ break;
+ case SQL_ARRAY:
+ ok &= d->writeArray(para, val.toList());
+ break;
+ default:
+ qWarning("QIBaseResult::exec: Unknown datatype %d",
+ d->inda->sqlvar[para].sqltype & ~1);
+ break;
+ }
+ }
+ }
+
+ if (ok) {
+ if (colCount() && d->queryType != isc_info_sql_stmt_exec_procedure) {
+ isc_dsql_free_statement(d->status, &d->stmt, DSQL_close);
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to close statement")))
+ return false;
+ cleanup();
+ }
+ if (d->queryType == isc_info_sql_stmt_exec_procedure)
+ isc_dsql_execute2(d->status, &d->trans, &d->stmt, FBVERSION, d->inda, d->sqlda);
+ else
+ isc_dsql_execute(d->status, &d->trans, &d->stmt, FBVERSION, d->inda);
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to execute query")))
+ return false;
+
+ // Not all stored procedures necessarily return values.
+ if (d->queryType == isc_info_sql_stmt_exec_procedure && d->sqlda && d->sqlda->sqld == 0)
+ delDA(d->sqlda);
+
+ if (d->sqlda)
+ init(d->sqlda->sqld);
+
+ if (!isSelect())
+ d->commit();
+
+ setActive(true);
+ return true;
+ }
+ return false;
+}
+
+bool QIBaseResult::reset (const QString& query)
+{
+ if (!prepare(query))
+ return false;
+ return exec();
+}
+
+bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx)
+{
+ Q_D(QIBaseResult);
+ ISC_STATUS stat = 0;
+
+ // Stored Procedures are special - they populate our d->sqlda when executing,
+ // so we don't have to call isc_dsql_fetch
+ if (d->queryType == isc_info_sql_stmt_exec_procedure) {
+ // the first "fetch" shall succeed, all consecutive ones will fail since
+ // we only have one row to fetch for stored procedures
+ if (rowIdx != 0)
+ stat = 100;
+ } else {
+ stat = isc_dsql_fetch(d->status, &d->stmt, FBVERSION, d->sqlda);
+ }
+
+ if (stat == 100) {
+ // no more rows
+ setAt(QSql::AfterLastRow);
+ return false;
+ }
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not fetch next item"),
+ QSqlError::StatementError))
+ return false;
+ if (rowIdx < 0) // not interested in actual values
+ return true;
+
+ for (int i = 0; i < d->sqlda->sqld; ++i) {
+ int idx = rowIdx + i;
+ char *buf = d->sqlda->sqlvar[i].sqldata;
+ int size = d->sqlda->sqlvar[i].sqllen;
+ Q_ASSERT(buf);
+
+ if ((d->sqlda->sqlvar[i].sqltype & 1) && *d->sqlda->sqlvar[i].sqlind) {
+ // null value
+ QVariant v;
+ v.convert(qIBaseTypeName2(d->sqlda->sqlvar[i].sqltype, d->sqlda->sqlvar[i].sqlscale < 0));
+ if(v.type() == QVariant::Double) {
+ switch(numericalPrecisionPolicy()) {
+ case QSql::LowPrecisionInt32:
+ v.convert(QVariant::Int);
+ break;
+ case QSql::LowPrecisionInt64:
+ v.convert(QVariant::LongLong);
+ break;
+ case QSql::HighPrecision:
+ v.convert(QVariant::String);
+ break;
+ case QSql::LowPrecisionDouble:
+ // no conversion
+ break;
+ }
+ }
+ row[idx] = v;
+ continue;
+ }
+
+ switch(d->sqlda->sqlvar[i].sqltype & ~1) {
+ case SQL_VARYING:
+ // pascal strings - a short with a length information followed by the data
+ if (d->tc)
+ row[idx] = d->tc->toUnicode(buf + sizeof(short), *(short*)buf);
+ else
+ row[idx] = QString::fromUtf8(buf + sizeof(short), *(short*)buf);
+ break;
+ case SQL_INT64:
+ if (d->sqlda->sqlvar[i].sqlscale < 0)
+ row[idx] = *(qint64*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale);
+ else
+ row[idx] = QVariant(*(qint64*)buf);
+ break;
+ case SQL_LONG:
+ if (d->sqlda->sqlvar[i].sqllen == 4)
+ if (d->sqlda->sqlvar[i].sqlscale < 0)
+ row[idx] = QVariant(*(qint32*)buf * pow(10.0, d->sqlda->sqlvar[i].sqlscale));
+ else
+ row[idx] = QVariant(*(qint32*)buf);
+ else
+ row[idx] = QVariant(*(qint64*)buf);
+ break;
+ case SQL_SHORT:
+ if (d->sqlda->sqlvar[i].sqlscale < 0)
+ row[idx] = QVariant(long((*(short*)buf)) * pow(10.0, d->sqlda->sqlvar[i].sqlscale));
+ else
+ row[idx] = QVariant(int((*(short*)buf)));
+ break;
+ case SQL_FLOAT:
+ row[idx] = QVariant(double((*(float*)buf)));
+ break;
+ case SQL_DOUBLE:
+ row[idx] = QVariant(*(double*)buf);
+ break;
+ case SQL_TIMESTAMP:
+ row[idx] = fromTimeStamp(buf);
+ break;
+ case SQL_TYPE_TIME:
+ row[idx] = fromTime(buf);
+ break;
+ case SQL_TYPE_DATE:
+ row[idx] = fromDate(buf);
+ break;
+ case SQL_TEXT:
+ if (d->tc)
+ row[idx] = d->tc->toUnicode(buf, size);
+ else
+ row[idx] = QString::fromUtf8(buf, size);
+ break;
+ case SQL_BLOB:
+ row[idx] = d->fetchBlob((ISC_QUAD*)buf);
+ break;
+ case SQL_ARRAY:
+ row[idx] = d->fetchArray(i, (ISC_QUAD*)buf);
+ break;
+ default:
+ // unknown type - don't even try to fetch
+ row[idx] = QVariant();
+ break;
+ }
+ if (d->sqlda->sqlvar[i].sqlscale < 0) {
+ QVariant v = row[idx];
+ switch(numericalPrecisionPolicy()) {
+ case QSql::LowPrecisionInt32:
+ if(v.convert(QVariant::Int))
+ row[idx]=v;
+ break;
+ case QSql::LowPrecisionInt64:
+ if(v.convert(QVariant::LongLong))
+ row[idx]=v;
+ break;
+ case QSql::LowPrecisionDouble:
+ if(v.convert(QVariant::Double))
+ row[idx]=v;
+ break;
+ case QSql::HighPrecision:
+ if(v.convert(QVariant::String))
+ row[idx]=v;
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+int QIBaseResult::size()
+{
+ return -1;
+
+#if 0 /// ### FIXME
+ static char sizeInfo[] = {isc_info_sql_records};
+ char buf[64];
+
+ //qDebug() << sizeInfo;
+ if (!isActive() || !isSelect())
+ return -1;
+
+ char ct;
+ short len;
+ int val = 0;
+// while(val == 0) {
+ isc_dsql_sql_info(d->status, &d->stmt, sizeof(sizeInfo), sizeInfo, sizeof(buf), buf);
+// isc_database_info(d->status, &d->ibase, sizeof(sizeInfo), sizeInfo, sizeof(buf), buf);
+
+ for(int i = 0; i < 66; ++i)
+ qDebug() << QString::number(buf[i]);
+
+ for (char* c = buf + 3; *c != isc_info_end; /*nothing*/) {
+ ct = *(c++);
+ len = isc_vax_integer(c, 2);
+ c += 2;
+ val = isc_vax_integer(c, len);
+ c += len;
+ qDebug() << "size" << val;
+ if (ct == isc_info_req_select_count)
+ return val;
+ }
+ //qDebug("size -1");
+ return -1;
+
+ unsigned int i, result_size;
+ if (buf[0] == isc_info_sql_records) {
+ i = 3;
+ result_size = isc_vax_integer(&buf[1],2);
+ while (buf[i] != isc_info_end && i < result_size) {
+ len = (short)isc_vax_integer(&buf[i+1],2);
+ if (buf[i] == isc_info_req_select_count)
+ return (isc_vax_integer(&buf[i+3],len));
+ i += len+3;
+ }
+ }
+// }
+ return -1;
+#endif
+}
+
+int QIBaseResult::numRowsAffected()
+{
+ Q_D(QIBaseResult);
+ static char acCountInfo[] = {isc_info_sql_records};
+ char cCountType;
+ bool bIsProcedure = false;
+
+ switch (d->queryType) {
+ case isc_info_sql_stmt_select:
+ cCountType = isc_info_req_select_count;
+ break;
+ case isc_info_sql_stmt_update:
+ cCountType = isc_info_req_update_count;
+ break;
+ case isc_info_sql_stmt_delete:
+ cCountType = isc_info_req_delete_count;
+ break;
+ case isc_info_sql_stmt_insert:
+ cCountType = isc_info_req_insert_count;
+ break;
+ case isc_info_sql_stmt_exec_procedure:
+ bIsProcedure = true; // will sum all changes
+ break;
+ default:
+ qWarning() << "numRowsAffected: Unknown statement type (" << d->queryType << ")";
+ return -1;
+ }
+
+ char acBuffer[33];
+ int iResult = -1;
+ isc_dsql_sql_info(d->status, &d->stmt, sizeof(acCountInfo), acCountInfo, sizeof(acBuffer), acBuffer);
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseResult", "Could not get statement info"),
+ QSqlError::StatementError))
+ return -1;
+ for (char *pcBuf = acBuffer + 3; *pcBuf != isc_info_end; /*nothing*/) {
+ char cType = *pcBuf++;
+ short sLength = isc_vax_integer (pcBuf, 2);
+ pcBuf += 2;
+ int iValue = isc_vax_integer (pcBuf, sLength);
+ pcBuf += sLength;
+ if (bIsProcedure) {
+ if (cType == isc_info_req_insert_count || cType == isc_info_req_update_count
+ || cType == isc_info_req_delete_count) {
+ if (iResult == -1)
+ iResult = 0;
+ iResult += iValue;
+ }
+ } else if (cType == cCountType) {
+ iResult = iValue;
+ break;
+ }
+ }
+ return iResult;
+}
+
+QSqlRecord QIBaseResult::record() const
+{
+ Q_D(const QIBaseResult);
+ QSqlRecord rec;
+ if (!isActive() || !d->sqlda)
+ return rec;
+
+ XSQLVAR v;
+ for (int i = 0; i < d->sqlda->sqld; ++i) {
+ v = d->sqlda->sqlvar[i];
+ QSqlField f(QString::fromLatin1(v.aliasname, v.aliasname_length).simplified(),
+ qIBaseTypeName2(v.sqltype, v.sqlscale < 0));
+ f.setLength(v.sqllen);
+ f.setPrecision(qAbs(v.sqlscale));
+ f.setRequiredStatus((v.sqltype & 1) == 0 ? QSqlField::Required : QSqlField::Optional);
+ if(v.sqlscale < 0) {
+ QSqlQuery q(driver()->createResult());
+ q.setForwardOnly(true);
+ q.exec(QLatin1String("select b.RDB$FIELD_PRECISION, b.RDB$FIELD_SCALE, b.RDB$FIELD_LENGTH, a.RDB$NULL_FLAG "
+ "FROM RDB$RELATION_FIELDS a, RDB$FIELDS b "
+ "WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE "
+ "AND a.RDB$RELATION_NAME = '") + QString::fromLatin1(v.relname, v.relname_length).toUpper() + QLatin1String("' "
+ "AND a.RDB$FIELD_NAME = '") + QString::fromLatin1(v.sqlname, v.sqlname_length).toUpper() + QLatin1String("' "));
+ if(q.first()) {
+ if(v.sqlscale < 0) {
+ f.setLength(q.value(0).toInt());
+ f.setPrecision(qAbs(q.value(1).toInt()));
+ } else {
+ f.setLength(q.value(2).toInt());
+ f.setPrecision(0);
+ }
+ f.setRequiredStatus(q.value(3).toBool() ? QSqlField::Required : QSqlField::Optional);
+ }
+ }
+ f.setSqlType(v.sqltype);
+ rec.append(f);
+ }
+ return rec;
+}
+
+QVariant QIBaseResult::handle() const
+{
+ Q_D(const QIBaseResult);
+ return QVariant(qRegisterMetaType<isc_stmt_handle>("isc_stmt_handle"), &d->stmt);
+}
+
+/*********************************/
+
+QIBaseDriver::QIBaseDriver(QObject * parent)
+ : QSqlDriver(*new QIBaseDriverPrivate, parent)
+{
+}
+
+QIBaseDriver::QIBaseDriver(isc_db_handle connection, QObject *parent)
+ : QSqlDriver(*new QIBaseDriverPrivate, parent)
+{
+ Q_D(QIBaseDriver);
+ d->ibase = connection;
+ setOpen(true);
+ setOpenError(false);
+}
+
+QIBaseDriver::~QIBaseDriver()
+{
+}
+
+bool QIBaseDriver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case QuerySize:
+ case NamedPlaceholders:
+ case LastInsertId:
+ case BatchOperations:
+ case SimpleLocking:
+ case FinishQuery:
+ case MultipleResultSets:
+ case CancelQuery:
+ return false;
+ case Transactions:
+ case PreparedQueries:
+ case PositionalPlaceholders:
+ case Unicode:
+ case BLOB:
+ case EventNotifications:
+ case LowPrecisionNumbers:
+ return true;
+ }
+ return false;
+}
+
+bool QIBaseDriver::open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString & connOpts)
+{
+ Q_D(QIBaseDriver);
+ if (isOpen())
+ close();
+
+ const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
+
+ QString encString;
+ QByteArray role;
+ for (int i = 0; i < opts.count(); ++i) {
+ QString tmp(opts.at(i).simplified());
+ int idx;
+ if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) {
+ QString val = tmp.mid(idx + 1).simplified();
+ QString opt = tmp.left(idx).simplified();
+ if (opt.toUpper() == QLatin1String("ISC_DPB_LC_CTYPE"))
+ encString = val;
+ else if (opt.toUpper() == QLatin1String("ISC_DPB_SQL_ROLE_NAME")) {
+ role = val.toLocal8Bit();
+ role.truncate(255);
+ }
+ }
+ }
+
+ // Use UNICODE_FSS when no ISC_DPB_LC_CTYPE is provided
+ if (encString.isEmpty())
+ encString = QLatin1String("UNICODE_FSS");
+ else {
+ d->tc = QTextCodec::codecForName(encString.toLocal8Bit());
+ if (!d->tc) {
+ qWarning("Unsupported encoding: %s. Using UNICODE_FFS for ISC_DPB_LC_CTYPE.", encString.toLocal8Bit().constData());
+ encString = QLatin1String("UNICODE_FSS"); // Fallback to UNICODE_FSS
+ }
+ }
+
+ QByteArray enc = encString.toLocal8Bit();
+ QByteArray usr = user.toLocal8Bit();
+ QByteArray pass = password.toLocal8Bit();
+ enc.truncate(255);
+ usr.truncate(255);
+ pass.truncate(255);
+
+ QByteArray ba;
+ ba.reserve(usr.length() + pass.length() + enc.length() + role.length() + 9);
+ ba.append(char(isc_dpb_version1));
+ ba.append(char(isc_dpb_user_name));
+ ba.append(char(usr.length()));
+ ba.append(usr.data(), usr.length());
+ ba.append(char(isc_dpb_password));
+ ba.append(char(pass.length()));
+ ba.append(pass.data(), pass.length());
+ ba.append(char(isc_dpb_lc_ctype));
+ ba.append(char(enc.length()));
+ ba.append(enc.data(), enc.length());
+
+ if (!role.isEmpty()) {
+ ba.append(char(isc_dpb_sql_role_name));
+ ba.append(char(role.length()));
+ ba.append(role.data(), role.length());
+ }
+
+ QString portString;
+ if (port != -1)
+ portString = QStringLiteral("/%1").arg(port);
+
+ QString ldb;
+ if (!host.isEmpty())
+ ldb += host + portString + QLatin1Char(':');
+ ldb += db;
+ isc_attach_database(d->status, 0, const_cast<char *>(ldb.toLocal8Bit().constData()),
+ &d->ibase, ba.size(), ba.data());
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Error opening database"),
+ QSqlError::ConnectionError)) {
+ setOpenError(true);
+ return false;
+ }
+
+ setOpen(true);
+ setOpenError(false);
+ return true;
+}
+
+void QIBaseDriver::close()
+{
+ Q_D(QIBaseDriver);
+ if (isOpen()) {
+
+ if (d->eventBuffers.size()) {
+ ISC_STATUS status[20];
+ QMap<QString, QIBaseEventBuffer *>::const_iterator i;
+ for (i = d->eventBuffers.constBegin(); i != d->eventBuffers.constEnd(); ++i) {
+ QIBaseEventBuffer *eBuffer = i.value();
+ eBuffer->subscriptionState = QIBaseEventBuffer::Finished;
+ isc_cancel_events(status, &d->ibase, &eBuffer->eventId);
+ qFreeEventBuffer(eBuffer);
+ }
+ d->eventBuffers.clear();
+
+#if defined(FB_API_VER)
+ // Workaround for Firebird crash
+ QTime timer;
+ timer.start();
+ while (timer.elapsed() < 500)
+ QCoreApplication::processEvents();
+#endif
+ }
+
+ isc_detach_database(d->status, &d->ibase);
+ d->ibase = 0;
+ setOpen(false);
+ setOpenError(false);
+ }
+}
+
+QSqlResult *QIBaseDriver::createResult() const
+{
+ return new QIBaseResult(this);
+}
+
+bool QIBaseDriver::beginTransaction()
+{
+ Q_D(QIBaseDriver);
+ if (!isOpen() || isOpenError())
+ return false;
+ if (d->trans)
+ return false;
+
+ isc_start_transaction(d->status, &d->trans, 1, &d->ibase, 0, NULL);
+ return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Could not start transaction"),
+ QSqlError::TransactionError);
+}
+
+bool QIBaseDriver::commitTransaction()
+{
+ Q_D(QIBaseDriver);
+ if (!isOpen() || isOpenError())
+ return false;
+ if (!d->trans)
+ return false;
+
+ isc_commit_transaction(d->status, &d->trans);
+ d->trans = 0;
+ return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Unable to commit transaction"),
+ QSqlError::TransactionError);
+}
+
+bool QIBaseDriver::rollbackTransaction()
+{
+ Q_D(QIBaseDriver);
+ if (!isOpen() || isOpenError())
+ return false;
+ if (!d->trans)
+ return false;
+
+ isc_rollback_transaction(d->status, &d->trans);
+ d->trans = 0;
+ return !d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Unable to rollback transaction"),
+ QSqlError::TransactionError);
+}
+
+QStringList QIBaseDriver::tables(QSql::TableType type) const
+{
+ QStringList res;
+ if (!isOpen())
+ return res;
+
+ QString typeFilter;
+
+ if (type == QSql::SystemTables) {
+ typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0");
+ } else if (type == (QSql::SystemTables | QSql::Views)) {
+ typeFilter += QLatin1String("RDB$SYSTEM_FLAG != 0 OR RDB$VIEW_BLR NOT NULL");
+ } else {
+ if (!(type & QSql::SystemTables))
+ typeFilter += QLatin1String("RDB$SYSTEM_FLAG = 0 AND ");
+ if (!(type & QSql::Views))
+ typeFilter += QLatin1String("RDB$VIEW_BLR IS NULL AND ");
+ if (!(type & QSql::Tables))
+ typeFilter += QLatin1String("RDB$VIEW_BLR IS NOT NULL AND ");
+ if (!typeFilter.isEmpty())
+ typeFilter.chop(5);
+ }
+ if (!typeFilter.isEmpty())
+ typeFilter.prepend(QLatin1String("where "));
+
+ QSqlQuery q(createResult());
+ q.setForwardOnly(true);
+ if (!q.exec(QLatin1String("select rdb$relation_name from rdb$relations ") + typeFilter))
+ return res;
+ while(q.next())
+ res << q.value(0).toString().simplified();
+
+ return res;
+}
+
+QSqlRecord QIBaseDriver::record(const QString& tablename) const
+{
+ QSqlRecord rec;
+ if (!isOpen())
+ return rec;
+
+ QSqlQuery q(createResult());
+ q.setForwardOnly(true);
+ QString table = tablename;
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ else
+ table = table.toUpper();
+ q.exec(QLatin1String("SELECT a.RDB$FIELD_NAME, b.RDB$FIELD_TYPE, b.RDB$FIELD_LENGTH, "
+ "b.RDB$FIELD_SCALE, b.RDB$FIELD_PRECISION, a.RDB$NULL_FLAG "
+ "FROM RDB$RELATION_FIELDS a, RDB$FIELDS b "
+ "WHERE b.RDB$FIELD_NAME = a.RDB$FIELD_SOURCE "
+ "AND a.RDB$RELATION_NAME = '") + table + QLatin1String("' "
+ "ORDER BY a.RDB$FIELD_POSITION"));
+
+ while (q.next()) {
+ int type = q.value(1).toInt();
+ bool hasScale = q.value(3).toInt() < 0;
+ QSqlField f(q.value(0).toString().simplified(), qIBaseTypeName(type, hasScale));
+ if(hasScale) {
+ f.setLength(q.value(4).toInt());
+ f.setPrecision(qAbs(q.value(3).toInt()));
+ } else {
+ f.setLength(q.value(2).toInt());
+ f.setPrecision(0);
+ }
+ f.setRequired(q.value(5).toInt() > 0);
+ f.setSqlType(type);
+
+ rec.append(f);
+ }
+ return rec;
+}
+
+QSqlIndex QIBaseDriver::primaryIndex(const QString &table) const
+{
+ QSqlIndex index(table);
+ if (!isOpen())
+ return index;
+
+ QString tablename = table;
+ if (isIdentifierEscaped(tablename, QSqlDriver::TableName))
+ tablename = stripDelimiters(tablename, QSqlDriver::TableName);
+ else
+ tablename = tablename.toUpper();
+
+ QSqlQuery q(createResult());
+ q.setForwardOnly(true);
+ q.exec(QLatin1String("SELECT a.RDB$INDEX_NAME, b.RDB$FIELD_NAME, d.RDB$FIELD_TYPE, d.RDB$FIELD_SCALE "
+ "FROM RDB$RELATION_CONSTRAINTS a, RDB$INDEX_SEGMENTS b, RDB$RELATION_FIELDS c, RDB$FIELDS d "
+ "WHERE a.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' "
+ "AND a.RDB$RELATION_NAME = '") + tablename +
+ QLatin1String(" 'AND a.RDB$INDEX_NAME = b.RDB$INDEX_NAME "
+ "AND c.RDB$RELATION_NAME = a.RDB$RELATION_NAME "
+ "AND c.RDB$FIELD_NAME = b.RDB$FIELD_NAME "
+ "AND d.RDB$FIELD_NAME = c.RDB$FIELD_SOURCE "
+ "ORDER BY b.RDB$FIELD_POSITION"));
+
+ while (q.next()) {
+ QSqlField field(q.value(1).toString().simplified(), qIBaseTypeName(q.value(2).toInt(), q.value(3).toInt() < 0));
+ index.append(field); //TODO: asc? desc?
+ index.setName(q.value(0).toString());
+ }
+
+ return index;
+}
+
+QString QIBaseDriver::formatValue(const QSqlField &field, bool trimStrings) const
+{
+ switch (field.type()) {
+ case QVariant::DateTime: {
+ QDateTime datetime = field.value().toDateTime();
+ if (datetime.isValid())
+ return QLatin1Char('\'') + QString::number(datetime.date().year()) + QLatin1Char('-') +
+ QString::number(datetime.date().month()) + QLatin1Char('-') +
+ QString::number(datetime.date().day()) + QLatin1Char(' ') +
+ QString::number(datetime.time().hour()) + QLatin1Char(':') +
+ QString::number(datetime.time().minute()) + QLatin1Char(':') +
+ QString::number(datetime.time().second()) + QLatin1Char('.') +
+ QString::number(datetime.time().msec()).rightJustified(3, QLatin1Char('0'), true) +
+ QLatin1Char('\'');
+ else
+ return QLatin1String("NULL");
+ }
+ case QVariant::Time: {
+ QTime time = field.value().toTime();
+ if (time.isValid())
+ return QLatin1Char('\'') + QString::number(time.hour()) + QLatin1Char(':') +
+ QString::number(time.minute()) + QLatin1Char(':') +
+ QString::number(time.second()) + QLatin1Char('.') +
+ QString::number(time.msec()).rightJustified(3, QLatin1Char('0'), true) +
+ QLatin1Char('\'');
+ else
+ return QLatin1String("NULL");
+ }
+ case QVariant::Date: {
+ QDate date = field.value().toDate();
+ if (date.isValid())
+ return QLatin1Char('\'') + QString::number(date.year()) + QLatin1Char('-') +
+ QString::number(date.month()) + QLatin1Char('-') +
+ QString::number(date.day()) + QLatin1Char('\'');
+ else
+ return QLatin1String("NULL");
+ }
+ default:
+ return QSqlDriver::formatValue(field, trimStrings);
+ }
+}
+
+QVariant QIBaseDriver::handle() const
+{
+ Q_D(const QIBaseDriver);
+ return QVariant(qRegisterMetaType<isc_db_handle>("isc_db_handle"), &d->ibase);
+}
+
+#if defined(FB_API_VER) && FB_API_VER >= 20
+static ISC_EVENT_CALLBACK qEventCallback(char *result, ISC_USHORT length, const ISC_UCHAR *updated)
+#else
+static isc_callback qEventCallback(char *result, short length, char *updated)
+#endif
+{
+ if (!updated)
+ return 0;
+
+
+ memcpy(result, updated, length);
+ qMutex()->lock();
+ QIBaseDriver *driver = qBufferDriverMap()->value(result);
+ qMutex()->unlock();
+
+ // We use an asynchronous call (i.e., queued connection) because the event callback
+ // is executed in a different thread than the one in which the driver lives.
+ if (driver)
+ QMetaObject::invokeMethod(driver, "qHandleEventNotification", Qt::QueuedConnection, Q_ARG(void *, reinterpret_cast<void *>(result)));
+
+ return 0;
+}
+
+bool QIBaseDriver::subscribeToNotification(const QString &name)
+{
+ Q_D(QIBaseDriver);
+ if (!isOpen()) {
+ qWarning("QIBaseDriver::subscribeFromNotificationImplementation: database not open.");
+ return false;
+ }
+
+ if (d->eventBuffers.contains(name)) {
+ qWarning("QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%s'.",
+ qPrintable(name));
+ return false;
+ }
+
+ QIBaseEventBuffer *eBuffer = new QIBaseEventBuffer;
+ eBuffer->subscriptionState = QIBaseEventBuffer::Starting;
+ eBuffer->bufferLength = isc_event_block(&eBuffer->eventBuffer,
+ &eBuffer->resultBuffer,
+ 1,
+ name.toLocal8Bit().constData());
+
+ qMutex()->lock();
+ qBufferDriverMap()->insert(eBuffer->resultBuffer, this);
+ qMutex()->unlock();
+
+ d->eventBuffers.insert(name, eBuffer);
+
+ ISC_STATUS status[20];
+ isc_que_events(status,
+ &d->ibase,
+ &eBuffer->eventId,
+ eBuffer->bufferLength,
+ eBuffer->eventBuffer,
+#if defined (FB_API_VER) && FB_API_VER >= 20
+ (ISC_EVENT_CALLBACK)qEventCallback,
+#else
+ (isc_callback)qEventCallback,
+#endif
+ eBuffer->resultBuffer);
+
+ if (status[0] == 1 && status[1]) {
+ setLastError(QSqlError(QString::fromLatin1("Could not subscribe to event notifications for %1.").arg(name)));
+ d->eventBuffers.remove(name);
+ qFreeEventBuffer(eBuffer);
+ return false;
+ }
+
+ return true;
+}
+
+bool QIBaseDriver::unsubscribeFromNotification(const QString &name)
+{
+ Q_D(QIBaseDriver);
+ if (!isOpen()) {
+ qWarning("QIBaseDriver::unsubscribeFromNotificationImplementation: database not open.");
+ return false;
+ }
+
+ if (!d->eventBuffers.contains(name)) {
+ qWarning("QIBaseDriver::QIBaseSubscriptionState not subscribed to '%s'.",
+ qPrintable(name));
+ return false;
+ }
+
+ QIBaseEventBuffer *eBuffer = d->eventBuffers.value(name);
+ ISC_STATUS status[20];
+ eBuffer->subscriptionState = QIBaseEventBuffer::Finished;
+ isc_cancel_events(status, &d->ibase, &eBuffer->eventId);
+
+ if (status[0] == 1 && status[1]) {
+ setLastError(QSqlError(QString::fromLatin1("Could not unsubscribe from event notifications for %1.").arg(name)));
+ return false;
+ }
+
+ d->eventBuffers.remove(name);
+ qFreeEventBuffer(eBuffer);
+
+ return true;
+}
+
+QStringList QIBaseDriver::subscribedToNotifications() const
+{
+ Q_D(const QIBaseDriver);
+ return QStringList(d->eventBuffers.keys());
+}
+
+void QIBaseDriver::qHandleEventNotification(void *updatedResultBuffer)
+{
+ Q_D(QIBaseDriver);
+ QMap<QString, QIBaseEventBuffer *>::const_iterator i;
+ for (i = d->eventBuffers.constBegin(); i != d->eventBuffers.constEnd(); ++i) {
+ QIBaseEventBuffer* eBuffer = i.value();
+ if (reinterpret_cast<void *>(eBuffer->resultBuffer) != updatedResultBuffer)
+ continue;
+
+ ISC_ULONG counts[20];
+ memset(counts, 0, sizeof(counts));
+ isc_event_counts(counts, eBuffer->bufferLength, eBuffer->eventBuffer, eBuffer->resultBuffer);
+ if (counts[0]) {
+
+ if (eBuffer->subscriptionState == QIBaseEventBuffer::Subscribed) {
+ emit notification(i.key());
+ emit notification(i.key(), QSqlDriver::UnknownSource, QVariant());
+ }
+ else if (eBuffer->subscriptionState == QIBaseEventBuffer::Starting)
+ eBuffer->subscriptionState = QIBaseEventBuffer::Subscribed;
+
+ ISC_STATUS status[20];
+ isc_que_events(status,
+ &d->ibase,
+ &eBuffer->eventId,
+ eBuffer->bufferLength,
+ eBuffer->eventBuffer,
+#if defined (FB_API_VER) && FB_API_VER >= 20
+ (ISC_EVENT_CALLBACK)qEventCallback,
+#else
+ (isc_callback)qEventCallback,
+#endif
+ eBuffer->resultBuffer);
+ if (Q_UNLIKELY(status[0] == 1 && status[1])) {
+ qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%s'",
+ qPrintable(i.key()));
+ }
+
+ return;
+ }
+ }
+}
+
+QString QIBaseDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
+{
+ QString res = identifier;
+ if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) {
+ res.replace(QLatin1Char('"'), QLatin1String("\"\""));
+ res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
+ res.replace(QLatin1Char('.'), QLatin1String("\".\""));
+ }
+ return res;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase_p.h b/src/plugins/sqldrivers/ibase/qsql_ibase_p.h
new file mode 100644
index 0000000000..c7cee41462
--- /dev/null
+++ b/src/plugins/sqldrivers/ibase/qsql_ibase_p.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_IBASE_H
+#define QSQL_IBASE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtSql/qsqldriver.h>
+#include <ibase.h>
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_IBASE
+#else
+#define Q_EXPORT_SQLDRIVER_IBASE Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QSqlResult;
+class QIBaseDriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_IBASE QIBaseDriver : public QSqlDriver
+{
+ friend class QIBaseResultPrivate;
+ Q_DECLARE_PRIVATE(QIBaseDriver)
+ Q_OBJECT
+public:
+ explicit QIBaseDriver(QObject *parent = 0);
+ explicit QIBaseDriver(isc_db_handle connection, QObject *parent = 0);
+ virtual ~QIBaseDriver();
+ bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE;
+ bool open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port,
+ const QString &connOpts) Q_DECL_OVERRIDE;
+ bool open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port) { return open(db, user, password, host, port, QString()); }
+ void close() Q_DECL_OVERRIDE;
+ QSqlResult *createResult() const Q_DECL_OVERRIDE;
+ bool beginTransaction() Q_DECL_OVERRIDE;
+ bool commitTransaction() Q_DECL_OVERRIDE;
+ bool rollbackTransaction() Q_DECL_OVERRIDE;
+ QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE;
+
+ QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE;
+ QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE;
+
+ QString formatValue(const QSqlField &field, bool trimStrings) const Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE;
+
+ bool subscribeToNotification(const QString &name) Q_DECL_OVERRIDE;
+ bool unsubscribeFromNotification(const QString &name) Q_DECL_OVERRIDE;
+ QStringList subscribedToNotifications() const Q_DECL_OVERRIDE;
+
+private Q_SLOTS:
+ void qHandleEventNotification(void* updatedResultBuffer);
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_IBASE_H
diff --git a/src/plugins/sqldrivers/mysql/main.cpp b/src/plugins/sqldrivers/mysql/main.cpp
index 855a6eeb9c..00d9c5bb34 100644
--- a/src/plugins/sqldrivers/mysql/main.cpp
+++ b/src/plugins/sqldrivers/mysql/main.cpp
@@ -39,7 +39,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
-#include "../../../sql/drivers/mysql/qsql_mysql_p.h"
+#include "qsql_mysql_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/sqldrivers/mysql/mysql.pro b/src/plugins/sqldrivers/mysql/mysql.pro
index c917bfca48..442ad7f5a4 100644
--- a/src/plugins/sqldrivers/mysql/mysql.pro
+++ b/src/plugins/sqldrivers/mysql/mysql.pro
@@ -1,8 +1,23 @@
TARGET = qsqlmysql
-SOURCES = main.cpp
+HEADERS += $$PWD/qsql_mysql_p.h
+SOURCES += $$PWD/qsql_mysql.cpp $$PWD/main.cpp
+
+QMAKE_CXXFLAGS *= $$QT_CFLAGS_MYSQL
+LIBS += $$QT_LFLAGS_MYSQL
+
+unix {
+ isEmpty(QT_LFLAGS_MYSQL) {
+ !contains(LIBS, .*mysqlclient.*):!contains(LIBS, .*mysqld.*) {
+ use_libmysqlclient_r:LIBS += -lmysqlclient_r
+ else:LIBS += -lmysqlclient
+ }
+ }
+} else {
+ !contains(LIBS, .*mysql.*):!contains(LIBS, .*mysqld.*):LIBS += -llibmysql
+}
+
OTHER_FILES += mysql.json
-include(../../../sql/drivers/mysql/qsql_mysql.pri)
PLUGIN_CLASS_NAME = QMYSQLDriverPlugin
include(../qsqldriverbase.pri)
diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
new file mode 100644
index 0000000000..56caf52dcb
--- /dev/null
+++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
@@ -0,0 +1,1665 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_mysql_p.h"
+
+#include <qcoreapplication.h>
+#include <qvariant.h>
+#include <qdatetime.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlquery.h>
+#include <qsqlrecord.h>
+#include <qstringlist.h>
+#include <qtextcodec.h>
+#include <qvector.h>
+#include <qfile.h>
+#include <qdebug.h>
+#include <QtSql/private/qsqldriver_p.h>
+#include <QtSql/private/qsqlresult_p.h>
+
+#ifdef Q_OS_WIN32
+// comment the next line out if you want to use MySQL/embedded on Win32 systems.
+// note that it will crash if you don't statically link to the mysql/e library!
+# define Q_NO_MYSQL_EMBEDDED
+#endif
+
+Q_DECLARE_METATYPE(MYSQL_RES*)
+Q_DECLARE_METATYPE(MYSQL*)
+
+#if MYSQL_VERSION_ID >= 40108
+Q_DECLARE_METATYPE(MYSQL_STMT*)
+#endif
+
+#if MYSQL_VERSION_ID >= 40100
+# define Q_CLIENT_MULTI_STATEMENTS CLIENT_MULTI_STATEMENTS
+#else
+# define Q_CLIENT_MULTI_STATEMENTS 0
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QMYSQLDriverPrivate : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QMYSQLDriver)
+
+public:
+ QMYSQLDriverPrivate() : QSqlDriverPrivate(), mysql(0),
+#ifndef QT_NO_TEXTCODEC
+ tc(QTextCodec::codecForLocale()),
+#else
+ tc(0),
+#endif
+ preparedQuerysEnabled(false) { dbmsType = QSqlDriver::MySqlServer; }
+ MYSQL *mysql;
+ QTextCodec *tc;
+
+ bool preparedQuerysEnabled;
+};
+
+static inline QString toUnicode(QTextCodec *tc, const char *str)
+{
+#ifdef QT_NO_TEXTCODEC
+ Q_UNUSED(tc);
+ return QString::fromLatin1(str);
+#else
+ return tc->toUnicode(str);
+#endif
+}
+
+static inline QString toUnicode(QTextCodec *tc, const char *str, int length)
+{
+#ifdef QT_NO_TEXTCODEC
+ Q_UNUSED(tc);
+ return QString::fromLatin1(str, length);
+#else
+ return tc->toUnicode(str, length);
+#endif
+}
+
+static inline QByteArray fromUnicode(QTextCodec *tc, const QString &str)
+{
+#ifdef QT_NO_TEXTCODEC
+ Q_UNUSED(tc);
+ return str.toLatin1();
+#else
+ return tc->fromUnicode(str);
+#endif
+}
+
+static inline QVariant qDateFromString(const QString &val)
+{
+#ifdef QT_NO_DATESTRING
+ Q_UNUSED(val);
+ return QVariant(val);
+#else
+ if (val.isEmpty())
+ return QVariant(QDate());
+ return QVariant(QDate::fromString(val, Qt::ISODate));
+#endif
+}
+
+static inline QVariant qTimeFromString(const QString &val)
+{
+#ifdef QT_NO_DATESTRING
+ Q_UNUSED(val);
+ return QVariant(val);
+#else
+ if (val.isEmpty())
+ return QVariant(QTime());
+ return QVariant(QTime::fromString(val, Qt::ISODate));
+#endif
+}
+
+static inline QVariant qDateTimeFromString(QString &val)
+{
+#ifdef QT_NO_DATESTRING
+ Q_UNUSED(val);
+ return QVariant(val);
+#else
+ if (val.isEmpty())
+ return QVariant(QDateTime());
+ if (val.length() == 14)
+ // TIMESTAMPS have the format yyyyMMddhhmmss
+ val.insert(4, QLatin1Char('-')).insert(7, QLatin1Char('-')).insert(10,
+ QLatin1Char('T')).insert(13, QLatin1Char(':')).insert(16, QLatin1Char(':'));
+ return QVariant(QDateTime::fromString(val, Qt::ISODate));
+#endif
+}
+
+class QMYSQLResultPrivate;
+
+class QMYSQLResult : public QSqlResult
+{
+ Q_DECLARE_PRIVATE(QMYSQLResult)
+ friend class QMYSQLDriver;
+
+public:
+ explicit QMYSQLResult(const QMYSQLDriver *db);
+ ~QMYSQLResult();
+
+ QVariant handle() const Q_DECL_OVERRIDE;
+protected:
+ void cleanup();
+ bool fetch(int i) Q_DECL_OVERRIDE;
+ bool fetchNext() Q_DECL_OVERRIDE;
+ bool fetchLast() Q_DECL_OVERRIDE;
+ bool fetchFirst() Q_DECL_OVERRIDE;
+ QVariant data(int field) Q_DECL_OVERRIDE;
+ bool isNull(int field) Q_DECL_OVERRIDE;
+ bool reset (const QString& query) Q_DECL_OVERRIDE;
+ int size() Q_DECL_OVERRIDE;
+ int numRowsAffected() Q_DECL_OVERRIDE;
+ QVariant lastInsertId() const Q_DECL_OVERRIDE;
+ QSqlRecord record() const Q_DECL_OVERRIDE;
+ void virtual_hook(int id, void *data) Q_DECL_OVERRIDE;
+ bool nextResult() Q_DECL_OVERRIDE;
+
+#if MYSQL_VERSION_ID >= 40108
+ bool prepare(const QString &stmt) Q_DECL_OVERRIDE;
+ bool exec() Q_DECL_OVERRIDE;
+#endif
+};
+
+class QMYSQLResultPrivate: public QSqlResultPrivate
+{
+ Q_DECLARE_PUBLIC(QMYSQLResult)
+
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QMYSQLDriver)
+
+ QMYSQLResultPrivate(QMYSQLResult *q, const QMYSQLDriver *drv)
+ : QSqlResultPrivate(q, drv),
+ result(0),
+ rowsAffected(0),
+ hasBlobs(false)
+#if MYSQL_VERSION_ID >= 40108
+ , stmt(0), meta(0), inBinds(0), outBinds(0)
+#endif
+ , preparedQuery(false)
+ { }
+
+ MYSQL_RES *result;
+ MYSQL_ROW row;
+
+ int rowsAffected;
+
+ bool bindInValues();
+ void bindBlobs();
+
+ bool hasBlobs;
+ struct QMyField
+ {
+ QMyField()
+ : outField(0), nullIndicator(false), bufLength(0ul),
+ myField(0), type(QVariant::Invalid)
+ {}
+ char *outField;
+ my_bool nullIndicator;
+ ulong bufLength;
+ MYSQL_FIELD *myField;
+ QVariant::Type type;
+ };
+
+ QVector<QMyField> fields;
+
+#if MYSQL_VERSION_ID >= 40108
+ MYSQL_STMT* stmt;
+ MYSQL_RES* meta;
+
+ MYSQL_BIND *inBinds;
+ MYSQL_BIND *outBinds;
+#endif
+
+ bool preparedQuery;
+};
+
+#ifndef QT_NO_TEXTCODEC
+static QTextCodec* codec(MYSQL* mysql)
+{
+#if MYSQL_VERSION_ID >= 32321
+ QTextCodec* heuristicCodec = QTextCodec::codecForName(mysql_character_set_name(mysql));
+ if (heuristicCodec)
+ return heuristicCodec;
+#endif
+ return QTextCodec::codecForLocale();
+}
+#endif // QT_NO_TEXTCODEC
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
+ const QMYSQLDriverPrivate* p)
+{
+ const char *cerr = p->mysql ? mysql_error(p->mysql) : 0;
+ return QSqlError(QLatin1String("QMYSQL: ") + err,
+ p->tc ? toUnicode(p->tc, cerr) : QString::fromLatin1(cerr),
+ type, mysql_errno(p->mysql));
+}
+
+
+static QVariant::Type qDecodeMYSQLType(int mysqltype, uint flags)
+{
+ QVariant::Type type;
+ switch (mysqltype) {
+ case FIELD_TYPE_TINY :
+ type = static_cast<QVariant::Type>((flags & UNSIGNED_FLAG) ? QMetaType::UChar : QMetaType::Char);
+ break;
+ case FIELD_TYPE_SHORT :
+ type = static_cast<QVariant::Type>((flags & UNSIGNED_FLAG) ? QMetaType::UShort : QMetaType::Short);
+ break;
+ case FIELD_TYPE_LONG :
+ case FIELD_TYPE_INT24 :
+ type = (flags & UNSIGNED_FLAG) ? QVariant::UInt : QVariant::Int;
+ break;
+ case FIELD_TYPE_YEAR :
+ type = QVariant::Int;
+ break;
+ case FIELD_TYPE_LONGLONG :
+ type = (flags & UNSIGNED_FLAG) ? QVariant::ULongLong : QVariant::LongLong;
+ break;
+ case FIELD_TYPE_FLOAT :
+ case FIELD_TYPE_DOUBLE :
+ case FIELD_TYPE_DECIMAL :
+#if defined(FIELD_TYPE_NEWDECIMAL)
+ case FIELD_TYPE_NEWDECIMAL:
+#endif
+ type = QVariant::Double;
+ break;
+ case FIELD_TYPE_DATE :
+ type = QVariant::Date;
+ break;
+ case FIELD_TYPE_TIME :
+ type = QVariant::Time;
+ break;
+ case FIELD_TYPE_DATETIME :
+ case FIELD_TYPE_TIMESTAMP :
+ type = QVariant::DateTime;
+ break;
+ case FIELD_TYPE_STRING :
+ case FIELD_TYPE_VAR_STRING :
+ case FIELD_TYPE_BLOB :
+ case FIELD_TYPE_TINY_BLOB :
+ case FIELD_TYPE_MEDIUM_BLOB :
+ case FIELD_TYPE_LONG_BLOB :
+ type = (flags & BINARY_FLAG) ? QVariant::ByteArray : QVariant::String;
+ break;
+ default:
+ case FIELD_TYPE_ENUM :
+ case FIELD_TYPE_SET :
+ type = QVariant::String;
+ break;
+ }
+ return type;
+}
+
+static QSqlField qToField(MYSQL_FIELD *field, QTextCodec *tc)
+{
+ QSqlField f(toUnicode(tc, field->name),
+ qDecodeMYSQLType(int(field->type), field->flags));
+ f.setRequired(IS_NOT_NULL(field->flags));
+ f.setLength(field->length);
+ f.setPrecision(field->decimals);
+ f.setSqlType(field->type);
+ f.setAutoValue(field->flags & AUTO_INCREMENT_FLAG);
+ return f;
+}
+
+#if MYSQL_VERSION_ID >= 40108
+
+static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type,
+ MYSQL_STMT* stmt)
+{
+ const char *cerr = mysql_stmt_error(stmt);
+ return QSqlError(QLatin1String("QMYSQL3: ") + err,
+ QString::fromLatin1(cerr),
+ type, mysql_stmt_errno(stmt));
+}
+
+static bool qIsBlob(int t)
+{
+ return t == MYSQL_TYPE_TINY_BLOB
+ || t == MYSQL_TYPE_BLOB
+ || t == MYSQL_TYPE_MEDIUM_BLOB
+ || t == MYSQL_TYPE_LONG_BLOB;
+}
+
+static bool qIsInteger(int t)
+{
+ return t == QMetaType::Char || t == QMetaType::UChar
+ || t == QMetaType::Short || t == QMetaType::UShort
+ || t == QMetaType::Int || t == QMetaType::UInt
+ || t == QMetaType::LongLong || t == QMetaType::ULongLong;
+}
+
+void QMYSQLResultPrivate::bindBlobs()
+{
+ int i;
+ MYSQL_FIELD *fieldInfo;
+ MYSQL_BIND *bind;
+
+ for(i = 0; i < fields.count(); ++i) {
+ fieldInfo = fields.at(i).myField;
+ if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo) {
+ bind = &inBinds[i];
+ bind->buffer_length = fieldInfo->max_length;
+ delete[] static_cast<char*>(bind->buffer);
+ bind->buffer = new char[fieldInfo->max_length];
+ fields[i].outField = static_cast<char*>(bind->buffer);
+ }
+ }
+}
+
+bool QMYSQLResultPrivate::bindInValues()
+{
+ MYSQL_BIND *bind;
+ char *field;
+ int i = 0;
+
+ if (!meta)
+ meta = mysql_stmt_result_metadata(stmt);
+ if (!meta)
+ return false;
+
+ fields.resize(mysql_num_fields(meta));
+
+ inBinds = new MYSQL_BIND[fields.size()];
+ memset(inBinds, 0, fields.size() * sizeof(MYSQL_BIND));
+
+ MYSQL_FIELD *fieldInfo;
+
+ while((fieldInfo = mysql_fetch_field(meta))) {
+ QMyField &f = fields[i];
+ f.myField = fieldInfo;
+
+ f.type = qDecodeMYSQLType(fieldInfo->type, fieldInfo->flags);
+ if (qIsBlob(fieldInfo->type)) {
+ // the size of a blob-field is available as soon as we call
+ // mysql_stmt_store_result()
+ // after mysql_stmt_exec() in QMYSQLResult::exec()
+ fieldInfo->length = 0;
+ hasBlobs = true;
+ } else if (qIsInteger(f.type)) {
+ fieldInfo->length = 8;
+ } else {
+ fieldInfo->type = MYSQL_TYPE_STRING;
+ }
+ bind = &inBinds[i];
+ field = new char[fieldInfo->length + 1];
+ memset(field, 0, fieldInfo->length + 1);
+
+ bind->buffer_type = fieldInfo->type;
+ bind->buffer = field;
+ bind->buffer_length = f.bufLength = fieldInfo->length + 1;
+ bind->is_null = &f.nullIndicator;
+ bind->length = &f.bufLength;
+ f.outField=field;
+
+ ++i;
+ }
+ return true;
+}
+#endif
+
+QMYSQLResult::QMYSQLResult(const QMYSQLDriver* db)
+ : QSqlResult(*new QMYSQLResultPrivate(this, db))
+{
+}
+
+QMYSQLResult::~QMYSQLResult()
+{
+ cleanup();
+}
+
+QVariant QMYSQLResult::handle() const
+{
+ Q_D(const QMYSQLResult);
+#if MYSQL_VERSION_ID >= 40108
+ if(d->preparedQuery)
+ return d->meta ? QVariant::fromValue(d->meta) : QVariant::fromValue(d->stmt);
+ else
+#endif
+ return QVariant::fromValue(d->result);
+}
+
+void QMYSQLResult::cleanup()
+{
+ Q_D(QMYSQLResult);
+ if (d->result)
+ mysql_free_result(d->result);
+
+// must iterate trough leftover result sets from multi-selects or stored procedures
+// if this isn't done subsequent queries will fail with "Commands out of sync"
+#if MYSQL_VERSION_ID >= 40100
+ while (driver() && d->drv_d_func()->mysql && mysql_next_result(d->drv_d_func()->mysql) == 0) {
+ MYSQL_RES *res = mysql_store_result(d->drv_d_func()->mysql);
+ if (res)
+ mysql_free_result(res);
+ }
+#endif
+
+#if MYSQL_VERSION_ID >= 40108
+ if (d->stmt) {
+ if (mysql_stmt_close(d->stmt))
+ qWarning("QMYSQLResult::cleanup: unable to free statement handle");
+ d->stmt = 0;
+ }
+
+ if (d->meta) {
+ mysql_free_result(d->meta);
+ d->meta = 0;
+ }
+
+ int i;
+ for (i = 0; i < d->fields.count(); ++i)
+ delete[] d->fields[i].outField;
+
+ if (d->outBinds) {
+ delete[] d->outBinds;
+ d->outBinds = 0;
+ }
+
+ if (d->inBinds) {
+ delete[] d->inBinds;
+ d->inBinds = 0;
+ }
+#endif
+
+ d->hasBlobs = false;
+ d->fields.clear();
+ d->result = NULL;
+ d->row = NULL;
+ setAt(-1);
+ setActive(false);
+}
+
+bool QMYSQLResult::fetch(int i)
+{
+ Q_D(QMYSQLResult);
+ if (!driver())
+ return false;
+ if (isForwardOnly()) { // fake a forward seek
+ if (at() < i) {
+ int x = i - at();
+ while (--x && fetchNext()) {};
+ return fetchNext();
+ } else {
+ return false;
+ }
+ }
+ if (at() == i)
+ return true;
+ if (d->preparedQuery) {
+#if MYSQL_VERSION_ID >= 40108
+ mysql_stmt_data_seek(d->stmt, i);
+
+ int nRC = mysql_stmt_fetch(d->stmt);
+ if (nRC) {
+#ifdef MYSQL_DATA_TRUNCATED
+ if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED)
+#else
+ if (nRC == 1)
+#endif
+ setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
+ "Unable to fetch data"), QSqlError::StatementError, d->stmt));
+ return false;
+ }
+#else
+ return false;
+#endif
+ } else {
+ mysql_data_seek(d->result, i);
+ d->row = mysql_fetch_row(d->result);
+ if (!d->row)
+ return false;
+ }
+
+ setAt(i);
+ return true;
+}
+
+bool QMYSQLResult::fetchNext()
+{
+ Q_D(QMYSQLResult);
+ if (!driver())
+ return false;
+ if (d->preparedQuery) {
+#if MYSQL_VERSION_ID >= 40108
+ int nRC = mysql_stmt_fetch(d->stmt);
+ if (nRC) {
+#ifdef MYSQL_DATA_TRUNCATED
+ if (nRC == 1 || nRC == MYSQL_DATA_TRUNCATED)
+#else
+ if (nRC == 1)
+#endif // MYSQL_DATA_TRUNCATED
+ setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
+ "Unable to fetch data"), QSqlError::StatementError, d->stmt));
+ return false;
+ }
+#else
+ return false;
+#endif
+ } else {
+ d->row = mysql_fetch_row(d->result);
+ if (!d->row)
+ return false;
+ }
+ setAt(at() + 1);
+ return true;
+}
+
+bool QMYSQLResult::fetchLast()
+{
+ Q_D(QMYSQLResult);
+ if (!driver())
+ return false;
+ if (isForwardOnly()) { // fake this since MySQL can't seek on forward only queries
+ bool success = fetchNext(); // did we move at all?
+ while (fetchNext()) {};
+ return success;
+ }
+
+ my_ulonglong numRows;
+ if (d->preparedQuery) {
+#if MYSQL_VERSION_ID >= 40108
+ numRows = mysql_stmt_num_rows(d->stmt);
+#else
+ numRows = 0;
+#endif
+ } else {
+ numRows = mysql_num_rows(d->result);
+ }
+ if (at() == int(numRows))
+ return true;
+ if (!numRows)
+ return false;
+ return fetch(numRows - 1);
+}
+
+bool QMYSQLResult::fetchFirst()
+{
+ if (at() == 0)
+ return true;
+
+ if (isForwardOnly())
+ return (at() == QSql::BeforeFirstRow) ? fetchNext() : false;
+ return fetch(0);
+}
+
+QVariant QMYSQLResult::data(int field)
+{
+ Q_D(QMYSQLResult);
+ if (!isSelect() || field >= d->fields.count()) {
+ qWarning("QMYSQLResult::data: column %d out of range", field);
+ return QVariant();
+ }
+
+ if (!driver())
+ return QVariant();
+
+ int fieldLength = 0;
+ const QMYSQLResultPrivate::QMyField &f = d->fields.at(field);
+ QString val;
+ if (d->preparedQuery) {
+ if (f.nullIndicator)
+ return QVariant(f.type);
+
+ if (qIsInteger(f.type))
+ return QVariant(f.type, f.outField);
+
+ if (f.type != QVariant::ByteArray)
+ val = toUnicode(d->drv_d_func()->tc, f.outField, f.bufLength);
+ } else {
+ if (d->row[field] == NULL) {
+ // NULL value
+ return QVariant(f.type);
+ }
+
+ fieldLength = mysql_fetch_lengths(d->result)[field];
+
+ if (f.type != QVariant::ByteArray)
+ val = toUnicode(d->drv_d_func()->tc, d->row[field], fieldLength);
+ }
+
+ switch (static_cast<int>(f.type)) {
+ case QVariant::LongLong:
+ return QVariant(val.toLongLong());
+ case QVariant::ULongLong:
+ return QVariant(val.toULongLong());
+ case QMetaType::Char:
+ case QMetaType::Short:
+ case QVariant::Int:
+ return QVariant(val.toInt());
+ case QMetaType::UChar:
+ case QMetaType::UShort:
+ case QVariant::UInt:
+ return QVariant(val.toUInt());
+ case QVariant::Double: {
+ QVariant v;
+ bool ok=false;
+ double dbl = val.toDouble(&ok);
+ switch(numericalPrecisionPolicy()) {
+ case QSql::LowPrecisionInt32:
+ v=QVariant(dbl).toInt();
+ break;
+ case QSql::LowPrecisionInt64:
+ v = QVariant(dbl).toLongLong();
+ break;
+ case QSql::LowPrecisionDouble:
+ v = QVariant(dbl);
+ break;
+ case QSql::HighPrecision:
+ default:
+ v = val;
+ ok = true;
+ break;
+ }
+ if(ok)
+ return v;
+ else
+ return QVariant();
+ }
+ return QVariant(val.toDouble());
+ case QVariant::Date:
+ return qDateFromString(val);
+ case QVariant::Time:
+ return qTimeFromString(val);
+ case QVariant::DateTime:
+ return qDateTimeFromString(val);
+ case QVariant::ByteArray: {
+
+ QByteArray ba;
+ if (d->preparedQuery) {
+ ba = QByteArray(f.outField, f.bufLength);
+ } else {
+ ba = QByteArray(d->row[field], fieldLength);
+ }
+ return QVariant(ba);
+ }
+ default:
+ case QVariant::String:
+ return QVariant(val);
+ }
+ qWarning("QMYSQLResult::data: unknown data type");
+ return QVariant();
+}
+
+bool QMYSQLResult::isNull(int field)
+{
+ Q_D(const QMYSQLResult);
+ if (field < 0 || field >= d->fields.count())
+ return true;
+ if (d->preparedQuery)
+ return d->fields.at(field).nullIndicator;
+ else
+ return d->row[field] == NULL;
+}
+
+bool QMYSQLResult::reset (const QString& query)
+{
+ Q_D(QMYSQLResult);
+ if (!driver() || !driver()->isOpen() || driver()->isOpenError())
+ return false;
+
+ d->preparedQuery = false;
+
+ cleanup();
+
+ const QByteArray encQuery(fromUnicode(d->drv_d_func()->tc, query));
+ if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.length())) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"),
+ QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ d->result = mysql_store_result(d->drv_d_func()->mysql);
+ if (!d->result && mysql_field_count(d->drv_d_func()->mysql) > 0) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store result"),
+ QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ int numFields = mysql_field_count(d->drv_d_func()->mysql);
+ setSelect(numFields != 0);
+ d->fields.resize(numFields);
+ d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql);
+
+ if (isSelect()) {
+ for(int i = 0; i < numFields; i++) {
+ MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
+ d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
+ }
+ setAt(QSql::BeforeFirstRow);
+ }
+ setActive(true);
+ return isActive();
+}
+
+int QMYSQLResult::size()
+{
+ Q_D(const QMYSQLResult);
+ if (driver() && isSelect())
+ if (d->preparedQuery)
+#if MYSQL_VERSION_ID >= 40108
+ return mysql_stmt_num_rows(d->stmt);
+#else
+ return -1;
+#endif
+ else
+ return int(mysql_num_rows(d->result));
+ else
+ return -1;
+}
+
+int QMYSQLResult::numRowsAffected()
+{
+ Q_D(const QMYSQLResult);
+ return d->rowsAffected;
+}
+
+QVariant QMYSQLResult::lastInsertId() const
+{
+ Q_D(const QMYSQLResult);
+ if (!isActive() || !driver())
+ return QVariant();
+
+ if (d->preparedQuery) {
+#if MYSQL_VERSION_ID >= 40108
+ quint64 id = mysql_stmt_insert_id(d->stmt);
+ if (id)
+ return QVariant(id);
+#endif
+ } else {
+ quint64 id = mysql_insert_id(d->drv_d_func()->mysql);
+ if (id)
+ return QVariant(id);
+ }
+ return QVariant();
+}
+
+QSqlRecord QMYSQLResult::record() const
+{
+ Q_D(const QMYSQLResult);
+ QSqlRecord info;
+ MYSQL_RES *res;
+ if (!isActive() || !isSelect() || !driver())
+ return info;
+
+#if MYSQL_VERSION_ID >= 40108
+ res = d->preparedQuery ? d->meta : d->result;
+#else
+ res = d->result;
+#endif
+
+ if (!mysql_errno(d->drv_d_func()->mysql)) {
+ mysql_field_seek(res, 0);
+ MYSQL_FIELD* field = mysql_fetch_field(res);
+ while(field) {
+ info.append(qToField(field, d->drv_d_func()->tc));
+ field = mysql_fetch_field(res);
+ }
+ }
+ mysql_field_seek(res, 0);
+ return info;
+}
+
+bool QMYSQLResult::nextResult()
+{
+ Q_D(QMYSQLResult);
+ if (!driver())
+ return false;
+#if MYSQL_VERSION_ID >= 40100
+ setAt(-1);
+ setActive(false);
+
+ if (d->result && isSelect())
+ mysql_free_result(d->result);
+ d->result = 0;
+ setSelect(false);
+
+ for (int i = 0; i < d->fields.count(); ++i)
+ delete[] d->fields[i].outField;
+ d->fields.clear();
+
+ int status = mysql_next_result(d->drv_d_func()->mysql);
+ if (status > 0) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute next query"),
+ QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ } else if (status == -1) {
+ return false; // No more result sets
+ }
+
+ d->result = mysql_store_result(d->drv_d_func()->mysql);
+ int numFields = mysql_field_count(d->drv_d_func()->mysql);
+ if (!d->result && numFields > 0) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"),
+ QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+
+ setSelect(numFields > 0);
+ d->fields.resize(numFields);
+ d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql);
+
+ if (isSelect()) {
+ for (int i = 0; i < numFields; i++) {
+ MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
+ d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
+ }
+ }
+
+ setActive(true);
+ return true;
+#else
+ return false;
+#endif
+}
+
+void QMYSQLResult::virtual_hook(int id, void *data)
+{
+ QSqlResult::virtual_hook(id, data);
+}
+
+
+#if MYSQL_VERSION_ID >= 40108
+
+static MYSQL_TIME *toMySqlDate(QDate date, QTime time, QVariant::Type type)
+{
+ Q_ASSERT(type == QVariant::Time || type == QVariant::Date
+ || type == QVariant::DateTime);
+
+ MYSQL_TIME *myTime = new MYSQL_TIME;
+ memset(myTime, 0, sizeof(MYSQL_TIME));
+
+ if (type == QVariant::Time || type == QVariant::DateTime) {
+ myTime->hour = time.hour();
+ myTime->minute = time.minute();
+ myTime->second = time.second();
+ myTime->second_part = time.msec() * 1000;
+ }
+ if (type == QVariant::Date || type == QVariant::DateTime) {
+ myTime->year = date.year();
+ myTime->month = date.month();
+ myTime->day = date.day();
+ }
+
+ return myTime;
+}
+
+bool QMYSQLResult::prepare(const QString& query)
+{
+ Q_D(QMYSQLResult);
+ if (!driver())
+ return false;
+#if MYSQL_VERSION_ID >= 40108
+ cleanup();
+ if (!d->drv_d_func()->preparedQuerysEnabled)
+ return QSqlResult::prepare(query);
+
+ int r;
+
+ if (query.isEmpty())
+ return false;
+
+ if (!d->stmt)
+ d->stmt = mysql_stmt_init(d->drv_d_func()->mysql);
+ if (!d->stmt) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to prepare statement"),
+ QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+
+ const QByteArray encQuery(fromUnicode(d->drv_d_func()->tc, query));
+ r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.length());
+ if (r != 0) {
+ setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
+ "Unable to prepare statement"), QSqlError::StatementError, d->stmt));
+ cleanup();
+ return false;
+ }
+
+ if (mysql_stmt_param_count(d->stmt) > 0) {// allocate memory for outvalues
+ d->outBinds = new MYSQL_BIND[mysql_stmt_param_count(d->stmt)];
+ }
+
+ setSelect(d->bindInValues());
+ d->preparedQuery = true;
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool QMYSQLResult::exec()
+{
+ Q_D(QMYSQLResult);
+ if (!driver())
+ return false;
+ if (!d->preparedQuery)
+ return QSqlResult::exec();
+ if (!d->stmt)
+ return false;
+
+ int r = 0;
+ MYSQL_BIND* currBind;
+ QVector<MYSQL_TIME *> timeVector;
+ QVector<QByteArray> stringVector;
+ QVector<my_bool> nullVector;
+
+ const QVector<QVariant> values = boundValues();
+
+ r = mysql_stmt_reset(d->stmt);
+ if (r != 0) {
+ setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
+ "Unable to reset statement"), QSqlError::StatementError, d->stmt));
+ return false;
+ }
+
+ if (mysql_stmt_param_count(d->stmt) > 0 &&
+ mysql_stmt_param_count(d->stmt) == (uint)values.count()) {
+
+ nullVector.resize(values.count());
+ for (int i = 0; i < values.count(); ++i) {
+ const QVariant &val = boundValues().at(i);
+ void *data = const_cast<void *>(val.constData());
+
+ currBind = &d->outBinds[i];
+
+ nullVector[i] = static_cast<my_bool>(val.isNull());
+ currBind->is_null = &nullVector[i];
+ currBind->length = 0;
+ currBind->is_unsigned = 0;
+
+ switch (val.type()) {
+ case QVariant::ByteArray:
+ currBind->buffer_type = MYSQL_TYPE_BLOB;
+ currBind->buffer = const_cast<char *>(val.toByteArray().constData());
+ currBind->buffer_length = val.toByteArray().size();
+ break;
+
+ case QVariant::Time:
+ case QVariant::Date:
+ case QVariant::DateTime: {
+ MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.type());
+ timeVector.append(myTime);
+
+ currBind->buffer = myTime;
+ switch(val.type()) {
+ case QVariant::Time:
+ currBind->buffer_type = MYSQL_TYPE_TIME;
+ myTime->time_type = MYSQL_TIMESTAMP_TIME;
+ break;
+ case QVariant::Date:
+ currBind->buffer_type = MYSQL_TYPE_DATE;
+ myTime->time_type = MYSQL_TIMESTAMP_DATE;
+ break;
+ case QVariant::DateTime:
+ currBind->buffer_type = MYSQL_TYPE_DATETIME;
+ myTime->time_type = MYSQL_TIMESTAMP_DATETIME;
+ break;
+ default:
+ break;
+ }
+ currBind->buffer_length = sizeof(MYSQL_TIME);
+ currBind->length = 0;
+ break; }
+ case QVariant::UInt:
+ case QVariant::Int:
+ currBind->buffer_type = MYSQL_TYPE_LONG;
+ currBind->buffer = data;
+ currBind->buffer_length = sizeof(int);
+ currBind->is_unsigned = (val.type() != QVariant::Int);
+ break;
+ case QVariant::Bool:
+ currBind->buffer_type = MYSQL_TYPE_TINY;
+ currBind->buffer = data;
+ currBind->buffer_length = sizeof(bool);
+ currBind->is_unsigned = false;
+ break;
+ case QVariant::Double:
+ currBind->buffer_type = MYSQL_TYPE_DOUBLE;
+ currBind->buffer = data;
+ currBind->buffer_length = sizeof(double);
+ break;
+ case QVariant::LongLong:
+ case QVariant::ULongLong:
+ currBind->buffer_type = MYSQL_TYPE_LONGLONG;
+ currBind->buffer = data;
+ currBind->buffer_length = sizeof(qint64);
+ currBind->is_unsigned = (val.type() == QVariant::ULongLong);
+ break;
+ case QVariant::String:
+ default: {
+ QByteArray ba = fromUnicode(d->drv_d_func()->tc, val.toString());
+ stringVector.append(ba);
+ currBind->buffer_type = MYSQL_TYPE_STRING;
+ currBind->buffer = const_cast<char *>(ba.constData());
+ currBind->buffer_length = ba.length();
+ break; }
+ }
+ }
+
+ r = mysql_stmt_bind_param(d->stmt, d->outBinds);
+ if (r != 0) {
+ setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
+ "Unable to bind value"), QSqlError::StatementError, d->stmt));
+ qDeleteAll(timeVector);
+ return false;
+ }
+ }
+ r = mysql_stmt_execute(d->stmt);
+
+ qDeleteAll(timeVector);
+
+ if (r != 0) {
+ setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
+ "Unable to execute statement"), QSqlError::StatementError, d->stmt));
+ return false;
+ }
+ //if there is meta-data there is also data
+ setSelect(d->meta);
+
+ d->rowsAffected = mysql_stmt_affected_rows(d->stmt);
+
+ if (isSelect()) {
+ my_bool update_max_length = true;
+
+ r = mysql_stmt_bind_result(d->stmt, d->inBinds);
+ if (r != 0) {
+ setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
+ "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
+ return false;
+ }
+ if (d->hasBlobs)
+ mysql_stmt_attr_set(d->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &update_max_length);
+
+ r = mysql_stmt_store_result(d->stmt);
+ if (r != 0) {
+ setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
+ "Unable to store statement results"), QSqlError::StatementError, d->stmt));
+ return false;
+ }
+
+ if (d->hasBlobs) {
+ // mysql_stmt_store_result() with STMT_ATTR_UPDATE_MAX_LENGTH set to true crashes
+ // when called without a preceding call to mysql_stmt_bind_result()
+ // in versions < 4.1.8
+ d->bindBlobs();
+ r = mysql_stmt_bind_result(d->stmt, d->inBinds);
+ if (r != 0) {
+ setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
+ "Unable to bind outvalues"), QSqlError::StatementError, d->stmt));
+ return false;
+ }
+ }
+ setAt(QSql::BeforeFirstRow);
+ }
+ setActive(true);
+ return true;
+}
+#endif
+/////////////////////////////////////////////////////////
+
+static int qMySqlConnectionCount = 0;
+static bool qMySqlInitHandledByUser = false;
+
+static void qLibraryInit()
+{
+#ifndef Q_NO_MYSQL_EMBEDDED
+# if MYSQL_VERSION_ID >= 40000
+ if (qMySqlInitHandledByUser || qMySqlConnectionCount > 1)
+ return;
+
+# if (MYSQL_VERSION_ID >= 40110 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50003
+ if (mysql_library_init(0, 0, 0)) {
+# else
+ if (mysql_server_init(0, 0, 0)) {
+# endif
+ qWarning("QMYSQLDriver::qServerInit: unable to start server.");
+ }
+# endif // MYSQL_VERSION_ID
+#endif // Q_NO_MYSQL_EMBEDDED
+}
+
+static void qLibraryEnd()
+{
+#ifndef Q_NO_MYSQL_EMBEDDED
+# if MYSQL_VERSION_ID > 40000
+# if (MYSQL_VERSION_ID >= 40110 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50003
+ mysql_library_end();
+# else
+ mysql_server_end();
+# endif
+# endif
+#endif
+}
+
+QMYSQLDriver::QMYSQLDriver(QObject * parent)
+ : QSqlDriver(*new QMYSQLDriverPrivate, parent)
+{
+ init();
+ qLibraryInit();
+}
+
+/*!
+ Create a driver instance with the open connection handle, \a con.
+ The instance's parent (owner) is \a parent.
+*/
+
+QMYSQLDriver::QMYSQLDriver(MYSQL * con, QObject * parent)
+ : QSqlDriver(*new QMYSQLDriverPrivate, parent)
+{
+ Q_D(QMYSQLDriver);
+ init();
+ if (con) {
+ d->mysql = (MYSQL *) con;
+#ifndef QT_NO_TEXTCODEC
+ d->tc = codec(con);
+#endif
+ setOpen(true);
+ setOpenError(false);
+ if (qMySqlConnectionCount == 1)
+ qMySqlInitHandledByUser = true;
+ } else {
+ qLibraryInit();
+ }
+}
+
+void QMYSQLDriver::init()
+{
+ Q_D(QMYSQLDriver);
+ d->mysql = 0;
+ qMySqlConnectionCount++;
+}
+
+QMYSQLDriver::~QMYSQLDriver()
+{
+ qMySqlConnectionCount--;
+ if (qMySqlConnectionCount == 0 && !qMySqlInitHandledByUser)
+ qLibraryEnd();
+}
+
+bool QMYSQLDriver::hasFeature(DriverFeature f) const
+{
+ Q_D(const QMYSQLDriver);
+ switch (f) {
+ case Transactions:
+// CLIENT_TRANSACTION should be defined in all recent mysql client libs > 3.23.34
+#ifdef CLIENT_TRANSACTIONS
+ if (d->mysql) {
+ if ((d->mysql->server_capabilities & CLIENT_TRANSACTIONS) == CLIENT_TRANSACTIONS)
+ return true;
+ }
+#endif
+ return false;
+ case NamedPlaceholders:
+ case BatchOperations:
+ case SimpleLocking:
+ case EventNotifications:
+ case FinishQuery:
+ case CancelQuery:
+ return false;
+ case QuerySize:
+ case BLOB:
+ case LastInsertId:
+ case Unicode:
+ case LowPrecisionNumbers:
+ return true;
+ case PreparedQueries:
+ case PositionalPlaceholders:
+#if MYSQL_VERSION_ID >= 40108
+ return d->preparedQuerysEnabled;
+#else
+ return false;
+#endif
+ case MultipleResultSets:
+#if MYSQL_VERSION_ID >= 40100
+ return true;
+#else
+ return false;
+#endif
+ }
+ return false;
+}
+
+static void setOptionFlag(uint &optionFlags, const QString &opt)
+{
+ if (opt == QLatin1String("CLIENT_COMPRESS"))
+ optionFlags |= CLIENT_COMPRESS;
+ else if (opt == QLatin1String("CLIENT_FOUND_ROWS"))
+ optionFlags |= CLIENT_FOUND_ROWS;
+ else if (opt == QLatin1String("CLIENT_IGNORE_SPACE"))
+ optionFlags |= CLIENT_IGNORE_SPACE;
+ else if (opt == QLatin1String("CLIENT_INTERACTIVE"))
+ optionFlags |= CLIENT_INTERACTIVE;
+ else if (opt == QLatin1String("CLIENT_NO_SCHEMA"))
+ optionFlags |= CLIENT_NO_SCHEMA;
+ else if (opt == QLatin1String("CLIENT_ODBC"))
+ optionFlags |= CLIENT_ODBC;
+ else if (opt == QLatin1String("CLIENT_SSL"))
+ qWarning("QMYSQLDriver: SSL_KEY, SSL_CERT and SSL_CA should be used instead of CLIENT_SSL.");
+ else
+ qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData());
+}
+
+bool QMYSQLDriver::open(const QString& db,
+ const QString& user,
+ const QString& password,
+ const QString& host,
+ int port,
+ const QString& connOpts)
+{
+ Q_D(QMYSQLDriver);
+ if (isOpen())
+ close();
+
+ /* This is a hack to get MySQL's stored procedure support working.
+ Since a stored procedure _may_ return multiple result sets,
+ we have to enable CLIEN_MULTI_STATEMENTS here, otherwise _any_
+ stored procedure call will fail.
+ */
+ unsigned int optionFlags = Q_CLIENT_MULTI_STATEMENTS;
+ const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
+ QString unixSocket;
+ QString sslCert;
+ QString sslCA;
+ QString sslKey;
+ QString sslCAPath;
+ QString sslCipher;
+#if MYSQL_VERSION_ID >= 50000
+ my_bool reconnect=false;
+ uint connectTimeout = 0;
+ uint readTimeout = 0;
+ uint writeTimeout = 0;
+#endif
+
+ // extract the real options from the string
+ for (int i = 0; i < opts.count(); ++i) {
+ QString tmp(opts.at(i).simplified());
+ int idx;
+ if ((idx = tmp.indexOf(QLatin1Char('='))) != -1) {
+ QString val = tmp.mid(idx + 1).simplified();
+ QString opt = tmp.left(idx).simplified();
+ if (opt == QLatin1String("UNIX_SOCKET"))
+ unixSocket = val;
+#if MYSQL_VERSION_ID >= 50000
+ else if (opt == QLatin1String("MYSQL_OPT_RECONNECT")) {
+ if (val == QLatin1String("TRUE") || val == QLatin1String("1") || val.isEmpty())
+ reconnect = true;
+ } else if (opt == QLatin1String("MYSQL_OPT_CONNECT_TIMEOUT")) {
+ connectTimeout = val.toInt();
+ } else if (opt == QLatin1String("MYSQL_OPT_READ_TIMEOUT")) {
+ readTimeout = val.toInt();
+ } else if (opt == QLatin1String("MYSQL_OPT_WRITE_TIMEOUT")) {
+ writeTimeout = val.toInt();
+ }
+#endif
+ else if (opt == QLatin1String("SSL_KEY"))
+ sslKey = val;
+ else if (opt == QLatin1String("SSL_CERT"))
+ sslCert = val;
+ else if (opt == QLatin1String("SSL_CA"))
+ sslCA = val;
+ else if (opt == QLatin1String("SSL_CAPATH"))
+ sslCAPath = val;
+ else if (opt == QLatin1String("SSL_CIPHER"))
+ sslCipher = val;
+ else if (val == QLatin1String("TRUE") || val == QLatin1String("1"))
+ setOptionFlag(optionFlags, tmp.left(idx).simplified());
+ else
+ qWarning("QMYSQLDriver::open: Illegal connect option value '%s'",
+ tmp.toLocal8Bit().constData());
+ } else {
+ setOptionFlag(optionFlags, tmp);
+ }
+ }
+
+ if (!(d->mysql = mysql_init((MYSQL*) 0))) {
+ setLastError(qMakeError(tr("Unable to allocate a MYSQL object"),
+ QSqlError::ConnectionError, d));
+ setOpenError(true);
+ return false;
+ }
+
+ if (!sslKey.isNull() || !sslCert.isNull() || !sslCA.isNull() ||
+ !sslCAPath.isNull() || !sslCipher.isNull()) {
+ mysql_ssl_set(d->mysql,
+ sslKey.isNull() ? static_cast<const char *>(0)
+ : QFile::encodeName(sslKey).constData(),
+ sslCert.isNull() ? static_cast<const char *>(0)
+ : QFile::encodeName(sslCert).constData(),
+ sslCA.isNull() ? static_cast<const char *>(0)
+ : QFile::encodeName(sslCA).constData(),
+ sslCAPath.isNull() ? static_cast<const char *>(0)
+ : QFile::encodeName(sslCAPath).constData(),
+ sslCipher.isNull() ? static_cast<const char *>(0)
+ : sslCipher.toLocal8Bit().constData());
+ }
+
+#if MYSQL_VERSION_ID >= 50000
+ if (connectTimeout != 0)
+ mysql_options(d->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connectTimeout);
+ if (readTimeout != 0)
+ mysql_options(d->mysql, MYSQL_OPT_READ_TIMEOUT, &readTimeout);
+ if (writeTimeout != 0)
+ mysql_options(d->mysql, MYSQL_OPT_WRITE_TIMEOUT, &writeTimeout);
+#endif
+ MYSQL *mysql = mysql_real_connect(d->mysql,
+ host.isNull() ? static_cast<const char *>(0)
+ : host.toLocal8Bit().constData(),
+ user.isNull() ? static_cast<const char *>(0)
+ : user.toLocal8Bit().constData(),
+ password.isNull() ? static_cast<const char *>(0)
+ : password.toLocal8Bit().constData(),
+ db.isNull() ? static_cast<const char *>(0)
+ : db.toLocal8Bit().constData(),
+ (port > -1) ? port : 0,
+ unixSocket.isNull() ? static_cast<const char *>(0)
+ : unixSocket.toLocal8Bit().constData(),
+ optionFlags);
+
+ if (mysql == d->mysql) {
+ if (!db.isEmpty() && mysql_select_db(d->mysql, db.toLocal8Bit().constData())) {
+ setLastError(qMakeError(tr("Unable to open database '%1'").arg(db), QSqlError::ConnectionError, d));
+ mysql_close(d->mysql);
+ setOpenError(true);
+ return false;
+ }
+#if MYSQL_VERSION_ID >= 50000
+ if (reconnect)
+ mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect);
+#endif
+ } else {
+ setLastError(qMakeError(tr("Unable to connect"),
+ QSqlError::ConnectionError, d));
+ mysql_close(d->mysql);
+ d->mysql = NULL;
+ setOpenError(true);
+ return false;
+ }
+
+#if (MYSQL_VERSION_ID >= 40113 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50007
+ // force the communication to be utf8
+ mysql_set_character_set(d->mysql, "utf8");
+#endif
+#ifndef QT_NO_TEXTCODEC
+ d->tc = codec(d->mysql);
+#endif
+
+#if MYSQL_VERSION_ID >= 40108
+ d->preparedQuerysEnabled = mysql_get_client_version() >= 40108
+ && mysql_get_server_version(d->mysql) >= 40100;
+#else
+ d->preparedQuerysEnabled = false;
+#endif
+
+#ifndef QT_NO_THREAD
+ mysql_thread_init();
+#endif
+
+
+ setOpen(true);
+ setOpenError(false);
+ return true;
+}
+
+void QMYSQLDriver::close()
+{
+ Q_D(QMYSQLDriver);
+ if (isOpen()) {
+#ifndef QT_NO_THREAD
+ mysql_thread_end();
+#endif
+ mysql_close(d->mysql);
+ d->mysql = NULL;
+ setOpen(false);
+ setOpenError(false);
+ }
+}
+
+QSqlResult *QMYSQLDriver::createResult() const
+{
+ return new QMYSQLResult(this);
+}
+
+QStringList QMYSQLDriver::tables(QSql::TableType type) const
+{
+ Q_D(const QMYSQLDriver);
+ QStringList tl;
+#if MYSQL_VERSION_ID >= 40100
+ if( mysql_get_server_version(d->mysql) < 50000)
+ {
+#endif
+ if (!isOpen())
+ return tl;
+ if (!(type & QSql::Tables))
+ return tl;
+
+ MYSQL_RES* tableRes = mysql_list_tables(d->mysql, NULL);
+ MYSQL_ROW row;
+ int i = 0;
+ while (tableRes) {
+ mysql_data_seek(tableRes, i);
+ row = mysql_fetch_row(tableRes);
+ if (!row)
+ break;
+ tl.append(toUnicode(d->tc, row[0]));
+ i++;
+ }
+ mysql_free_result(tableRes);
+#if MYSQL_VERSION_ID >= 40100
+ } else {
+ QSqlQuery q(createResult());
+ if(type & QSql::Tables) {
+ QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'BASE TABLE'");
+ q.exec(sql);
+
+ while(q.next())
+ tl.append(q.value(0).toString());
+ }
+ if(type & QSql::Views) {
+ QString sql = QLatin1String("select table_name from information_schema.tables where table_schema = '") + QLatin1String(d->mysql->db) + QLatin1String("' and table_type = 'VIEW'");
+ q.exec(sql);
+
+ while(q.next())
+ tl.append(q.value(0).toString());
+ }
+ }
+#endif
+ return tl;
+}
+
+QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const
+{
+ QSqlIndex idx;
+ if (!isOpen())
+ return idx;
+
+ QSqlQuery i(createResult());
+ QString stmt(QLatin1String("show index from %1;"));
+ QSqlRecord fil = record(tablename);
+ i.exec(stmt.arg(tablename));
+ while (i.isActive() && i.next()) {
+ if (i.value(2).toString() == QLatin1String("PRIMARY")) {
+ idx.append(fil.field(i.value(4).toString()));
+ idx.setCursorName(i.value(0).toString());
+ idx.setName(i.value(2).toString());
+ }
+ }
+
+ return idx;
+}
+
+QSqlRecord QMYSQLDriver::record(const QString& tablename) const
+{
+ Q_D(const QMYSQLDriver);
+ QString table=tablename;
+ if(isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+
+ QSqlRecord info;
+ if (!isOpen())
+ return info;
+ MYSQL_RES* r = mysql_list_fields(d->mysql, table.toLocal8Bit().constData(), 0);
+ if (!r) {
+ return info;
+ }
+ MYSQL_FIELD* field;
+
+ while ((field = mysql_fetch_field(r)))
+ info.append(qToField(field, d->tc));
+ mysql_free_result(r);
+ return info;
+}
+
+QVariant QMYSQLDriver::handle() const
+{
+ Q_D(const QMYSQLDriver);
+ return QVariant::fromValue(d->mysql);
+}
+
+bool QMYSQLDriver::beginTransaction()
+{
+ Q_D(QMYSQLDriver);
+#ifndef CLIENT_TRANSACTIONS
+ return false;
+#endif
+ if (!isOpen()) {
+ qWarning("QMYSQLDriver::beginTransaction: Database not open");
+ return false;
+ }
+ if (mysql_query(d->mysql, "BEGIN WORK")) {
+ setLastError(qMakeError(tr("Unable to begin transaction"),
+ QSqlError::StatementError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QMYSQLDriver::commitTransaction()
+{
+ Q_D(QMYSQLDriver);
+#ifndef CLIENT_TRANSACTIONS
+ return false;
+#endif
+ if (!isOpen()) {
+ qWarning("QMYSQLDriver::commitTransaction: Database not open");
+ return false;
+ }
+ if (mysql_query(d->mysql, "COMMIT")) {
+ setLastError(qMakeError(tr("Unable to commit transaction"),
+ QSqlError::StatementError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QMYSQLDriver::rollbackTransaction()
+{
+ Q_D(QMYSQLDriver);
+#ifndef CLIENT_TRANSACTIONS
+ return false;
+#endif
+ if (!isOpen()) {
+ qWarning("QMYSQLDriver::rollbackTransaction: Database not open");
+ return false;
+ }
+ if (mysql_query(d->mysql, "ROLLBACK")) {
+ setLastError(qMakeError(tr("Unable to rollback transaction"),
+ QSqlError::StatementError, d));
+ return false;
+ }
+ return true;
+}
+
+QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
+{
+ Q_D(const QMYSQLDriver);
+ QString r;
+ if (field.isNull()) {
+ r = QLatin1String("NULL");
+ } else {
+ switch(field.type()) {
+ case QVariant::Double:
+ r = QString::number(field.value().toDouble(), 'g', field.precision());
+ break;
+ case QVariant::String:
+ // Escape '\' characters
+ r = QSqlDriver::formatValue(field, trimStrings);
+ r.replace(QLatin1String("\\"), QLatin1String("\\\\"));
+ break;
+ case QVariant::ByteArray:
+ if (isOpen()) {
+ const QByteArray ba = field.value().toByteArray();
+ // buffer has to be at least length*2+1 bytes
+ char* buffer = new char[ba.size() * 2 + 1];
+ int escapedSize = int(mysql_real_escape_string(d->mysql, buffer,
+ ba.data(), ba.size()));
+ r.reserve(escapedSize + 3);
+ r.append(QLatin1Char('\'')).append(toUnicode(d->tc, buffer)).append(QLatin1Char('\''));
+ delete[] buffer;
+ break;
+ } else {
+ qWarning("QMYSQLDriver::formatValue: Database not open");
+ }
+ // fall through
+ default:
+ r = QSqlDriver::formatValue(field, trimStrings);
+ }
+ }
+ return r;
+}
+
+QString QMYSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
+{
+ QString res = identifier;
+ if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('`')) && !identifier.endsWith(QLatin1Char('`')) ) {
+ res.prepend(QLatin1Char('`')).append(QLatin1Char('`'));
+ res.replace(QLatin1Char('.'), QLatin1String("`.`"));
+ }
+ return res;
+}
+
+bool QMYSQLDriver::isIdentifierEscaped(const QString &identifier, IdentifierType type) const
+{
+ Q_UNUSED(type);
+ return identifier.size() > 2
+ && identifier.startsWith(QLatin1Char('`')) //left delimited
+ && identifier.endsWith(QLatin1Char('`')); //right delimited
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql_p.h b/src/plugins/sqldrivers/mysql/qsql_mysql_p.h
new file mode 100644
index 0000000000..7641f9aa34
--- /dev/null
+++ b/src/plugins/sqldrivers/mysql/qsql_mysql_p.h
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_MYSQL_H
+#define QSQL_MYSQL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtSql/qsqldriver.h>
+
+#if defined (Q_OS_WIN32)
+#include <QtCore/qt_windows.h>
+#endif
+
+#include <mysql.h>
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_MYSQL
+#else
+#define Q_EXPORT_SQLDRIVER_MYSQL Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QMYSQLDriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_MYSQL QMYSQLDriver : public QSqlDriver
+{
+ friend class QMYSQLResultPrivate;
+ Q_DECLARE_PRIVATE(QMYSQLDriver)
+ Q_OBJECT
+public:
+ explicit QMYSQLDriver(QObject *parent=0);
+ explicit QMYSQLDriver(MYSQL *con, QObject * parent=0);
+ ~QMYSQLDriver();
+ bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE;
+ bool open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString& connOpts) Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ QSqlResult *createResult() const Q_DECL_OVERRIDE;
+ QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE;
+ QSqlIndex primaryIndex(const QString& tablename) const Q_DECL_OVERRIDE;
+ QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE;
+ QString formatValue(const QSqlField &field,
+ bool trimStrings) const Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE;
+
+ bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE;
+
+protected:
+ bool beginTransaction() Q_DECL_OVERRIDE;
+ bool commitTransaction() Q_DECL_OVERRIDE;
+ bool rollbackTransaction() Q_DECL_OVERRIDE;
+private:
+ void init();
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_MYSQL_H
diff --git a/src/plugins/sqldrivers/oci/main.cpp b/src/plugins/sqldrivers/oci/main.cpp
index 980116123d..e576928d31 100644
--- a/src/plugins/sqldrivers/oci/main.cpp
+++ b/src/plugins/sqldrivers/oci/main.cpp
@@ -39,7 +39,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
-#include "../../../sql/drivers/oci/qsql_oci_p.h"
+#include "qsql_oci_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/sqldrivers/oci/oci.pro b/src/plugins/sqldrivers/oci/oci.pro
index 96a0dd9ab6..a22d1181bf 100644
--- a/src/plugins/sqldrivers/oci/oci.pro
+++ b/src/plugins/sqldrivers/oci/oci.pro
@@ -1,8 +1,16 @@
TARGET = qsqloci
-SOURCES = main.cpp
+HEADERS += $$PWD/qsql_oci_p.h
+SOURCES += $$PWD/qsql_oci.cpp $$PWD/main.cpp
+
+unix {
+ !contains(LIBS, .*clnts.*):LIBS += -lclntsh
+} else {
+ LIBS *= -loci
+}
+darwin:QMAKE_LFLAGS += -Wl,-flat_namespace,-U,_environ
+
OTHER_FILES += oci.json
-include(../../../sql/drivers/oci/qsql_oci.pri)
PLUGIN_CLASS_NAME = QOCIDriverPlugin
include(../qsqldriverbase.pri)
diff --git a/src/plugins/sqldrivers/oci/qsql_oci.cpp b/src/plugins/sqldrivers/oci/qsql_oci.cpp
new file mode 100644
index 0000000000..47d6db7ea4
--- /dev/null
+++ b/src/plugins/sqldrivers/oci/qsql_oci.cpp
@@ -0,0 +1,2725 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_oci_p.h"
+
+#include <qcoreapplication.h>
+#include <qvariant.h>
+#include <qdatetime.h>
+#include <qmetatype.h>
+#include <qregexp.h>
+#include <qshareddata.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlquery.h>
+#include <QtSql/private/qsqlcachedresult_p.h>
+#include <QtSql/private/qsqldriver_p.h>
+#include <qstringlist.h>
+#include <qvarlengtharray.h>
+#include <qvector.h>
+#include <qdebug.h>
+
+// This is needed for oracle oci when compiling with mingw-w64 headers
+#if defined(__MINGW64_VERSION_MAJOR) && defined(_WIN64)
+#define _int64 __int64
+#endif
+
+
+#include <oci.h>
+#ifdef max
+#undef max
+#endif
+#ifdef min
+#undef min
+#endif
+
+#include <stdlib.h>
+
+#define QOCI_DYNAMIC_CHUNK_SIZE 65535
+#define QOCI_PREFETCH_MEM 10240
+
+// setting this define will allow using a query from a different
+// thread than its database connection.
+// warning - this is not fully tested and can lead to race conditions
+#define QOCI_THREADED
+
+//#define QOCI_DEBUG
+
+Q_DECLARE_OPAQUE_POINTER(OCIEnv*);
+Q_DECLARE_METATYPE(OCIEnv*)
+Q_DECLARE_OPAQUE_POINTER(OCIStmt*);
+Q_DECLARE_METATYPE(OCIStmt*)
+
+QT_BEGIN_NAMESPACE
+
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
+enum { QOCIEncoding = 2002 }; // AL16UTF16LE
+#else
+enum { QOCIEncoding = 2000 }; // AL16UTF16
+#endif
+
+#ifdef OCI_ATTR_CHARSET_FORM
+// Always set the OCI_ATTR_CHARSET_FORM to SQLCS_NCHAR is safe
+// because Oracle server will deal with the implicit Conversion
+// Between CHAR and NCHAR.
+// see: http://download.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89857/oci05bnd.htm#422705
+static const ub1 qOraCharsetForm = SQLCS_NCHAR;
+#endif
+
+#if defined (OCI_UTF16ID)
+static const ub2 qOraCharset = OCI_UTF16ID;
+#else
+static const ub2 qOraCharset = OCI_UCS2ID;
+#endif
+
+typedef QVarLengthArray<sb2, 32> IndicatorArray;
+typedef QVarLengthArray<ub2, 32> SizeArray;
+
+static QByteArray qMakeOraDate(const QDateTime& dt);
+static QDateTime qMakeDate(const char* oraDate);
+
+static QByteArray qMakeOCINumber(const qlonglong &ll, OCIError *err);
+static QByteArray qMakeOCINumber(const qulonglong& ull, OCIError* err);
+
+static qlonglong qMakeLongLong(const char* ociNumber, OCIError* err);
+static qulonglong qMakeULongLong(const char* ociNumber, OCIError* err);
+
+static QString qOraWarn(OCIError *err, int *errorCode = 0);
+
+#ifndef Q_CC_SUN
+static // for some reason, Sun CC can't use qOraWarning when it's declared static
+#endif
+void qOraWarning(const char* msg, OCIError *err);
+static QSqlError qMakeError(const QString& errString, QSqlError::ErrorType type, OCIError *err);
+
+
+
+class QOCIRowId: public QSharedData
+{
+public:
+ QOCIRowId(OCIEnv *env);
+ ~QOCIRowId();
+
+ OCIRowid *id;
+
+private:
+ QOCIRowId(const QOCIRowId &other): QSharedData(other) { Q_ASSERT(false); }
+};
+
+QOCIRowId::QOCIRowId(OCIEnv *env)
+ : id(0)
+{
+ OCIDescriptorAlloc (env, reinterpret_cast<dvoid **>(&id),
+ OCI_DTYPE_ROWID, 0, 0);
+}
+
+QOCIRowId::~QOCIRowId()
+{
+ if (id)
+ OCIDescriptorFree(id, OCI_DTYPE_ROWID);
+}
+
+typedef QSharedDataPointer<QOCIRowId> QOCIRowIdPointer;
+QT_BEGIN_INCLUDE_NAMESPACE
+Q_DECLARE_METATYPE(QOCIRowIdPointer)
+QT_END_INCLUDE_NAMESPACE
+
+class QOCIDriverPrivate : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QOCIDriver)
+
+public:
+ QOCIDriverPrivate();
+
+ OCIEnv *env;
+ OCISvcCtx *svc;
+ OCIServer *srvhp;
+ OCISession *authp;
+ OCIError *err;
+ bool transaction;
+ int serverVersion;
+ int prefetchRows;
+ int prefetchMem;
+ QString user;
+
+ void allocErrorHandle();
+};
+
+class QOCICols;
+class QOCIResultPrivate;
+
+class QOCIResult: public QSqlCachedResult
+{
+ Q_DECLARE_PRIVATE(QOCIResult)
+ friend class QOCIDriver;
+ friend class QOCICols;
+public:
+ QOCIResult(const QOCIDriver *db);
+ ~QOCIResult();
+ bool prepare(const QString &query) Q_DECL_OVERRIDE;
+ bool exec() Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+
+protected:
+ bool gotoNext(ValueCache &values, int index) Q_DECL_OVERRIDE;
+ bool reset(const QString &query) Q_DECL_OVERRIDE;
+ int size() Q_DECL_OVERRIDE;
+ int numRowsAffected() Q_DECL_OVERRIDE;
+ QSqlRecord record() const Q_DECL_OVERRIDE;
+ QVariant lastInsertId() const Q_DECL_OVERRIDE;
+ bool execBatch(bool arrayBind = false) Q_DECL_OVERRIDE;
+ void virtual_hook(int id, void *data) Q_DECL_OVERRIDE;
+};
+
+class QOCIResultPrivate: public QSqlCachedResultPrivate
+{
+public:
+ Q_DECLARE_PUBLIC(QOCIResult)
+ Q_DECLARE_SQLDRIVER_PRIVATE(QOCIDriver)
+ QOCIResultPrivate(QOCIResult *q, const QOCIDriver *drv);
+ ~QOCIResultPrivate();
+
+ QOCICols *cols;
+ OCIEnv *env;
+ OCIError *err;
+ OCISvcCtx *&svc;
+ OCIStmt *sql;
+ bool transaction;
+ int serverVersion;
+ int prefetchRows, prefetchMem;
+
+ void setStatementAttributes();
+ int bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos,
+ const QVariant &val, dvoid *indPtr, ub2 *tmpSize, QList<QByteArray> &tmpStorage);
+ int bindValues(QVector<QVariant> &values, IndicatorArray &indicators, SizeArray &tmpSizes,
+ QList<QByteArray> &tmpStorage);
+ void outValues(QVector<QVariant> &values, IndicatorArray &indicators,
+ QList<QByteArray> &tmpStorage);
+ inline bool isOutValue(int i) const
+ { Q_Q(const QOCIResult); return q->bindValueType(i) & QSql::Out; }
+ inline bool isBinaryValue(int i) const
+ { Q_Q(const QOCIResult); return q->bindValueType(i) & QSql::Binary; }
+
+ void setCharset(dvoid* handle, ub4 type) const
+ {
+ int r = 0;
+ Q_ASSERT(handle);
+
+#ifdef OCI_ATTR_CHARSET_FORM
+ r = OCIAttrSet(handle,
+ type,
+ // this const cast is safe since OCI doesn't touch
+ // the charset.
+ const_cast<void *>(static_cast<const void *>(&qOraCharsetForm)),
+ 0,
+ OCI_ATTR_CHARSET_FORM,
+ //Strange Oracle bug: some Oracle servers crash the server process with non-zero error handle (mostly for 10g).
+ //So ignore the error message here.
+ 0);
+ #ifdef QOCI_DEBUG
+ if (r != 0)
+ qWarning("QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM.");
+ #endif
+#endif
+
+ r = OCIAttrSet(handle,
+ type,
+ // this const cast is safe since OCI doesn't touch
+ // the charset.
+ const_cast<void *>(static_cast<const void *>(&qOraCharset)),
+ 0,
+ OCI_ATTR_CHARSET_ID,
+ err);
+ if (r != 0)
+ qOraWarning("QOCIResultPrivate::setCharsetI Couldn't set OCI_ATTR_CHARSET_ID: ", err);
+
+ }
+};
+
+void QOCIResultPrivate::setStatementAttributes()
+{
+ Q_ASSERT(sql);
+
+ int r = 0;
+
+ if (prefetchRows >= 0) {
+ r = OCIAttrSet(sql,
+ OCI_HTYPE_STMT,
+ &prefetchRows,
+ 0,
+ OCI_ATTR_PREFETCH_ROWS,
+ err);
+ if (r != 0)
+ qOraWarning("QOCIResultPrivate::setStatementAttributes:"
+ " Couldn't set OCI_ATTR_PREFETCH_ROWS: ", err);
+ }
+ if (prefetchMem >= 0) {
+ r = OCIAttrSet(sql,
+ OCI_HTYPE_STMT,
+ &prefetchMem,
+ 0,
+ OCI_ATTR_PREFETCH_MEMORY,
+ err);
+ if (r != 0)
+ qOraWarning("QOCIResultPrivate::setStatementAttributes:"
+ " Couldn't set OCI_ATTR_PREFETCH_MEMORY: ", err);
+ }
+}
+
+int QOCIResultPrivate::bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos,
+ const QVariant &val, dvoid *indPtr, ub2 *tmpSize, QList<QByteArray> &tmpStorage)
+{
+ int r = OCI_SUCCESS;
+ void *data = const_cast<void *>(val.constData());
+
+ switch (val.type()) {
+ case QVariant::ByteArray:
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ isOutValue(pos)
+ ? const_cast<char *>(reinterpret_cast<QByteArray *>(data)->constData())
+ : reinterpret_cast<QByteArray *>(data)->data(),
+ reinterpret_cast<QByteArray *>(data)->size(),
+ SQLT_BIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ break;
+ case QVariant::Time:
+ case QVariant::Date:
+ case QVariant::DateTime: {
+ QByteArray ba = qMakeOraDate(val.toDateTime());
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ ba.data(),
+ ba.size(),
+ SQLT_DAT, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ tmpStorage.append(ba);
+ break; }
+ case QVariant::Int:
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ // if it's an out value, the data is already detached
+ // so the const cast is safe.
+ const_cast<void *>(data),
+ sizeof(int),
+ SQLT_INT, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ break;
+ case QVariant::UInt:
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ // if it's an out value, the data is already detached
+ // so the const cast is safe.
+ const_cast<void *>(data),
+ sizeof(uint),
+ SQLT_UIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ break;
+ case QVariant::LongLong:
+ {
+ QByteArray ba = qMakeOCINumber(val.toLongLong(), err);
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ ba.data(),
+ ba.size(),
+ SQLT_VNU, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ tmpStorage.append(ba);
+ break;
+ }
+ case QVariant::ULongLong:
+ {
+ QByteArray ba = qMakeOCINumber(val.toULongLong(), err);
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ ba.data(),
+ ba.size(),
+ SQLT_VNU, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ tmpStorage.append(ba);
+ break;
+ }
+ case QVariant::Double:
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ // if it's an out value, the data is already detached
+ // so the const cast is safe.
+ const_cast<void *>(data),
+ sizeof(double),
+ SQLT_FLT, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ break;
+ case QVariant::UserType:
+ if (val.canConvert<QOCIRowIdPointer>() && !isOutValue(pos)) {
+ // use a const pointer to prevent a detach
+ const QOCIRowIdPointer rptr = qvariant_cast<QOCIRowIdPointer>(val);
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ // it's an IN value, so const_cast is ok
+ const_cast<OCIRowid **>(&rptr->id),
+ -1,
+ SQLT_RDD, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ } else {
+ qWarning("Unknown bind variable");
+ r = OCI_ERROR;
+ }
+ break;
+ case QVariant::String: {
+ const QString s = val.toString();
+ if (isBinaryValue(pos)) {
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ const_cast<ushort *>(s.utf16()),
+ s.length() * sizeof(QChar),
+ SQLT_LNG, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ break;
+ } else if (!isOutValue(pos)) {
+ // don't detach the string
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ // safe since oracle doesn't touch OUT values
+ const_cast<ushort *>(s.utf16()),
+ (s.length() + 1) * sizeof(QChar),
+ SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ if (r == OCI_SUCCESS)
+ setCharset(*hbnd, OCI_HTYPE_BIND);
+ break;
+ }
+ } // fall through for OUT values
+ default: {
+ const QString s = val.toString();
+ // create a deep-copy
+ QByteArray ba(reinterpret_cast<const char *>(s.utf16()), (s.length() + 1) * sizeof(QChar));
+ if (isOutValue(pos)) {
+ ba.reserve((s.capacity() + 1) * sizeof(QChar));
+ *tmpSize = ba.size();
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ ba.data(),
+ ba.capacity(),
+ SQLT_STR, indPtr, tmpSize, 0, 0, 0, OCI_DEFAULT);
+ } else {
+ r = OCIBindByPos(sql, hbnd, err,
+ pos + 1,
+ ba.data(),
+ ba.size(),
+ SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
+ }
+ if (r == OCI_SUCCESS)
+ setCharset(*hbnd, OCI_HTYPE_BIND);
+ tmpStorage.append(ba);
+ break;
+ } // default case
+ } // switch
+ if (r != OCI_SUCCESS)
+ qOraWarning("QOCIResultPrivate::bindValue:", err);
+ return r;
+}
+
+int QOCIResultPrivate::bindValues(QVector<QVariant> &values, IndicatorArray &indicators,
+ SizeArray &tmpSizes, QList<QByteArray> &tmpStorage)
+{
+ int r = OCI_SUCCESS;
+ for (int i = 0; i < values.count(); ++i) {
+ if (isOutValue(i))
+ values[i].detach();
+ const QVariant &val = values.at(i);
+
+ OCIBind * hbnd = 0; // Oracle handles these automatically
+ sb2 *indPtr = &indicators[i];
+ *indPtr = val.isNull() ? -1 : 0;
+
+ bindValue(sql, &hbnd, err, i, val, indPtr, &tmpSizes[i], tmpStorage);
+ }
+ return r;
+}
+
+// will assign out value and remove its temp storage.
+static void qOraOutValue(QVariant &value, QList<QByteArray> &storage, OCIError* err)
+{
+ switch (value.type()) {
+ case QVariant::Time:
+ value = qMakeDate(storage.takeFirst()).time();
+ break;
+ case QVariant::Date:
+ value = qMakeDate(storage.takeFirst()).date();
+ break;
+ case QVariant::DateTime:
+ value = qMakeDate(storage.takeFirst());
+ break;
+ case QVariant::LongLong:
+ value = qMakeLongLong(storage.takeFirst(), err);
+ break;
+ case QVariant::ULongLong:
+ value = qMakeULongLong(storage.takeFirst(), err);
+ break;
+ case QVariant::String:
+ value = QString(
+ reinterpret_cast<const QChar *>(storage.takeFirst().constData()));
+ break;
+ default:
+ break; //nothing
+ }
+}
+
+void QOCIResultPrivate::outValues(QVector<QVariant> &values, IndicatorArray &indicators,
+ QList<QByteArray> &tmpStorage)
+{
+ for (int i = 0; i < values.count(); ++i) {
+
+ if (!isOutValue(i))
+ continue;
+
+ qOraOutValue(values[i], tmpStorage, err);
+
+ QVariant::Type typ = values.at(i).type();
+ if (indicators[i] == -1) // NULL
+ values[i] = QVariant(typ);
+ else
+ values[i] = QVariant(typ, values.at(i).constData());
+ }
+}
+
+
+QOCIDriverPrivate::QOCIDriverPrivate()
+ : QSqlDriverPrivate(), env(0), svc(0), srvhp(0), authp(0), err(0), transaction(false),
+ serverVersion(-1), prefetchRows(-1), prefetchMem(QOCI_PREFETCH_MEM)
+{
+ dbmsType = QSqlDriver::Oracle;
+}
+
+void QOCIDriverPrivate::allocErrorHandle()
+{
+ int r = OCIHandleAlloc(env,
+ reinterpret_cast<void **>(&err),
+ OCI_HTYPE_ERROR,
+ 0,
+ 0);
+ if (r != 0)
+ qWarning("QOCIDriver: unable to allocate error handle");
+}
+
+struct OraFieldInfo
+{
+ QString name;
+ QVariant::Type type;
+ ub1 oraIsNull;
+ ub4 oraType;
+ sb1 oraScale;
+ ub4 oraLength; // size in bytes
+ ub4 oraFieldLength; // amount of characters
+ sb2 oraPrecision;
+};
+
+QString qOraWarn(OCIError *err, int *errorCode)
+{
+ sb4 errcode;
+ text errbuf[1024];
+ errbuf[0] = 0;
+ errbuf[1] = 0;
+
+ OCIErrorGet(err,
+ 1,
+ 0,
+ &errcode,
+ errbuf,
+ sizeof(errbuf),
+ OCI_HTYPE_ERROR);
+ if (errorCode)
+ *errorCode = errcode;
+ return QString(reinterpret_cast<const QChar *>(errbuf));
+}
+
+void qOraWarning(const char* msg, OCIError *err)
+{
+#ifdef QOCI_DEBUG
+ qWarning("%s %s", msg, qPrintable(qOraWarn(err)));
+#else
+ Q_UNUSED(msg);
+ Q_UNUSED(err);
+#endif
+}
+
+static int qOraErrorNumber(OCIError *err)
+{
+ sb4 errcode;
+ OCIErrorGet(err,
+ 1,
+ 0,
+ &errcode,
+ 0,
+ 0,
+ OCI_HTYPE_ERROR);
+ return errcode;
+}
+
+QSqlError qMakeError(const QString& errString, QSqlError::ErrorType type, OCIError *err)
+{
+ int errorCode = 0;
+ const QString oraErrorString = qOraWarn(err, &errorCode);
+ return QSqlError(errString, oraErrorString, type, errorCode);
+}
+
+QVariant::Type qDecodeOCIType(const QString& ocitype, QSql::NumericalPrecisionPolicy precisionPolicy)
+{
+ QVariant::Type type = QVariant::Invalid;
+ if (ocitype == QLatin1String("VARCHAR2") || ocitype == QLatin1String("VARCHAR")
+ || ocitype.startsWith(QLatin1String("INTERVAL"))
+ || ocitype == QLatin1String("CHAR") || ocitype == QLatin1String("NVARCHAR2")
+ || ocitype == QLatin1String("NCHAR"))
+ type = QVariant::String;
+ else if (ocitype == QLatin1String("NUMBER")
+ || ocitype == QLatin1String("FLOAT")
+ || ocitype == QLatin1String("BINARY_FLOAT")
+ || ocitype == QLatin1String("BINARY_DOUBLE")) {
+ switch(precisionPolicy) {
+ case QSql::LowPrecisionInt32:
+ type = QVariant::Int;
+ break;
+ case QSql::LowPrecisionInt64:
+ type = QVariant::LongLong;
+ break;
+ case QSql::LowPrecisionDouble:
+ type = QVariant::Double;
+ break;
+ case QSql::HighPrecision:
+ default:
+ type = QVariant::String;
+ break;
+ }
+ }
+ else if (ocitype == QLatin1String("LONG") || ocitype == QLatin1String("NCLOB")
+ || ocitype == QLatin1String("CLOB"))
+ type = QVariant::ByteArray;
+ else if (ocitype == QLatin1String("RAW") || ocitype == QLatin1String("LONG RAW")
+ || ocitype == QLatin1String("ROWID") || ocitype == QLatin1String("BLOB")
+ || ocitype == QLatin1String("CFILE") || ocitype == QLatin1String("BFILE"))
+ type = QVariant::ByteArray;
+ else if (ocitype == QLatin1String("DATE") || ocitype.startsWith(QLatin1String("TIME")))
+ type = QVariant::DateTime;
+ else if (ocitype == QLatin1String("UNDEFINED"))
+ type = QVariant::Invalid;
+ if (type == QVariant::Invalid)
+ qWarning("qDecodeOCIType: unknown type: %s", ocitype.toLocal8Bit().constData());
+ return type;
+}
+
+QVariant::Type qDecodeOCIType(int ocitype, QSql::NumericalPrecisionPolicy precisionPolicy)
+{
+ QVariant::Type type = QVariant::Invalid;
+ switch (ocitype) {
+ case SQLT_STR:
+ case SQLT_VST:
+ case SQLT_CHR:
+ case SQLT_AFC:
+ case SQLT_VCS:
+ case SQLT_AVC:
+ case SQLT_RDD:
+ case SQLT_LNG:
+#ifdef SQLT_INTERVAL_YM
+ case SQLT_INTERVAL_YM:
+#endif
+#ifdef SQLT_INTERVAL_DS
+ case SQLT_INTERVAL_DS:
+#endif
+ type = QVariant::String;
+ break;
+ case SQLT_INT:
+ type = QVariant::Int;
+ break;
+ case SQLT_FLT:
+ case SQLT_NUM:
+ case SQLT_VNU:
+ case SQLT_UIN:
+ switch(precisionPolicy) {
+ case QSql::LowPrecisionInt32:
+ type = QVariant::Int;
+ break;
+ case QSql::LowPrecisionInt64:
+ type = QVariant::LongLong;
+ break;
+ case QSql::LowPrecisionDouble:
+ type = QVariant::Double;
+ break;
+ case QSql::HighPrecision:
+ default:
+ type = QVariant::String;
+ break;
+ }
+ break;
+ case SQLT_VBI:
+ case SQLT_BIN:
+ case SQLT_LBI:
+ case SQLT_LVC:
+ case SQLT_LVB:
+ case SQLT_BLOB:
+ case SQLT_CLOB:
+ case SQLT_FILE:
+ case SQLT_NTY:
+ case SQLT_REF:
+ case SQLT_RID:
+ type = QVariant::ByteArray;
+ break;
+ case SQLT_DAT:
+ case SQLT_ODT:
+#ifdef SQLT_TIMESTAMP
+ case SQLT_TIMESTAMP:
+ case SQLT_TIMESTAMP_TZ:
+ case SQLT_TIMESTAMP_LTZ:
+#endif
+ type = QVariant::DateTime;
+ break;
+ default:
+ type = QVariant::Invalid;
+ qWarning("qDecodeOCIType: unknown OCI datatype: %d", ocitype);
+ break;
+ }
+ return type;
+}
+
+static QSqlField qFromOraInf(const OraFieldInfo &ofi)
+{
+ QSqlField f(ofi.name, ofi.type);
+ f.setRequired(ofi.oraIsNull == 0);
+
+ if (ofi.type == QVariant::String && ofi.oraType != SQLT_NUM && ofi.oraType != SQLT_VNU)
+ f.setLength(ofi.oraFieldLength);
+ else
+ f.setLength(ofi.oraPrecision == 0 ? 38 : int(ofi.oraPrecision));
+
+ f.setPrecision(ofi.oraScale);
+ f.setSqlType(int(ofi.oraType));
+ return f;
+}
+
+/*!
+ \internal
+
+ Convert QDateTime to the internal Oracle DATE format NB!
+ It does not handle BCE dates.
+*/
+QByteArray qMakeOraDate(const QDateTime& dt)
+{
+ QByteArray ba;
+ ba.resize(7);
+ int year = dt.date().year();
+ ba[0]= (year / 100) + 100; // century
+ ba[1]= (year % 100) + 100; // year
+ ba[2]= dt.date().month();
+ ba[3]= dt.date().day();
+ ba[4]= dt.time().hour() + 1;
+ ba[5]= dt.time().minute() + 1;
+ ba[6]= dt.time().second() + 1;
+ return ba;
+}
+
+/*!
+ \internal
+
+ Convert qlonglong to the internal Oracle OCINumber format.
+ */
+QByteArray qMakeOCINumber(const qlonglong& ll, OCIError* err)
+{
+ QByteArray ba(sizeof(OCINumber), 0);
+
+ OCINumberFromInt(err,
+ &ll,
+ sizeof(qlonglong),
+ OCI_NUMBER_SIGNED,
+ reinterpret_cast<OCINumber*>(ba.data()));
+ return ba;
+}
+
+/*!
+ \internal
+
+ Convert qulonglong to the internal Oracle OCINumber format.
+ */
+QByteArray qMakeOCINumber(const qulonglong& ull, OCIError* err)
+{
+ QByteArray ba(sizeof(OCINumber), 0);
+
+ OCINumberFromInt(err,
+ &ull,
+ sizeof(qlonglong),
+ OCI_NUMBER_UNSIGNED,
+ reinterpret_cast<OCINumber*>(ba.data()));
+ return ba;
+}
+
+qlonglong qMakeLongLong(const char* ociNumber, OCIError* err)
+{
+ qlonglong qll = 0;
+ OCINumberToInt(err, reinterpret_cast<const OCINumber *>(ociNumber), sizeof(qlonglong),
+ OCI_NUMBER_SIGNED, &qll);
+ return qll;
+}
+
+qulonglong qMakeULongLong(const char* ociNumber, OCIError* err)
+{
+ qulonglong qull = 0;
+ OCINumberToInt(err, reinterpret_cast<const OCINumber *>(ociNumber), sizeof(qulonglong),
+ OCI_NUMBER_UNSIGNED, &qull);
+ return qull;
+}
+
+QDateTime qMakeDate(const char* oraDate)
+{
+ int century = uchar(oraDate[0]);
+ if(century >= 100){
+ int year = uchar(oraDate[1]);
+ year = ((century-100)*100) + (year-100);
+ int month = oraDate[2];
+ int day = oraDate[3];
+ int hour = oraDate[4] - 1;
+ int min = oraDate[5] - 1;
+ int sec = oraDate[6] - 1;
+ return QDateTime(QDate(year,month,day), QTime(hour,min,sec));
+ }
+ return QDateTime();
+}
+
+class QOCICols
+{
+public:
+ QOCICols(int size, QOCIResultPrivate* dp);
+ ~QOCICols();
+ int readPiecewise(QVector<QVariant> &values, int index = 0);
+ int readLOBs(QVector<QVariant> &values, int index = 0);
+ int fieldFromDefine(OCIDefine* d);
+ void getValues(QVector<QVariant> &v, int index);
+ inline int size() { return fieldInf.size(); }
+ static bool execBatch(QOCIResultPrivate *d, QVector<QVariant> &boundValues, bool arrayBind);
+
+ QSqlRecord rec;
+
+private:
+ char* create(int position, int size);
+ OCILobLocator ** createLobLocator(int position, OCIEnv* env);
+ OraFieldInfo qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) const;
+
+ class OraFieldInf
+ {
+ public:
+ OraFieldInf(): data(0), len(0), ind(0), typ(QVariant::Invalid), oraType(0), def(0), lob(0)
+ {}
+ ~OraFieldInf();
+ char *data;
+ int len;
+ sb2 ind;
+ QVariant::Type typ;
+ ub4 oraType;
+ OCIDefine *def;
+ OCILobLocator *lob;
+ };
+
+ QVector<OraFieldInf> fieldInf;
+ const QOCIResultPrivate *const d;
+};
+
+QOCICols::OraFieldInf::~OraFieldInf()
+{
+ delete [] data;
+ if (lob) {
+ int r = OCIDescriptorFree(lob, OCI_DTYPE_LOB);
+ if (r != 0)
+ qWarning("QOCICols: Cannot free LOB descriptor");
+ }
+}
+
+QOCICols::QOCICols(int size, QOCIResultPrivate* dp)
+ : fieldInf(size), d(dp)
+{
+ ub4 dataSize = 0;
+ OCIDefine* dfn = 0;
+ int r;
+
+ OCIParam* param = 0;
+ sb4 parmStatus = 0;
+ ub4 count = 1;
+ int idx = 0;
+ parmStatus = OCIParamGet(d->sql,
+ OCI_HTYPE_STMT,
+ d->err,
+ reinterpret_cast<void **>(&param),
+ count);
+
+ while (parmStatus == OCI_SUCCESS) {
+ OraFieldInfo ofi = qMakeOraField(d, param);
+ if (ofi.oraType == SQLT_RDD)
+ dataSize = 50;
+#ifdef SQLT_INTERVAL_YM
+#ifdef SQLT_INTERVAL_DS
+ else if (ofi.oraType == SQLT_INTERVAL_YM || ofi.oraType == SQLT_INTERVAL_DS)
+ // since we are binding interval datatype as string,
+ // we are not interested in the number of bytes but characters.
+ dataSize = 50; // magic number
+#endif //SQLT_INTERVAL_DS
+#endif //SQLT_INTERVAL_YM
+ else if (ofi.oraType == SQLT_NUM || ofi.oraType == SQLT_VNU){
+ if (ofi.oraPrecision > 0)
+ dataSize = (ofi.oraPrecision + 1) * sizeof(utext);
+ else
+ dataSize = (38 + 1) * sizeof(utext);
+ }
+ else
+ dataSize = ofi.oraLength;
+
+ fieldInf[idx].typ = ofi.type;
+ fieldInf[idx].oraType = ofi.oraType;
+ rec.append(qFromOraInf(ofi));
+
+ switch (ofi.type) {
+ case QVariant::DateTime:
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ create(idx, dataSize+1),
+ dataSize+1,
+ SQLT_DAT,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DEFAULT);
+ break;
+ case QVariant::Double:
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ create(idx, sizeof(double) - 1),
+ sizeof(double),
+ SQLT_FLT,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DEFAULT);
+ break;
+ case QVariant::Int:
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ create(idx, sizeof(qint32) - 1),
+ sizeof(qint32),
+ SQLT_INT,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DEFAULT);
+ break;
+ case QVariant::LongLong:
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ create(idx, sizeof(OCINumber)),
+ sizeof(OCINumber),
+ SQLT_VNU,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DEFAULT);
+ break;
+ case QVariant::ByteArray:
+ // RAW and LONG RAW fields can't be bound to LOB locators
+ if (ofi.oraType == SQLT_BIN) {
+// qDebug("binding SQLT_BIN");
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ create(idx, dataSize),
+ dataSize,
+ SQLT_BIN,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DYNAMIC_FETCH);
+ } else if (ofi.oraType == SQLT_LBI) {
+// qDebug("binding SQLT_LBI");
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ 0,
+ SB4MAXVAL,
+ SQLT_LBI,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DYNAMIC_FETCH);
+ } else if (ofi.oraType == SQLT_CLOB) {
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ createLobLocator(idx, d->env),
+ -1,
+ SQLT_CLOB,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DEFAULT);
+ } else {
+// qDebug("binding SQLT_BLOB");
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ createLobLocator(idx, d->env),
+ -1,
+ SQLT_BLOB,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DEFAULT);
+ }
+ break;
+ case QVariant::String:
+ if (ofi.oraType == SQLT_LNG) {
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ 0,
+ SB4MAXVAL,
+ SQLT_LNG,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DYNAMIC_FETCH);
+ } else {
+ dataSize += dataSize + sizeof(QChar);
+ //qDebug("OCIDefineByPosStr(%d): %d", count, dataSize);
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ create(idx, dataSize),
+ dataSize,
+ SQLT_STR,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DEFAULT);
+ if (r == 0)
+ d->setCharset(dfn, OCI_HTYPE_DEFINE);
+ }
+ break;
+ default:
+ // this should make enough space even with character encoding
+ dataSize = (dataSize + 1) * sizeof(utext) ;
+ //qDebug("OCIDefineByPosDef(%d): %d", count, dataSize);
+ r = OCIDefineByPos(d->sql,
+ &dfn,
+ d->err,
+ count,
+ create(idx, dataSize),
+ dataSize+1,
+ SQLT_STR,
+ &(fieldInf[idx].ind),
+ 0, 0, OCI_DEFAULT);
+ break;
+ }
+ if (r != 0)
+ qOraWarning("QOCICols::bind:", d->err);
+ fieldInf[idx].def = dfn;
+ ++count;
+ ++idx;
+ parmStatus = OCIParamGet(d->sql,
+ OCI_HTYPE_STMT,
+ d->err,
+ reinterpret_cast<void **>(&param),
+ count);
+ }
+}
+
+QOCICols::~QOCICols()
+{
+}
+
+char* QOCICols::create(int position, int size)
+{
+ char* c = new char[size+1];
+ // Oracle may not fill fixed width fields
+ memset(c, 0, size+1);
+ fieldInf[position].data = c;
+ fieldInf[position].len = size;
+ return c;
+}
+
+OCILobLocator **QOCICols::createLobLocator(int position, OCIEnv* env)
+{
+ OCILobLocator *& lob = fieldInf[position].lob;
+ int r = OCIDescriptorAlloc(env,
+ reinterpret_cast<void **>(&lob),
+ OCI_DTYPE_LOB,
+ 0,
+ 0);
+ if (r != 0) {
+ qWarning("QOCICols: Cannot create LOB locator");
+ lob = 0;
+ }
+ return &lob;
+}
+
+int QOCICols::readPiecewise(QVector<QVariant> &values, int index)
+{
+ OCIDefine* dfn;
+ ub4 typep;
+ ub1 in_outp;
+ ub4 iterp;
+ ub4 idxp;
+ ub1 piecep;
+ sword status;
+ text col [QOCI_DYNAMIC_CHUNK_SIZE+1];
+ int fieldNum = -1;
+ int r = 0;
+ bool nullField;
+
+ do {
+ r = OCIStmtGetPieceInfo(d->sql, d->err, reinterpret_cast<void **>(&dfn), &typep,
+ &in_outp, &iterp, &idxp, &piecep);
+ if (r != OCI_SUCCESS)
+ qOraWarning("OCIResultPrivate::readPiecewise: unable to get piece info:", d->err);
+ fieldNum = fieldFromDefine(dfn);
+ bool isStringField = fieldInf.at(fieldNum).oraType == SQLT_LNG;
+ ub4 chunkSize = QOCI_DYNAMIC_CHUNK_SIZE;
+ nullField = false;
+ r = OCIStmtSetPieceInfo(dfn, OCI_HTYPE_DEFINE,
+ d->err, col,
+ &chunkSize, piecep, NULL, NULL);
+ if (r != OCI_SUCCESS)
+ qOraWarning("OCIResultPrivate::readPiecewise: unable to set piece info:", d->err);
+ status = OCIStmtFetch (d->sql, d->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT);
+ if (status == -1) {
+ sb4 errcode;
+ OCIErrorGet(d->err, 1, 0, &errcode, 0, 0,OCI_HTYPE_ERROR);
+ switch (errcode) {
+ case 1405: /* NULL */
+ nullField = true;
+ break;
+ default:
+ qOraWarning("OCIResultPrivate::readPiecewise: unable to fetch next:", d->err);
+ break;
+ }
+ }
+ if (status == OCI_NO_DATA)
+ break;
+ if (nullField || !chunkSize) {
+ fieldInf[fieldNum].ind = -1;
+ } else {
+ if (isStringField) {
+ QString str = values.at(fieldNum + index).toString();
+ str += QString(reinterpret_cast<const QChar *>(col), chunkSize / 2);
+ values[fieldNum + index] = str;
+ fieldInf[fieldNum].ind = 0;
+ } else {
+ QByteArray ba = values.at(fieldNum + index).toByteArray();
+ int sz = ba.size();
+ ba.resize(sz + chunkSize);
+ memcpy(ba.data() + sz, reinterpret_cast<char *>(col), chunkSize);
+ values[fieldNum + index] = ba;
+ fieldInf[fieldNum].ind = 0;
+ }
+ }
+ } while (status == OCI_SUCCESS_WITH_INFO || status == OCI_NEED_DATA);
+ return r;
+}
+
+OraFieldInfo QOCICols::qMakeOraField(const QOCIResultPrivate* p, OCIParam* param) const
+{
+ OraFieldInfo ofi;
+ ub2 colType(0);
+ text *colName = 0;
+ ub4 colNameLen(0);
+ sb1 colScale(0);
+ ub2 colLength(0);
+ ub2 colFieldLength(0);
+ sb2 colPrecision(0);
+ ub1 colIsNull(0);
+ int r(0);
+ QVariant::Type type(QVariant::Invalid);
+
+ r = OCIAttrGet(param,
+ OCI_DTYPE_PARAM,
+ &colType,
+ 0,
+ OCI_ATTR_DATA_TYPE,
+ p->err);
+ if (r != 0)
+ qOraWarning("qMakeOraField:", p->err);
+
+ r = OCIAttrGet(param,
+ OCI_DTYPE_PARAM,
+ &colName,
+ &colNameLen,
+ OCI_ATTR_NAME,
+ p->err);
+ if (r != 0)
+ qOraWarning("qMakeOraField:", p->err);
+
+ r = OCIAttrGet(param,
+ OCI_DTYPE_PARAM,
+ &colLength,
+ 0,
+ OCI_ATTR_DATA_SIZE, /* in bytes */
+ p->err);
+ if (r != 0)
+ qOraWarning("qMakeOraField:", p->err);
+
+#ifdef OCI_ATTR_CHAR_SIZE
+ r = OCIAttrGet(param,
+ OCI_DTYPE_PARAM,
+ &colFieldLength,
+ 0,
+ OCI_ATTR_CHAR_SIZE,
+ p->err);
+ if (r != 0)
+ qOraWarning("qMakeOraField:", p->err);
+#else
+ // for Oracle8.
+ colFieldLength = colLength;
+#endif
+
+ r = OCIAttrGet(param,
+ OCI_DTYPE_PARAM,
+ &colPrecision,
+ 0,
+ OCI_ATTR_PRECISION,
+ p->err);
+ if (r != 0)
+ qOraWarning("qMakeOraField:", p->err);
+
+ r = OCIAttrGet(param,
+ OCI_DTYPE_PARAM,
+ &colScale,
+ 0,
+ OCI_ATTR_SCALE,
+ p->err);
+ if (r != 0)
+ qOraWarning("qMakeOraField:", p->err);
+ r = OCIAttrGet(param,
+ OCI_DTYPE_PARAM,
+ &colType,
+ 0,
+ OCI_ATTR_DATA_TYPE,
+ p->err);
+ if (r != 0)
+ qOraWarning("qMakeOraField:", p->err);
+ r = OCIAttrGet(param,
+ OCI_DTYPE_PARAM,
+ &colIsNull,
+ 0,
+ OCI_ATTR_IS_NULL,
+ p->err);
+ if (r != 0)
+ qOraWarning("qMakeOraField:", p->err);
+
+ type = qDecodeOCIType(colType, p->q_func()->numericalPrecisionPolicy());
+
+ if (type == QVariant::Int) {
+ if (colLength == 22 && colPrecision == 0 && colScale == 0)
+ type = QVariant::String;
+ if (colScale > 0)
+ type = QVariant::String;
+ }
+
+ // bind as double if the precision policy asks for it
+ if (((colType == SQLT_FLT) || (colType == SQLT_NUM))
+ && (p->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionDouble)) {
+ type = QVariant::Double;
+ }
+
+ // bind as int32 or int64 if the precision policy asks for it
+ if ((colType == SQLT_NUM) || (colType == SQLT_VNU) || (colType == SQLT_UIN)
+ || (colType == SQLT_INT)) {
+ if (p->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt64)
+ type = QVariant::LongLong;
+ else if (p->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt32)
+ type = QVariant::Int;
+ }
+
+ if (colType == SQLT_BLOB)
+ colLength = 0;
+
+ // colNameLen is length in bytes
+ ofi.name = QString(reinterpret_cast<const QChar*>(colName), colNameLen / 2);
+ ofi.type = type;
+ ofi.oraType = colType;
+ ofi.oraFieldLength = colFieldLength;
+ ofi.oraLength = colLength;
+ ofi.oraScale = colScale;
+ ofi.oraPrecision = colPrecision;
+ ofi.oraIsNull = colIsNull;
+
+ return ofi;
+}
+
+struct QOCIBatchColumn
+{
+ inline QOCIBatchColumn()
+ : bindh(0), bindAs(0), maxLen(0), recordCount(0),
+ data(0), lengths(0), indicators(0), maxarr_len(0), curelep(0) {}
+
+ OCIBind* bindh;
+ ub2 bindAs;
+ ub4 maxLen;
+ ub4 recordCount;
+ char* data;
+ ub2* lengths;
+ sb2* indicators;
+ ub4 maxarr_len;
+ ub4 curelep;
+};
+
+struct QOCIBatchCleanupHandler
+{
+ inline QOCIBatchCleanupHandler(QVector<QOCIBatchColumn> &columns)
+ : col(columns) {}
+
+ ~QOCIBatchCleanupHandler()
+ {
+ // deleting storage, length and indicator arrays
+ for ( int j = 0; j < col.count(); ++j){
+ delete[] col[j].lengths;
+ delete[] col[j].indicators;
+ delete[] col[j].data;
+ }
+ }
+
+ QVector<QOCIBatchColumn> &col;
+};
+
+bool QOCICols::execBatch(QOCIResultPrivate *d, QVector<QVariant> &boundValues, bool arrayBind)
+{
+ int columnCount = boundValues.count();
+ if (boundValues.isEmpty() || columnCount == 0)
+ return false;
+
+#ifdef QOCI_DEBUG
+ qDebug() << "columnCount:" << columnCount << boundValues;
+#endif
+
+ int i;
+ sword r;
+
+ QVarLengthArray<QVariant::Type> fieldTypes;
+ for (i = 0; i < columnCount; ++i) {
+ QVariant::Type tp = boundValues.at(i).type();
+ fieldTypes.append(tp == QVariant::List ? boundValues.at(i).toList().value(0).type()
+ : tp);
+ }
+
+ QList<QByteArray> tmpStorage;
+ SizeArray tmpSizes(columnCount);
+ QVector<QOCIBatchColumn> columns(columnCount);
+ QOCIBatchCleanupHandler cleaner(columns);
+
+ // figuring out buffer sizes
+ for (i = 0; i < columnCount; ++i) {
+
+ if (boundValues.at(i).type() != QVariant::List) {
+
+ // not a list - create a deep-copy of the single value
+ QOCIBatchColumn &singleCol = columns[i];
+ singleCol.indicators = new sb2[1];
+ *singleCol.indicators = boundValues.at(i).isNull() ? -1 : 0;
+
+ r = d->bindValue(d->sql, &singleCol.bindh, d->err, i,
+ boundValues.at(i), singleCol.indicators, &tmpSizes[i], tmpStorage);
+
+ if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) {
+ qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err);
+ d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
+ "Unable to bind column for batch execute"),
+ QSqlError::StatementError, d->err));
+ return false;
+ }
+ continue;
+ }
+
+ QOCIBatchColumn &col = columns[i];
+ col.recordCount = boundValues.at(i).toList().count();
+
+ col.lengths = new ub2[col.recordCount];
+ col.indicators = new sb2[col.recordCount];
+ col.maxarr_len = col.recordCount;
+ col.curelep = col.recordCount;
+
+ switch (fieldTypes[i]) {
+ case QVariant::Time:
+ case QVariant::Date:
+ case QVariant::DateTime:
+ col.bindAs = SQLT_DAT;
+ col.maxLen = 7;
+ break;
+
+ case QVariant::Int:
+ col.bindAs = SQLT_INT;
+ col.maxLen = sizeof(int);
+ break;
+
+ case QVariant::UInt:
+ col.bindAs = SQLT_UIN;
+ col.maxLen = sizeof(uint);
+ break;
+
+ case QVariant::LongLong:
+ col.bindAs = SQLT_VNU;
+ col.maxLen = sizeof(OCINumber);
+ break;
+
+ case QVariant::ULongLong:
+ col.bindAs = SQLT_VNU;
+ col.maxLen = sizeof(OCINumber);
+ break;
+
+ case QVariant::Double:
+ col.bindAs = SQLT_FLT;
+ col.maxLen = sizeof(double);
+ break;
+
+ case QVariant::UserType:
+ col.bindAs = SQLT_RDD;
+ col.maxLen = sizeof(OCIRowid*);
+ break;
+
+ case QVariant::String: {
+ col.bindAs = SQLT_STR;
+ for (uint j = 0; j < col.recordCount; ++j) {
+ uint len;
+ if(d->isOutValue(i))
+ len = boundValues.at(i).toList().at(j).toString().capacity() + 1;
+ else
+ len = boundValues.at(i).toList().at(j).toString().length() + 1;
+ if (len > col.maxLen)
+ col.maxLen = len;
+ }
+ col.maxLen *= sizeof(QChar);
+ break; }
+
+ case QVariant::ByteArray:
+ default: {
+ col.bindAs = SQLT_LBI;
+ for (uint j = 0; j < col.recordCount; ++j) {
+ if(d->isOutValue(i))
+ col.lengths[j] = boundValues.at(i).toList().at(j).toByteArray().capacity();
+ else
+ col.lengths[j] = boundValues.at(i).toList().at(j).toByteArray().size();
+ if (col.lengths[j] > col.maxLen)
+ col.maxLen = col.lengths[j];
+ }
+ break; }
+ }
+
+ col.data = new char[col.maxLen * col.recordCount];
+ memset(col.data, 0, col.maxLen * col.recordCount);
+
+ // we may now populate column with data
+ for (uint row = 0; row < col.recordCount; ++row) {
+ const QVariant &val = boundValues.at(i).toList().at(row);
+
+ if (val.isNull()){
+ columns[i].indicators[row] = -1;
+ columns[i].lengths[row] = 0;
+ } else {
+ columns[i].indicators[row] = 0;
+ char *dataPtr = columns[i].data + (columns[i].maxLen * row);
+ switch (fieldTypes[i]) {
+ case QVariant::Time:
+ case QVariant::Date:
+ case QVariant::DateTime:{
+ columns[i].lengths[row] = columns[i].maxLen;
+ const QByteArray ba = qMakeOraDate(val.toDateTime());
+ Q_ASSERT(ba.size() == int(columns[i].maxLen));
+ memcpy(dataPtr, ba.constData(), columns[i].maxLen);
+ break;
+ }
+ case QVariant::Int:
+ columns[i].lengths[row] = columns[i].maxLen;
+ *reinterpret_cast<int*>(dataPtr) = val.toInt();
+ break;
+
+ case QVariant::UInt:
+ columns[i].lengths[row] = columns[i].maxLen;
+ *reinterpret_cast<uint*>(dataPtr) = val.toUInt();
+ break;
+
+ case QVariant::LongLong:
+ {
+ columns[i].lengths[row] = columns[i].maxLen;
+ const QByteArray ba = qMakeOCINumber(val.toLongLong(), d->err);
+ Q_ASSERT(ba.size() == int(columns[i].maxLen));
+ memcpy(dataPtr, ba.constData(), columns[i].maxLen);
+ break;
+ }
+ case QVariant::ULongLong:
+ {
+ columns[i].lengths[row] = columns[i].maxLen;
+ const QByteArray ba = qMakeOCINumber(val.toULongLong(), d->err);
+ Q_ASSERT(ba.size() == int(columns[i].maxLen));
+ memcpy(dataPtr, ba.constData(), columns[i].maxLen);
+ break;
+ }
+ case QVariant::Double:
+ columns[i].lengths[row] = columns[i].maxLen;
+ *reinterpret_cast<double*>(dataPtr) = val.toDouble();
+ break;
+
+ case QVariant::String: {
+ const QString s = val.toString();
+ columns[i].lengths[row] = (s.length() + 1) * sizeof(QChar);
+ memcpy(dataPtr, s.utf16(), columns[i].lengths[row]);
+ break;
+ }
+ case QVariant::UserType:
+ if (val.canConvert<QOCIRowIdPointer>()) {
+ const QOCIRowIdPointer rptr = qvariant_cast<QOCIRowIdPointer>(val);
+ *reinterpret_cast<OCIRowid**>(dataPtr) = rptr->id;
+ columns[i].lengths[row] = 0;
+ break;
+ }
+ case QVariant::ByteArray:
+ default: {
+ const QByteArray ba = val.toByteArray();
+ columns[i].lengths[row] = ba.size();
+ memcpy(dataPtr, ba.constData(), ba.size());
+ break;
+ }
+ }
+ }
+ }
+
+ QOCIBatchColumn &bindColumn = columns[i];
+
+#ifdef QOCI_DEBUG
+ qDebug("OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)",
+ d->sql, &bindColumn.bindh, d->err, i + 1, bindColumn.data,
+ bindColumn.maxLen, bindColumn.bindAs, bindColumn.indicators, bindColumn.lengths,
+ arrayBind ? bindColumn.maxarr_len : 0, arrayBind ? &bindColumn.curelep : 0);
+
+ for (int ii = 0; ii < (int)bindColumn.recordCount; ++ii) {
+ qDebug(" record %d: indicator %d, length %d", ii, bindColumn.indicators[ii],
+ bindColumn.lengths[ii]);
+ }
+#endif
+
+
+ // binding the column
+ r = OCIBindByPos(
+ d->sql, &bindColumn.bindh, d->err, i + 1,
+ bindColumn.data,
+ bindColumn.maxLen,
+ bindColumn.bindAs,
+ bindColumn.indicators,
+ bindColumn.lengths,
+ 0,
+ arrayBind ? bindColumn.maxarr_len : 0,
+ arrayBind ? &bindColumn.curelep : 0,
+ OCI_DEFAULT);
+
+#ifdef QOCI_DEBUG
+ qDebug("After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh);
+#endif
+
+ if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) {
+ qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err);
+ d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
+ "Unable to bind column for batch execute"),
+ QSqlError::StatementError, d->err));
+ return false;
+ }
+
+ r = OCIBindArrayOfStruct (
+ columns[i].bindh, d->err,
+ columns[i].maxLen,
+ sizeof(columns[i].indicators[0]),
+ sizeof(columns[i].lengths[0]),
+ 0);
+
+ if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) {
+ qOraWarning("QOCIPrivate::execBatch: unable to bind column:", d->err);
+ d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
+ "Unable to bind column for batch execute"),
+ QSqlError::StatementError, d->err));
+ return false;
+ }
+ }
+
+ //finaly we can execute
+ r = OCIStmtExecute(d->svc, d->sql, d->err,
+ arrayBind ? 1 : columns[0].recordCount,
+ 0, NULL, NULL,
+ d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS);
+
+ if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) {
+ qOraWarning("QOCIPrivate::execBatch: unable to execute batch statement:", d->err);
+ d->q_func()->setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
+ "Unable to execute batch statement"),
+ QSqlError::StatementError, d->err));
+ return false;
+ }
+
+ // for out parameters we copy data back to value vector
+ for (i = 0; i < columnCount; ++i) {
+
+ if (!d->isOutValue(i))
+ continue;
+
+ QVariant::Type tp = boundValues.at(i).type();
+ if (tp != QVariant::List) {
+ qOraOutValue(boundValues[i], tmpStorage, d->err);
+ if (*columns[i].indicators == -1)
+ boundValues[i] = QVariant(tp);
+ continue;
+ }
+
+ QVariantList *list = static_cast<QVariantList *>(const_cast<void*>(boundValues.at(i).data()));
+
+ char* data = columns[i].data;
+ for (uint r = 0; r < columns[i].recordCount; ++r){
+
+ if (columns[i].indicators[r] == -1) {
+ (*list)[r] = QVariant();
+ continue;
+ }
+
+ switch(columns[i].bindAs) {
+
+ case SQLT_DAT:
+ (*list)[r] = qMakeDate(data + r * columns[i].maxLen);
+ break;
+
+ case SQLT_INT:
+ (*list)[r] = *reinterpret_cast<int*>(data + r * columns[i].maxLen);
+ break;
+
+ case SQLT_UIN:
+ (*list)[r] = *reinterpret_cast<uint*>(data + r * columns[i].maxLen);
+ break;
+
+ case SQLT_VNU:
+ {
+ switch (boundValues.at(i).type()) {
+ case QVariant::LongLong:
+ (*list)[r] = qMakeLongLong(data + r * columns[i].maxLen, d->err);
+ break;
+ case QVariant::ULongLong:
+ (*list)[r] = qMakeULongLong(data + r * columns[i].maxLen, d->err);
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ case SQLT_FLT:
+ (*list)[r] = *reinterpret_cast<double*>(data + r * columns[i].maxLen);
+ break;
+
+ case SQLT_STR:
+ (*list)[r] = QString(reinterpret_cast<const QChar *>(data
+ + r * columns[i].maxLen));
+ break;
+
+ default:
+ (*list)[r] = QByteArray(data + r * columns[i].maxLen, columns[i].maxLen);
+ break;
+ }
+ }
+ }
+
+ d->q_func()->setSelect(false);
+ d->q_func()->setAt(QSql::BeforeFirstRow);
+ d->q_func()->setActive(true);
+
+ return true;
+}
+
+template<class T, int sz>
+int qReadLob(T &buf, const QOCIResultPrivate *d, OCILobLocator *lob)
+{
+ ub1 csfrm;
+ ub4 amount;
+ int r;
+
+ // Read this from the database, don't assume we know what it is set to
+ r = OCILobCharSetForm(d->env, d->err, lob, &csfrm);
+ if (r != OCI_SUCCESS) {
+ qOraWarning("OCIResultPrivate::readLobs: Couldn't get LOB char set form: ", d->err);
+ csfrm = 0;
+ }
+
+ // Get the length of the LOB (this is in characters)
+ r = OCILobGetLength(d->svc, d->err, lob, &amount);
+ if (r == OCI_SUCCESS) {
+ if (amount == 0) {
+ // Short cut for null LOBs
+ buf.resize(0);
+ return OCI_SUCCESS;
+ }
+ } else {
+ qOraWarning("OCIResultPrivate::readLobs: Couldn't get LOB length: ", d->err);
+ return r;
+ }
+
+ // Resize the buffer to hold the LOB contents
+ buf.resize(amount);
+
+ // Read the LOB into the buffer
+ r = OCILobRead(d->svc,
+ d->err,
+ lob,
+ &amount,
+ 1,
+ buf.data(),
+ buf.size() * sz, // this argument is in bytes, not characters
+ 0,
+ 0,
+ // Extract the data from a CLOB in UTF-16 (ie. what QString uses internally)
+ sz == 1 ? ub2(0) : ub2(QOCIEncoding),
+ csfrm);
+
+ if (r != OCI_SUCCESS)
+ qOraWarning("OCIResultPrivate::readLOBs: Cannot read LOB: ", d->err);
+
+ return r;
+}
+
+int QOCICols::readLOBs(QVector<QVariant> &values, int index)
+{
+ OCILobLocator *lob;
+ int r = OCI_SUCCESS;
+
+ for (int i = 0; i < size(); ++i) {
+ const OraFieldInf &fi = fieldInf.at(i);
+ if (fi.ind == -1 || !(lob = fi.lob))
+ continue;
+
+ bool isClob = fi.oraType == SQLT_CLOB;
+ QVariant var;
+
+ if (isClob) {
+ QString str;
+ r = qReadLob<QString, sizeof(QChar)>(str, d, lob);
+ var = str;
+ } else {
+ QByteArray buf;
+ r = qReadLob<QByteArray, sizeof(char)>(buf, d, lob);
+ var = buf;
+ }
+ if (r == OCI_SUCCESS)
+ values[index + i] = var;
+ else
+ break;
+ }
+ return r;
+}
+
+int QOCICols::fieldFromDefine(OCIDefine* d)
+{
+ for (int i = 0; i < fieldInf.count(); ++i) {
+ if (fieldInf.at(i).def == d)
+ return i;
+ }
+ return -1;
+}
+
+void QOCICols::getValues(QVector<QVariant> &v, int index)
+{
+ for (int i = 0; i < fieldInf.size(); ++i) {
+ const OraFieldInf &fld = fieldInf.at(i);
+
+ if (fld.ind == -1) {
+ // got a NULL value
+ v[index + i] = QVariant(fld.typ);
+ continue;
+ }
+
+ if (fld.oraType == SQLT_BIN || fld.oraType == SQLT_LBI || fld.oraType == SQLT_LNG)
+ continue; // already fetched piecewise
+
+ switch (fld.typ) {
+ case QVariant::DateTime:
+ v[index + i] = QVariant(qMakeDate(fld.data));
+ break;
+ case QVariant::Double:
+ case QVariant::Int:
+ case QVariant::LongLong:
+ if (d->q_func()->numericalPrecisionPolicy() != QSql::HighPrecision) {
+ if ((d->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionDouble)
+ && (fld.typ == QVariant::Double)) {
+ v[index + i] = *reinterpret_cast<double *>(fld.data);
+ break;
+ } else if ((d->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt64)
+ && (fld.typ == QVariant::LongLong)) {
+ qint64 qll = 0;
+ int r = OCINumberToInt(d->err, reinterpret_cast<OCINumber *>(fld.data), sizeof(qint64),
+ OCI_NUMBER_SIGNED, &qll);
+ if(r == OCI_SUCCESS)
+ v[index + i] = qll;
+ else
+ v[index + i] = QVariant();
+ break;
+ } else if ((d->q_func()->numericalPrecisionPolicy() == QSql::LowPrecisionInt32)
+ && (fld.typ == QVariant::Int)) {
+ v[index + i] = *reinterpret_cast<int *>(fld.data);
+ break;
+ }
+ }
+ // else fall through
+ case QVariant::String:
+ v[index + i] = QString(reinterpret_cast<const QChar *>(fld.data));
+ break;
+ case QVariant::ByteArray:
+ if (fld.len > 0)
+ v[index + i] = QByteArray(fld.data, fld.len);
+ else
+ v[index + i] = QVariant(QVariant::ByteArray);
+ break;
+ default:
+ qWarning("QOCICols::value: unknown data type");
+ break;
+ }
+ }
+}
+
+QOCIResultPrivate::QOCIResultPrivate(QOCIResult *q, const QOCIDriver *drv)
+ : QSqlCachedResultPrivate(q, drv),
+ cols(0),
+ env(drv_d_func()->env),
+ err(0),
+ svc(const_cast<OCISvcCtx*&>(drv_d_func()->svc)),
+ sql(0),
+ transaction(drv_d_func()->transaction),
+ serverVersion(drv_d_func()->serverVersion),
+ prefetchRows(drv_d_func()->prefetchRows),
+ prefetchMem(drv_d_func()->prefetchMem)
+{
+ int r = OCIHandleAlloc(env,
+ reinterpret_cast<void **>(&err),
+ OCI_HTYPE_ERROR,
+ 0,
+ 0);
+ if (r != 0)
+ qWarning("QOCIResult: unable to alloc error handle");
+}
+
+QOCIResultPrivate::~QOCIResultPrivate()
+{
+ delete cols;
+
+ int r = OCIHandleFree(err, OCI_HTYPE_ERROR);
+ if (r != 0)
+ qWarning("~QOCIResult: unable to free statement handle");
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+QOCIResult::QOCIResult(const QOCIDriver *db)
+ : QSqlCachedResult(*new QOCIResultPrivate(this, db))
+{
+}
+
+QOCIResult::~QOCIResult()
+{
+ Q_D(QOCIResult);
+ if (d->sql) {
+ int r = OCIHandleFree(d->sql, OCI_HTYPE_STMT);
+ if (r != 0)
+ qWarning("~QOCIResult: unable to free statement handle");
+ }
+}
+
+QVariant QOCIResult::handle() const
+{
+ Q_D(const QOCIResult);
+ return QVariant::fromValue(d->sql);
+}
+
+bool QOCIResult::reset (const QString& query)
+{
+ if (!prepare(query))
+ return false;
+ return exec();
+}
+
+bool QOCIResult::gotoNext(QSqlCachedResult::ValueCache &values, int index)
+{
+ Q_D(QOCIResult);
+ if (at() == QSql::AfterLastRow)
+ return false;
+
+ bool piecewise = false;
+ int r = OCI_SUCCESS;
+ r = OCIStmtFetch(d->sql, d->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT);
+
+ if (index < 0) //not interested in values
+ return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO;
+
+ switch (r) {
+ case OCI_SUCCESS:
+ break;
+ case OCI_SUCCESS_WITH_INFO:
+ qOraWarning("QOCIResult::gotoNext: SuccessWithInfo: ", d->err);
+ r = OCI_SUCCESS; //ignore it
+ break;
+ case OCI_NO_DATA:
+ // end of rowset
+ return false;
+ case OCI_NEED_DATA:
+ piecewise = true;
+ r = OCI_SUCCESS;
+ break;
+ case OCI_ERROR:
+ if (qOraErrorNumber(d->err) == 1406) {
+ qWarning("QOCI Warning: data truncated for %s", lastQuery().toLocal8Bit().constData());
+ r = OCI_SUCCESS; /* ignore it */
+ break;
+ }
+ // fall through
+ default:
+ qOraWarning("QOCIResult::gotoNext: ", d->err);
+ setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
+ "Unable to goto next"),
+ QSqlError::StatementError, d->err));
+ break;
+ }
+
+ // need to read piecewise before assigning values
+ if (r == OCI_SUCCESS && piecewise)
+ r = d->cols->readPiecewise(values, index);
+
+ if (r == OCI_SUCCESS)
+ d->cols->getValues(values, index);
+ if (r == OCI_SUCCESS)
+ r = d->cols->readLOBs(values, index);
+ if (r != OCI_SUCCESS)
+ setAt(QSql::AfterLastRow);
+ return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO;
+}
+
+int QOCIResult::size()
+{
+ return -1;
+}
+
+int QOCIResult::numRowsAffected()
+{
+ Q_D(QOCIResult);
+ int rowCount;
+ OCIAttrGet(d->sql,
+ OCI_HTYPE_STMT,
+ &rowCount,
+ NULL,
+ OCI_ATTR_ROW_COUNT,
+ d->err);
+ return rowCount;
+}
+
+bool QOCIResult::prepare(const QString& query)
+{
+ Q_D(QOCIResult);
+ int r = 0;
+ QSqlResult::prepare(query);
+
+ delete d->cols;
+ d->cols = 0;
+ QSqlCachedResult::cleanup();
+
+ if (d->sql) {
+ r = OCIHandleFree(d->sql, OCI_HTYPE_STMT);
+ if (r != OCI_SUCCESS)
+ qOraWarning("QOCIResult::prepare: unable to free statement handle:", d->err);
+ }
+ if (query.isEmpty())
+ return false;
+ r = OCIHandleAlloc(d->env,
+ reinterpret_cast<void **>(&d->sql),
+ OCI_HTYPE_STMT,
+ 0,
+ 0);
+ if (r != OCI_SUCCESS) {
+ qOraWarning("QOCIResult::prepare: unable to alloc statement:", d->err);
+ setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
+ "Unable to alloc statement"), QSqlError::StatementError, d->err));
+ return false;
+ }
+ d->setStatementAttributes();
+ const OraText *txt = reinterpret_cast<const OraText *>(query.utf16());
+ const int len = query.length() * sizeof(QChar);
+ r = OCIStmtPrepare(d->sql,
+ d->err,
+ txt,
+ len,
+ OCI_NTV_SYNTAX,
+ OCI_DEFAULT);
+ if (r != OCI_SUCCESS) {
+ qOraWarning("QOCIResult::prepare: unable to prepare statement:", d->err);
+ setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
+ "Unable to prepare statement"), QSqlError::StatementError, d->err));
+ return false;
+ }
+ return true;
+}
+
+bool QOCIResult::exec()
+{
+ Q_D(QOCIResult);
+ int r = 0;
+ ub2 stmtType=0;
+ ub4 iters;
+ ub4 mode;
+ QList<QByteArray> tmpStorage;
+ IndicatorArray indicators(boundValueCount());
+ SizeArray tmpSizes(boundValueCount());
+
+ r = OCIAttrGet(d->sql,
+ OCI_HTYPE_STMT,
+ &stmtType,
+ NULL,
+ OCI_ATTR_STMT_TYPE,
+ d->err);
+
+ if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) {
+ qOraWarning("QOCIResult::exec: Unable to get statement type:", d->err);
+ setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
+ "Unable to get statement type"), QSqlError::StatementError, d->err));
+#ifdef QOCI_DEBUG
+ qDebug() << "lastQuery()" << lastQuery();
+#endif
+ return false;
+ }
+
+ iters = stmtType == OCI_STMT_SELECT ? 0 : 1;
+ mode = d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS;
+
+ // bind placeholders
+ if (boundValueCount() > 0
+ && d->bindValues(boundValues(), indicators, tmpSizes, tmpStorage) != OCI_SUCCESS) {
+ qOraWarning("QOCIResult::exec: unable to bind value: ", d->err);
+ setLastError(qMakeError(QCoreApplication::translate("QOCIResult", "Unable to bind value"),
+ QSqlError::StatementError, d->err));
+#ifdef QOCI_DEBUG
+ qDebug() << "lastQuery()" << lastQuery();
+#endif
+ return false;
+ }
+
+ // execute
+ r = OCIStmtExecute(d->svc,
+ d->sql,
+ d->err,
+ iters,
+ 0,
+ 0,
+ 0,
+ mode);
+ if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) {
+ qOraWarning("QOCIResult::exec: unable to execute statement:", d->err);
+ setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
+ "Unable to execute statement"), QSqlError::StatementError, d->err));
+#ifdef QOCI_DEBUG
+ qDebug() << "lastQuery()" << lastQuery();
+#endif
+ return false;
+ }
+
+ if (stmtType == OCI_STMT_SELECT) {
+ ub4 parmCount = 0;
+ int r = OCIAttrGet(d->sql, OCI_HTYPE_STMT, reinterpret_cast<void **>(&parmCount),
+ 0, OCI_ATTR_PARAM_COUNT, d->err);
+ if (r == 0 && !d->cols)
+ d->cols = new QOCICols(parmCount, d);
+ setSelect(true);
+ QSqlCachedResult::init(parmCount);
+ } else { /* non-SELECT */
+ setSelect(false);
+ }
+ setAt(QSql::BeforeFirstRow);
+ setActive(true);
+
+ if (hasOutValues())
+ d->outValues(boundValues(), indicators, tmpStorage);
+
+ return true;
+}
+
+QSqlRecord QOCIResult::record() const
+{
+ Q_D(const QOCIResult);
+ QSqlRecord inf;
+ if (!isActive() || !isSelect() || !d->cols)
+ return inf;
+ return d->cols->rec;
+}
+
+QVariant QOCIResult::lastInsertId() const
+{
+ Q_D(const QOCIResult);
+ if (isActive()) {
+ QOCIRowIdPointer ptr(new QOCIRowId(d->env));
+
+ int r = OCIAttrGet(d->sql, OCI_HTYPE_STMT, ptr.constData()->id,
+ 0, OCI_ATTR_ROWID, d->err);
+ if (r == OCI_SUCCESS)
+ return QVariant::fromValue(ptr);
+ }
+ return QVariant();
+}
+
+bool QOCIResult::execBatch(bool arrayBind)
+{
+ Q_D(QOCIResult);
+ QOCICols::execBatch(d, boundValues(), arrayBind);
+ resetBindCount();
+ return lastError().type() == QSqlError::NoError;
+}
+
+void QOCIResult::virtual_hook(int id, void *data)
+{
+ Q_ASSERT(data);
+
+ QSqlCachedResult::virtual_hook(id, data);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+
+QOCIDriver::QOCIDriver(QObject* parent)
+ : QSqlDriver(*new QOCIDriverPrivate, parent)
+{
+ Q_D(QOCIDriver);
+#ifdef QOCI_THREADED
+ const ub4 mode = OCI_UTF16 | OCI_OBJECT | OCI_THREADED;
+#else
+ const ub4 mode = OCI_UTF16 | OCI_OBJECT;
+#endif
+ int r = OCIEnvCreate(&d->env,
+ mode,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ NULL);
+ if (r != 0) {
+ qWarning("QOCIDriver: unable to create environment");
+ setLastError(qMakeError(tr("Unable to initialize", "QOCIDriver"),
+ QSqlError::ConnectionError, d->err));
+ return;
+ }
+
+ d->allocErrorHandle();
+}
+
+QOCIDriver::QOCIDriver(OCIEnv* env, OCISvcCtx* ctx, QObject* parent)
+ : QSqlDriver(*new QOCIDriverPrivate, parent)
+{
+ Q_D(QOCIDriver);
+ d->env = env;
+ d->svc = ctx;
+
+ d->allocErrorHandle();
+
+ if (env && ctx) {
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+QOCIDriver::~QOCIDriver()
+{
+ Q_D(QOCIDriver);
+ if (isOpen())
+ close();
+ int r = OCIHandleFree(d->err, OCI_HTYPE_ERROR);
+ if (r != OCI_SUCCESS)
+ qWarning("Unable to free Error handle: %d", r);
+ r = OCIHandleFree(d->env, OCI_HTYPE_ENV);
+ if (r != OCI_SUCCESS)
+ qWarning("Unable to free Environment handle: %d", r);
+}
+
+bool QOCIDriver::hasFeature(DriverFeature f) const
+{
+ Q_D(const QOCIDriver);
+ switch (f) {
+ case Transactions:
+ case LastInsertId:
+ case BLOB:
+ case PreparedQueries:
+ case NamedPlaceholders:
+ case BatchOperations:
+ case LowPrecisionNumbers:
+ return true;
+ case QuerySize:
+ case PositionalPlaceholders:
+ case SimpleLocking:
+ case EventNotifications:
+ case FinishQuery:
+ case CancelQuery:
+ case MultipleResultSets:
+ return false;
+ case Unicode:
+ return d->serverVersion >= 9;
+ }
+ return false;
+}
+
+static void qParseOpts(const QString &options, QOCIDriverPrivate *d)
+{
+ const QStringList opts(options.split(QLatin1Char(';'), QString::SkipEmptyParts));
+ for (int i = 0; i < opts.count(); ++i) {
+ const QString tmp(opts.at(i));
+ int idx;
+ if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) {
+ qWarning("QOCIDriver::parseArgs: Invalid parameter: '%s'",
+ tmp.toLocal8Bit().constData());
+ continue;
+ }
+ const QString opt = tmp.left(idx);
+ const QString val = tmp.mid(idx + 1).simplified();
+ bool ok;
+ if (opt == QLatin1String("OCI_ATTR_PREFETCH_ROWS")) {
+ d->prefetchRows = val.toInt(&ok);
+ if (!ok)
+ d->prefetchRows = -1;
+ } else if (opt == QLatin1String("OCI_ATTR_PREFETCH_MEMORY")) {
+ d->prefetchMem = val.toInt(&ok);
+ if (!ok)
+ d->prefetchMem = -1;
+ } else {
+ qWarning ("QOCIDriver::parseArgs: Invalid parameter: '%s'",
+ opt.toLocal8Bit().constData());
+ }
+ }
+}
+
+bool QOCIDriver::open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & hostname,
+ int port,
+ const QString &opts)
+{
+ Q_D(QOCIDriver);
+ int r;
+
+ if (isOpen())
+ close();
+
+ qParseOpts(opts, d);
+
+ // Connect without tnsnames.ora if a hostname is given
+ QString connectionString = db;
+ if (!hostname.isEmpty())
+ connectionString =
+ QString::fromLatin1("(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=%1)(Port=%2))"
+ "(CONNECT_DATA=(SID=%3)))").arg(hostname).arg((port > -1 ? port : 1521)).arg(db);
+
+ r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->srvhp), OCI_HTYPE_SERVER, 0, 0);
+ if (r == OCI_SUCCESS)
+ r = OCIServerAttach(d->srvhp, d->err, reinterpret_cast<const OraText *>(connectionString.utf16()),
+ connectionString.length() * sizeof(QChar), OCI_DEFAULT);
+ if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO)
+ r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->svc), OCI_HTYPE_SVCCTX, 0, 0);
+ if (r == OCI_SUCCESS)
+ r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->srvhp, 0, OCI_ATTR_SERVER, d->err);
+ if (r == OCI_SUCCESS)
+ r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&d->authp), OCI_HTYPE_SESSION, 0, 0);
+ if (r == OCI_SUCCESS)
+ r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast<ushort *>(user.utf16()),
+ user.length() * sizeof(QChar), OCI_ATTR_USERNAME, d->err);
+ if (r == OCI_SUCCESS)
+ r = OCIAttrSet(d->authp, OCI_HTYPE_SESSION, const_cast<ushort *>(password.utf16()),
+ password.length() * sizeof(QChar), OCI_ATTR_PASSWORD, d->err);
+
+ OCITrans* trans;
+ if (r == OCI_SUCCESS)
+ r = OCIHandleAlloc(d->env, reinterpret_cast<void **>(&trans), OCI_HTYPE_TRANS, 0, 0);
+ if (r == OCI_SUCCESS)
+ r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, trans, 0, OCI_ATTR_TRANS, d->err);
+
+ if (r == OCI_SUCCESS) {
+ if (user.isEmpty() && password.isEmpty())
+ r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_EXT, OCI_DEFAULT);
+ else
+ r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_RDBMS, OCI_DEFAULT);
+ }
+ if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO)
+ r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->authp, 0, OCI_ATTR_SESSION, d->err);
+
+ if (r != OCI_SUCCESS) {
+ setLastError(qMakeError(tr("Unable to logon"), QSqlError::ConnectionError, d->err));
+ setOpenError(true);
+ if (d->authp)
+ OCIHandleFree(d->authp, OCI_HTYPE_SESSION);
+ d->authp = 0;
+ if (d->srvhp)
+ OCIHandleFree(d->srvhp, OCI_HTYPE_SERVER);
+ d->srvhp = 0;
+ return false;
+ }
+
+ // get server version
+ char vertxt[512];
+ r = OCIServerVersion(d->svc,
+ d->err,
+ reinterpret_cast<OraText *>(vertxt),
+ sizeof(vertxt),
+ OCI_HTYPE_SVCCTX);
+ if (r != 0) {
+ qWarning("QOCIDriver::open: could not get Oracle server version.");
+ } else {
+ QString versionStr;
+ versionStr = QString(reinterpret_cast<const QChar *>(vertxt));
+ QRegExp vers(QLatin1String("([0-9]+)\\.[0-9\\.]+[0-9]"));
+ if (vers.indexIn(versionStr) >= 0)
+ d->serverVersion = vers.cap(1).toInt();
+ if (d->serverVersion == 0)
+ d->serverVersion = -1;
+ }
+
+ setOpen(true);
+ setOpenError(false);
+ d->user = user;
+
+ return true;
+}
+
+void QOCIDriver::close()
+{
+ Q_D(QOCIDriver);
+ if (!isOpen())
+ return;
+
+ OCISessionEnd(d->svc, d->err, d->authp, OCI_DEFAULT);
+ OCIServerDetach(d->srvhp, d->err, OCI_DEFAULT);
+ OCIHandleFree(d->authp, OCI_HTYPE_SESSION);
+ d->authp = 0;
+ OCIHandleFree(d->srvhp, OCI_HTYPE_SERVER);
+ d->srvhp = 0;
+ OCIHandleFree(d->svc, OCI_HTYPE_SVCCTX);
+ d->svc = 0;
+ setOpen(false);
+ setOpenError(false);
+}
+
+QSqlResult *QOCIDriver::createResult() const
+{
+ return new QOCIResult(this);
+}
+
+bool QOCIDriver::beginTransaction()
+{
+ Q_D(QOCIDriver);
+ if (!isOpen()) {
+ qWarning("QOCIDriver::beginTransaction: Database not open");
+ return false;
+ }
+ int r = OCITransStart(d->svc,
+ d->err,
+ 2,
+ OCI_TRANS_READWRITE);
+ if (r == OCI_ERROR) {
+ qOraWarning("QOCIDriver::beginTransaction: ", d->err);
+ setLastError(qMakeError(QCoreApplication::translate("QOCIDriver",
+ "Unable to begin transaction"), QSqlError::TransactionError, d->err));
+ return false;
+ }
+ d->transaction = true;
+ return true;
+}
+
+bool QOCIDriver::commitTransaction()
+{
+ Q_D(QOCIDriver);
+ if (!isOpen()) {
+ qWarning("QOCIDriver::commitTransaction: Database not open");
+ return false;
+ }
+ int r = OCITransCommit(d->svc,
+ d->err,
+ 0);
+ if (r == OCI_ERROR) {
+ qOraWarning("QOCIDriver::commitTransaction:", d->err);
+ setLastError(qMakeError(QCoreApplication::translate("QOCIDriver",
+ "Unable to commit transaction"), QSqlError::TransactionError, d->err));
+ return false;
+ }
+ d->transaction = false;
+ return true;
+}
+
+bool QOCIDriver::rollbackTransaction()
+{
+ Q_D(QOCIDriver);
+ if (!isOpen()) {
+ qWarning("QOCIDriver::rollbackTransaction: Database not open");
+ return false;
+ }
+ int r = OCITransRollback(d->svc,
+ d->err,
+ 0);
+ if (r == OCI_ERROR) {
+ qOraWarning("QOCIDriver::rollbackTransaction:", d->err);
+ setLastError(qMakeError(QCoreApplication::translate("QOCIDriver",
+ "Unable to rollback transaction"), QSqlError::TransactionError, d->err));
+ return false;
+ }
+ d->transaction = false;
+ return true;
+}
+
+enum Expression {
+ OrExpression,
+ AndExpression
+};
+
+static QString make_where_clause(const QString &user, Expression e)
+{
+ static const char sysUsers[][8] = {
+ "MDSYS",
+ "LBACSYS",
+ "SYS",
+ "SYSTEM",
+ "WKSYS",
+ "CTXSYS",
+ "WMSYS",
+ };
+ static const char joinC[][4] = { "or" , "and" };
+ static Q_CONSTEXPR QLatin1Char bang[] = { QLatin1Char(' '), QLatin1Char('!') };
+
+ const QLatin1String join(joinC[e], -1); // -1: force strlen call
+
+ QString result;
+ result.reserve(sizeof sysUsers / sizeof *sysUsers *
+ // max-sizeof(owner != <sysuser> and )
+ (9 + sizeof *sysUsers + 5));
+ for (const auto &sysUser : sysUsers) {
+ const QLatin1String l1(sysUser, -1); // -1: force strlen call
+ if (l1 != user)
+ result += QLatin1String("owner ") + bang[e] + QLatin1String("= '") + l1 + QLatin1Char(' ') + join + QLatin1Char(' ');
+ }
+
+ result.chop(join.size() + 2); // remove final " <join> "
+
+ return result;
+}
+
+QStringList QOCIDriver::tables(QSql::TableType type) const
+{
+ Q_D(const QOCIDriver);
+ QStringList tl;
+
+ QString user = d->user;
+ if ( isIdentifierEscaped(user, QSqlDriver::TableName))
+ user = stripDelimiters(user, QSqlDriver::TableName);
+ else
+ user = user.toUpper();
+
+ if (!isOpen())
+ return tl;
+
+ QSqlQuery t(createResult());
+ t.setForwardOnly(true);
+ if (type & QSql::Tables) {
+ const QLatin1String tableQuery("select owner, table_name from all_tables where ");
+ const QString where = make_where_clause(user, AndExpression);
+ t.exec(tableQuery + where);
+ while (t.next()) {
+ if (t.value(0).toString().toUpper() != user.toUpper())
+ tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString());
+ else
+ tl.append(t.value(1).toString());
+ }
+
+ // list all table synonyms as well
+ const QLatin1String synonymQuery("select owner, synonym_name from all_synonyms where ");
+ t.exec(synonymQuery + where);
+ while (t.next()) {
+ if (t.value(0).toString() != d->user)
+ tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString());
+ else
+ tl.append(t.value(1).toString());
+ }
+ }
+ if (type & QSql::Views) {
+ const QLatin1String query("select owner, view_name from all_views where ");
+ const QString where = make_where_clause(user, AndExpression);
+ t.exec(query + where);
+ while (t.next()) {
+ if (t.value(0).toString().toUpper() != d->user.toUpper())
+ tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString());
+ else
+ tl.append(t.value(1).toString());
+ }
+ }
+ if (type & QSql::SystemTables) {
+ t.exec(QLatin1String("select table_name from dictionary"));
+ while (t.next()) {
+ tl.append(t.value(0).toString());
+ }
+ const QLatin1String tableQuery("select owner, table_name from all_tables where ");
+ const QString where = make_where_clause(user, OrExpression);
+ t.exec(tableQuery + where);
+ while (t.next()) {
+ if (t.value(0).toString().toUpper() != user.toUpper())
+ tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString());
+ else
+ tl.append(t.value(1).toString());
+ }
+
+ // list all table synonyms as well
+ const QLatin1String synonymQuery("select owner, synonym_name from all_synonyms where ");
+ t.exec(synonymQuery + where);
+ while (t.next()) {
+ if (t.value(0).toString() != d->user)
+ tl.append(t.value(0).toString() + QLatin1Char('.') + t.value(1).toString());
+ else
+ tl.append(t.value(1).toString());
+ }
+ }
+ return tl;
+}
+
+void qSplitTableAndOwner(const QString & tname, QString * tbl,
+ QString * owner)
+{
+ int i = tname.indexOf(QLatin1Char('.')); // prefixed with owner?
+ if (i != -1) {
+ *tbl = tname.right(tname.length() - i - 1);
+ *owner = tname.left(i);
+ } else {
+ *tbl = tname;
+ }
+}
+
+QSqlRecord QOCIDriver::record(const QString& tablename) const
+{
+ Q_D(const QOCIDriver);
+ QSqlRecord fil;
+ if (!isOpen())
+ return fil;
+
+ QSqlQuery t(createResult());
+ // using two separate queries for this is A LOT faster than using
+ // eg. a sub-query on the sys.synonyms table
+ QString stmt(QLatin1String("select column_name, data_type, data_length, "
+ "data_precision, data_scale, nullable, data_default%1"
+ "from all_tab_columns a "
+ "where a.table_name=%2"));
+ if (d->serverVersion >= 9)
+ stmt = stmt.arg(QLatin1String(", char_length "));
+ else
+ stmt = stmt.arg(QLatin1String(" "));
+ bool buildRecordInfo = false;
+ QString table, owner, tmpStmt;
+ qSplitTableAndOwner(tablename, &table, &owner);
+
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ else
+ table = table.toUpper();
+
+ tmpStmt = stmt.arg(QLatin1Char('\'') + table + QLatin1Char('\''));
+ if (owner.isEmpty()) {
+ owner = d->user;
+ }
+
+ if (isIdentifierEscaped(owner, QSqlDriver::TableName))
+ owner = stripDelimiters(owner, QSqlDriver::TableName);
+ else
+ owner = owner.toUpper();
+
+ tmpStmt += QLatin1String(" and a.owner='") + owner + QLatin1Char('\'');
+ t.setForwardOnly(true);
+ t.exec(tmpStmt);
+ if (!t.next()) { // try and see if the tablename is a synonym
+ stmt = stmt + QLatin1String(" join all_synonyms b "
+ "on a.owner=b.table_owner and a.table_name=b.table_name "
+ "where b.owner='") + owner +
+ QLatin1String("' and b.synonym_name='") + table +
+ QLatin1Char('\'');
+ t.setForwardOnly(true);
+ t.exec(stmt);
+ if (t.next())
+ buildRecordInfo = true;
+ } else {
+ buildRecordInfo = true;
+ }
+ QStringList keywords = QStringList() << QLatin1String("NUMBER") << QLatin1String("FLOAT") << QLatin1String("BINARY_FLOAT")
+ << QLatin1String("BINARY_DOUBLE");
+ if (buildRecordInfo) {
+ do {
+ QVariant::Type ty = qDecodeOCIType(t.value(1).toString(), t.numericalPrecisionPolicy());
+ QSqlField f(t.value(0).toString(), ty);
+ f.setRequired(t.value(5).toString() == QLatin1String("N"));
+ f.setPrecision(t.value(4).toInt());
+ if (d->serverVersion >= 9 && (ty == QVariant::String) && !t.isNull(3) && !keywords.contains(t.value(1).toString())) {
+ // Oracle9: data_length == size in bytes, char_length == amount of characters
+ f.setLength(t.value(7).toInt());
+ } else {
+ f.setLength(t.value(t.isNull(3) ? 2 : 3).toInt());
+ }
+ f.setDefaultValue(t.value(6));
+ fil.append(f);
+ } while (t.next());
+ }
+ return fil;
+}
+
+QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const
+{
+ Q_D(const QOCIDriver);
+ QSqlIndex idx(tablename);
+ if (!isOpen())
+ return idx;
+ QSqlQuery t(createResult());
+ QString stmt(QLatin1String("select b.column_name, b.index_name, a.table_name, a.owner "
+ "from all_constraints a, all_ind_columns b "
+ "where a.constraint_type='P' "
+ "and b.index_name = a.constraint_name "
+ "and b.index_owner = a.owner"));
+
+ bool buildIndex = false;
+ QString table, owner, tmpStmt;
+ qSplitTableAndOwner(tablename, &table, &owner);
+
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ else
+ table = table.toUpper();
+
+ tmpStmt = stmt + QLatin1String(" and a.table_name='") + table + QLatin1Char('\'');
+ if (owner.isEmpty()) {
+ owner = d->user;
+ }
+
+ if (isIdentifierEscaped(owner, QSqlDriver::TableName))
+ owner = stripDelimiters(owner, QSqlDriver::TableName);
+ else
+ owner = owner.toUpper();
+
+ tmpStmt += QLatin1String(" and a.owner='") + owner + QLatin1Char('\'');
+ t.setForwardOnly(true);
+ t.exec(tmpStmt);
+
+ if (!t.next()) {
+ stmt += QLatin1String(" and a.table_name=(select tname from sys.synonyms "
+ "where sname='") + table + QLatin1String("' and creator=a.owner)");
+ t.setForwardOnly(true);
+ t.exec(stmt);
+ if (t.next()) {
+ owner = t.value(3).toString();
+ buildIndex = true;
+ }
+ } else {
+ buildIndex = true;
+ }
+ if (buildIndex) {
+ QSqlQuery tt(createResult());
+ tt.setForwardOnly(true);
+ idx.setName(t.value(1).toString());
+ do {
+ tt.exec(QLatin1String("select data_type from all_tab_columns where table_name='") +
+ t.value(2).toString() + QLatin1String("' and column_name='") +
+ t.value(0).toString() + QLatin1String("' and owner='") +
+ owner + QLatin1Char('\''));
+ if (!tt.next()) {
+ return QSqlIndex();
+ }
+ QSqlField f(t.value(0).toString(), qDecodeOCIType(tt.value(0).toString(), t.numericalPrecisionPolicy()));
+ idx.append(f);
+ } while (t.next());
+ return idx;
+ }
+ return QSqlIndex();
+}
+
+QString QOCIDriver::formatValue(const QSqlField &field, bool trimStrings) const
+{
+ switch (field.type()) {
+ case QVariant::DateTime: {
+ QDateTime datetime = field.value().toDateTime();
+ QString datestring;
+ if (datetime.isValid()) {
+ datestring = QLatin1String("TO_DATE('") + QString::number(datetime.date().year())
+ + QLatin1Char('-')
+ + QString::number(datetime.date().month()) + QLatin1Char('-')
+ + QString::number(datetime.date().day()) + QLatin1Char(' ')
+ + QString::number(datetime.time().hour()) + QLatin1Char(':')
+ + QString::number(datetime.time().minute()) + QLatin1Char(':')
+ + QString::number(datetime.time().second())
+ + QLatin1String("','YYYY-MM-DD HH24:MI:SS')");
+ } else {
+ datestring = QLatin1String("NULL");
+ }
+ return datestring;
+ }
+ case QVariant::Time: {
+ QDateTime datetime = field.value().toDateTime();
+ QString datestring;
+ if (datetime.isValid()) {
+ datestring = QLatin1String("TO_DATE('")
+ + QString::number(datetime.time().hour()) + QLatin1Char(':')
+ + QString::number(datetime.time().minute()) + QLatin1Char(':')
+ + QString::number(datetime.time().second())
+ + QLatin1String("','HH24:MI:SS')");
+ } else {
+ datestring = QLatin1String("NULL");
+ }
+ return datestring;
+ }
+ case QVariant::Date: {
+ QDate date = field.value().toDate();
+ QString datestring;
+ if (date.isValid()) {
+ datestring = QLatin1String("TO_DATE('") + QString::number(date.year()) +
+ QLatin1Char('-') +
+ QString::number(date.month()) + QLatin1Char('-') +
+ QString::number(date.day()) + QLatin1String("','YYYY-MM-DD')");
+ } else {
+ datestring = QLatin1String("NULL");
+ }
+ return datestring;
+ }
+ default:
+ break;
+ }
+ return QSqlDriver::formatValue(field, trimStrings);
+}
+
+QVariant QOCIDriver::handle() const
+{
+ Q_D(const QOCIDriver);
+ return QVariant::fromValue(d->env);
+}
+
+QString QOCIDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
+{
+ QString res = identifier;
+ if(!identifier.isEmpty() && !isIdentifierEscaped(identifier, type)) {
+ res.replace(QLatin1Char('"'), QLatin1String("\"\""));
+ res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
+ res.replace(QLatin1Char('.'), QLatin1String("\".\""));
+ }
+ return res;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/sqldrivers/oci/qsql_oci_p.h b/src/plugins/sqldrivers/oci/qsql_oci_p.h
new file mode 100644
index 0000000000..69911f4bee
--- /dev/null
+++ b/src/plugins/sqldrivers/oci/qsql_oci_p.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_OCI_H
+#define QSQL_OCI_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtSql/qsqldriver.h>
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_OCI
+#else
+#define Q_EXPORT_SQLDRIVER_OCI Q_SQL_EXPORT
+#endif
+
+typedef struct OCIEnv OCIEnv;
+typedef struct OCISvcCtx OCISvcCtx;
+
+QT_BEGIN_NAMESPACE
+
+class QSqlResult;
+class QOCIDriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_OCI QOCIDriver : public QSqlDriver
+{
+ Q_DECLARE_PRIVATE(QOCIDriver)
+ Q_OBJECT
+ friend class QOCICols;
+ friend class QOCIResultPrivate;
+
+public:
+ explicit QOCIDriver(QObject* parent = 0);
+ QOCIDriver(OCIEnv* env, OCISvcCtx* ctx, QObject* parent = 0);
+ ~QOCIDriver();
+ bool hasFeature(DriverFeature f) const;
+ bool open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port,
+ const QString &connOpts) Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ QSqlResult *createResult() const Q_DECL_OVERRIDE;
+ QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE;
+ QSqlRecord record(const QString &tablename) const Q_DECL_OVERRIDE;
+ QSqlIndex primaryIndex(const QString& tablename) const Q_DECL_OVERRIDE;
+ QString formatValue(const QSqlField &field,
+ bool trimStrings) const Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+ QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE;
+
+protected:
+ bool beginTransaction() Q_DECL_OVERRIDE;
+ bool commitTransaction() Q_DECL_OVERRIDE;
+ bool rollbackTransaction() Q_DECL_OVERRIDE;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_OCI_H
diff --git a/src/plugins/sqldrivers/odbc/main.cpp b/src/plugins/sqldrivers/odbc/main.cpp
index 69ea7b1f8e..ac63941a82 100644
--- a/src/plugins/sqldrivers/odbc/main.cpp
+++ b/src/plugins/sqldrivers/odbc/main.cpp
@@ -39,7 +39,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
-#include "../../../sql/drivers/odbc/qsql_odbc_p.h"
+#include "qsql_odbc_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/sqldrivers/odbc/odbc.pro b/src/plugins/sqldrivers/odbc/odbc.pro
index c0020c065f..e002f06576 100644
--- a/src/plugins/sqldrivers/odbc/odbc.pro
+++ b/src/plugins/sqldrivers/odbc/odbc.pro
@@ -1,8 +1,19 @@
TARGET = qsqlodbc
-SOURCES = main.cpp
+HEADERS += $$PWD/qsql_odbc_p.h
+SOURCES += $$PWD/qsql_odbc.cpp $$PWD/main.cpp
+
+unix {
+ DEFINES += UNICODE
+ !contains(LIBS, .*odbc.*) {
+ osx:LIBS += -liodbc
+ else:LIBS += $$QT_LFLAGS_ODBC
+ }
+} else {
+ LIBS *= -lodbc32
+}
+
OTHER_FILES += odbc.json
-include(../../../sql/drivers/odbc/qsql_odbc.pri)
PLUGIN_CLASS_NAME = QODBCDriverPlugin
include(../qsqldriverbase.pri)
diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp
new file mode 100644
index 0000000000..59ef42d609
--- /dev/null
+++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp
@@ -0,0 +1,2639 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_odbc_p.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 <qmath.h>
+#include <QDebug>
+#include <QSqlQuery>
+#include <QtSql/private/qsqldriver_p.h>
+#include <QtSql/private/qsqlresult_p.h>
+
+QT_BEGIN_NAMESPACE
+
+// undefine this to prevent initial check of the ODBC driver
+#define ODBC_CHECK_DRIVER
+
+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 };
+
+inline static QString fromSQLTCHAR(const QVarLengthArray<SQLTCHAR>& input, int size=-1)
+{
+ QString result;
+
+ int realsize = qMin(size, input.size());
+ if(realsize > 0 && input[realsize-1] == 0)
+ realsize--;
+ switch(sizeof(SQLTCHAR)) {
+ case 1:
+ result=QString::fromUtf8((const char *)input.constData(), realsize);
+ break;
+ case 2:
+ result=QString::fromUtf16((const ushort *)input.constData(), realsize);
+ break;
+ case 4:
+ result=QString::fromUcs4((const uint *)input.constData(), realsize);
+ break;
+ default:
+ qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR)));
+ }
+ return result;
+}
+
+inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(const QString &input)
+{
+ QVarLengthArray<SQLTCHAR> result;
+ result.resize(input.size());
+ switch(sizeof(SQLTCHAR)) {
+ case 1:
+ memcpy(result.data(), input.toUtf8().data(), input.size());
+ break;
+ case 2:
+ memcpy(result.data(), input.unicode(), input.size() * 2);
+ break;
+ case 4:
+ memcpy(result.data(), input.toUcs4().data(), input.size() * 4);
+ break;
+ default:
+ qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR)));
+ }
+ result.append(0); // make sure it's null terminated, doesn't matter if it already is, it does if it isn't.
+ return result;
+}
+
+class QODBCDriverPrivate : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QODBCDriver)
+
+public:
+ enum DefaultCase{Lower, Mixed, Upper, Sensitive};
+ QODBCDriverPrivate()
+ : QSqlDriverPrivate(), hEnv(0), hDbc(0), unicode(false), useSchema(false), disconnectCount(0), datetime_precision(19),
+ isFreeTDSDriver(false), hasSQLFetchScroll(true), hasMultiResultSets(false), isQuoteInitialized(false), quote(QLatin1Char('"'))
+ {
+ }
+
+ SQLHANDLE hEnv;
+ SQLHANDLE hDbc;
+
+ bool unicode;
+ bool useSchema;
+ int disconnectCount;
+ int datetime_precision;
+ bool isFreeTDSDriver;
+ bool hasSQLFetchScroll;
+ bool hasMultiResultSets;
+
+ bool checkDriver() const;
+ void checkUnicode();
+ void checkDBMS();
+ void checkHasSQLFetchScroll();
+ void checkHasMultiResults();
+ void checkSchemaUsage();
+ void checkDateTimePrecision();
+ 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 QODBCResultPrivate;
+
+class QODBCResult: public QSqlResult
+{
+ Q_DECLARE_PRIVATE(QODBCResult)
+
+public:
+ QODBCResult(const QODBCDriver *db);
+ virtual ~QODBCResult();
+
+ bool prepare(const QString &query) Q_DECL_OVERRIDE;
+ bool exec() Q_DECL_OVERRIDE;
+
+ QVariant lastInsertId() const Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+
+protected:
+ bool fetchNext() Q_DECL_OVERRIDE;
+ bool fetchFirst() Q_DECL_OVERRIDE;
+ bool fetchLast() Q_DECL_OVERRIDE;
+ bool fetchPrevious() Q_DECL_OVERRIDE;
+ bool fetch(int i) Q_DECL_OVERRIDE;
+ bool reset(const QString &query) Q_DECL_OVERRIDE;
+ QVariant data(int field) Q_DECL_OVERRIDE;
+ bool isNull(int field) Q_DECL_OVERRIDE;
+ int size() Q_DECL_OVERRIDE;
+ int numRowsAffected() Q_DECL_OVERRIDE;
+ QSqlRecord record() const Q_DECL_OVERRIDE;
+ void virtual_hook(int id, void *data) Q_DECL_OVERRIDE;
+ void detachFromResultSet() Q_DECL_OVERRIDE;
+ bool nextResult() Q_DECL_OVERRIDE;
+};
+
+class QODBCResultPrivate: public QSqlResultPrivate
+{
+ Q_DECLARE_PUBLIC(QODBCResult)
+
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QODBCDriver)
+ QODBCResultPrivate(QODBCResult *q, const QODBCDriver *db)
+ : QSqlResultPrivate(q, db),
+ hStmt(0),
+ useSchema(false),
+ hasSQLFetchScroll(true)
+ {
+ unicode = drv_d_func()->unicode;
+ useSchema = drv_d_func()->useSchema;
+ disconnectCount = drv_d_func()->disconnectCount;
+ hasSQLFetchScroll = drv_d_func()->hasSQLFetchScroll;
+ }
+
+ inline void clearValues()
+ { fieldCache.fill(QVariant()); fieldCacheIdx = 0; }
+
+ SQLHANDLE dpEnv() const { return drv_d_func() ? drv_d_func()->hEnv : 0;}
+ SQLHANDLE dpDbc() const { return drv_d_func() ? drv_d_func()->hDbc : 0;}
+ SQLHANDLE hStmt;
+
+ bool unicode;
+ bool useSchema;
+
+ QSqlRecord rInf;
+ QVector<QVariant> fieldCache;
+ int fieldCacheIdx;
+ int disconnectCount;
+ bool hasSQLFetchScroll;
+
+ bool isStmtHandleValid();
+ void updateStmtHandleState();
+};
+
+bool QODBCResultPrivate::isStmtHandleValid()
+{
+ return disconnectCount == drv_d_func()->disconnectCount;
+}
+
+void QODBCResultPrivate::updateStmtHandleState()
+{
+ disconnectCount = drv_d_func()->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];
+ QVarLengthArray<SQLTCHAR> description_(SQL_MAX_MESSAGE_LENGTH);
+ QString result;
+ int i = 1;
+
+ description_[0] = 0;
+ do {
+ r = SQLGetDiagRec(handleType,
+ handle,
+ i,
+ state_,
+ &nativeCode_,
+ 0,
+ 0,
+ &msgLen);
+ if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && msgLen > 0)
+ description_.resize(msgLen+1);
+ r = SQLGetDiagRec(handleType,
+ handle,
+ i,
+ state_,
+ &nativeCode_,
+ description_.data(),
+ description_.size(),
+ &msgLen);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ if (nativeCode)
+ *nativeCode = nativeCode_;
+ const QString tmpstore = fromSQLTCHAR(description_, msgLen);
+ 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 SQLHANDLE hStmt, const SQLHANDLE envHandle = 0,
+ const SQLHANDLE pDbC = 0, int *nativeCode = 0)
+{
+ QString result;
+ if (envHandle)
+ result += qWarnODBCHandle(SQL_HANDLE_ENV, envHandle, nativeCode);
+ if (pDbC) {
+ const QString dMessage = qWarnODBCHandle(SQL_HANDLE_DBC, pDbC, nativeCode);
+ if (!dMessage.isEmpty()) {
+ if (!result.isEmpty())
+ result += QLatin1Char(' ');
+ result += dMessage;
+ }
+ }
+ if (hStmt) {
+ const QString hMessage = qWarnODBCHandle(SQL_HANDLE_STMT, hStmt, nativeCode);
+ if (!hMessage.isEmpty()) {
+ if (!result.isEmpty())
+ result += QLatin1Char(' ');
+ result += hMessage;
+ }
+ }
+ return result;
+}
+
+static QString qODBCWarn(const QODBCResultPrivate* odbc, int *nativeCode = 0)
+{
+ return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc(), nativeCode);
+}
+
+static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = 0)
+{
+ return qODBCWarn(0, odbc->hEnv, odbc->hDbc, nativeCode);
+}
+
+static void qSqlWarning(const QString& message, const QODBCResultPrivate* odbc)
+{
+ qWarning() << message << "\tError:" << qODBCWarn(odbc);
+}
+
+static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc)
+{
+ qWarning() << message << "\tError:" << qODBCWarn(odbc);
+}
+
+static void qSqlWarning(const QString &message, const SQLHANDLE hStmt)
+{
+ qWarning() << message << "\tError:" << qODBCWarn(hStmt);
+}
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCResultPrivate* 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);
+}
+
+static QVariant::Type qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true)
+{
+ 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;
+ case SQL_WCHAR:
+ case SQL_WVARCHAR:
+ case SQL_WLONGVARCHAR:
+ type = QVariant::String;
+ break;
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+#if (ODBCVER >= 0x0350)
+ case SQL_GUID:
+#endif
+ 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;
+ SQLLEN 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) {
+ r = SQLGetData(hStmt,
+ column+1,
+ SQL_C_TCHAR,
+ NULL,
+ 0,
+ &lengthIndicator);
+ if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
+ colSize = int(lengthIndicator / sizeof(SQLTCHAR) + 1);
+ QVarLengthArray<SQLTCHAR> buf(colSize);
+ memset(buf.data(), 0, colSize*sizeof(SQLTCHAR));
+ while (true) {
+ r = SQLGetData(hStmt,
+ column+1,
+ SQL_C_TCHAR,
+ (SQLPOINTER)buf.data(),
+ colSize*sizeof(SQLTCHAR),
+ &lengthIndicator);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ if (lengthIndicator == SQL_NULL_DATA) {
+ fieldVal.clear();
+ break;
+ }
+ // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned
+ // instead of the length (which sometimes was wrong in older versions)
+ // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx
+ // if length indicator equals SQL_NO_TOTAL, indicating that
+ // more data can be fetched, but size not known, collect data
+ // and fetch next block
+ if (lengthIndicator == SQL_NO_TOTAL) {
+ fieldVal += fromSQLTCHAR(buf, colSize);
+ continue;
+ }
+ // 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) ? colSize : int(lengthIndicator / sizeof(SQLTCHAR));
+ fieldVal += fromSQLTCHAR(buf, rSize);
+ if (lengthIndicator < SQLLEN(colSize*sizeof(SQLTCHAR))) {
+ // 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;
+ }
+ }
+ } else {
+ r = SQLGetData(hStmt,
+ column+1,
+ SQL_C_CHAR,
+ NULL,
+ 0,
+ &lengthIndicator);
+ if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
+ colSize = lengthIndicator + 1;
+ QVarLengthArray<SQLCHAR> buf(colSize);
+ while (true) {
+ r = SQLGetData(hStmt,
+ column+1,
+ 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) ? colSize : lengthIndicator;
+ fieldVal += QString::fromUtf8((const char *)buf.constData(), rSize);
+ if (lengthIndicator < SQLLEN(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;
+ SQLULEN colSize;
+ SQLSMALLINT colScale;
+ SQLSMALLINT nullable;
+ SQLLEN lengthIndicator = 0;
+ SQLRETURN r = SQL_ERROR;
+
+ QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE);
+
+ r = SQLDescribeCol(hStmt,
+ column + 1,
+ colName.data(),
+ 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,
+ const_cast<char *>(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 > SQLLEN(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;
+ SQLLEN 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;
+ SQLLEN 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;
+ SQLLEN 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);
+}
+
+static bool isAutoValue(const SQLHANDLE hStmt, int column)
+{
+ SQLLEN nNumericAttribute = 0; // Check for auto-increment
+ const SQLRETURN r = ::SQLColAttribute(hStmt, column + 1, SQL_DESC_AUTO_UNIQUE_VALUE,
+ 0, 0, 0, &nNumericAttribute);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ qSqlWarning(QStringLiteral("qMakeField: Unable to get autovalue attribute for column ")
+ + QString::number(column), hStmt);
+ return false;
+ }
+ return nNumericAttribute != SQL_FALSE;
+}
+
+static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage);
+
+// 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));
+ 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);
+ 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
+ return f;
+}
+
+static QSqlField qMakeFieldInfo(const QODBCResultPrivate* p, int i )
+{
+ QString errorMessage;
+ const QSqlField result = qMakeFieldInfo(p->hStmt, i, &errorMessage);
+ if (!errorMessage.isEmpty())
+ qSqlWarning(errorMessage, p);
+ return result;
+}
+
+static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage)
+{
+ SQLSMALLINT colNameLen;
+ SQLSMALLINT colType;
+ SQLULEN colSize;
+ SQLSMALLINT colScale;
+ SQLSMALLINT nullable;
+ SQLRETURN r = SQL_ERROR;
+ QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE);
+ errorMessage->clear();
+ r = SQLDescribeCol(hStmt,
+ i+1,
+ colName.data(),
+ (SQLSMALLINT)COLNAMESIZE,
+ &colNameLen,
+ &colType,
+ &colSize,
+ &colScale,
+ &nullable);
+
+ if (r != SQL_SUCCESS) {
+ *errorMessage = QStringLiteral("qMakeField: Unable to describe column ") + QString::number(i);
+ return QSqlField();
+ }
+
+ SQLLEN unsignedFlag = SQL_FALSE;
+ r = SQLColAttribute (hStmt,
+ i + 1,
+ SQL_DESC_UNSIGNED,
+ 0,
+ 0,
+ 0,
+ &unsignedFlag);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QStringLiteral("qMakeField: Unable to get column attributes for column ")
+ + QString::number(i), hStmt);
+ }
+
+ const QString qColName(fromSQLTCHAR(colName, colNameLen));
+ // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
+ QVariant::Type type = qDecodeODBCType(colType, 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
+ f.setAutoValue(isAutoValue(hStmt, i));
+ return f;
+}
+
+static size_t qGetODBCVersion(const QString &connOpts)
+{
+ if (connOpts.contains(QLatin1String("SQL_ATTR_ODBC_VERSION=SQL_OV_ODBC3"), Qt::CaseInsensitive))
+ return SQL_OV_ODBC3;
+ return SQL_OV_ODBC2;
+}
+
+QChar QODBCDriverPrivate::quoteChar()
+{
+ if (!isQuoteInitialized) {
+ SQLTCHAR 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 = QChar(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) size_t(v), 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_TIMEOUT")) {
+ v = val.toUInt();
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) size_t(v), 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) {
+ v = val.toUInt();
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) size_t(v), 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CURRENT_CATALOG")) {
+ val.utf16(); // 0 terminate
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG,
+ toSQLTCHAR(val).data(),
+ val.length()*sizeof(SQLTCHAR));
+ } 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) size_t(v), 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_PACKET_SIZE")) {
+ v = val.toUInt();
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) size_t(v), 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACEFILE")) {
+ val.utf16(); // 0 terminate
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE,
+ toSQLTCHAR(val).data(),
+ val.length()*sizeof(SQLTCHAR));
+ } 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) size_t(v), 0);
+ } 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) size_t(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) size_t(v), 0);
+ } 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)
+ : QSqlResult(*new QODBCResultPrivate(this, db))
+{
+}
+
+QODBCResult::~QODBCResult()
+{
+ Q_D(QODBCResult);
+ if (d->hStmt && d->isStmtHandleValid() && 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);
+ }
+}
+
+bool QODBCResult::reset (const QString& query)
+{
+ Q_D(QODBCResult);
+ 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()) {
+ 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();
+
+ if (isForwardOnly()) {
+ 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;
+ }
+
+ r = SQLExecDirect(d->hStmt,
+ toSQLTCHAR(query).data(),
+ (SQLINTEGER) query.length());
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) {
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to execute statement"), QSqlError::StatementError, d));
+ return false;
+ }
+
+ SQLULEN isScrollable = 0;
+ r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
+ if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
+
+ SQLSMALLINT count = 0;
+ 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)
+{
+ Q_D(QODBCResult);
+ 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()
+{
+ Q_D(QODBCResult);
+ 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()
+{
+ Q_D(QODBCResult);
+ 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()
+{
+ Q_D(QODBCResult);
+ 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()
+{
+ Q_D(QODBCResult);
+ 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;
+ }
+ SQLULEN currRow = 0;
+ 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)
+{
+ Q_D(QODBCResult);
+ 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);
+ SQLLEN 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)
+{
+ Q_D(const QODBCResult);
+ 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()
+{
+ Q_D(QODBCResult);
+ SQLLEN 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)
+{
+ Q_D(QODBCResult);
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ SQLRETURN r;
+
+ d->rInf.clear();
+ if (d->hStmt && d->isStmtHandleValid()) {
+ 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();
+
+ if (isForwardOnly()) {
+ 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;
+ }
+
+ r = SQLPrepare(d->hStmt,
+ toSQLTCHAR(query).data(),
+ (SQLINTEGER) query.length());
+
+ if (r != SQL_SUCCESS) {
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to prepare statement"), QSqlError::StatementError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QODBCResult::exec()
+{
+ Q_D(QODBCResult);
+ 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);
+
+ QVector<QVariant>& values = boundValues();
+ QVector<QByteArray> tmpStorage(values.count(), QByteArray()); // holds temporary buffers
+ QVarLengthArray<SQLLEN, 32> indicators(values.count());
+ memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN));
+
+ // bind parameters - only positional binding allowed
+ 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);
+ SQLLEN *ind = &indicators[i];
+ if (val.isNull())
+ *ind = SQL_NULL_DATA;
+ switch (val.type()) {
+ case QVariant::Date: {
+ QByteArray &ba = tmpStorage[i];
+ ba.resize(sizeof(DATE_STRUCT));
+ DATE_STRUCT *dt = (DATE_STRUCT *)const_cast<char *>(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[bindValueType(i) & QSql::InOut],
+ SQL_C_DATE,
+ SQL_DATE,
+ 0,
+ 0,
+ (void *) dt,
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break; }
+ case QVariant::Time: {
+ QByteArray &ba = tmpStorage[i];
+ ba.resize(sizeof(TIME_STRUCT));
+ TIME_STRUCT *dt = (TIME_STRUCT *)const_cast<char *>(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[bindValueType(i) & QSql::InOut],
+ SQL_C_TIME,
+ SQL_TIME,
+ 0,
+ 0,
+ (void *) dt,
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break; }
+ case QVariant::DateTime: {
+ QByteArray &ba = tmpStorage[i];
+ ba.resize(sizeof(TIMESTAMP_STRUCT));
+ TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)const_cast<char *>(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();
+
+ int precision = d->drv_d_func()->datetime_precision - 20; // (20 includes a separating period)
+ if (precision <= 0) {
+ dt->fraction = 0;
+ } else {
+ dt->fraction = qdt.time().msec() * 1000000;
+
+ // (How many leading digits do we want to keep? With SQL Server 2005, this should be 3: 123000000)
+ int keep = (int)qPow(10.0, 9 - qMin(9, precision));
+ dt->fraction = (dt->fraction / keep) * keep;
+ }
+
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_TIMESTAMP,
+ SQL_TIMESTAMP,
+ d->drv_d_func()->datetime_precision,
+ precision,
+ (void *) dt,
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break; }
+ case QVariant::Int:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_SLONG,
+ SQL_INTEGER,
+ 0,
+ 0,
+ const_cast<void *>(val.constData()),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::UInt:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_ULONG,
+ SQL_NUMERIC,
+ 15,
+ 0,
+ const_cast<void *>(val.constData()),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::Double:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_DOUBLE,
+ SQL_DOUBLE,
+ 0,
+ 0,
+ const_cast<void *>(val.constData()),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::LongLong:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_SBIGINT,
+ SQL_BIGINT,
+ 0,
+ 0,
+ const_cast<void *>(val.constData()),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::ULongLong:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_UBIGINT,
+ SQL_BIGINT,
+ 0,
+ 0,
+ const_cast<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[bindValueType(i) & QSql::InOut],
+ SQL_C_BINARY,
+ SQL_LONGVARBINARY,
+ val.toByteArray().size(),
+ 0,
+ const_cast<char *>(val.toByteArray().constData()),
+ val.toByteArray().size(),
+ ind);
+ break;
+ case QVariant::Bool:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_BIT,
+ SQL_BIT,
+ 0,
+ 0,
+ const_cast<void *>(val.constData()),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::String:
+ if (d->unicode) {
+ QByteArray &ba = tmpStorage[i];
+ QString str = val.toString();
+ if (*ind != SQL_NULL_DATA)
+ *ind = str.length() * sizeof(SQLTCHAR);
+ int strSize = str.length() * sizeof(SQLTCHAR);
+
+ if (bindValueType(i) & QSql::Out) {
+ const QVarLengthArray<SQLTCHAR> a(toSQLTCHAR(str));
+ ba = QByteArray((const char *)a.constData(), a.size() * sizeof(SQLTCHAR));
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_TCHAR,
+ strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
+ 0, // god knows... don't change this!
+ 0,
+ ba.data(),
+ ba.size(),
+ ind);
+ break;
+ }
+ ba = QByteArray ((const char *)toSQLTCHAR(str).constData(), str.size()*sizeof(SQLTCHAR));
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_TCHAR,
+ strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
+ strSize,
+ 0,
+ const_cast<char *>(ba.constData()),
+ ba.size(),
+ ind);
+ break;
+ }
+ else
+ {
+ QByteArray &str = tmpStorage[i];
+ str = val.toString().toUtf8();
+ if (*ind != SQL_NULL_DATA)
+ *ind = str.length();
+ int strSize = str.length();
+
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_CHAR,
+ strSize > 254 ? SQL_LONGVARCHAR : SQL_VARCHAR,
+ strSize,
+ 0,
+ const_cast<char *>(str.constData()),
+ strSize,
+ ind);
+ break;
+ }
+ // fall through
+ default: {
+ QByteArray &ba = tmpStorage[i];
+ if (*ind != SQL_NULL_DATA)
+ *ind = ba.size();
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[bindValueType(i) & QSql::InOut],
+ SQL_C_BINARY,
+ SQL_VARBINARY,
+ ba.length() + 1,
+ 0,
+ const_cast<char *>(ba.constData()),
+ ba.length() + 1,
+ ind);
+ 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 && r != SQL_NO_DATA) {
+ qWarning() << "QODBCResult::exec: Unable to execute statement:" << qODBCWarn(d);
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to execute statement"), QSqlError::StatementError, d));
+ return false;
+ }
+
+ SQLULEN isScrollable = 0;
+ r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
+ if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
+
+ SQLSMALLINT count = 0;
+ 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 *)const_cast<char *>(tmpStorage.at(i).constData()));
+ values[i] = QVariant(QDate(ds.year, ds.month, ds.day));
+ break; }
+ case QVariant::Time: {
+ TIME_STRUCT dt = *((TIME_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData()));
+ values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second));
+ break; }
+ case QVariant::DateTime: {
+ TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT*)
+ const_cast<char *>(tmpStorage.at(i).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) {
+ const QByteArray &first = tmpStorage.at(i);
+ QVarLengthArray<SQLTCHAR> array;
+ array.append((const SQLTCHAR *)first.constData(), first.size());
+ values[i] = fromSQLTCHAR(array, first.size()/sizeof(SQLTCHAR));
+ }
+ break;
+ }
+ // fall through
+ default: {
+ if (bindValueType(i) & QSql::Out)
+ values[i] = tmpStorage.at(i);
+ break; }
+ }
+ if (indicators[i] == SQL_NULL_DATA)
+ values[i] = QVariant(values[i].type());
+ }
+ return true;
+}
+
+QSqlRecord QODBCResult::record() const
+{
+ Q_D(const QODBCResult);
+ if (!isActive() || !isSelect())
+ return QSqlRecord();
+ return d->rInf;
+}
+
+QVariant QODBCResult::lastInsertId() const
+{
+ Q_D(const QODBCResult);
+ QString sql;
+
+ switch (driver()->dbmsType()) {
+ case QSqlDriver::MSSqlServer:
+ case QSqlDriver::Sybase:
+ sql = QLatin1String("SELECT @@IDENTITY;");
+ break;
+ case QSqlDriver::MySqlServer:
+ sql = QLatin1String("SELECT LAST_INSERT_ID();");
+ break;
+ case QSqlDriver::PostgreSQL:
+ sql = QLatin1String("SELECT lastval();");
+ break;
+ default:
+ break;
+ }
+
+ if (!sql.isEmpty()) {
+ QSqlQuery qry(driver()->createResult());
+ if (qry.exec(sql) && qry.next())
+ return qry.value(0);
+
+ qSqlWarning(QLatin1String("QODBCResult::lastInsertId: Unable to get lastInsertId"), d);
+ } else {
+ qSqlWarning(QLatin1String("QODBCResult::lastInsertId: not implemented for this DBMS"), d);
+ }
+
+ return QVariant();
+}
+
+QVariant QODBCResult::handle() const
+{
+ Q_D(const QODBCResult);
+ return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt);
+}
+
+bool QODBCResult::nextResult()
+{
+ Q_D(QODBCResult);
+ 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 = 0;
+ 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)
+{
+ QSqlResult::virtual_hook(id, data);
+}
+
+void QODBCResult::detachFromResultSet()
+{
+ Q_D(QODBCResult);
+ if (d->hStmt)
+ SQLCloseCursor(d->hStmt);
+}
+
+////////////////////////////////////////
+
+
+QODBCDriver::QODBCDriver(QObject *parent)
+ : QSqlDriver(*new QODBCDriverPrivate, parent)
+{
+}
+
+QODBCDriver::QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject *parent)
+ : QSqlDriver(*new QODBCDriverPrivate, parent)
+{
+ Q_D(QODBCDriver);
+ d->hEnv = env;
+ d->hDbc = con;
+ if (env && con) {
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+QODBCDriver::~QODBCDriver()
+{
+ cleanup();
+}
+
+bool QODBCDriver::hasFeature(DriverFeature f) const
+{
+ Q_D(const QODBCDriver);
+ 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 BatchOperations:
+ case SimpleLocking:
+ case EventNotifications:
+ case CancelQuery:
+ return false;
+ case LastInsertId:
+ return (d->dbmsType == MSSqlServer)
+ || (d->dbmsType == Sybase)
+ || (d->dbmsType == MySqlServer)
+ || (d->dbmsType == PostgreSQL);
+ case MultipleResultSets:
+ return d->hasMultiResultSets;
+ case BLOB: {
+ if (d->dbmsType == MySqlServer)
+ 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)
+{
+ Q_D(QODBCDriver);
+ 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);
+ cleanup();
+ return false;
+ }
+
+ if (!d->setConnectionOptions(connOpts)) {
+ cleanup();
+ 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;
+ QVarLengthArray<SQLTCHAR> connOut(1024);
+ memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR));
+ r = SQLDriverConnect(d->hDbc,
+ NULL,
+ toSQLTCHAR(connQStr).data(),
+ (SQLSMALLINT)connQStr.length(),
+ connOut.data(),
+ 1024,
+ &cb,
+ /*SQL_DRIVER_NOPROMPT*/0);
+
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
+ setOpenError(true);
+ cleanup();
+ return false;
+ }
+
+ if (!d->checkDriver()) {
+ setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all "
+ "functionality required"), QSqlError::ConnectionError, d));
+ setOpenError(true);
+ cleanup();
+ return false;
+ }
+
+ d->checkUnicode();
+ d->checkSchemaUsage();
+ d->checkDBMS();
+ d->checkHasSQLFetchScroll();
+ d->checkHasMultiResults();
+ d->checkDateTimePrecision();
+ setOpen(true);
+ setOpenError(false);
+ if (d->dbmsType == MSSqlServer) {
+ QSqlQuery i(createResult());
+ i.exec(QLatin1String("SET QUOTED_IDENTIFIER ON"));
+ }
+ return true;
+}
+
+void QODBCDriver::close()
+{
+ cleanup();
+ setOpen(false);
+ setOpenError(false);
+}
+
+void QODBCDriver::cleanup()
+{
+ Q_D(QODBCDriver);
+ SQLRETURN r;
+
+ 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()
+{
+ 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;
+ return;
+ }
+
+ 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;
+ return;
+ }
+
+ 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;
+ return;
+ }
+ SQLHANDLE hStmt;
+ r = SQLAllocHandle(SQL_HANDLE_STMT,
+ hDbc,
+ &hStmt);
+
+ r = SQLExecDirect(hStmt, toSQLTCHAR(QLatin1String("select 'test'")).data(), SQL_NTS);
+ if(r == SQL_SUCCESS) {
+ r = SQLFetch(hStmt);
+ if(r == SQL_SUCCESS) {
+ QVarLengthArray<SQLWCHAR> buffer(10);
+ r = SQLGetData(hStmt, 1, SQL_C_WCHAR, buffer.data(), buffer.size() * sizeof(SQLWCHAR), NULL);
+ if(r == SQL_SUCCESS && fromSQLTCHAR(buffer) == QLatin1String("test")) {
+ unicode = true;
+ }
+ }
+ }
+ r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+}
+
+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::checkDBMS()
+{
+ SQLRETURN r;
+ QVarLengthArray<SQLTCHAR> serverString(200);
+ SQLSMALLINT t;
+ memset(serverString.data(), 0, serverString.size() * sizeof(SQLTCHAR));
+
+ r = SQLGetInfo(hDbc,
+ SQL_DBMS_NAME,
+ serverString.data(),
+ SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
+ &t);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
+ if (serverType.contains(QLatin1String("PostgreSQL"), Qt::CaseInsensitive))
+ dbmsType = QSqlDriver::PostgreSQL;
+ else if (serverType.contains(QLatin1String("Oracle"), Qt::CaseInsensitive))
+ dbmsType = QSqlDriver::Oracle;
+ else if (serverType.contains(QLatin1String("MySql"), Qt::CaseInsensitive))
+ dbmsType = QSqlDriver::MySqlServer;
+ else if (serverType.contains(QLatin1String("Microsoft SQL Server"), Qt::CaseInsensitive))
+ dbmsType = QSqlDriver::MSSqlServer;
+ else if (serverType.contains(QLatin1String("Sybase"), Qt::CaseInsensitive))
+ dbmsType = QSqlDriver::Sybase;
+ }
+ r = SQLGetInfo(hDbc,
+ SQL_DRIVER_NAME,
+ serverString.data(),
+ SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
+ &t);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
+ isFreeTDSDriver = serverType.contains(QLatin1String("tdsodbc"), Qt::CaseInsensitive);
+ unicode = unicode && !isFreeTDSDriver;
+ }
+}
+
+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()
+{
+ QVarLengthArray<SQLTCHAR> driverResponse(2);
+ SQLSMALLINT length;
+ SQLRETURN r = SQLGetInfo(hDbc,
+ SQL_MULT_RESULT_SETS,
+ driverResponse.data(),
+ SQLSMALLINT(driverResponse.size() * sizeof(SQLTCHAR)),
+ &length);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ hasMultiResultSets = fromSQLTCHAR(driverResponse, length/sizeof(SQLTCHAR)).startsWith(QLatin1Char('Y'));
+}
+
+void QODBCDriverPrivate::checkDateTimePrecision()
+{
+ SQLINTEGER columnSize;
+ SQLHANDLE hStmt;
+
+ SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
+ if (r != SQL_SUCCESS) {
+ return;
+ }
+
+ r = SQLGetTypeInfo(hStmt, SQL_TIMESTAMP);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ r = SQLFetch(hStmt);
+ if ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO )
+ {
+ if (SQLGetData(hStmt, 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS) {
+ datetime_precision = (int)columnSize;
+ }
+ }
+ }
+ SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+}
+
+QSqlResult *QODBCDriver::createResult() const
+{
+ return new QODBCResult(this);
+}
+
+bool QODBCDriver::beginTransaction()
+{
+ Q_D(QODBCDriver);
+ if (!isOpen()) {
+ qWarning("QODBCDriver::beginTransaction: Database not open");
+ return false;
+ }
+ SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
+ SQLRETURN r = SQLSetConnectAttr(d->hDbc,
+ SQL_ATTR_AUTOCOMMIT,
+ (SQLPOINTER)size_t(ac),
+ sizeof(ac));
+ if (r != SQL_SUCCESS) {
+ setLastError(qMakeError(tr("Unable to disable autocommit"),
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QODBCDriver::commitTransaction()
+{
+ Q_D(QODBCDriver);
+ 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()
+{
+ Q_D(QODBCDriver);
+ 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()
+{
+ Q_D(QODBCDriver);
+ SQLUINTEGER ac(SQL_AUTOCOMMIT_ON);
+ SQLRETURN r = SQLSetConnectAttr(d->hDbc,
+ SQL_ATTR_AUTOCOMMIT,
+ (SQLPOINTER)size_t(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
+{
+ Q_D(const QODBCDriver);
+ 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(QLatin1Char(','));
+
+ r = SQLTables(hStmt,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ toSQLTCHAR(joinedTableTypeString).data(),
+ 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);
+
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {
+ qWarning() << "QODBCDriver::tables failed to retrieve table/view list: (" << r << "," << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ")";
+ return QStringList();
+ }
+
+ 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
+{
+ Q_D(const QODBCDriver);
+ 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;
+ const_cast<QODBCDriverPrivate*>(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,
+ catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
+ catalog.length(),
+ schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
+ schema.length(),
+ toSQLTCHAR(table).data(),
+ 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,
+ catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
+ catalog.length(),
+ schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
+ schema.length(),
+ toSQLTCHAR(table).data(),
+ 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
+{
+ Q_D(const QODBCDriver);
+ QSqlRecord fil;
+ if (!isOpen())
+ return fil;
+
+ SQLHANDLE hStmt;
+ QString catalog, schema, table;
+ const_cast<QODBCDriverPrivate*>(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,
+ catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
+ catalog.length(),
+ schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
+ schema.length(),
+ toSQLTCHAR(table).data(),
+ 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
+{
+ Q_D(const QODBCDriver);
+ return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc);
+}
+
+QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
+{
+ Q_D(const QODBCDriver);
+ QChar quote = const_cast<QODBCDriverPrivate*>(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::isIdentifierEscaped(const QString &identifier, IdentifierType) const
+{
+ Q_D(const QODBCDriver);
+ QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar();
+ return identifier.size() > 2
+ && identifier.startsWith(quote) //left delimited
+ && identifier.endsWith(quote); //right delimited
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc_p.h b/src/plugins/sqldrivers/odbc/qsql_odbc_p.h
new file mode 100644
index 0000000000..f4ce8bc243
--- /dev/null
+++ b/src/plugins/sqldrivers/odbc/qsql_odbc_p.h
@@ -0,0 +1,127 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_ODBC_H
+#define QSQL_ODBC_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtSql/qsqldriver.h>
+
+#if defined (Q_OS_WIN32)
+#include <QtCore/qt_windows.h>
+#endif
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_ODBC
+#else
+#define Q_EXPORT_SQLDRIVER_ODBC Q_SQL_EXPORT
+#endif
+
+#ifdef Q_OS_UNIX
+#define HAVE_LONG_LONG 1 // force UnixODBC NOT to fall back to a struct for BIGINTs
+#endif
+
+#if defined(Q_CC_BOR)
+// workaround for Borland to make sure that SQLBIGINT is defined
+# define _MSC_VER 900
+#endif
+#include <sql.h>
+#if defined(Q_CC_BOR)
+# undef _MSC_VER
+#endif
+
+#include <sqlext.h>
+
+QT_BEGIN_NAMESPACE
+
+class QODBCDriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_ODBC QODBCDriver : public QSqlDriver
+{
+ Q_DECLARE_PRIVATE(QODBCDriver)
+ Q_OBJECT
+ friend class QODBCResultPrivate;
+
+public:
+ explicit QODBCDriver(QObject *parent=0);
+ QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject * parent=0);
+ virtual ~QODBCDriver();
+ bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ QSqlResult *createResult() const Q_DECL_OVERRIDE;
+ QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE;
+ QSqlRecord record(const QString &tablename) const Q_DECL_OVERRIDE;
+ QSqlIndex primaryIndex(const QString &tablename) const Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+ QString formatValue(const QSqlField &field,
+ bool trimStrings) const Q_DECL_OVERRIDE;
+ bool open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port,
+ const QString &connOpts) Q_DECL_OVERRIDE;
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE;
+
+ bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE;
+
+protected:
+ bool beginTransaction() Q_DECL_OVERRIDE;
+ bool commitTransaction() Q_DECL_OVERRIDE;
+ bool rollbackTransaction() Q_DECL_OVERRIDE;
+
+private:
+ bool endTrans();
+ void cleanup();
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_ODBC_H
diff --git a/src/plugins/sqldrivers/psql/main.cpp b/src/plugins/sqldrivers/psql/main.cpp
index eae1c1c554..7657fcbfdf 100644
--- a/src/plugins/sqldrivers/psql/main.cpp
+++ b/src/plugins/sqldrivers/psql/main.cpp
@@ -39,7 +39,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
-#include "../../../sql/drivers/psql/qsql_psql_p.h"
+#include "qsql_psql_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/sqldrivers/psql/psql.pro b/src/plugins/sqldrivers/psql/psql.pro
index 0fabe0e616..43eebab7d3 100644
--- a/src/plugins/sqldrivers/psql/psql.pro
+++ b/src/plugins/sqldrivers/psql/psql.pro
@@ -1,8 +1,17 @@
TARGET = qsqlpsql
-SOURCES = main.cpp
+HEADERS += $$PWD/qsql_psql_p.h
+SOURCES += $$PWD/qsql_psql.cpp $$PWD/main.cpp
+
+unix|mingw {
+ LIBS += $$QT_LFLAGS_PSQL
+ !contains(LIBS, .*pq.*):LIBS += -lpq
+ QMAKE_CXXFLAGS *= $$QT_CFLAGS_PSQL
+} else {
+ !contains(LIBS, .*pq.*):LIBS += -llibpq -lws2_32 -ladvapi32
+}
+
OTHER_FILES += psql.json
-include(../../../sql/drivers/psql/qsql_psql.pri)
PLUGIN_CLASS_NAME = QPSQLDriverPlugin
include(../qsqldriverbase.pri)
diff --git a/src/plugins/sqldrivers/psql/qsql_psql.cpp b/src/plugins/sqldrivers/psql/qsql_psql.cpp
new file mode 100644
index 0000000000..fcf75af298
--- /dev/null
+++ b/src/plugins/sqldrivers/psql/qsql_psql.cpp
@@ -0,0 +1,1508 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_psql_p.h"
+
+#include <qcoreapplication.h>
+#include <qvariant.h>
+#include <qdatetime.h>
+#include <qregexp.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlrecord.h>
+#include <qsqlquery.h>
+#include <qsocketnotifier.h>
+#include <qstringlist.h>
+#include <qmutex.h>
+#include <QtSql/private/qsqlresult_p.h>
+#include <QtSql/private/qsqldriver_p.h>
+
+#include <libpq-fe.h>
+#include <pg_config.h>
+
+#include <stdlib.h>
+#include <math.h>
+// below code taken from an example at http://www.gnu.org/software/hello/manual/autoconf/Function-Portability.html
+#ifndef isnan
+ # define isnan(x) \
+ (sizeof (x) == sizeof (long double) ? isnan_ld (x) \
+ : sizeof (x) == sizeof (double) ? isnan_d (x) \
+ : isnan_f (x))
+ static inline int isnan_f (float x) { return x != x; }
+ static inline int isnan_d (double x) { return x != x; }
+ static inline int isnan_ld (long double x) { return x != x; }
+#endif
+
+#ifndef isinf
+ # define isinf(x) \
+ (sizeof (x) == sizeof (long double) ? isinf_ld (x) \
+ : sizeof (x) == sizeof (double) ? isinf_d (x) \
+ : isinf_f (x))
+ static inline int isinf_f (float x) { return isnan (x - x); }
+ static inline int isinf_d (double x) { return isnan (x - x); }
+ static inline int isinf_ld (long double x) { return isnan (x - x); }
+#endif
+
+
+// workaround for postgres defining their OIDs in a private header file
+#define QBOOLOID 16
+#define QINT8OID 20
+#define QINT2OID 21
+#define QINT4OID 23
+#define QNUMERICOID 1700
+#define QFLOAT4OID 700
+#define QFLOAT8OID 701
+#define QABSTIMEOID 702
+#define QRELTIMEOID 703
+#define QDATEOID 1082
+#define QTIMEOID 1083
+#define QTIMETZOID 1266
+#define QTIMESTAMPOID 1114
+#define QTIMESTAMPTZOID 1184
+#define QOIDOID 2278
+#define QBYTEAOID 17
+#define QREGPROCOID 24
+#define QXIDOID 28
+#define QCIDOID 29
+
+#define QBITOID 1560
+#define QVARBITOID 1562
+
+#define VARHDRSZ 4
+
+/* This is a compile time switch - if PQfreemem is declared, the compiler will use that one,
+ otherwise it'll run in this template */
+template <typename T>
+inline void PQfreemem(T *t, int = 0) { free(t); }
+
+Q_DECLARE_OPAQUE_POINTER(PGconn*)
+Q_DECLARE_METATYPE(PGconn*)
+
+Q_DECLARE_OPAQUE_POINTER(PGresult*)
+Q_DECLARE_METATYPE(PGresult*)
+
+QT_BEGIN_NAMESPACE
+
+inline void qPQfreemem(void *buffer)
+{
+ PQfreemem(buffer);
+}
+
+class QPSQLResultPrivate;
+
+class QPSQLResult: public QSqlResult
+{
+ Q_DECLARE_PRIVATE(QPSQLResult)
+
+public:
+ QPSQLResult(const QPSQLDriver *db);
+ ~QPSQLResult();
+
+ QVariant handle() const Q_DECL_OVERRIDE;
+ void virtual_hook(int id, void *data) Q_DECL_OVERRIDE;
+
+protected:
+ void cleanup();
+ bool fetch(int i) Q_DECL_OVERRIDE;
+ bool fetchFirst() Q_DECL_OVERRIDE;
+ bool fetchLast() Q_DECL_OVERRIDE;
+ QVariant data(int i) Q_DECL_OVERRIDE;
+ bool isNull(int field) Q_DECL_OVERRIDE;
+ bool reset (const QString &query) Q_DECL_OVERRIDE;
+ int size() Q_DECL_OVERRIDE;
+ int numRowsAffected() Q_DECL_OVERRIDE;
+ QSqlRecord record() const Q_DECL_OVERRIDE;
+ QVariant lastInsertId() const Q_DECL_OVERRIDE;
+ bool prepare(const QString &query) Q_DECL_OVERRIDE;
+ bool exec() Q_DECL_OVERRIDE;
+};
+
+class QPSQLDriverPrivate : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QPSQLDriver)
+public:
+ QPSQLDriverPrivate() : QSqlDriverPrivate(),
+ connection(0),
+ isUtf8(false),
+ pro(QPSQLDriver::Version6),
+ sn(0),
+ pendingNotifyCheck(false),
+ hasBackslashEscape(false)
+ { dbmsType = QSqlDriver::PostgreSQL; }
+
+ PGconn *connection;
+ bool isUtf8;
+ QPSQLDriver::Protocol pro;
+ QSocketNotifier *sn;
+ QStringList seid;
+ mutable bool pendingNotifyCheck;
+ bool hasBackslashEscape;
+
+ void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
+ PGresult * exec(const char * stmt) const;
+ PGresult * exec(const QString & stmt) const;
+ QPSQLDriver::Protocol getPSQLVersion();
+ bool setEncodingUtf8();
+ void setDatestyle();
+ void detectBackslashEscape();
+};
+
+void QPSQLDriverPrivate::appendTables(QStringList &tl, QSqlQuery &t, QChar type)
+{
+ QString query;
+ if (pro >= QPSQLDriver::Version73) {
+ query = QString::fromLatin1("select pg_class.relname, pg_namespace.nspname from pg_class "
+ "left join pg_namespace on (pg_class.relnamespace = pg_namespace.oid) "
+ "where (pg_class.relkind = '%1') and (pg_class.relname !~ '^Inv') "
+ "and (pg_class.relname !~ '^pg_') "
+ "and (pg_namespace.nspname != 'information_schema') ").arg(type);
+ } else {
+ query = QString::fromLatin1("select relname, null from pg_class where (relkind = '%1') "
+ "and (relname !~ '^Inv') "
+ "and (relname !~ '^pg_') ").arg(type);
+ }
+ t.exec(query);
+ while (t.next()) {
+ QString schema = t.value(1).toString();
+ if (schema.isEmpty() || schema == QLatin1String("public"))
+ tl.append(t.value(0).toString());
+ else
+ tl.append(t.value(0).toString().prepend(QLatin1Char('.')).prepend(schema));
+ }
+}
+
+PGresult * QPSQLDriverPrivate::exec(const char * stmt) const
+{
+ Q_Q(const QPSQLDriver);
+ PGresult *result = PQexec(connection, stmt);
+ if (seid.size() && !pendingNotifyCheck) {
+ pendingNotifyCheck = true;
+ QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), "_q_handleNotification", Qt::QueuedConnection, Q_ARG(int,0));
+ }
+ return result;
+}
+
+PGresult * QPSQLDriverPrivate::exec(const QString & stmt) const
+{
+ return exec(isUtf8 ? stmt.toUtf8().constData() : stmt.toLocal8Bit().constData());
+}
+
+class QPSQLResultPrivate : public QSqlResultPrivate
+{
+ Q_DECLARE_PUBLIC(QPSQLResult)
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QPSQLDriver);
+ QPSQLResultPrivate(QPSQLResult *q, const QPSQLDriver *drv)
+ : QSqlResultPrivate(q, drv),
+ result(0),
+ currentSize(-1),
+ preparedQueriesEnabled(false)
+ { }
+
+ QString fieldSerial(int i) const Q_DECL_OVERRIDE { return QLatin1Char('$') + QString::number(i + 1); }
+ void deallocatePreparedStmt();
+
+ PGresult *result;
+ int currentSize;
+ bool preparedQueriesEnabled;
+ QString preparedStmtId;
+
+ bool processResults();
+};
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
+ const QPSQLDriverPrivate *p, PGresult* result = 0)
+{
+ const char *s = PQerrorMessage(p->connection);
+ QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
+ QString errorCode;
+ if (result) {
+ errorCode = QString::fromLatin1(PQresultErrorField(result, PG_DIAG_SQLSTATE));
+ msg += QString::fromLatin1("(%1)").arg(errorCode);
+ }
+ return QSqlError(QLatin1String("QPSQL: ") + err, msg, type, errorCode);
+}
+
+bool QPSQLResultPrivate::processResults()
+{
+ Q_Q(QPSQLResult);
+ if (!result)
+ return false;
+
+ int status = PQresultStatus(result);
+ if (status == PGRES_TUPLES_OK) {
+ q->setSelect(true);
+ q->setActive(true);
+ currentSize = PQntuples(result);
+ return true;
+ } else if (status == PGRES_COMMAND_OK) {
+ q->setSelect(false);
+ q->setActive(true);
+ currentSize = -1;
+ return true;
+ }
+ q->setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
+ "Unable to create query"), QSqlError::StatementError, drv_d_func(), result));
+ return false;
+}
+
+static QVariant::Type qDecodePSQLType(int t)
+{
+ QVariant::Type type = QVariant::Invalid;
+ switch (t) {
+ case QBOOLOID:
+ type = QVariant::Bool;
+ break;
+ case QINT8OID:
+ type = QVariant::LongLong;
+ break;
+ case QINT2OID:
+ case QINT4OID:
+ case QOIDOID:
+ case QREGPROCOID:
+ case QXIDOID:
+ case QCIDOID:
+ type = QVariant::Int;
+ break;
+ case QNUMERICOID:
+ case QFLOAT4OID:
+ case QFLOAT8OID:
+ type = QVariant::Double;
+ break;
+ case QABSTIMEOID:
+ case QRELTIMEOID:
+ case QDATEOID:
+ type = QVariant::Date;
+ break;
+ case QTIMEOID:
+ case QTIMETZOID:
+ type = QVariant::Time;
+ break;
+ case QTIMESTAMPOID:
+ case QTIMESTAMPTZOID:
+ type = QVariant::DateTime;
+ break;
+ case QBYTEAOID:
+ type = QVariant::ByteArray;
+ break;
+ default:
+ type = QVariant::String;
+ break;
+ }
+ return type;
+}
+
+void QPSQLResultPrivate::deallocatePreparedStmt()
+{
+ const QString stmt = QLatin1String("DEALLOCATE ") + preparedStmtId;
+ PGresult *result = drv_d_func()->exec(stmt);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ qWarning("Unable to free statement: %s", PQerrorMessage(drv_d_func()->connection));
+ PQclear(result);
+ preparedStmtId.clear();
+}
+
+QPSQLResult::QPSQLResult(const QPSQLDriver* db)
+ : QSqlResult(*new QPSQLResultPrivate(this, db))
+{
+ Q_D(QPSQLResult);
+ d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries);
+}
+
+QPSQLResult::~QPSQLResult()
+{
+ Q_D(QPSQLResult);
+ cleanup();
+
+ if (d->preparedQueriesEnabled && !d->preparedStmtId.isNull())
+ d->deallocatePreparedStmt();
+}
+
+QVariant QPSQLResult::handle() const
+{
+ Q_D(const QPSQLResult);
+ return QVariant::fromValue(d->result);
+}
+
+void QPSQLResult::cleanup()
+{
+ Q_D(QPSQLResult);
+ if (d->result)
+ PQclear(d->result);
+ d->result = 0;
+ setAt(QSql::BeforeFirstRow);
+ d->currentSize = -1;
+ setActive(false);
+}
+
+bool QPSQLResult::fetch(int i)
+{
+ Q_D(const QPSQLResult);
+ if (!isActive())
+ return false;
+ if (i < 0)
+ return false;
+ if (i >= d->currentSize)
+ return false;
+ if (at() == i)
+ return true;
+ setAt(i);
+ return true;
+}
+
+bool QPSQLResult::fetchFirst()
+{
+ return fetch(0);
+}
+
+bool QPSQLResult::fetchLast()
+{
+ Q_D(const QPSQLResult);
+ return fetch(PQntuples(d->result) - 1);
+}
+
+QVariant QPSQLResult::data(int i)
+{
+ Q_D(const QPSQLResult);
+ if (i >= PQnfields(d->result)) {
+ qWarning("QPSQLResult::data: column %d out of range", i);
+ return QVariant();
+ }
+ int ptype = PQftype(d->result, i);
+ QVariant::Type type = qDecodePSQLType(ptype);
+ const char *val = PQgetvalue(d->result, at(), i);
+ if (PQgetisnull(d->result, at(), i))
+ return QVariant(type);
+ switch (type) {
+ case QVariant::Bool:
+ return QVariant((bool)(val[0] == 't'));
+ case QVariant::String:
+ return d->drv_d_func()->isUtf8 ? QString::fromUtf8(val) : QString::fromLatin1(val);
+ case QVariant::LongLong:
+ if (val[0] == '-')
+ return QString::fromLatin1(val).toLongLong();
+ else
+ return QString::fromLatin1(val).toULongLong();
+ case QVariant::Int:
+ return atoi(val);
+ case QVariant::Double:
+ if (ptype == QNUMERICOID) {
+ if (numericalPrecisionPolicy() != QSql::HighPrecision) {
+ QVariant retval;
+ bool convert;
+ double dbl=QString::fromLatin1(val).toDouble(&convert);
+ if (numericalPrecisionPolicy() == QSql::LowPrecisionInt64)
+ retval = (qlonglong)dbl;
+ else if (numericalPrecisionPolicy() == QSql::LowPrecisionInt32)
+ retval = (int)dbl;
+ else if (numericalPrecisionPolicy() == QSql::LowPrecisionDouble)
+ retval = dbl;
+ if (!convert)
+ return QVariant();
+ return retval;
+ }
+ return QString::fromLatin1(val);
+ }
+ return QString::fromLatin1(val).toDouble();
+ case QVariant::Date:
+ if (val[0] == '\0') {
+ return QVariant(QDate());
+ } else {
+#ifndef QT_NO_DATESTRING
+ return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate));
+#else
+ return QVariant(QString::fromLatin1(val));
+#endif
+ }
+ case QVariant::Time: {
+ const QString str = QString::fromLatin1(val);
+#ifndef QT_NO_DATESTRING
+ if (str.isEmpty())
+ return QVariant(QTime());
+ else
+ return QVariant(QTime::fromString(str, Qt::ISODate));
+#else
+ return QVariant(str);
+#endif
+ }
+ case QVariant::DateTime: {
+ QString dtval = QString::fromLatin1(val);
+#ifndef QT_NO_DATESTRING
+ if (dtval.length() < 10) {
+ return QVariant(QDateTime());
+ } else {
+ QChar sign = dtval[dtval.size() - 3];
+ if (sign == QLatin1Char('-') || sign == QLatin1Char('+')) dtval += QLatin1String(":00");
+ return QVariant(QDateTime::fromString(dtval, Qt::ISODate).toLocalTime());
+ }
+#else
+ return QVariant(dtval);
+#endif
+ }
+ case QVariant::ByteArray: {
+ size_t len;
+ unsigned char *data = PQunescapeBytea((const unsigned char*)val, &len);
+ QByteArray ba(reinterpret_cast<const char *>(data), int(len));
+ qPQfreemem(data);
+ return QVariant(ba);
+ }
+ default:
+ case QVariant::Invalid:
+ qWarning("QPSQLResult::data: unknown data type");
+ }
+ return QVariant();
+}
+
+bool QPSQLResult::isNull(int field)
+{
+ Q_D(const QPSQLResult);
+ PQgetvalue(d->result, at(), field);
+ return PQgetisnull(d->result, at(), field);
+}
+
+bool QPSQLResult::reset (const QString& query)
+{
+ Q_D(QPSQLResult);
+ cleanup();
+ if (!driver())
+ return false;
+ if (!driver()->isOpen() || driver()->isOpenError())
+ return false;
+ d->result = d->drv_d_func()->exec(query);
+ return d->processResults();
+}
+
+int QPSQLResult::size()
+{
+ Q_D(const QPSQLResult);
+ return d->currentSize;
+}
+
+int QPSQLResult::numRowsAffected()
+{
+ Q_D(const QPSQLResult);
+ return QString::fromLatin1(PQcmdTuples(d->result)).toInt();
+}
+
+QVariant QPSQLResult::lastInsertId() const
+{
+ Q_D(const QPSQLResult);
+ if (d->drv_d_func()->pro >= QPSQLDriver::Version81) {
+ QSqlQuery qry(driver()->createResult());
+ // Most recent sequence value obtained from nextval
+ if (qry.exec(QLatin1String("SELECT lastval();")) && qry.next())
+ return qry.value(0);
+ } else if (isActive()) {
+ Oid id = PQoidValue(d->result);
+ if (id != InvalidOid)
+ return QVariant(id);
+ }
+ return QVariant();
+}
+
+QSqlRecord QPSQLResult::record() const
+{
+ Q_D(const QPSQLResult);
+ QSqlRecord info;
+ if (!isActive() || !isSelect())
+ return info;
+
+ int count = PQnfields(d->result);
+ for (int i = 0; i < count; ++i) {
+ QSqlField f;
+ if (d->drv_d_func()->isUtf8)
+ f.setName(QString::fromUtf8(PQfname(d->result, i)));
+ else
+ f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
+ int ptype = PQftype(d->result, i);
+ f.setType(qDecodePSQLType(ptype));
+ int len = PQfsize(d->result, i);
+ int precision = PQfmod(d->result, i);
+
+ switch (ptype) {
+ case QTIMESTAMPOID:
+ case QTIMESTAMPTZOID:
+ precision = 3;
+ break;
+
+ case QNUMERICOID:
+ if (precision != -1) {
+ len = (precision >> 16);
+ precision = ((precision - VARHDRSZ) & 0xffff);
+ }
+ break;
+ case QBITOID:
+ case QVARBITOID:
+ len = precision;
+ precision = -1;
+ break;
+ default:
+ if (len == -1 && precision >= VARHDRSZ) {
+ len = precision - VARHDRSZ;
+ precision = -1;
+ }
+ }
+
+ f.setLength(len);
+ f.setPrecision(precision);
+ f.setSqlType(ptype);
+ info.append(f);
+ }
+ return info;
+}
+
+void QPSQLResult::virtual_hook(int id, void *data)
+{
+ Q_ASSERT(data);
+
+ QSqlResult::virtual_hook(id, data);
+}
+
+static QString qCreateParamString(const QVector<QVariant> &boundValues, const QSqlDriver *driver)
+{
+ if (boundValues.isEmpty())
+ return QString();
+
+ QString params;
+ QSqlField f;
+ for (int i = 0; i < boundValues.count(); ++i) {
+ const QVariant &val = boundValues.at(i);
+
+ f.setType(val.type());
+ if (val.isNull())
+ f.clear();
+ else
+ f.setValue(val);
+ if(!params.isNull())
+ params.append(QLatin1String(", "));
+ params.append(driver->formatValue(f));
+ }
+ return params;
+}
+
+Q_GLOBAL_STATIC(QMutex, qMutex)
+QString qMakePreparedStmtId()
+{
+ qMutex()->lock();
+ static unsigned int qPreparedStmtCount = 0;
+ QString id = QLatin1String("qpsqlpstmt_") + QString::number(++qPreparedStmtCount, 16);
+ qMutex()->unlock();
+ return id;
+}
+
+bool QPSQLResult::prepare(const QString &query)
+{
+ Q_D(QPSQLResult);
+ if (!d->preparedQueriesEnabled)
+ return QSqlResult::prepare(query);
+
+ cleanup();
+
+ if (!d->preparedStmtId.isEmpty())
+ d->deallocatePreparedStmt();
+
+ const QString stmtId = qMakePreparedStmtId();
+ const QString stmt = QString::fromLatin1("PREPARE %1 AS ").arg(stmtId).append(d->positionalToNamedBinding(query));
+
+ PGresult *result = d->drv_d_func()->exec(stmt);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+ setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
+ "Unable to prepare statement"), QSqlError::StatementError, d->drv_d_func(), result));
+ PQclear(result);
+ d->preparedStmtId.clear();
+ return false;
+ }
+
+ PQclear(result);
+ d->preparedStmtId = stmtId;
+ return true;
+}
+
+bool QPSQLResult::exec()
+{
+ Q_D(QPSQLResult);
+ if (!d->preparedQueriesEnabled)
+ return QSqlResult::exec();
+
+ cleanup();
+
+ QString stmt;
+ const QString params = qCreateParamString(boundValues(), driver());
+ if (params.isEmpty())
+ stmt = QString::fromLatin1("EXECUTE %1").arg(d->preparedStmtId);
+ else
+ stmt = QString::fromLatin1("EXECUTE %1 (%2)").arg(d->preparedStmtId).arg(params);
+
+ d->result = d->drv_d_func()->exec(stmt);
+
+ return d->processResults();
+}
+
+///////////////////////////////////////////////////////////////////
+
+bool QPSQLDriverPrivate::setEncodingUtf8()
+{
+ PGresult* result = exec("SET CLIENT_ENCODING TO 'UNICODE'");
+ int status = PQresultStatus(result);
+ PQclear(result);
+ return status == PGRES_COMMAND_OK;
+}
+
+void QPSQLDriverPrivate::setDatestyle()
+{
+ PGresult* result = exec("SET DATESTYLE TO 'ISO'");
+ int status = PQresultStatus(result);
+ if (status != PGRES_COMMAND_OK)
+ qWarning("%s", PQerrorMessage(connection));
+ PQclear(result);
+}
+
+void QPSQLDriverPrivate::detectBackslashEscape()
+{
+ // standard_conforming_strings option introduced in 8.2
+ // http://www.postgresql.org/docs/8.2/static/runtime-config-compatible.html
+ if (pro < QPSQLDriver::Version82) {
+ hasBackslashEscape = true;
+ } else {
+ hasBackslashEscape = false;
+ PGresult* result = exec(QLatin1Literal("SELECT '\\\\' x"));
+ int status = PQresultStatus(result);
+ if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK)
+ if (QString::fromLatin1(PQgetvalue(result, 0, 0)) == QLatin1Literal("\\"))
+ hasBackslashEscape = true;
+ PQclear(result);
+ }
+}
+
+static QPSQLDriver::Protocol qMakePSQLVersion(int vMaj, int vMin)
+{
+ switch (vMaj) {
+ case 6:
+ return QPSQLDriver::Version6;
+ case 7:
+ {
+ switch (vMin) {
+ case 1:
+ return QPSQLDriver::Version71;
+ case 3:
+ return QPSQLDriver::Version73;
+ case 4:
+ return QPSQLDriver::Version74;
+ default:
+ return QPSQLDriver::Version7;
+ }
+ break;
+ }
+ case 8:
+ {
+ switch (vMin) {
+ case 1:
+ return QPSQLDriver::Version81;
+ case 2:
+ return QPSQLDriver::Version82;
+ case 3:
+ return QPSQLDriver::Version83;
+ case 4:
+ return QPSQLDriver::Version84;
+ default:
+ return QPSQLDriver::Version8;
+ }
+ break;
+ }
+ case 9:
+ return QPSQLDriver::Version9;
+ break;
+ default:
+ break;
+ }
+ return QPSQLDriver::VersionUnknown;
+}
+
+QPSQLDriver::Protocol QPSQLDriverPrivate::getPSQLVersion()
+{
+ QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6;
+ PGresult* result = exec("select version()");
+ int status = PQresultStatus(result);
+ if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) {
+ QString val = QString::fromLatin1(PQgetvalue(result, 0, 0));
+
+ QRegExp rx(QLatin1String("(\\d+)\\.(\\d+)"));
+ rx.setMinimal(true); // enforce non-greedy RegExp
+
+ if (rx.indexIn(val) != -1) {
+ int vMaj = rx.cap(1).toInt();
+ int vMin = rx.cap(2).toInt();
+ serverVersion = qMakePSQLVersion(vMaj, vMin);
+#if defined(PG_MAJORVERSION)
+ if (rx.indexIn(QLatin1String(PG_MAJORVERSION)) != -1)
+#elif defined(PG_VERSION)
+ if (rx.indexIn(QLatin1String(PG_VERSION)) != -1)
+#else
+ if (0)
+#endif
+ {
+ vMaj = rx.cap(1).toInt();
+ vMin = rx.cap(2).toInt();
+ QPSQLDriver::Protocol clientVersion = qMakePSQLVersion(vMaj, vMin);
+
+ if (serverVersion >= QPSQLDriver::Version9 && clientVersion < QPSQLDriver::Version9) {
+ //Client version before QPSQLDriver::Version9 only supports escape mode for bytea type,
+ //but bytea format is set to hex by default in PSQL 9 and above. So need to force the
+ //server use the old escape mode when connects to the new server with old client library.
+ PQclear(result);
+ result = exec("SET bytea_output=escape; ");
+ status = PQresultStatus(result);
+ } else if (serverVersion == QPSQLDriver::VersionUnknown) {
+ serverVersion = clientVersion;
+ if (serverVersion != QPSQLDriver::VersionUnknown)
+ qWarning("The server version of this PostgreSQL is unknown, falling back to the client version.");
+ }
+ }
+ }
+ }
+ PQclear(result);
+
+ //keep the old behavior unchanged
+ if (serverVersion == QPSQLDriver::VersionUnknown)
+ serverVersion = QPSQLDriver::Version6;
+
+ if (serverVersion < QPSQLDriver::Version71) {
+ qWarning("This version of PostgreSQL is not supported and may not work.");
+ }
+
+ return serverVersion;
+}
+
+QPSQLDriver::QPSQLDriver(QObject *parent)
+ : QSqlDriver(*new QPSQLDriverPrivate, parent)
+{
+}
+
+QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent)
+ : QSqlDriver(*new QPSQLDriverPrivate, parent)
+{
+ Q_D(QPSQLDriver);
+ d->connection = conn;
+ if (conn) {
+ d->pro = d->getPSQLVersion();
+ d->detectBackslashEscape();
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+QPSQLDriver::~QPSQLDriver()
+{
+ Q_D(QPSQLDriver);
+ if (d->connection)
+ PQfinish(d->connection);
+}
+
+QVariant QPSQLDriver::handle() const
+{
+ Q_D(const QPSQLDriver);
+ return QVariant::fromValue(d->connection);
+}
+
+bool QPSQLDriver::hasFeature(DriverFeature f) const
+{
+ Q_D(const QPSQLDriver);
+ switch (f) {
+ case Transactions:
+ case QuerySize:
+ case LastInsertId:
+ case LowPrecisionNumbers:
+ case EventNotifications:
+ return true;
+ case PreparedQueries:
+ case PositionalPlaceholders:
+ return d->pro >= QPSQLDriver::Version82;
+ case BatchOperations:
+ case NamedPlaceholders:
+ case SimpleLocking:
+ case FinishQuery:
+ case MultipleResultSets:
+ case CancelQuery:
+ return false;
+ case BLOB:
+ return d->pro >= QPSQLDriver::Version71;
+ case Unicode:
+ return d->isUtf8;
+ }
+ return false;
+}
+
+/*
+ Quote a string for inclusion into the connection string
+ \ -> \\
+ ' -> \'
+ surround string by single quotes
+ */
+static QString qQuote(QString s)
+{
+ s.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
+ s.replace(QLatin1Char('\''), QLatin1String("\\'"));
+ s.append(QLatin1Char('\'')).prepend(QLatin1Char('\''));
+ return s;
+}
+
+bool QPSQLDriver::open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString& connOpts)
+{
+ Q_D(QPSQLDriver);
+ if (isOpen())
+ close();
+ QString connectString;
+ if (!host.isEmpty())
+ connectString.append(QLatin1String("host=")).append(qQuote(host));
+ if (!db.isEmpty())
+ connectString.append(QLatin1String(" dbname=")).append(qQuote(db));
+ if (!user.isEmpty())
+ connectString.append(QLatin1String(" user=")).append(qQuote(user));
+ if (!password.isEmpty())
+ connectString.append(QLatin1String(" password=")).append(qQuote(password));
+ if (port != -1)
+ connectString.append(QLatin1String(" port=")).append(qQuote(QString::number(port)));
+
+ // add any connect options - the server will handle error detection
+ if (!connOpts.isEmpty()) {
+ QString opt = connOpts;
+ opt.replace(QLatin1Char(';'), QLatin1Char(' '), Qt::CaseInsensitive);
+ connectString.append(QLatin1Char(' ')).append(opt);
+ }
+
+ d->connection = PQconnectdb(connectString.toLocal8Bit().constData());
+ if (PQstatus(d->connection) == CONNECTION_BAD) {
+ setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
+ setOpenError(true);
+ PQfinish(d->connection);
+ d->connection = 0;
+ return false;
+ }
+
+ d->pro = d->getPSQLVersion();
+ d->detectBackslashEscape();
+ d->isUtf8 = d->setEncodingUtf8();
+ d->setDatestyle();
+
+ setOpen(true);
+ setOpenError(false);
+ return true;
+}
+
+void QPSQLDriver::close()
+{
+ Q_D(QPSQLDriver);
+ if (isOpen()) {
+
+ d->seid.clear();
+ if (d->sn) {
+ disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
+ delete d->sn;
+ d->sn = 0;
+ }
+
+ if (d->connection)
+ PQfinish(d->connection);
+ d->connection = 0;
+ setOpen(false);
+ setOpenError(false);
+ }
+}
+
+QSqlResult *QPSQLDriver::createResult() const
+{
+ return new QPSQLResult(this);
+}
+
+bool QPSQLDriver::beginTransaction()
+{
+ Q_D(const QPSQLDriver);
+ if (!isOpen()) {
+ qWarning("QPSQLDriver::beginTransaction: Database not open");
+ return false;
+ }
+ PGresult* res = d->exec("BEGIN");
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
+ setLastError(qMakeError(tr("Could not begin transaction"),
+ QSqlError::TransactionError, d, res));
+ PQclear(res);
+ return false;
+ }
+ PQclear(res);
+ return true;
+}
+
+bool QPSQLDriver::commitTransaction()
+{
+ Q_D(QPSQLDriver);
+ if (!isOpen()) {
+ qWarning("QPSQLDriver::commitTransaction: Database not open");
+ return false;
+ }
+ PGresult* res = d->exec("COMMIT");
+
+ bool transaction_failed = false;
+
+ // XXX
+ // This hack is used to tell if the transaction has succeeded for the protocol versions of
+ // PostgreSQL below. For 7.x and other protocol versions we are left in the dark.
+ // This hack can dissapear once there is an API to query this sort of information.
+ if (d->pro == QPSQLDriver::Version8 ||
+ d->pro == QPSQLDriver::Version81 ||
+ d->pro == QPSQLDriver::Version82 ||
+ d->pro == QPSQLDriver::Version83 ||
+ d->pro == QPSQLDriver::Version84 ||
+ d->pro == QPSQLDriver::Version9) {
+ transaction_failed = qstrcmp(PQcmdStatus(res), "ROLLBACK") == 0;
+ }
+
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK || transaction_failed) {
+ setLastError(qMakeError(tr("Could not commit transaction"),
+ QSqlError::TransactionError, d, res));
+ PQclear(res);
+ return false;
+ }
+ PQclear(res);
+ return true;
+}
+
+bool QPSQLDriver::rollbackTransaction()
+{
+ Q_D(QPSQLDriver);
+ if (!isOpen()) {
+ qWarning("QPSQLDriver::rollbackTransaction: Database not open");
+ return false;
+ }
+ PGresult* res = d->exec("ROLLBACK");
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
+ setLastError(qMakeError(tr("Could not rollback transaction"),
+ QSqlError::TransactionError, d, res));
+ PQclear(res);
+ return false;
+ }
+ PQclear(res);
+ return true;
+}
+
+QStringList QPSQLDriver::tables(QSql::TableType type) const
+{
+ Q_D(const QPSQLDriver);
+ QStringList tl;
+ if (!isOpen())
+ return tl;
+ QSqlQuery t(createResult());
+ t.setForwardOnly(true);
+
+ if (type & QSql::Tables)
+ const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, QLatin1Char('r'));
+ if (type & QSql::Views)
+ const_cast<QPSQLDriverPrivate*>(d)->appendTables(tl, t, QLatin1Char('v'));
+ if (type & QSql::SystemTables) {
+ t.exec(QLatin1String("select relname from pg_class where (relkind = 'r') "
+ "and (relname like 'pg_%') "));
+ while (t.next())
+ tl.append(t.value(0).toString());
+ }
+
+ return tl;
+}
+
+static void qSplitTableName(QString &tablename, QString &schema)
+{
+ int dot = tablename.indexOf(QLatin1Char('.'));
+ if (dot == -1)
+ return;
+ schema = tablename.left(dot);
+ tablename = tablename.mid(dot + 1);
+}
+
+QSqlIndex QPSQLDriver::primaryIndex(const QString& tablename) const
+{
+ Q_D(const QPSQLDriver);
+ QSqlIndex idx(tablename);
+ if (!isOpen())
+ return idx;
+ QSqlQuery i(createResult());
+ QString stmt;
+
+ QString tbl = tablename;
+ QString schema;
+ qSplitTableName(tbl, schema);
+
+ if (isIdentifierEscaped(tbl, QSqlDriver::TableName))
+ tbl = stripDelimiters(tbl, QSqlDriver::TableName);
+ else
+ tbl = tbl.toLower();
+
+ if (isIdentifierEscaped(schema, QSqlDriver::TableName))
+ schema = stripDelimiters(schema, QSqlDriver::TableName);
+ else
+ schema = schema.toLower();
+
+ switch(d->pro) {
+ case QPSQLDriver::Version6:
+ stmt = QLatin1String("select pg_att1.attname, int(pg_att1.atttypid), pg_cl.relname "
+ "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind "
+ "where pg_cl.relname = '%1_pkey' "
+ "and pg_cl.oid = pg_ind.indexrelid "
+ "and pg_att2.attrelid = pg_ind.indexrelid "
+ "and pg_att1.attrelid = pg_ind.indrelid "
+ "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] "
+ "order by pg_att2.attnum");
+ break;
+ case QPSQLDriver::Version7:
+ case QPSQLDriver::Version71:
+ stmt = QLatin1String("select pg_att1.attname, pg_att1.atttypid::int, pg_cl.relname "
+ "from pg_attribute pg_att1, pg_attribute pg_att2, pg_class pg_cl, pg_index pg_ind "
+ "where pg_cl.relname = '%1_pkey' "
+ "and pg_cl.oid = pg_ind.indexrelid "
+ "and pg_att2.attrelid = pg_ind.indexrelid "
+ "and pg_att1.attrelid = pg_ind.indrelid "
+ "and pg_att1.attnum = pg_ind.indkey[pg_att2.attnum-1] "
+ "order by pg_att2.attnum");
+ break;
+ case QPSQLDriver::Version73:
+ case QPSQLDriver::Version74:
+ case QPSQLDriver::Version8:
+ case QPSQLDriver::Version81:
+ case QPSQLDriver::Version82:
+ case QPSQLDriver::Version83:
+ case QPSQLDriver::Version84:
+ case QPSQLDriver::Version9:
+ stmt = QLatin1String("SELECT pg_attribute.attname, pg_attribute.atttypid::int, "
+ "pg_class.relname "
+ "FROM pg_attribute, pg_class "
+ "WHERE %1 pg_class.oid IN "
+ "(SELECT indexrelid FROM pg_index WHERE indisprimary = true AND indrelid IN "
+ " (SELECT oid FROM pg_class WHERE relname = '%2')) "
+ "AND pg_attribute.attrelid = pg_class.oid "
+ "AND pg_attribute.attisdropped = false "
+ "ORDER BY pg_attribute.attnum");
+ if (schema.isEmpty())
+ stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid) AND"));
+ else
+ stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from "
+ "pg_namespace where pg_namespace.nspname = '%1') AND ").arg(schema));
+ break;
+ case QPSQLDriver::VersionUnknown:
+ qFatal("PSQL version is unknown");
+ break;
+ }
+
+ i.exec(stmt.arg(tbl));
+ while (i.isActive() && i.next()) {
+ QSqlField f(i.value(0).toString(), qDecodePSQLType(i.value(1).toInt()));
+ idx.append(f);
+ idx.setName(i.value(2).toString());
+ }
+ return idx;
+}
+
+QSqlRecord QPSQLDriver::record(const QString& tablename) const
+{
+ Q_D(const QPSQLDriver);
+ QSqlRecord info;
+ if (!isOpen())
+ return info;
+
+ QString tbl = tablename;
+ QString schema;
+ qSplitTableName(tbl, schema);
+
+ if (isIdentifierEscaped(tbl, QSqlDriver::TableName))
+ tbl = stripDelimiters(tbl, QSqlDriver::TableName);
+ else
+ tbl = tbl.toLower();
+
+ if (isIdentifierEscaped(schema, QSqlDriver::TableName))
+ schema = stripDelimiters(schema, QSqlDriver::TableName);
+ else
+ schema = schema.toLower();
+
+ QString stmt;
+ switch(d->pro) {
+ case QPSQLDriver::Version6:
+ stmt = QLatin1String("select pg_attribute.attname, int(pg_attribute.atttypid), "
+ "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
+ "int(pg_attribute.attrelid), pg_attribute.attnum "
+ "from pg_class, pg_attribute "
+ "where pg_class.relname = '%1' "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid ");
+ break;
+ case QPSQLDriver::Version7:
+ stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, "
+ "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
+ "pg_attribute.attrelid::int, pg_attribute.attnum "
+ "from pg_class, pg_attribute "
+ "where pg_class.relname = '%1' "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid ");
+ break;
+ case QPSQLDriver::Version71:
+ stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, "
+ "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
+ "pg_attrdef.adsrc "
+ "from pg_class, pg_attribute "
+ "left join pg_attrdef on (pg_attrdef.adrelid = "
+ "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) "
+ "where pg_class.relname = '%1' "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid "
+ "order by pg_attribute.attnum ");
+ break;
+ case QPSQLDriver::Version73:
+ case QPSQLDriver::Version74:
+ case QPSQLDriver::Version8:
+ case QPSQLDriver::Version81:
+ case QPSQLDriver::Version82:
+ case QPSQLDriver::Version83:
+ case QPSQLDriver::Version84:
+ case QPSQLDriver::Version9:
+ stmt = QLatin1String("select pg_attribute.attname, pg_attribute.atttypid::int, "
+ "pg_attribute.attnotnull, pg_attribute.attlen, pg_attribute.atttypmod, "
+ "pg_attrdef.adsrc "
+ "from pg_class, pg_attribute "
+ "left join pg_attrdef on (pg_attrdef.adrelid = "
+ "pg_attribute.attrelid and pg_attrdef.adnum = pg_attribute.attnum) "
+ "where %1 "
+ "and pg_class.relname = '%2' "
+ "and pg_attribute.attnum > 0 "
+ "and pg_attribute.attrelid = pg_class.oid "
+ "and pg_attribute.attisdropped = false "
+ "order by pg_attribute.attnum ");
+ if (schema.isEmpty())
+ stmt = stmt.arg(QLatin1String("pg_table_is_visible(pg_class.oid)"));
+ else
+ stmt = stmt.arg(QString::fromLatin1("pg_class.relnamespace = (select oid from "
+ "pg_namespace where pg_namespace.nspname = '%1')").arg(schema));
+ break;
+ case QPSQLDriver::VersionUnknown:
+ qFatal("PSQL version is unknown");
+ break;
+ }
+
+ QSqlQuery query(createResult());
+ query.exec(stmt.arg(tbl));
+ if (d->pro >= QPSQLDriver::Version71) {
+ while (query.next()) {
+ int len = query.value(3).toInt();
+ int precision = query.value(4).toInt();
+ // swap length and precision if length == -1
+ if (len == -1 && precision > -1) {
+ len = precision - 4;
+ precision = -1;
+ }
+ QString defVal = query.value(5).toString();
+ if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\''))
+ defVal = defVal.mid(1, defVal.length() - 2);
+ QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()));
+ f.setRequired(query.value(2).toBool());
+ f.setLength(len);
+ f.setPrecision(precision);
+ f.setDefaultValue(defVal);
+ f.setSqlType(query.value(1).toInt());
+ info.append(f);
+ }
+ } else {
+ // Postgres < 7.1 cannot handle outer joins
+ while (query.next()) {
+ QString defVal;
+ QString stmt2 = QLatin1String("select pg_attrdef.adsrc from pg_attrdef where "
+ "pg_attrdef.adrelid = %1 and pg_attrdef.adnum = %2 ");
+ QSqlQuery query2(createResult());
+ query2.exec(stmt2.arg(query.value(5).toInt()).arg(query.value(6).toInt()));
+ if (query2.isActive() && query2.next())
+ defVal = query2.value(0).toString();
+ if (!defVal.isEmpty() && defVal.at(0) == QLatin1Char('\''))
+ defVal = defVal.mid(1, defVal.length() - 2);
+ int len = query.value(3).toInt();
+ int precision = query.value(4).toInt();
+ // swap length and precision if length == -1
+ if (len == -1 && precision > -1) {
+ len = precision - 4;
+ precision = -1;
+ }
+ QSqlField f(query.value(0).toString(), qDecodePSQLType(query.value(1).toInt()));
+ f.setRequired(query.value(2).toBool());
+ f.setLength(len);
+ f.setPrecision(precision);
+ f.setDefaultValue(defVal);
+ f.setSqlType(query.value(1).toInt());
+ info.append(f);
+ }
+ }
+
+ return info;
+}
+
+template <class FloatType>
+inline void assignSpecialPsqlFloatValue(FloatType val, QString *target)
+{
+ if (isnan(val)) {
+ *target = QLatin1String("'NaN'");
+ } else {
+ switch (isinf(val)) {
+ case 1:
+ *target = QLatin1String("'Infinity'");
+ break;
+ case -1:
+ *target = QLatin1String("'-Infinity'");
+ break;
+ }
+ }
+}
+
+QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
+{
+ Q_D(const QPSQLDriver);
+ QString r;
+ if (field.isNull()) {
+ r = QLatin1String("NULL");
+ } else {
+ switch (int(field.type())) {
+ case QVariant::DateTime:
+#ifndef QT_NO_DATESTRING
+ if (field.value().toDateTime().isValid()) {
+ // we force the value to be considered with a timezone information, and we force it to be UTC
+ // this is safe since postgresql stores only the UTC value and not the timezone offset (only used
+ // while parsing), so we have correct behavior in both case of with timezone and without tz
+ r = QLatin1String("TIMESTAMP WITH TIME ZONE ") + QLatin1Char('\'') + field.value().toDateTime().toUTC().toString(QLatin1String("yyyy-MM-ddThh:mm:ss.zzz")) + QLatin1Char('Z') + QLatin1Char('\'');
+ } else {
+ r = QLatin1String("NULL");
+ }
+#else
+ r = QLatin1String("NULL");
+#endif // QT_NO_DATESTRING
+ break;
+ case QVariant::Time:
+#ifndef QT_NO_DATESTRING
+ if (field.value().toTime().isValid()) {
+ r = QLatin1Char('\'') + field.value().toTime().toString(QLatin1String("hh:mm:ss.zzz")) + QLatin1Char('\'');
+ } else
+#endif
+ {
+ r = QLatin1String("NULL");
+ }
+ break;
+ case QVariant::String:
+ r = QSqlDriver::formatValue(field, trimStrings);
+ if (d->hasBackslashEscape)
+ r.replace(QLatin1String("\\"), QLatin1String("\\\\"));
+ break;
+ case QVariant::Bool:
+ if (field.value().toBool())
+ r = QLatin1String("TRUE");
+ else
+ r = QLatin1String("FALSE");
+ break;
+ case QVariant::ByteArray: {
+ QByteArray ba(field.value().toByteArray());
+ size_t len;
+#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 80200
+ unsigned char *data = PQescapeByteaConn(d->connection, (const unsigned char*)ba.constData(), ba.size(), &len);
+#else
+ unsigned char *data = PQescapeBytea((const unsigned char*)ba.constData(), ba.size(), &len);
+#endif
+ r += QLatin1Char('\'');
+ r += QLatin1String((const char*)data);
+ r += QLatin1Char('\'');
+ qPQfreemem(data);
+ break;
+ }
+ case QMetaType::Float:
+ assignSpecialPsqlFloatValue(field.value().toFloat(), &r);
+ if (r.isEmpty())
+ r = QSqlDriver::formatValue(field, trimStrings);
+ break;
+ case QVariant::Double:
+ assignSpecialPsqlFloatValue(field.value().toDouble(), &r);
+ if (r.isEmpty())
+ r = QSqlDriver::formatValue(field, trimStrings);
+ break;
+ case QVariant::Uuid:
+ r = QLatin1Char('\'') + field.value().toString() + QLatin1Char('\'');
+ break;
+ default:
+ r = QSqlDriver::formatValue(field, trimStrings);
+ break;
+ }
+ }
+ return r;
+}
+
+QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
+{
+ QString res = identifier;
+ if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) {
+ res.replace(QLatin1Char('"'), QLatin1String("\"\""));
+ res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
+ res.replace(QLatin1Char('.'), QLatin1String("\".\""));
+ }
+ return res;
+}
+
+bool QPSQLDriver::isOpen() const
+{
+ Q_D(const QPSQLDriver);
+ return PQstatus(d->connection) == CONNECTION_OK;
+}
+
+QPSQLDriver::Protocol QPSQLDriver::protocol() const
+{
+ Q_D(const QPSQLDriver);
+ return d->pro;
+}
+
+bool QPSQLDriver::subscribeToNotification(const QString &name)
+{
+ Q_D(QPSQLDriver);
+ if (!isOpen()) {
+ qWarning("QPSQLDriver::subscribeToNotificationImplementation: database not open.");
+ return false;
+ }
+
+ if (d->seid.contains(name)) {
+ qWarning("QPSQLDriver::subscribeToNotificationImplementation: already subscribing to '%s'.",
+ qPrintable(name));
+ return false;
+ }
+
+ int socket = PQsocket(d->connection);
+ if (socket) {
+ // Add the name to the list of subscriptions here so that QSQLDriverPrivate::exec knows
+ // to check for notifications immediately after executing the LISTEN
+ d->seid << name;
+ QString query = QLatin1String("LISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
+ PGresult *result = d->exec(query);
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+ setLastError(qMakeError(tr("Unable to subscribe"), QSqlError::StatementError, d, result));
+ PQclear(result);
+ return false;
+ }
+ PQclear(result);
+
+ if (!d->sn) {
+ d->sn = new QSocketNotifier(socket, QSocketNotifier::Read);
+ connect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
+ }
+ } else {
+ qWarning("QPSQLDriver::subscribeToNotificationImplementation: PQsocket didn't return a valid socket to listen on");
+ return false;
+ }
+
+ return true;
+}
+
+bool QPSQLDriver::unsubscribeFromNotification(const QString &name)
+{
+ Q_D(QPSQLDriver);
+ if (!isOpen()) {
+ qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: database not open.");
+ return false;
+ }
+
+ if (!d->seid.contains(name)) {
+ qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: not subscribed to '%s'.",
+ qPrintable(name));
+ return false;
+ }
+
+ QString query = QLatin1String("UNLISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
+ PGresult *result = d->exec(query);
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+ setLastError(qMakeError(tr("Unable to unsubscribe"), QSqlError::StatementError, d, result));
+ PQclear(result);
+ return false;
+ }
+ PQclear(result);
+
+ d->seid.removeAll(name);
+
+ if (d->seid.isEmpty()) {
+ disconnect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
+ delete d->sn;
+ d->sn = 0;
+ }
+
+ return true;
+}
+
+QStringList QPSQLDriver::subscribedToNotifications() const
+{
+ Q_D(const QPSQLDriver);
+ return d->seid;
+}
+
+void QPSQLDriver::_q_handleNotification(int)
+{
+ Q_D(QPSQLDriver);
+ d->pendingNotifyCheck = false;
+ PQconsumeInput(d->connection);
+
+ PGnotify *notify = 0;
+ while((notify = PQnotifies(d->connection)) != 0) {
+ QString name(QLatin1String(notify->relname));
+ if (d->seid.contains(name)) {
+ QString payload;
+#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 70400
+ if (notify->extra)
+ payload = d->isUtf8 ? QString::fromUtf8(notify->extra) : QString::fromLatin1(notify->extra);
+#endif
+ emit notification(name);
+ QSqlDriver::NotificationSource source = (notify->be_pid == PQbackendPID(d->connection)) ? QSqlDriver::SelfSource : QSqlDriver::OtherSource;
+ emit notification(name, source, payload);
+ }
+ else
+ qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.",
+ qPrintable(name));
+
+ qPQfreemem(notify);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/sqldrivers/psql/qsql_psql_p.h b/src/plugins/sqldrivers/psql/qsql_psql_p.h
new file mode 100644
index 0000000000..8468b9af93
--- /dev/null
+++ b/src/plugins/sqldrivers/psql/qsql_psql_p.h
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_PSQL_H
+#define QSQL_PSQL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtSql/qsqldriver.h>
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_PSQL
+#else
+#define Q_EXPORT_SQLDRIVER_PSQL Q_SQL_EXPORT
+#endif
+
+typedef struct pg_conn PGconn;
+typedef struct pg_result PGresult;
+
+QT_BEGIN_NAMESPACE
+
+class QPSQLDriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_PSQL QPSQLDriver : public QSqlDriver
+{
+ friend class QPSQLResultPrivate;
+ Q_DECLARE_PRIVATE(QPSQLDriver)
+ Q_OBJECT
+public:
+ enum Protocol {
+ VersionUnknown = -1,
+ Version6 = 6,
+ Version7 = 7,
+ Version71 = 8,
+ Version73 = 9,
+ Version74 = 10,
+ Version8 = 11,
+ Version81 = 12,
+ Version82 = 13,
+ Version83 = 14,
+ Version84 = 15,
+ Version9 = 16
+ };
+
+ explicit QPSQLDriver(QObject *parent=0);
+ explicit QPSQLDriver(PGconn *conn, QObject *parent=0);
+ ~QPSQLDriver();
+ bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE;
+ bool open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString& connOpts) Q_DECL_OVERRIDE;
+ bool isOpen() const Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ QSqlResult *createResult() const Q_DECL_OVERRIDE;
+ QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE;
+ QSqlIndex primaryIndex(const QString& tablename) const Q_DECL_OVERRIDE;
+ QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE;
+
+ Protocol protocol() const;
+ QVariant handle() const Q_DECL_OVERRIDE;
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE;
+ QString formatValue(const QSqlField &field, bool trimStrings) const Q_DECL_OVERRIDE;
+
+ bool subscribeToNotification(const QString &name) Q_DECL_OVERRIDE;
+ bool unsubscribeFromNotification(const QString &name) Q_DECL_OVERRIDE;
+ QStringList subscribedToNotifications() const Q_DECL_OVERRIDE;
+
+protected:
+ bool beginTransaction() Q_DECL_OVERRIDE;
+ bool commitTransaction() Q_DECL_OVERRIDE;
+ bool rollbackTransaction() Q_DECL_OVERRIDE;
+
+private Q_SLOTS:
+ void _q_handleNotification(int);
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_PSQL_H
diff --git a/src/plugins/sqldrivers/sqldrivers.pro b/src/plugins/sqldrivers/sqldrivers.pro
index 39c58d4f2b..37dbd2394e 100644
--- a/src/plugins/sqldrivers/sqldrivers.pro
+++ b/src/plugins/sqldrivers/sqldrivers.pro
@@ -1,11 +1,11 @@
TEMPLATE = subdirs
-contains(sql-plugins, psql) : SUBDIRS += psql
-contains(sql-plugins, mysql) : SUBDIRS += mysql
-contains(sql-plugins, odbc) : SUBDIRS += odbc
-contains(sql-plugins, tds) : SUBDIRS += tds
-contains(sql-plugins, oci) : SUBDIRS += oci
-contains(sql-plugins, db2) : SUBDIRS += db2
-contains(sql-plugins, sqlite) : SUBDIRS += sqlite
-contains(sql-plugins, sqlite2) : SUBDIRS += sqlite2
-contains(sql-plugins, ibase) : SUBDIRS += ibase
+contains(sql-drivers, psql) : SUBDIRS += psql
+contains(sql-drivers, mysql) : SUBDIRS += mysql
+contains(sql-drivers, odbc) : SUBDIRS += odbc
+contains(sql-drivers, tds) : SUBDIRS += tds
+contains(sql-drivers, oci) : SUBDIRS += oci
+contains(sql-drivers, db2) : SUBDIRS += db2
+contains(sql-drivers, sqlite) : SUBDIRS += sqlite
+contains(sql-drivers, sqlite2) : SUBDIRS += sqlite2
+contains(sql-drivers, ibase) : SUBDIRS += ibase
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
new file mode 100644
index 0000000000..2a45b73d14
--- /dev/null
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
@@ -0,0 +1,828 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_sqlite_p.h"
+
+#include <qcoreapplication.h>
+#include <qdatetime.h>
+#include <qvariant.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlquery.h>
+#include <QtSql/private/qsqlcachedresult_p.h>
+#include <QtSql/private/qsqldriver_p.h>
+#include <qstringlist.h>
+#include <qvector.h>
+#include <qdebug.h>
+
+#if defined Q_OS_WIN
+# include <qt_windows.h>
+#else
+# include <unistd.h>
+#endif
+
+#include <sqlite3.h>
+
+Q_DECLARE_OPAQUE_POINTER(sqlite3*)
+Q_DECLARE_METATYPE(sqlite3*)
+
+Q_DECLARE_OPAQUE_POINTER(sqlite3_stmt*)
+Q_DECLARE_METATYPE(sqlite3_stmt*)
+
+QT_BEGIN_NAMESPACE
+
+static QString _q_escapeIdentifier(const QString &identifier)
+{
+ QString res = identifier;
+ if(!identifier.isEmpty() && identifier.left(1) != QString(QLatin1Char('"')) && identifier.right(1) != QString(QLatin1Char('"')) ) {
+ res.replace(QLatin1Char('"'), QLatin1String("\"\""));
+ res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
+ res.replace(QLatin1Char('.'), QLatin1String("\".\""));
+ }
+ return res;
+}
+
+static QVariant::Type qGetColumnType(const QString &tpName)
+{
+ const QString typeName = tpName.toLower();
+
+ if (typeName == QLatin1String("integer")
+ || typeName == QLatin1String("int"))
+ return QVariant::Int;
+ if (typeName == QLatin1String("double")
+ || typeName == QLatin1String("float")
+ || typeName == QLatin1String("real")
+ || typeName.startsWith(QLatin1String("numeric")))
+ return QVariant::Double;
+ if (typeName == QLatin1String("blob"))
+ return QVariant::ByteArray;
+ if (typeName == QLatin1String("boolean")
+ || typeName == QLatin1String("bool"))
+ return QVariant::Bool;
+ return QVariant::String;
+}
+
+static QSqlError qMakeError(sqlite3 *access, const QString &descr, QSqlError::ErrorType type,
+ int errorCode = -1)
+{
+ return QSqlError(descr,
+ QString(reinterpret_cast<const QChar *>(sqlite3_errmsg16(access))),
+ type, QString::number(errorCode));
+}
+
+class QSQLiteResultPrivate;
+
+class QSQLiteResult : public QSqlCachedResult
+{
+ Q_DECLARE_PRIVATE(QSQLiteResult)
+ friend class QSQLiteDriver;
+
+public:
+ explicit QSQLiteResult(const QSQLiteDriver* db);
+ ~QSQLiteResult();
+ QVariant handle() const Q_DECL_OVERRIDE;
+
+protected:
+ bool gotoNext(QSqlCachedResult::ValueCache& row, int idx) Q_DECL_OVERRIDE;
+ bool reset(const QString &query) Q_DECL_OVERRIDE;
+ bool prepare(const QString &query) Q_DECL_OVERRIDE;
+ bool exec() Q_DECL_OVERRIDE;
+ int size() Q_DECL_OVERRIDE;
+ int numRowsAffected() Q_DECL_OVERRIDE;
+ QVariant lastInsertId() const Q_DECL_OVERRIDE;
+ QSqlRecord record() const Q_DECL_OVERRIDE;
+ void detachFromResultSet() Q_DECL_OVERRIDE;
+ void virtual_hook(int id, void *data) Q_DECL_OVERRIDE;
+};
+
+class QSQLiteDriverPrivate : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QSQLiteDriver)
+
+public:
+ inline QSQLiteDriverPrivate() : QSqlDriverPrivate(), access(0) { dbmsType = QSqlDriver::SQLite; }
+ sqlite3 *access;
+ QList <QSQLiteResult *> results;
+};
+
+
+class QSQLiteResultPrivate: public QSqlCachedResultPrivate
+{
+ Q_DECLARE_PUBLIC(QSQLiteResult)
+
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QSQLiteDriver)
+ QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv);
+ void cleanup();
+ bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch);
+ // initializes the recordInfo and the cache
+ void initColumns(bool emptyResultset);
+ void finalize();
+
+ sqlite3_stmt *stmt;
+
+ bool skippedStatus; // the status of the fetchNext() that's skipped
+ bool skipRow; // skip the next fetchNext()?
+ QSqlRecord rInf;
+ QVector<QVariant> firstRow;
+};
+
+QSQLiteResultPrivate::QSQLiteResultPrivate(QSQLiteResult *q, const QSQLiteDriver *drv)
+ : QSqlCachedResultPrivate(q, drv),
+ stmt(0),
+ skippedStatus(false),
+ skipRow(false)
+{
+}
+
+void QSQLiteResultPrivate::cleanup()
+{
+ Q_Q(QSQLiteResult);
+ finalize();
+ rInf.clear();
+ skippedStatus = false;
+ skipRow = false;
+ q->setAt(QSql::BeforeFirstRow);
+ q->setActive(false);
+ q->cleanup();
+}
+
+void QSQLiteResultPrivate::finalize()
+{
+ if (!stmt)
+ return;
+
+ sqlite3_finalize(stmt);
+ stmt = 0;
+}
+
+void QSQLiteResultPrivate::initColumns(bool emptyResultset)
+{
+ Q_Q(QSQLiteResult);
+ int nCols = sqlite3_column_count(stmt);
+ if (nCols <= 0)
+ return;
+
+ q->init(nCols);
+
+ for (int i = 0; i < nCols; ++i) {
+ QString colName = QString(reinterpret_cast<const QChar *>(
+ sqlite3_column_name16(stmt, i))
+ ).remove(QLatin1Char('"'));
+
+ // must use typeName for resolving the type to match QSqliteDriver::record
+ QString typeName = QString(reinterpret_cast<const QChar *>(
+ sqlite3_column_decltype16(stmt, i)));
+ // sqlite3_column_type is documented to have undefined behavior if the result set is empty
+ int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i);
+
+ QVariant::Type fieldType;
+
+ if (!typeName.isEmpty()) {
+ fieldType = qGetColumnType(typeName);
+ } else {
+ // Get the proper type for the field based on stp value
+ switch (stp) {
+ case SQLITE_INTEGER:
+ fieldType = QVariant::Int;
+ break;
+ case SQLITE_FLOAT:
+ fieldType = QVariant::Double;
+ break;
+ case SQLITE_BLOB:
+ fieldType = QVariant::ByteArray;
+ break;
+ case SQLITE_TEXT:
+ fieldType = QVariant::String;
+ break;
+ case SQLITE_NULL:
+ default:
+ fieldType = QVariant::Invalid;
+ break;
+ }
+ }
+
+ QSqlField fld(colName, fieldType);
+ fld.setSqlType(stp);
+ rInf.append(fld);
+ }
+}
+
+bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch)
+{
+ Q_Q(QSQLiteResult);
+ int res;
+ int i;
+
+ if (skipRow) {
+ // already fetched
+ Q_ASSERT(!initialFetch);
+ skipRow = false;
+ for(int i=0;i<firstRow.count();i++)
+ values[i]=firstRow[i];
+ return skippedStatus;
+ }
+ skipRow = initialFetch;
+
+ if(initialFetch) {
+ firstRow.clear();
+ firstRow.resize(sqlite3_column_count(stmt));
+ }
+
+ if (!stmt) {
+ q->setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult", "Unable to fetch row"),
+ QCoreApplication::translate("QSQLiteResult", "No query"), QSqlError::ConnectionError));
+ q->setAt(QSql::AfterLastRow);
+ return false;
+ }
+ res = sqlite3_step(stmt);
+
+ switch(res) {
+ case SQLITE_ROW:
+ // check to see if should fill out columns
+ if (rInf.isEmpty())
+ // must be first call.
+ initColumns(false);
+ if (idx < 0 && !initialFetch)
+ return true;
+ for (i = 0; i < rInf.count(); ++i) {
+ switch (sqlite3_column_type(stmt, i)) {
+ case SQLITE_BLOB:
+ values[i + idx] = QByteArray(static_cast<const char *>(
+ sqlite3_column_blob(stmt, i)),
+ sqlite3_column_bytes(stmt, i));
+ break;
+ case SQLITE_INTEGER:
+ values[i + idx] = sqlite3_column_int64(stmt, i);
+ break;
+ case SQLITE_FLOAT:
+ switch(q->numericalPrecisionPolicy()) {
+ case QSql::LowPrecisionInt32:
+ values[i + idx] = sqlite3_column_int(stmt, i);
+ break;
+ case QSql::LowPrecisionInt64:
+ values[i + idx] = sqlite3_column_int64(stmt, i);
+ break;
+ case QSql::LowPrecisionDouble:
+ case QSql::HighPrecision:
+ default:
+ values[i + idx] = sqlite3_column_double(stmt, i);
+ break;
+ };
+ break;
+ case SQLITE_NULL:
+ values[i + idx] = QVariant(QVariant::String);
+ break;
+ default:
+ values[i + idx] = QString(reinterpret_cast<const QChar *>(
+ sqlite3_column_text16(stmt, i)),
+ sqlite3_column_bytes16(stmt, i) / sizeof(QChar));
+ break;
+ }
+ }
+ return true;
+ case SQLITE_DONE:
+ if (rInf.isEmpty())
+ // must be first call.
+ initColumns(true);
+ q->setAt(QSql::AfterLastRow);
+ sqlite3_reset(stmt);
+ return false;
+ case SQLITE_CONSTRAINT:
+ case SQLITE_ERROR:
+ // SQLITE_ERROR is a generic error code and we must call sqlite3_reset()
+ // to get the specific error message.
+ res = sqlite3_reset(stmt);
+ q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult",
+ "Unable to fetch row"), QSqlError::ConnectionError, res));
+ q->setAt(QSql::AfterLastRow);
+ return false;
+ case SQLITE_MISUSE:
+ case SQLITE_BUSY:
+ default:
+ // something wrong, don't get col info, but still return false
+ q->setLastError(qMakeError(drv_d_func()->access, QCoreApplication::translate("QSQLiteResult",
+ "Unable to fetch row"), QSqlError::ConnectionError, res));
+ sqlite3_reset(stmt);
+ q->setAt(QSql::AfterLastRow);
+ return false;
+ }
+ return false;
+}
+
+QSQLiteResult::QSQLiteResult(const QSQLiteDriver* db)
+ : QSqlCachedResult(*new QSQLiteResultPrivate(this, db))
+{
+ Q_D(QSQLiteResult);
+ const_cast<QSQLiteDriverPrivate*>(d->drv_d_func())->results.append(this);
+}
+
+QSQLiteResult::~QSQLiteResult()
+{
+ Q_D(QSQLiteResult);
+ if (d->drv_d_func())
+ const_cast<QSQLiteDriverPrivate*>(d->drv_d_func())->results.removeOne(this);
+ d->cleanup();
+}
+
+void QSQLiteResult::virtual_hook(int id, void *data)
+{
+ QSqlCachedResult::virtual_hook(id, data);
+}
+
+bool QSQLiteResult::reset(const QString &query)
+{
+ if (!prepare(query))
+ return false;
+ return exec();
+}
+
+bool QSQLiteResult::prepare(const QString &query)
+{
+ Q_D(QSQLiteResult);
+ if (!driver() || !driver()->isOpen() || driver()->isOpenError())
+ return false;
+
+ d->cleanup();
+
+ setSelect(false);
+
+ const void *pzTail = NULL;
+
+#if (SQLITE_VERSION_NUMBER >= 3003011)
+ int res = sqlite3_prepare16_v2(d->drv_d_func()->access, query.constData(), (query.size() + 1) * sizeof(QChar),
+ &d->stmt, &pzTail);
+#else
+ int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar),
+ &d->stmt, &pzTail);
+#endif
+
+ if (res != SQLITE_OK) {
+ setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult",
+ "Unable to execute statement"), QSqlError::StatementError, res));
+ d->finalize();
+ return false;
+ } else if (pzTail && !QString(reinterpret_cast<const QChar *>(pzTail)).trimmed().isEmpty()) {
+ setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult",
+ "Unable to execute multiple statements at a time"), QSqlError::StatementError, SQLITE_MISUSE));
+ d->finalize();
+ return false;
+ }
+ return true;
+}
+
+bool QSQLiteResult::exec()
+{
+ Q_D(QSQLiteResult);
+ const QVector<QVariant> values = boundValues();
+
+ d->skippedStatus = false;
+ d->skipRow = false;
+ d->rInf.clear();
+ clearValues();
+ setLastError(QSqlError());
+
+ int res = sqlite3_reset(d->stmt);
+ if (res != SQLITE_OK) {
+ setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult",
+ "Unable to reset statement"), QSqlError::StatementError, res));
+ d->finalize();
+ return false;
+ }
+ int paramCount = sqlite3_bind_parameter_count(d->stmt);
+ if (paramCount == values.count()) {
+ for (int i = 0; i < paramCount; ++i) {
+ res = SQLITE_OK;
+ const QVariant value = values.at(i);
+
+ if (value.isNull()) {
+ res = sqlite3_bind_null(d->stmt, i + 1);
+ } else {
+ switch (value.type()) {
+ case QVariant::ByteArray: {
+ const QByteArray *ba = static_cast<const QByteArray*>(value.constData());
+ res = sqlite3_bind_blob(d->stmt, i + 1, ba->constData(),
+ ba->size(), SQLITE_STATIC);
+ break; }
+ case QVariant::Int:
+ case QVariant::Bool:
+ res = sqlite3_bind_int(d->stmt, i + 1, value.toInt());
+ break;
+ case QVariant::Double:
+ res = sqlite3_bind_double(d->stmt, i + 1, value.toDouble());
+ break;
+ case QVariant::UInt:
+ case QVariant::LongLong:
+ res = sqlite3_bind_int64(d->stmt, i + 1, value.toLongLong());
+ break;
+ case QVariant::DateTime: {
+ const QDateTime dateTime = value.toDateTime();
+ const QString str = dateTime.toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz"));
+ res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(),
+ str.size() * sizeof(ushort), SQLITE_TRANSIENT);
+ break;
+ }
+ case QVariant::Time: {
+ const QTime time = value.toTime();
+ const QString str = time.toString(QStringLiteral("hh:mm:ss.zzz"));
+ res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(),
+ str.size() * sizeof(ushort), SQLITE_TRANSIENT);
+ break;
+ }
+ case QVariant::String: {
+ // lifetime of string == lifetime of its qvariant
+ const QString *str = static_cast<const QString*>(value.constData());
+ res = sqlite3_bind_text16(d->stmt, i + 1, str->utf16(),
+ (str->size()) * sizeof(QChar), SQLITE_STATIC);
+ break; }
+ default: {
+ QString str = value.toString();
+ // SQLITE_TRANSIENT makes sure that sqlite buffers the data
+ res = sqlite3_bind_text16(d->stmt, i + 1, str.utf16(),
+ (str.size()) * sizeof(QChar), SQLITE_TRANSIENT);
+ break; }
+ }
+ }
+ if (res != SQLITE_OK) {
+ setLastError(qMakeError(d->drv_d_func()->access, QCoreApplication::translate("QSQLiteResult",
+ "Unable to bind parameters"), QSqlError::StatementError, res));
+ d->finalize();
+ return false;
+ }
+ }
+ } else {
+ setLastError(QSqlError(QCoreApplication::translate("QSQLiteResult",
+ "Parameter count mismatch"), QString(), QSqlError::StatementError));
+ return false;
+ }
+ d->skippedStatus = d->fetchNext(d->firstRow, 0, true);
+ if (lastError().isValid()) {
+ setSelect(false);
+ setActive(false);
+ return false;
+ }
+ setSelect(!d->rInf.isEmpty());
+ setActive(true);
+ return true;
+}
+
+bool QSQLiteResult::gotoNext(QSqlCachedResult::ValueCache& row, int idx)
+{
+ Q_D(QSQLiteResult);
+ return d->fetchNext(row, idx, false);
+}
+
+int QSQLiteResult::size()
+{
+ return -1;
+}
+
+int QSQLiteResult::numRowsAffected()
+{
+ Q_D(const QSQLiteResult);
+ return sqlite3_changes(d->drv_d_func()->access);
+}
+
+QVariant QSQLiteResult::lastInsertId() const
+{
+ Q_D(const QSQLiteResult);
+ if (isActive()) {
+ qint64 id = sqlite3_last_insert_rowid(d->drv_d_func()->access);
+ if (id)
+ return id;
+ }
+ return QVariant();
+}
+
+QSqlRecord QSQLiteResult::record() const
+{
+ Q_D(const QSQLiteResult);
+ if (!isActive() || !isSelect())
+ return QSqlRecord();
+ return d->rInf;
+}
+
+void QSQLiteResult::detachFromResultSet()
+{
+ Q_D(QSQLiteResult);
+ if (d->stmt)
+ sqlite3_reset(d->stmt);
+}
+
+QVariant QSQLiteResult::handle() const
+{
+ Q_D(const QSQLiteResult);
+ return QVariant::fromValue(d->stmt);
+}
+
+/////////////////////////////////////////////////////////
+
+QSQLiteDriver::QSQLiteDriver(QObject * parent)
+ : QSqlDriver(*new QSQLiteDriverPrivate, parent)
+{
+}
+
+QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent)
+ : QSqlDriver(*new QSQLiteDriverPrivate, parent)
+{
+ Q_D(QSQLiteDriver);
+ d->access = connection;
+ setOpen(true);
+ setOpenError(false);
+}
+
+
+QSQLiteDriver::~QSQLiteDriver()
+{
+}
+
+bool QSQLiteDriver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case BLOB:
+ case Transactions:
+ case Unicode:
+ case LastInsertId:
+ case PreparedQueries:
+ case PositionalPlaceholders:
+ case SimpleLocking:
+ case FinishQuery:
+ case LowPrecisionNumbers:
+ return true;
+ case QuerySize:
+ case NamedPlaceholders:
+ case BatchOperations:
+ case EventNotifications:
+ case MultipleResultSets:
+ case CancelQuery:
+ return false;
+ }
+ return false;
+}
+
+/*
+ SQLite dbs have no user name, passwords, hosts or ports.
+ just file names.
+*/
+bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &conOpts)
+{
+ Q_D(QSQLiteDriver);
+ if (isOpen())
+ close();
+
+
+ int timeOut = 5000;
+ bool sharedCache = false;
+ bool openReadOnlyOption = false;
+ bool openUriOption = false;
+
+ const auto opts = conOpts.splitRef(QLatin1Char(';'));
+ for (auto option : opts) {
+ option = option.trimmed();
+ if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT"))) {
+ option = option.mid(20).trimmed();
+ if (option.startsWith(QLatin1Char('='))) {
+ bool ok;
+ const int nt = option.mid(1).trimmed().toInt(&ok);
+ if (ok)
+ timeOut = nt;
+ }
+ } else if (option == QLatin1String("QSQLITE_OPEN_READONLY")) {
+ openReadOnlyOption = true;
+ } else if (option == QLatin1String("QSQLITE_OPEN_URI")) {
+ openUriOption = true;
+ } else if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE")) {
+ sharedCache = true;
+ }
+ }
+
+ int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE));
+ if (openUriOption)
+ openMode |= SQLITE_OPEN_URI;
+
+ sqlite3_enable_shared_cache(sharedCache);
+
+ if (sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, NULL) == SQLITE_OK) {
+ sqlite3_busy_timeout(d->access, timeOut);
+ setOpen(true);
+ setOpenError(false);
+ return true;
+ } else {
+ if (d->access) {
+ sqlite3_close(d->access);
+ d->access = 0;
+ }
+
+ setLastError(qMakeError(d->access, tr("Error opening database"),
+ QSqlError::ConnectionError));
+ setOpenError(true);
+ return false;
+ }
+}
+
+void QSQLiteDriver::close()
+{
+ Q_D(QSQLiteDriver);
+ if (isOpen()) {
+ for (QSQLiteResult *result : qAsConst(d->results))
+ result->d_func()->finalize();
+
+ if (sqlite3_close(d->access) != SQLITE_OK)
+ setLastError(qMakeError(d->access, tr("Error closing database"),
+ QSqlError::ConnectionError));
+ d->access = 0;
+ setOpen(false);
+ setOpenError(false);
+ }
+}
+
+QSqlResult *QSQLiteDriver::createResult() const
+{
+ return new QSQLiteResult(this);
+}
+
+bool QSQLiteDriver::beginTransaction()
+{
+ if (!isOpen() || isOpenError())
+ return false;
+
+ QSqlQuery q(createResult());
+ if (!q.exec(QLatin1String("BEGIN"))) {
+ setLastError(QSqlError(tr("Unable to begin transaction"),
+ q.lastError().databaseText(), QSqlError::TransactionError));
+ return false;
+ }
+
+ return true;
+}
+
+bool QSQLiteDriver::commitTransaction()
+{
+ if (!isOpen() || isOpenError())
+ return false;
+
+ QSqlQuery q(createResult());
+ if (!q.exec(QLatin1String("COMMIT"))) {
+ setLastError(QSqlError(tr("Unable to commit transaction"),
+ q.lastError().databaseText(), QSqlError::TransactionError));
+ return false;
+ }
+
+ return true;
+}
+
+bool QSQLiteDriver::rollbackTransaction()
+{
+ if (!isOpen() || isOpenError())
+ return false;
+
+ QSqlQuery q(createResult());
+ if (!q.exec(QLatin1String("ROLLBACK"))) {
+ setLastError(QSqlError(tr("Unable to rollback transaction"),
+ q.lastError().databaseText(), QSqlError::TransactionError));
+ return false;
+ }
+
+ return true;
+}
+
+QStringList QSQLiteDriver::tables(QSql::TableType type) const
+{
+ QStringList res;
+ if (!isOpen())
+ return res;
+
+ QSqlQuery q(createResult());
+ q.setForwardOnly(true);
+
+ QString sql = QLatin1String("SELECT name FROM sqlite_master WHERE %1 "
+ "UNION ALL SELECT name FROM sqlite_temp_master WHERE %1");
+ if ((type & QSql::Tables) && (type & QSql::Views))
+ sql = sql.arg(QLatin1String("type='table' OR type='view'"));
+ else if (type & QSql::Tables)
+ sql = sql.arg(QLatin1String("type='table'"));
+ else if (type & QSql::Views)
+ sql = sql.arg(QLatin1String("type='view'"));
+ else
+ sql.clear();
+
+ if (!sql.isEmpty() && q.exec(sql)) {
+ while(q.next())
+ res.append(q.value(0).toString());
+ }
+
+ if (type & QSql::SystemTables) {
+ // there are no internal tables beside this one:
+ res.append(QLatin1String("sqlite_master"));
+ }
+
+ return res;
+}
+
+static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false)
+{
+ QString schema;
+ QString table(tableName);
+ int indexOfSeparator = tableName.indexOf(QLatin1Char('.'));
+ if (indexOfSeparator > -1) {
+ schema = tableName.left(indexOfSeparator).append(QLatin1Char('.'));
+ table = tableName.mid(indexOfSeparator + 1);
+ }
+ q.exec(QLatin1String("PRAGMA ") + schema + QLatin1String("table_info (") + _q_escapeIdentifier(table) + QLatin1Char(')'));
+
+ QSqlIndex ind;
+ while (q.next()) {
+ bool isPk = q.value(5).toInt();
+ if (onlyPIndex && !isPk)
+ continue;
+ QString typeName = q.value(2).toString().toLower();
+ QSqlField fld(q.value(1).toString(), qGetColumnType(typeName));
+ if (isPk && (typeName == QLatin1String("integer")))
+ // INTEGER PRIMARY KEY fields are auto-generated in sqlite
+ // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY!
+ fld.setAutoValue(true);
+ fld.setRequired(q.value(3).toInt() != 0);
+ fld.setDefaultValue(q.value(4));
+ ind.append(fld);
+ }
+ return ind;
+}
+
+QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const
+{
+ if (!isOpen())
+ return QSqlIndex();
+
+ QString table = tblname;
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+
+ QSqlQuery q(createResult());
+ q.setForwardOnly(true);
+ return qGetTableInfo(q, table, true);
+}
+
+QSqlRecord QSQLiteDriver::record(const QString &tbl) const
+{
+ if (!isOpen())
+ return QSqlRecord();
+
+ QString table = tbl;
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+
+ QSqlQuery q(createResult());
+ q.setForwardOnly(true);
+ return qGetTableInfo(q, table);
+}
+
+QVariant QSQLiteDriver::handle() const
+{
+ Q_D(const QSQLiteDriver);
+ return QVariant::fromValue(d->access);
+}
+
+QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
+{
+ Q_UNUSED(type);
+ return _q_escapeIdentifier(identifier);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h
new file mode 100644
index 0000000000..c9b7708698
--- /dev/null
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_SQLITE_H
+#define QSQL_SQLITE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtSql/qsqldriver.h>
+
+struct sqlite3;
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_SQLITE
+#else
+#define Q_EXPORT_SQLDRIVER_SQLITE Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QSqlResult;
+class QSQLiteDriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_SQLITE QSQLiteDriver : public QSqlDriver
+{
+ Q_DECLARE_PRIVATE(QSQLiteDriver)
+ Q_OBJECT
+ friend class QSQLiteResultPrivate;
+public:
+ explicit QSQLiteDriver(QObject *parent = 0);
+ explicit QSQLiteDriver(sqlite3 *connection, QObject *parent = 0);
+ ~QSQLiteDriver();
+ bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE;
+ bool open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString & connOpts) Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ QSqlResult *createResult() const Q_DECL_OVERRIDE;
+ bool beginTransaction() Q_DECL_OVERRIDE;
+ bool commitTransaction() Q_DECL_OVERRIDE;
+ bool rollbackTransaction() Q_DECL_OVERRIDE;
+ QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE;
+
+ QSqlRecord record(const QString& tablename) const Q_DECL_OVERRIDE;
+ QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+ QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_SQLITE_H
diff --git a/src/plugins/sqldrivers/sqlite/smain.cpp b/src/plugins/sqldrivers/sqlite/smain.cpp
index 94b41c9878..2ad466a61e 100644
--- a/src/plugins/sqldrivers/sqlite/smain.cpp
+++ b/src/plugins/sqldrivers/sqlite/smain.cpp
@@ -39,7 +39,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
-#include "../../../../src/sql/drivers/sqlite/qsql_sqlite_p.h"
+#include "qsql_sqlite_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/sqldrivers/sqlite/sqlite.pro b/src/plugins/sqldrivers/sqlite/sqlite.pro
index cc6d02cbe8..6b2c083878 100644
--- a/src/plugins/sqldrivers/sqlite/sqlite.pro
+++ b/src/plugins/sqldrivers/sqlite/sqlite.pro
@@ -1,8 +1,16 @@
TARGET = qsqlite
-SOURCES = smain.cpp
+HEADERS += $$PWD/qsql_sqlite_p.h
+SOURCES += $$PWD/qsql_sqlite.cpp $$PWD/smain.cpp
+
+!system-sqlite:!contains(LIBS, .*sqlite3.*) {
+ include($$PWD/../../../3rdparty/sqlite.pri)
+} else {
+ LIBS += $$QT_LFLAGS_SQLITE
+ QMAKE_CXXFLAGS *= $$QT_CFLAGS_SQLITE
+}
+
OTHER_FILES += sqlite.json
-include(../../../sql/drivers/sqlite/qsql_sqlite.pri)
PLUGIN_CLASS_NAME = QSQLiteDriverPlugin
include(../qsqldriverbase.pri)
diff --git a/src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp
new file mode 100644
index 0000000000..67c24e4168
--- /dev/null
+++ b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2.cpp
@@ -0,0 +1,615 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_sqlite2_p.h"
+
+#include <qcoreapplication.h>
+#include <qvariant.h>
+#include <qdatetime.h>
+#include <qfile.h>
+#include <qregexp.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlquery.h>
+#include <QtSql/private/qsqlcachedresult_p.h>
+#include <QtSql/private/qsqldriver_p.h>
+#include <qstringlist.h>
+#include <qvector.h>
+
+#if !defined Q_OS_WIN
+# include <unistd.h>
+#endif
+#include <sqlite.h>
+
+typedef struct sqlite_vm sqlite_vm;
+
+Q_DECLARE_OPAQUE_POINTER(sqlite_vm*)
+Q_DECLARE_METATYPE(sqlite_vm*)
+
+Q_DECLARE_OPAQUE_POINTER(sqlite*)
+Q_DECLARE_METATYPE(sqlite*)
+
+QT_BEGIN_NAMESPACE
+
+static QVariant::Type nameToType(const QString& typeName)
+{
+ QString tName = typeName.toUpper();
+ if (tName.startsWith(QLatin1String("INT")))
+ return QVariant::Int;
+ if (tName.startsWith(QLatin1String("FLOAT")) || tName.startsWith(QLatin1String("NUMERIC")))
+ return QVariant::Double;
+ if (tName.startsWith(QLatin1String("BOOL")))
+ return QVariant::Bool;
+ // SQLite is typeless - consider everything else as string
+ return QVariant::String;
+}
+
+class QSQLite2DriverPrivate : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QSQLite2Driver)
+
+public:
+ QSQLite2DriverPrivate();
+ sqlite *access;
+ bool utf8;
+};
+
+QSQLite2DriverPrivate::QSQLite2DriverPrivate() : QSqlDriverPrivate(), access(0)
+{
+ utf8 = (qstrcmp(sqlite_encoding, "UTF-8") == 0);
+ dbmsType = QSqlDriver::SQLite;
+}
+
+class QSQLite2ResultPrivate;
+
+class QSQLite2Result : public QSqlCachedResult
+{
+ Q_DECLARE_PRIVATE(QSQLite2Result)
+ friend class QSQLite2Driver;
+
+public:
+ explicit QSQLite2Result(const QSQLite2Driver* db);
+ ~QSQLite2Result();
+ QVariant handle() const Q_DECL_OVERRIDE;
+
+protected:
+ bool gotoNext(QSqlCachedResult::ValueCache &row, int idx) Q_DECL_OVERRIDE;
+ bool reset(const QString &query) Q_DECL_OVERRIDE;
+ int size() Q_DECL_OVERRIDE;
+ int numRowsAffected() Q_DECL_OVERRIDE;
+ QSqlRecord record() const Q_DECL_OVERRIDE;
+ void detachFromResultSet() Q_DECL_OVERRIDE;
+ void virtual_hook(int id, void *data) Q_DECL_OVERRIDE;
+};
+
+class QSQLite2ResultPrivate: public QSqlCachedResultPrivate
+{
+ Q_DECLARE_PUBLIC(QSQLite2Result)
+
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QSQLite2Driver);
+ QSQLite2ResultPrivate(QSQLite2Result *q, const QSQLite2Driver *drv);
+ void cleanup();
+ bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch);
+ bool isSelect();
+ // initializes the recordInfo and the cache
+ void init(const char **cnames, int numCols);
+ void finalize();
+
+ // and we have too keep our own struct for the data (sqlite works via
+ // callback.
+ const char *currentTail;
+ sqlite_vm *currentMachine;
+
+ bool skippedStatus; // the status of the fetchNext() that's skipped
+ bool skipRow; // skip the next fetchNext()?
+ QSqlRecord rInf;
+ QVector<QVariant> firstRow;
+};
+
+QSQLite2ResultPrivate::QSQLite2ResultPrivate(QSQLite2Result *q, const QSQLite2Driver *drv)
+ : QSqlCachedResultPrivate(q, drv),
+ currentTail(0),
+ currentMachine(0),
+ skippedStatus(false),
+ skipRow(false)
+{
+}
+
+void QSQLite2ResultPrivate::cleanup()
+{
+ Q_Q(QSQLite2Result);
+ finalize();
+ rInf.clear();
+ currentTail = 0;
+ currentMachine = 0;
+ skippedStatus = false;
+ skipRow = false;
+ q->setAt(QSql::BeforeFirstRow);
+ q->setActive(false);
+ q->cleanup();
+}
+
+void QSQLite2ResultPrivate::finalize()
+{
+ Q_Q(QSQLite2Result);
+ if (!currentMachine)
+ return;
+
+ char* err = 0;
+ int res = sqlite_finalize(currentMachine, &err);
+ if (err) {
+ q->setLastError(QSqlError(QCoreApplication::translate("QSQLite2Result",
+ "Unable to fetch results"), QString::fromLatin1(err),
+ QSqlError::StatementError, res));
+ sqlite_freemem(err);
+ }
+ currentMachine = 0;
+}
+
+// called on first fetch
+void QSQLite2ResultPrivate::init(const char **cnames, int numCols)
+{
+ Q_Q(QSQLite2Result);
+ if (!cnames)
+ return;
+
+ rInf.clear();
+ if (numCols <= 0)
+ return;
+ q->init(numCols);
+
+ for (int i = 0; i < numCols; ++i) {
+ const char* lastDot = strrchr(cnames[i], '.');
+ const char* fieldName = lastDot ? lastDot + 1 : cnames[i];
+
+ //remove quotations around the field name if any
+ QString fieldStr = QString::fromLatin1(fieldName);
+ QLatin1Char quote('\"');
+ if ( fieldStr.length() > 2 && fieldStr.startsWith(quote) && fieldStr.endsWith(quote)) {
+ fieldStr = fieldStr.mid(1);
+ fieldStr.chop(1);
+ }
+ rInf.append(QSqlField(fieldStr,
+ nameToType(QString::fromLatin1(cnames[i+numCols]))));
+ }
+}
+
+bool QSQLite2ResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch)
+{
+ Q_Q(QSQLite2Result);
+ // may be caching.
+ const char **fvals;
+ const char **cnames;
+ int colNum;
+ int res;
+ int i;
+
+ if (skipRow) {
+ // already fetched
+ Q_ASSERT(!initialFetch);
+ skipRow = false;
+ for(int i=0;i<firstRow.count(); i++)
+ values[i] = firstRow[i];
+ return skippedStatus;
+ }
+ skipRow = initialFetch;
+
+ if (!currentMachine)
+ return false;
+
+ // keep trying while busy, wish I could implement this better.
+ while ((res = sqlite_step(currentMachine, &colNum, &fvals, &cnames)) == SQLITE_BUSY) {
+ // sleep instead requesting result again immidiately.
+#if defined Q_OS_WIN
+ Sleep(1000);
+#else
+ sleep(1);
+#endif
+ }
+
+ if(initialFetch) {
+ firstRow.clear();
+ firstRow.resize(colNum);
+ }
+
+ switch(res) {
+ case SQLITE_ROW:
+ // check to see if should fill out columns
+ if (rInf.isEmpty())
+ // must be first call.
+ init(cnames, colNum);
+ if (!fvals)
+ return false;
+ if (idx < 0 && !initialFetch)
+ return true;
+ for (i = 0; i < colNum; ++i)
+ values[i + idx] = drv_d_func()->utf8 ? QString::fromUtf8(fvals[i]) : QString::fromLatin1(fvals[i]);
+ return true;
+ case SQLITE_DONE:
+ if (rInf.isEmpty())
+ // must be first call.
+ init(cnames, colNum);
+ q->setAt(QSql::AfterLastRow);
+ return false;
+ case SQLITE_ERROR:
+ case SQLITE_MISUSE:
+ default:
+ // something wrong, don't get col info, but still return false
+ finalize(); // finalize to get the error message.
+ q->setAt(QSql::AfterLastRow);
+ return false;
+ }
+ return false;
+}
+
+QSQLite2Result::QSQLite2Result(const QSQLite2Driver* db)
+ : QSqlCachedResult(*new QSQLite2ResultPrivate(this, db))
+{
+}
+
+QSQLite2Result::~QSQLite2Result()
+{
+ Q_D(QSQLite2Result);
+ d->cleanup();
+}
+
+void QSQLite2Result::virtual_hook(int id, void *data)
+{
+ QSqlCachedResult::virtual_hook(id, data);
+}
+
+/*
+ Execute \a query.
+*/
+bool QSQLite2Result::reset (const QString& query)
+{
+ Q_D(QSQLite2Result);
+ // this is where we build a query.
+ if (!driver())
+ return false;
+ if (!driver()-> isOpen() || driver()->isOpenError())
+ return false;
+
+ d->cleanup();
+
+ // Um, ok. callback based so.... pass private static function for this.
+ setSelect(false);
+ char *err = 0;
+ int res = sqlite_compile(d->drv_d_func()->access,
+ d->drv_d_func()->utf8 ? query.toUtf8().constData()
+ : query.toLatin1().constData(),
+ &(d->currentTail),
+ &(d->currentMachine),
+ &err);
+ if (res != SQLITE_OK || err) {
+ setLastError(QSqlError(QCoreApplication::translate("QSQLite2Result",
+ "Unable to execute statement"), QString::fromLatin1(err),
+ QSqlError::StatementError, res));
+ sqlite_freemem(err);
+ }
+ //if (*d->currentTail != '\000' then there is more sql to eval
+ if (!d->currentMachine) {
+ setActive(false);
+ return false;
+ }
+ // we have to fetch one row to find out about
+ // the structure of the result set
+ d->skippedStatus = d->fetchNext(d->firstRow, 0, true);
+ if (lastError().isValid()) {
+ setSelect(false);
+ setActive(false);
+ return false;
+ }
+ setSelect(!d->rInf.isEmpty());
+ setActive(true);
+ return true;
+}
+
+bool QSQLite2Result::gotoNext(QSqlCachedResult::ValueCache& row, int idx)
+{
+ Q_D(QSQLite2Result);
+ return d->fetchNext(row, idx, false);
+}
+
+int QSQLite2Result::size()
+{
+ return -1;
+}
+
+int QSQLite2Result::numRowsAffected()
+{
+ Q_D(QSQLite2Result);
+ return sqlite_changes(d->drv_d_func()->access);
+}
+
+QSqlRecord QSQLite2Result::record() const
+{
+ Q_D(const QSQLite2Result);
+ if (!isActive() || !isSelect())
+ return QSqlRecord();
+ return d->rInf;
+}
+
+void QSQLite2Result::detachFromResultSet()
+{
+ Q_D(QSQLite2Result);
+ d->finalize();
+}
+
+QVariant QSQLite2Result::handle() const
+{
+ Q_D(const QSQLite2Result);
+ return QVariant::fromValue(d->currentMachine);
+}
+
+/////////////////////////////////////////////////////////
+
+QSQLite2Driver::QSQLite2Driver(QObject *parent)
+ : QSqlDriver(*new QSQLite2DriverPrivate, parent)
+{
+}
+
+QSQLite2Driver::QSQLite2Driver(sqlite *connection, QObject *parent)
+ : QSqlDriver(*new QSQLite2DriverPrivate, parent)
+{
+ Q_D(QSQLite2Driver);
+ d->access = connection;
+ setOpen(true);
+ setOpenError(false);
+}
+
+
+QSQLite2Driver::~QSQLite2Driver()
+{
+}
+
+bool QSQLite2Driver::hasFeature(DriverFeature f) const
+{
+ Q_D(const QSQLite2Driver);
+ switch (f) {
+ case Transactions:
+ case SimpleLocking:
+ return true;
+ case Unicode:
+ return d->utf8;
+ default:
+ return false;
+ }
+}
+
+/*
+ SQLite dbs have no user name, passwords, hosts or ports.
+ just file names.
+*/
+bool QSQLite2Driver::open(const QString & db, const QString &, const QString &, const QString &, int, const QString &)
+{
+ Q_D(QSQLite2Driver);
+ if (isOpen())
+ close();
+
+ if (db.isEmpty())
+ return false;
+
+ char* err = 0;
+ d->access = sqlite_open(QFile::encodeName(db), 0, &err);
+ if (err) {
+ setLastError(QSqlError(tr("Error opening database"), QString::fromLatin1(err),
+ QSqlError::ConnectionError));
+ sqlite_freemem(err);
+ err = 0;
+ }
+
+ if (d->access) {
+ setOpen(true);
+ setOpenError(false);
+ return true;
+ }
+ setOpenError(true);
+ return false;
+}
+
+void QSQLite2Driver::close()
+{
+ Q_D(QSQLite2Driver);
+ if (isOpen()) {
+ sqlite_close(d->access);
+ d->access = 0;
+ setOpen(false);
+ setOpenError(false);
+ }
+}
+
+QSqlResult *QSQLite2Driver::createResult() const
+{
+ return new QSQLite2Result(this);
+}
+
+bool QSQLite2Driver::beginTransaction()
+{
+ Q_D(QSQLite2Driver);
+ if (!isOpen() || isOpenError())
+ return false;
+
+ char* err;
+ int res = sqlite_exec(d->access, "BEGIN", 0, this, &err);
+
+ if (res == SQLITE_OK)
+ return true;
+
+ setLastError(QSqlError(tr("Unable to begin transaction"),
+ QString::fromLatin1(err), QSqlError::TransactionError, res));
+ sqlite_freemem(err);
+ return false;
+}
+
+bool QSQLite2Driver::commitTransaction()
+{
+ Q_D(QSQLite2Driver);
+ if (!isOpen() || isOpenError())
+ return false;
+
+ char* err;
+ int res = sqlite_exec(d->access, "COMMIT", 0, this, &err);
+
+ if (res == SQLITE_OK)
+ return true;
+
+ setLastError(QSqlError(tr("Unable to commit transaction"),
+ QString::fromLatin1(err), QSqlError::TransactionError, res));
+ sqlite_freemem(err);
+ return false;
+}
+
+bool QSQLite2Driver::rollbackTransaction()
+{
+ Q_D(QSQLite2Driver);
+ if (!isOpen() || isOpenError())
+ return false;
+
+ char* err;
+ int res = sqlite_exec(d->access, "ROLLBACK", 0, this, &err);
+
+ if (res == SQLITE_OK)
+ return true;
+
+ setLastError(QSqlError(tr("Unable to rollback transaction"),
+ QString::fromLatin1(err), QSqlError::TransactionError, res));
+ sqlite_freemem(err);
+ return false;
+}
+
+QStringList QSQLite2Driver::tables(QSql::TableType type) const
+{
+ QStringList res;
+ if (!isOpen())
+ return res;
+
+ QSqlQuery q(createResult());
+ q.setForwardOnly(true);
+ if ((type & QSql::Tables) && (type & QSql::Views))
+ q.exec(QLatin1String("SELECT name FROM sqlite_master WHERE type='table' OR type='view'"));
+ else if (type & QSql::Tables)
+ q.exec(QLatin1String("SELECT name FROM sqlite_master WHERE type='table'"));
+ else if (type & QSql::Views)
+ q.exec(QLatin1String("SELECT name FROM sqlite_master WHERE type='view'"));
+
+ if (q.isActive()) {
+ while(q.next())
+ res.append(q.value(0).toString());
+ }
+
+ if (type & QSql::SystemTables) {
+ // there are no internal tables beside this one:
+ res.append(QLatin1String("sqlite_master"));
+ }
+
+ return res;
+}
+
+QSqlIndex QSQLite2Driver::primaryIndex(const QString &tblname) const
+{
+ QSqlRecord rec(record(tblname)); // expensive :(
+
+ if (!isOpen())
+ return QSqlIndex();
+
+ QSqlQuery q(createResult());
+ q.setForwardOnly(true);
+ QString table = tblname;
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ // finrst find a UNIQUE INDEX
+ q.exec(QLatin1String("PRAGMA index_list('") + table + QLatin1String("');"));
+ QString indexname;
+ while(q.next()) {
+ if (q.value(2).toInt()==1) {
+ indexname = q.value(1).toString();
+ break;
+ }
+ }
+ if (indexname.isEmpty())
+ return QSqlIndex();
+
+ q.exec(QLatin1String("PRAGMA index_info('") + indexname + QLatin1String("');"));
+
+ QSqlIndex index(table, indexname);
+ while(q.next()) {
+ QString name = q.value(2).toString();
+ QVariant::Type type = QVariant::Invalid;
+ if (rec.contains(name))
+ type = rec.field(name).type();
+ index.append(QSqlField(name, type));
+ }
+ return index;
+}
+
+QSqlRecord QSQLite2Driver::record(const QString &tbl) const
+{
+ if (!isOpen())
+ return QSqlRecord();
+ QString table = tbl;
+ if (isIdentifierEscaped(tbl, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+
+ QSqlQuery q(createResult());
+ q.setForwardOnly(true);
+ q.exec(QLatin1String("SELECT * FROM ") + tbl + QLatin1String(" LIMIT 1"));
+ return q.record();
+}
+
+QVariant QSQLite2Driver::handle() const
+{
+ Q_D(const QSQLite2Driver);
+ return QVariant::fromValue(d->access);
+}
+
+QString QSQLite2Driver::escapeIdentifier(const QString &identifier, IdentifierType /*type*/) const
+{
+ QString res = identifier;
+ if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) {
+ res.replace(QLatin1Char('"'), QLatin1String("\"\""));
+ res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
+ res.replace(QLatin1Char('.'), QLatin1String("\".\""));
+ }
+ return res;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/sqldrivers/sqlite2/qsql_sqlite2_p.h b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2_p.h
new file mode 100644
index 0000000000..83b248ec6a
--- /dev/null
+++ b/src/plugins/sqldrivers/sqlite2/qsql_sqlite2_p.h
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_SQLITE2_H
+#define QSQL_SQLITE2_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtSql/qsqldriver.h>
+
+#if defined (Q_OS_WIN32)
+# include <QtCore/qt_windows.h>
+#endif
+
+struct sqlite;
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_SQLITE2
+#else
+#define Q_EXPORT_SQLDRIVER_SQLITE2 Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QSqlResult;
+class QSQLite2DriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_SQLITE2 QSQLite2Driver : public QSqlDriver
+{
+ friend class QSQLite2ResultPrivate;
+ Q_DECLARE_PRIVATE(QSQLite2Driver)
+ Q_OBJECT
+public:
+ explicit QSQLite2Driver(QObject *parent = 0);
+ explicit QSQLite2Driver(sqlite *connection, QObject *parent = 0);
+ ~QSQLite2Driver();
+ bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE;
+ bool open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port,
+ const QString &connOpts) Q_DECL_OVERRIDE;
+ bool open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port) { return open(db, user, password, host, port, QString()); }
+ void close() Q_DECL_OVERRIDE;
+ QSqlResult *createResult() const Q_DECL_OVERRIDE;
+ bool beginTransaction() Q_DECL_OVERRIDE;
+ bool commitTransaction() Q_DECL_OVERRIDE;
+ bool rollbackTransaction() Q_DECL_OVERRIDE;
+ QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE;
+
+ QSqlRecord record(const QString &tablename) const Q_DECL_OVERRIDE;
+ QSqlIndex primaryIndex(const QString &table) const Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+ QString escapeIdentifier(const QString &identifier, IdentifierType) const Q_DECL_OVERRIDE;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_SQLITE2_H
diff --git a/src/plugins/sqldrivers/sqlite2/smain.cpp b/src/plugins/sqldrivers/sqlite2/smain.cpp
index 06b428c8ef..3a5734f8c9 100644
--- a/src/plugins/sqldrivers/sqlite2/smain.cpp
+++ b/src/plugins/sqldrivers/sqlite2/smain.cpp
@@ -39,7 +39,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
-#include "../../../../src/sql/drivers/sqlite2/qsql_sqlite2_p.h"
+#include "qsql_sqlite2_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/sqldrivers/sqlite2/sqlite2.pro b/src/plugins/sqldrivers/sqlite2/sqlite2.pro
index d69afc119f..d0ab0eef41 100644
--- a/src/plugins/sqldrivers/sqlite2/sqlite2.pro
+++ b/src/plugins/sqldrivers/sqlite2/sqlite2.pro
@@ -1,8 +1,11 @@
TARGET = qsqlite2
-SOURCES = smain.cpp
+HEADERS += $$PWD/qsql_sqlite2_p.h
+SOURCES += $$PWD/qsql_sqlite2.cpp $$PWD/smain.cpp
+
+!contains(LIBS, .*sqlite.*):LIBS += -lsqlite
+
OTHER_FILES += sqlite2.json
-include(../../../sql/drivers/sqlite2/qsql_sqlite2.pri)
PLUGIN_CLASS_NAME = QSQLite2DriverPlugin
include(../qsqldriverbase.pri)
diff --git a/src/plugins/sqldrivers/tds/main.cpp b/src/plugins/sqldrivers/tds/main.cpp
index ffb31ae179..4aa1444608 100644
--- a/src/plugins/sqldrivers/tds/main.cpp
+++ b/src/plugins/sqldrivers/tds/main.cpp
@@ -45,7 +45,7 @@
#define _WINSCARD_H_
#include <windows.h>
#endif
-#include "../../../sql/drivers/tds/qsql_tds_p.h"
+#include "qsql_tds_p.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/sqldrivers/tds/qsql_tds.cpp b/src/plugins/sqldrivers/tds/qsql_tds.cpp
new file mode 100644
index 0000000000..10d9fe7298
--- /dev/null
+++ b/src/plugins/sqldrivers/tds/qsql_tds.cpp
@@ -0,0 +1,881 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_tds_p.h"
+
+#include <qglobal.h>
+#ifdef Q_OS_WIN32 // We assume that MS SQL Server is used. Set Q_USE_SYBASE to force Sybase.
+// Conflicting declarations of LPCBYTE in sqlfront.h and winscard.h
+#define _WINSCARD_H_
+#include <windows.h>
+#else
+#define Q_USE_SYBASE
+#endif
+
+#include <qvariant.h>
+#include <qdatetime.h>
+#include <qhash.h>
+#include <qregexp.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlquery.h>
+#include <QtSql/private/qsqlcachedresult_p.h>
+#include <QtSql/private/qsqldriver_p.h>
+#include <qstringlist.h>
+#include <qvector.h>
+
+#include <stdlib.h>
+
+Q_DECLARE_OPAQUE_POINTER(LOGINREC*)
+Q_DECLARE_OPAQUE_POINTER(DBPROCESS*)
+
+QT_BEGIN_NAMESPACE
+
+#ifdef DBNTWIN32
+#define QMSGHANDLE DBMSGHANDLE_PROC
+#define QERRHANDLE DBERRHANDLE_PROC
+#define QTDSCHAR SQLCHAR
+#define QTDSDATETIME4 SQLDATETIM4
+#define QTDSDATETIME SQLDATETIME
+#define QTDSDATETIME_N SQLDATETIMN
+#define QTDSDECIMAL SQLDECIMAL
+#define QTDSFLT4 SQLFLT4
+#define QTDSFLT8 SQLFLT8
+#define QTDSFLT8_N SQLFLTN
+#define QTDSINT1 SQLINT1
+#define QTDSINT2 SQLINT2
+#define QTDSINT4 SQLINT4
+#define QTDSINT4_N SQLINTN
+#define QTDSMONEY4 SQLMONEY4
+#define QTDSMONEY SQLMONEY
+#define QTDSMONEY_N SQLMONEYN
+#define QTDSNUMERIC SQLNUMERIC
+#define QTDSTEXT SQLTEXT
+#define QTDSVARCHAR SQLVARCHAR
+#define QTDSBIT SQLBIT
+#define QTDSBINARY SQLBINARY
+#define QTDSVARBINARY SQLVARBINARY
+#define QTDSIMAGE SQLIMAGE
+#else
+#define QMSGHANDLE MHANDLEFUNC
+#define QERRHANDLE EHANDLEFUNC
+#define QTDSCHAR SYBCHAR
+#define QTDSDATETIME4 SYBDATETIME4
+#define QTDSDATETIME SYBDATETIME
+#define QTDSDATETIME_N SYBDATETIMN
+#define QTDSDECIMAL SYBDECIMAL
+#define QTDSFLT8 SYBFLT8
+#define QTDSFLT8_N SYBFLTN
+#define QTDSFLT4 SYBREAL
+#define QTDSINT1 SYBINT1
+#define QTDSINT2 SYBINT2
+#define QTDSINT4 SYBINT4
+#define QTDSINT4_N SYBINTN
+#define QTDSMONEY4 SYBMONEY4
+#define QTDSMONEY SYBMONEY
+#define QTDSMONEY_N SYBMONEYN
+#define QTDSNUMERIC SYBNUMERIC
+#define QTDSTEXT SYBTEXT
+#define QTDSVARCHAR SYBVARCHAR
+#define QTDSBIT SYBBIT
+#define QTDSBINARY SYBBINARY
+#define QTDSVARBINARY SYBVARBINARY
+#define QTDSIMAGE SYBIMAGE
+// magic numbers not defined anywhere in Sybase headers
+#define QTDSDECIMAL_2 55
+#define QTDSNUMERIC_2 63
+#endif //DBNTWIN32
+
+#define TDS_CURSOR_SIZE 50
+
+// workaround for FreeTDS
+#ifndef CS_PUBLIC
+#define CS_PUBLIC
+#endif
+
+QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, int errNo = -1)
+{
+ return QSqlError(QLatin1String("QTDS: ") + err, QString(), type, errNo);
+}
+
+class QTDSDriverPrivate : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QTDSDriver)
+
+public:
+ QTDSDriverPrivate() : QSqlDriverPrivate(), login(0), initialized(false) { dbmsType = QSqlDriver::Sybase; }
+ LOGINREC* login; // login information
+ QString hostName;
+ QString db;
+ bool initialized;
+};
+
+struct QTDSColumnData
+{
+ void *data;
+ DBINT nullbind;
+};
+Q_DECLARE_TYPEINFO(QTDSColumnData, Q_MOVABLE_TYPE);
+
+class QTDSResultPrivate;
+
+class QTDSResult : public QSqlCachedResult
+{
+ Q_DECLARE_PRIVATE(QTDSResult)
+
+public:
+ explicit QTDSResult(const QTDSDriver* db);
+ ~QTDSResult();
+ QVariant handle() const;
+
+protected:
+ void cleanup();
+ bool reset(const QString &query) Q_DECL_OVERRIDE;
+ int size() Q_DECL_OVERRIDE;
+ int numRowsAffected() Q_DECL_OVERRIDE;
+ bool gotoNext(QSqlCachedResult::ValueCache &values, int index) Q_DECL_OVERRIDE;
+ QSqlRecord record() const Q_DECL_OVERRIDE;
+};
+
+class QTDSResultPrivate: public QSqlCachedResultPrivate
+{
+ Q_DECLARE_PUBLIC(QTDSResult)
+
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QTDSDriver)
+ QTDSResultPrivate(QTDSResult *q, const QTDSDriver *drv)
+ : QSqlCachedResultPrivate(q, drv),
+ login(0),
+ dbproc(0) {}
+ LOGINREC* login; // login information
+ DBPROCESS* dbproc; // connection from app to server
+ QSqlError lastError;
+ void addErrorMsg(QString& errMsg) { errorMsgs.append(errMsg); }
+ QString getErrorMsgs() { return errorMsgs.join(QLatin1String("\n")); }
+ void clearErrorMsgs() { errorMsgs.clear(); }
+ QVector<QTDSColumnData> buffer;
+ QSqlRecord rec;
+
+private:
+ QStringList errorMsgs;
+};
+
+typedef QHash<DBPROCESS *, QTDSResultPrivate *> QTDSErrorHash;
+Q_GLOBAL_STATIC(QTDSErrorHash, errs)
+
+extern "C" {
+static int CS_PUBLIC qTdsMsgHandler (DBPROCESS* dbproc,
+ DBINT msgno,
+ int msgstate,
+ int severity,
+ char* msgtext,
+ char* srvname,
+ char* /*procname*/,
+ int line)
+{
+ QTDSResultPrivate* p = errs()->value(dbproc);
+
+ if (!p) {
+// ### umm... temporary disabled since this throws a lot of warnings...
+// qWarning("QTDSDriver warning (%d): [%s] from server [%s]", msgstate, msgtext, srvname);
+ return INT_CANCEL;
+ }
+
+ if (severity > 0) {
+ QString errMsg = QString::fromLatin1("%1 (Msg %2, Level %3, State %4, Server %5, Line %6)")
+ .arg(QString::fromLatin1(msgtext))
+ .arg(msgno)
+ .arg(severity)
+ .arg(msgstate)
+ .arg(QString::fromLatin1(srvname))
+ .arg(line);
+ p->addErrorMsg(errMsg);
+ if (severity > 10) {
+ // Severe messages are really errors in the sense of lastError
+ errMsg = p->getErrorMsgs();
+ p->lastError = qMakeError(errMsg, QSqlError::UnknownError, msgno);
+ p->clearErrorMsgs();
+ }
+ }
+
+ return INT_CANCEL;
+}
+
+static int CS_PUBLIC qTdsErrHandler(DBPROCESS* dbproc,
+ int /*severity*/,
+ int dberr,
+ int /*oserr*/,
+ char* dberrstr,
+ char* oserrstr)
+{
+ QTDSResultPrivate* p = errs()->value(dbproc);
+ if (!p) {
+ qWarning("QTDSDriver error (%d): [%s] [%s]", dberr, dberrstr, oserrstr);
+ return INT_CANCEL;
+ }
+ /*
+ * If the process is dead or NULL and
+ * we are not in the middle of logging in...
+ */
+ if((dbproc == NULL || DBDEAD(dbproc))) {
+ qWarning("QTDSDriver error (%d): [%s] [%s]", dberr, dberrstr, oserrstr);
+ return INT_CANCEL;
+ }
+
+
+ QString errMsg = QString::fromLatin1("%1 %2\n").arg(QLatin1String(dberrstr)).arg(
+ QLatin1String(oserrstr));
+ errMsg += p->getErrorMsgs();
+ p->lastError = qMakeError(errMsg, QSqlError::UnknownError, dberr);
+ p->clearErrorMsgs();
+
+ return INT_CANCEL ;
+}
+
+} //extern "C"
+
+
+QVariant::Type qDecodeTDSType(int type)
+{
+ QVariant::Type t = QVariant::Invalid;
+ switch (type) {
+ case QTDSCHAR:
+ case QTDSTEXT:
+ case QTDSVARCHAR:
+ t = QVariant::String;
+ break;
+ case QTDSINT1:
+ case QTDSINT2:
+ case QTDSINT4:
+ case QTDSINT4_N:
+ case QTDSBIT:
+ t = QVariant::Int;
+ break;
+ case QTDSFLT4:
+ case QTDSFLT8:
+ case QTDSFLT8_N:
+ case QTDSMONEY4:
+ case QTDSMONEY:
+ case QTDSDECIMAL:
+ case QTDSNUMERIC:
+#ifdef QTDSNUMERIC_2
+ case QTDSNUMERIC_2:
+#endif
+#ifdef QTDSDECIMAL_2
+ case QTDSDECIMAL_2:
+#endif
+ case QTDSMONEY_N:
+ t = QVariant::Double;
+ break;
+ case QTDSDATETIME4:
+ case QTDSDATETIME:
+ case QTDSDATETIME_N:
+ t = QVariant::DateTime;
+ break;
+ case QTDSBINARY:
+ case QTDSVARBINARY:
+ case QTDSIMAGE:
+ t = QVariant::ByteArray;
+ break;
+ default:
+ t = QVariant::Invalid;
+ break;
+ }
+ return t;
+}
+
+QVariant::Type qFieldType(QTDSResultPrivate* d, int i)
+{
+ QVariant::Type type = qDecodeTDSType(dbcoltype(d->dbproc, i+1));
+ return type;
+}
+
+
+QTDSResult::QTDSResult(const QTDSDriver* db)
+ : QSqlCachedResult(*new QTDSResultPrivate(this, db))
+{
+ Q_D(QTDSResult);
+ d->login = d->drv_d_func()->login;
+
+ d->dbproc = dbopen(d->login, const_cast<char*>(d->drv_d_func()->hostName.toLatin1().constData()));
+ if (!d->dbproc)
+ return;
+ if (dbuse(d->dbproc, const_cast<char*>(d->drv_d_func()->db.toLatin1().constData())) == FAIL)
+ return;
+
+ // insert d in error handler dict
+ errs()->insert(d->dbproc, d);
+ dbcmd(d->dbproc, "set quoted_identifier on");
+ dbsqlexec(d->dbproc);
+}
+
+QTDSResult::~QTDSResult()
+{
+ Q_D(QTDSResult);
+ cleanup();
+ if (d->dbproc)
+ dbclose(d->dbproc);
+ errs()->remove(d->dbproc);
+}
+
+void QTDSResult::cleanup()
+{
+ Q_D(QTDSResult);
+ d->clearErrorMsgs();
+ d->rec.clear();
+ for (int i = 0; i < d->buffer.size(); ++i)
+ free(d->buffer.at(i).data);
+ d->buffer.clear();
+ // "can" stands for "cancel"... very clever.
+ dbcanquery(d->dbproc);
+ dbfreebuf(d->dbproc);
+
+ QSqlCachedResult::cleanup();
+}
+
+QVariant QTDSResult::handle() const
+{
+ Q_D(const QTDSResult);
+ return QVariant(qRegisterMetaType<DBPROCESS *>("DBPROCESS*"), &d->dbproc);
+}
+
+static inline bool qIsNull(const QTDSColumnData &p)
+{
+ return p.nullbind == -1;
+}
+
+bool QTDSResult::gotoNext(QSqlCachedResult::ValueCache &values, int index)
+{
+ Q_D(QTDSResult);
+ STATUS stat = dbnextrow(d->dbproc);
+ if (stat == NO_MORE_ROWS) {
+ setAt(QSql::AfterLastRow);
+ return false;
+ }
+ if ((stat == FAIL) || (stat == BUF_FULL)) {
+ setLastError(d->lastError);
+ return false;
+ }
+
+ if (index < 0)
+ return true;
+
+ for (int i = 0; i < d->rec.count(); ++i) {
+ int idx = index + i;
+ switch (d->rec.field(i).type()) {
+ case QVariant::DateTime:
+ if (qIsNull(d->buffer.at(i))) {
+ values[idx] = QVariant(QVariant::DateTime);
+ } else {
+ DBDATETIME *bdt = (DBDATETIME*) d->buffer.at(i).data;
+ QDate date = QDate::fromString(QLatin1String("1900-01-01"), Qt::ISODate);
+ QTime time = QTime::fromString(QLatin1String("00:00:00"), Qt::ISODate);
+ values[idx] = QDateTime(date.addDays(bdt->dtdays), time.addMSecs(int(bdt->dttime / 0.3)));
+ }
+ break;
+ case QVariant::Int:
+ if (qIsNull(d->buffer.at(i)))
+ values[idx] = QVariant(QVariant::Int);
+ else
+ values[idx] = *((int*)d->buffer.at(i).data);
+ break;
+ case QVariant::Double:
+ case QVariant::String:
+ if (qIsNull(d->buffer.at(i)))
+ values[idx] = QVariant(QVariant::String);
+ else
+ values[idx] = QString::fromLocal8Bit((const char*)d->buffer.at(i).data).trimmed();
+ break;
+ case QVariant::ByteArray: {
+ if (qIsNull(d->buffer.at(i)))
+ values[idx] = QVariant(QVariant::ByteArray);
+ else
+ values[idx] = QByteArray((const char*)d->buffer.at(i).data);
+ break;
+ }
+ default:
+ // should never happen, and we already fired
+ // a warning while binding.
+ values[idx] = QVariant();
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool QTDSResult::reset (const QString& query)
+{
+ Q_D(QTDSResult);
+ cleanup();
+ if (!driver() || !driver()-> isOpen() || driver()->isOpenError())
+ return false;
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ if (dbcmd(d->dbproc, const_cast<char*>(query.toLocal8Bit().constData())) == FAIL) {
+ setLastError(d->lastError);
+ return false;
+ }
+
+ if (dbsqlexec(d->dbproc) == FAIL) {
+ setLastError(d->lastError);
+ dbfreebuf(d->dbproc);
+ return false;
+ }
+ if (dbresults(d->dbproc) != SUCCEED) {
+ setLastError(d->lastError);
+ dbfreebuf(d->dbproc);
+ return false;
+ }
+
+ setSelect((DBCMDROW(d->dbproc) == SUCCEED)); // decide whether or not we are dealing with a SELECT query
+ int numCols = dbnumcols(d->dbproc);
+ if (numCols > 0) {
+ d->buffer.resize(numCols);
+ init(numCols);
+ }
+ for (int i = 0; i < numCols; ++i) {
+ int dbType = dbcoltype(d->dbproc, i+1);
+ QVariant::Type vType = qDecodeTDSType(dbType);
+ QSqlField f(QString::fromLatin1(dbcolname(d->dbproc, i+1)), vType);
+ f.setSqlType(dbType);
+ f.setLength(dbcollen(d->dbproc, i+1));
+ d->rec.append(f);
+
+ RETCODE ret = -1;
+ void* p = 0;
+ switch (vType) {
+ case QVariant::Int:
+ p = malloc(4);
+ ret = dbbind(d->dbproc, i+1, INTBIND, (DBINT) 4, (unsigned char *)p);
+ break;
+ case QVariant::Double:
+ // use string binding to prevent loss of precision
+ p = malloc(50);
+ ret = dbbind(d->dbproc, i+1, STRINGBIND, 50, (unsigned char *)p);
+ break;
+ case QVariant::String:
+ p = malloc(dbcollen(d->dbproc, i+1) + 1);
+ ret = dbbind(d->dbproc, i+1, STRINGBIND, DBINT(dbcollen(d->dbproc, i+1) + 1), (unsigned char *)p);
+ break;
+ case QVariant::DateTime:
+ p = malloc(8);
+ ret = dbbind(d->dbproc, i+1, DATETIMEBIND, (DBINT) 8, (unsigned char *)p);
+ break;
+ case QVariant::ByteArray:
+ p = malloc(dbcollen(d->dbproc, i+1) + 1);
+ ret = dbbind(d->dbproc, i+1, BINARYBIND, DBINT(dbcollen(d->dbproc, i+1) + 1), (unsigned char *)p);
+ break;
+ default: //don't bind the field since we do not support it
+ qWarning("QTDSResult::reset: Unsupported type for field \"%s\"", dbcolname(d->dbproc, i+1));
+ break;
+ }
+ if (ret == SUCCEED) {
+ d->buffer[i].data = p;
+ ret = dbnullbind(d->dbproc, i+1, &d->buffer[i].nullbind);
+ } else {
+ d->buffer[i].data = 0;
+ d->buffer[i].nullbind = 0;
+ free(p);
+ }
+ if ((ret != SUCCEED) && (ret != -1)) {
+ setLastError(d->lastError);
+ return false;
+ }
+ }
+
+ setActive(true);
+ return true;
+}
+
+int QTDSResult::size()
+{
+ return -1;
+}
+
+int QTDSResult::numRowsAffected()
+{
+ Q_D(const QTDSResult);
+#ifdef DBNTWIN32
+ if (dbiscount(d->dbproc)) {
+ return DBCOUNT(d->dbproc);
+ }
+ return -1;
+#else
+ return DBCOUNT(d->dbproc);
+#endif
+}
+
+QSqlRecord QTDSResult::record() const
+{
+ Q_D(const QTDSResult);
+ return d->rec;
+}
+
+///////////////////////////////////////////////////////////////////
+
+QTDSDriver::QTDSDriver(QObject* parent)
+ : QSqlDriver(*new QTDSDriverPrivate, parent)
+{
+ init();
+}
+
+QTDSDriver::QTDSDriver(LOGINREC* rec, const QString& host, const QString &db, QObject* parent)
+ : QSqlDriver(*new QTDSDriverPrivate, parent)
+{
+ Q_D(QTDSDriver);
+ init();
+ d->login = rec;
+ d->hostName = host;
+ d->db = db;
+ if (rec) {
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+QVariant QTDSDriver::handle() const
+{
+ Q_D(const QTDSDriver);
+ return QVariant(qRegisterMetaType<LOGINREC *>("LOGINREC*"), &d->login);
+}
+
+void QTDSDriver::init()
+{
+ Q_D(QTDSDriver);
+ d->initialized = (dbinit() == SUCCEED);
+ // the following two code-lines will fail compilation on some FreeTDS versions
+ // just comment them out if you have FreeTDS (you won't get any errors and warnings then)
+ dberrhandle((QERRHANDLE)qTdsErrHandler);
+ dbmsghandle((QMSGHANDLE)qTdsMsgHandler);
+}
+
+QTDSDriver::~QTDSDriver()
+{
+ dberrhandle(0);
+ dbmsghandle(0);
+ // dbexit also calls dbclose if necessary
+ dbexit();
+}
+
+bool QTDSDriver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case Transactions:
+ case QuerySize:
+ case Unicode:
+ case SimpleLocking:
+ case EventNotifications:
+ case MultipleResultSets:
+ return false;
+ case BLOB:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool QTDSDriver::open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int /*port*/,
+ const QString& /*connOpts*/)
+{
+ Q_D(QTDSDriver);
+ if (isOpen())
+ close();
+ if (!d->initialized) {
+ setOpenError(true);
+ return false;
+ }
+ d->login = dblogin();
+ if (!d->login) {
+ setOpenError(true);
+ return false;
+ }
+ DBSETLPWD(d->login, const_cast<char*>(password.toLocal8Bit().constData()));
+ DBSETLUSER(d->login, const_cast<char*>(user.toLocal8Bit().constData()));
+
+ // Now, try to open and use the database. If this fails, return false.
+ DBPROCESS* dbproc;
+
+ dbproc = dbopen(d->login, const_cast<char*>(host.toLatin1().constData()));
+ if (!dbproc) {
+ setLastError(qMakeError(tr("Unable to open connection"), QSqlError::ConnectionError, -1));
+ setOpenError(true);
+ return false;
+ }
+ if (dbuse(dbproc, const_cast<char*>(db.toLatin1().constData())) == FAIL) {
+ setLastError(qMakeError(tr("Unable to use database"), QSqlError::ConnectionError, -1));
+ setOpenError(true);
+ return false;
+ }
+ dbclose( dbproc );
+
+ setOpen(true);
+ setOpenError(false);
+ d->hostName = host;
+ d->db = db;
+ return true;
+}
+
+void QTDSDriver::close()
+{
+ Q_D(QTDSDriver);
+ if (isOpen()) {
+#ifdef Q_USE_SYBASE
+ dbloginfree(d->login);
+#else
+ dbfreelogin(d->login);
+#endif
+ d->login = 0;
+ setOpen(false);
+ setOpenError(false);
+ }
+}
+
+QSqlResult *QTDSDriver::createResult() const
+{
+ return new QTDSResult(this);
+}
+
+bool QTDSDriver::beginTransaction()
+{
+ return false;
+/*
+ if (!isOpen()) {
+ qWarning("QTDSDriver::beginTransaction: Database not open");
+ return false;
+ }
+ if (dbcmd(d->dbproc, "BEGIN TRANSACTION") == FAIL) {
+ setLastError(d->lastError);
+ dbfreebuf(d->dbproc);
+ return false;
+ }
+ if (dbsqlexec(d->dbproc) == FAIL) {
+ setLastError(d->lastError);
+ dbfreebuf(d->dbproc);
+ return false;
+ }
+ while(dbresults(d->dbproc) == NO_MORE_RESULTS) {}
+ dbfreebuf(d->dbproc);
+ inTransaction = true;
+ return true;
+*/
+}
+
+bool QTDSDriver::commitTransaction()
+{
+ return false;
+/*
+ if (!isOpen()) {
+ qWarning("QTDSDriver::commitTransaction: Database not open");
+ return false;
+ }
+ if (dbcmd(d->dbproc, "COMMIT TRANSACTION") == FAIL) {
+ setLastError(d->lastError);
+ dbfreebuf(d->dbproc);
+ return false;
+ }
+ if (dbsqlexec(d->dbproc) == FAIL) {
+ setLastError(d->lastError);
+ dbfreebuf(d->dbproc);
+ return false;
+ }
+ while(dbresults(d->dbproc) == NO_MORE_RESULTS) {}
+ dbfreebuf(d->dbproc);
+ inTransaction = false;
+ return true;
+*/
+}
+
+bool QTDSDriver::rollbackTransaction()
+{
+ return false;
+/*
+ if (!isOpen()) {
+ qWarning("QTDSDriver::rollbackTransaction: Database not open");
+ return false;
+ }
+ if (dbcmd(d->dbproc, "ROLLBACK TRANSACTION") == FAIL) {
+ setLastError(d->lastError);
+ dbfreebuf(d->dbproc);
+ return false;
+ }
+ if (dbsqlexec(d->dbproc) == FAIL) {
+ setLastError(d->lastError);
+ dbfreebuf(d->dbproc);
+ return false;
+ }
+ while(dbresults(d->dbproc) == NO_MORE_RESULTS) {}
+ dbfreebuf(d->dbproc);
+ inTransaction = false;
+ return true;
+*/
+}
+
+QSqlRecord QTDSDriver::record(const QString& tablename) const
+{
+ QSqlRecord info;
+ if (!isOpen())
+ return info;
+ QSqlQuery t(createResult());
+ t.setForwardOnly(true);
+
+ QString table = tablename;
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+
+ QString stmt (QLatin1String("select name, type, length, prec from syscolumns "
+ "where id = (select id from sysobjects where name = '%1')"));
+ t.exec(stmt.arg(table));
+ while (t.next()) {
+ QSqlField f(t.value(0).toString().simplified(), qDecodeTDSType(t.value(1).toInt()));
+ f.setLength(t.value(2).toInt());
+ f.setPrecision(t.value(3).toInt());
+ f.setSqlType(t.value(1).toInt());
+ info.append(f);
+ }
+ return info;
+}
+
+QStringList QTDSDriver::tables(QSql::TableType type) const
+{
+ QStringList list;
+
+ if (!isOpen())
+ return list;
+
+ QStringList typeFilter;
+
+ if (type & QSql::Tables)
+ typeFilter += QLatin1String("type='U'");
+ if (type & QSql::SystemTables)
+ typeFilter += QLatin1String("type='S'");
+ if (type & QSql::Views)
+ typeFilter += QLatin1String("type='V'");
+
+ if (typeFilter.isEmpty())
+ return list;
+
+ QSqlQuery t(createResult());
+ t.setForwardOnly(true);
+ t.exec(QLatin1String("select name from sysobjects where ") + typeFilter.join(QLatin1String(" or ")));
+ while (t.next())
+ list.append(t.value(0).toString().simplified());
+
+ return list;
+}
+
+QString QTDSDriver::formatValue(const QSqlField &field,
+ bool trim) const
+{
+ QString r;
+ if (field.isNull())
+ r = QLatin1String("NULL");
+ else if (field.type() == QVariant::DateTime) {
+ if (field.value().toDateTime().isValid()){
+ r = field.value().toDateTime().toString(QLatin1String("yyyyMMdd hh:mm:ss"));
+ r.prepend(QLatin1String("'"));
+ r.append(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, trim);
+ }
+ return r;
+}
+
+QSqlIndex QTDSDriver::primaryIndex(const QString& tablename) const
+{
+ QSqlRecord rec = record(tablename);
+
+ QString table = tablename;
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+
+ QSqlIndex idx(table);
+ if ((!isOpen()) || (table.isEmpty()))
+ return QSqlIndex();
+
+ QSqlQuery t(createResult());
+ t.setForwardOnly(true);
+ t.exec(QString::fromLatin1("sp_helpindex '%1'").arg(table));
+ if (t.next()) {
+ QStringList fNames = t.value(2).toString().simplified().split(QLatin1Char(','));
+ QRegExp regx(QLatin1String("\\s*(\\S+)(?:\\s+(DESC|desc))?\\s*"));
+ for(QStringList::Iterator it = fNames.begin(); it != fNames.end(); ++it) {
+ regx.indexIn(*it);
+ QSqlField f(regx.cap(1), rec.field(regx.cap(1)).type());
+ if (regx.cap(2).toLower() == QLatin1String("desc")) {
+ idx.append(f, true);
+ } else {
+ idx.append(f, false);
+ }
+ }
+ idx.setName(t.value(0).toString().simplified());
+ }
+ return idx;
+}
+
+QString QTDSDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
+{
+ Q_UNUSED(type)
+ QString res = identifier;
+ if(!identifier.isEmpty() && !identifier.startsWith(QLatin1Char('"')) && !identifier.endsWith(QLatin1Char('"')) ) {
+ res.replace(QLatin1Char('"'), QLatin1String("\"\""));
+ res.prepend(QLatin1Char('"')).append(QLatin1Char('"'));
+ res.replace(QLatin1Char('.'), QLatin1String("\".\""));
+ }
+ return res;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/sqldrivers/tds/qsql_tds_p.h b/src/plugins/sqldrivers/tds/qsql_tds_p.h
new file mode 100644
index 0000000000..d0914455a2
--- /dev/null
+++ b/src/plugins/sqldrivers/tds/qsql_tds_p.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_TDS_H
+#define QSQL_TDS_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtSql/qsqldriver.h>
+
+#ifdef Q_OS_WIN32
+#define WIN32_LEAN_AND_MEAN
+#ifndef Q_USE_SYBASE
+#define DBNTWIN32 // indicates 32bit windows dblib
+#endif
+#include <winsock2.h>
+#include <QtCore/qt_windows.h>
+#include <sqlfront.h>
+#include <sqldb.h>
+#define CS_PUBLIC
+#else
+#include <sybfront.h>
+#include <sybdb.h>
+#endif //Q_OS_WIN32
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_TDS
+#else
+#define Q_EXPORT_SQLDRIVER_TDS Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QSqlResult;
+class QTDSDriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_TDS QTDSDriver : public QSqlDriver
+{
+ Q_DECLARE_PRIVATE(QTDSDriver)
+ Q_OBJECT
+ friend class QTDSResultPrivate;
+public:
+ explicit QTDSDriver(QObject* parent = 0);
+ QTDSDriver(LOGINREC* rec, const QString& host, const QString &db, QObject* parent = 0);
+ ~QTDSDriver();
+ bool hasFeature(DriverFeature f) const Q_DECL_OVERRIDE;
+ bool open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port,
+ const QString &connOpts) Q_DECL_OVERRIDE;
+ void close() Q_DECL_OVERRIDE;
+ QStringList tables(QSql::TableType) const Q_DECL_OVERRIDE;
+ QSqlResult *createResult() const Q_DECL_OVERRIDE;
+ QSqlRecord record(const QString &tablename) const Q_DECL_OVERRIDE;
+ QSqlIndex primaryIndex(const QString &tablename) const Q_DECL_OVERRIDE;
+
+ QString formatValue(const QSqlField &field,
+ bool trimStrings) const Q_DECL_OVERRIDE;
+ QVariant handle() const Q_DECL_OVERRIDE;
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const Q_DECL_OVERRIDE;
+
+protected:
+ bool beginTransaction() Q_DECL_OVERRIDE;
+ bool commitTransaction() Q_DECL_OVERRIDE;
+ bool rollbackTransaction() Q_DECL_OVERRIDE;
+private:
+ void init();
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_TDS_H
diff --git a/src/plugins/sqldrivers/tds/tds.pro b/src/plugins/sqldrivers/tds/tds.pro
index 88f4b7c451..790c74de4e 100644
--- a/src/plugins/sqldrivers/tds/tds.pro
+++ b/src/plugins/sqldrivers/tds/tds.pro
@@ -1,8 +1,17 @@
TARGET = qsqltds
-SOURCES = main.cpp
+HEADERS += $$PWD/qsql_tds_p.h
+SOURCES += $$PWD/qsql_tds.cpp $$PWD/main.cpp
+
+unix|mingw: {
+ LIBS += $$QT_LFLAGS_TDS
+ !contains(LIBS, .*sybdb.*):LIBS += -lsybdb
+ QMAKE_CXXFLAGS *= $$QT_CFLAGS_TDS
+} else {
+ LIBS *= -lNTWDBLIB
+}
+
OTHER_FILES += tds.json
-include(../../../sql/drivers/tds/qsql_tds.pri)
PLUGIN_CLASS_NAME = QTDSDriverPlugin
include(../qsqldriverbase.pri)