summaryrefslogtreecommitdiffstats
path: root/src/sql
diff options
context:
space:
mode:
authorQt by Nokia <qt-info@nokia.com>2011-04-27 12:05:43 +0200
committeraxis <qt-info@nokia.com>2011-04-27 12:05:43 +0200
commit38be0d13830efd2d98281c645c3a60afe05ffece (patch)
tree6ea73f3ec77f7d153333779883e8120f82820abe /src/sql
Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12
Diffstat (limited to 'src/sql')
-rw-r--r--src/sql/README.module37
-rw-r--r--src/sql/drivers/db2/qsql_db2.cpp1649
-rw-r--r--src/sql/drivers/db2/qsql_db2.h124
-rw-r--r--src/sql/drivers/db2/qsql_db2.pri8
-rw-r--r--src/sql/drivers/drivers.pri11
-rw-r--r--src/sql/drivers/ibase/qsql_ibase.cpp1886
-rw-r--r--src/sql/drivers/ibase/qsql_ibase.h131
-rw-r--r--src/sql/drivers/ibase/qsql_ibase.pri11
-rw-r--r--src/sql/drivers/mysql/qsql_mysql.cpp1543
-rw-r--r--src/sql/drivers/mysql/qsql_mysql.h143
-rw-r--r--src/sql/drivers/mysql/qsql_mysql.pri16
-rw-r--r--src/sql/drivers/oci/qsql_oci.cpp2656
-rw-r--r--src/sql/drivers/oci/qsql_oci.h130
-rw-r--r--src/sql/drivers/oci/qsql_oci.pri9
-rw-r--r--src/sql/drivers/odbc/qsql_odbc.cpp2557
-rw-r--r--src/sql/drivers/odbc/qsql_odbc.h158
-rw-r--r--src/sql/drivers/odbc/qsql_odbc.pri13
-rw-r--r--src/sql/drivers/psql/qsql_psql.cpp1372
-rw-r--r--src/sql/drivers/psql/qsql_psql.h159
-rw-r--r--src/sql/drivers/psql/qsql_psql.pri10
-rw-r--r--src/sql/drivers/sqlite/qsql_sqlite.cpp709
-rw-r--r--src/sql/drivers/sqlite/qsql_sqlite.h123
-rw-r--r--src/sql/drivers/sqlite/qsql_sqlite.pri11
-rw-r--r--src/sql/drivers/sqlite2/qsql_sqlite2.cpp575
-rw-r--r--src/sql/drivers/sqlite2/qsql_sqlite2.h126
-rw-r--r--src/sql/drivers/sqlite2/qsql_sqlite2.pri4
-rw-r--r--src/sql/drivers/tds/qsql_tds.cpp832
-rw-r--r--src/sql/drivers/tds/qsql_tds.h137
-rw-r--r--src/sql/drivers/tds/qsql_tds.pri12
-rw-r--r--src/sql/kernel/kernel.pri24
-rw-r--r--src/sql/kernel/qsql.h113
-rw-r--r--src/sql/kernel/qsql.qdoc125
-rw-r--r--src/sql/kernel/qsqlcachedresult.cpp318
-rw-r--r--src/sql/kernel/qsqlcachedresult_p.h100
-rw-r--r--src/sql/kernel/qsqldatabase.cpp1547
-rw-r--r--src/sql/kernel/qsqldatabase.h161
-rw-r--r--src/sql/kernel/qsqldriver.cpp948
-rw-r--r--src/sql/kernel/qsqldriver.h160
-rw-r--r--src/sql/kernel/qsqldriverplugin.cpp108
-rw-r--r--src/sql/kernel/qsqldriverplugin.h81
-rw-r--r--src/sql/kernel/qsqlerror.cpp253
-rw-r--r--src/sql/kernel/qsqlerror.h104
-rw-r--r--src/sql/kernel/qsqlfield.cpp560
-rw-r--r--src/sql/kernel/qsqlfield.h119
-rw-r--r--src/sql/kernel/qsqlindex.cpp256
-rw-r--r--src/sql/kernel/qsqlindex.h92
-rw-r--r--src/sql/kernel/qsqlnulldriver_p.h114
-rw-r--r--src/sql/kernel/qsqlquery.cpp1236
-rw-r--r--src/sql/kernel/qsqlquery.h130
-rw-r--r--src/sql/kernel/qsqlrecord.cpp605
-rw-r--r--src/sql/kernel/qsqlrecord.h123
-rw-r--r--src/sql/kernel/qsqlresult.cpp1040
-rw-r--r--src/sql/kernel/qsqlresult.h153
-rw-r--r--src/sql/models/models.pri12
-rw-r--r--src/sql/models/qsqlquerymodel.cpp600
-rw-r--r--src/sql/models/qsqlquerymodel.h105
-rw-r--r--src/sql/models/qsqlquerymodel_p.h87
-rw-r--r--src/sql/models/qsqlrelationaldelegate.cpp101
-rw-r--r--src/sql/models/qsqlrelationaldelegate.h129
-rw-r--r--src/sql/models/qsqlrelationaltablemodel.cpp753
-rw-r--r--src/sql/models/qsqlrelationaltablemodel.h112
-rw-r--r--src/sql/models/qsqltablemodel.cpp1366
-rw-r--r--src/sql/models/qsqltablemodel.h141
-rw-r--r--src/sql/models/qsqltablemodel_p.h120
-rw-r--r--src/sql/sql.pro29
65 files changed, 27177 insertions, 0 deletions
diff --git a/src/sql/README.module b/src/sql/README.module
new file mode 100644
index 0000000000..511d90e83f
--- /dev/null
+++ b/src/sql/README.module
@@ -0,0 +1,37 @@
+Before building the Qt library, the Qt SQL module can be enabled for
+specific databases using 'configure'. 'configure' is located at the
+top of your QTDIR.
+
+Specific databases drivers can be enabled using one of the following
+options:
+
+ ./configure [-qt-sql-<driver>] [-plugin-sql-<driver>]
+
+or disabled using the following option:
+
+ ./configure [-no-sql-<driver>]
+
+Where <driver> is the name of the driver, for example 'psql'. This
+will configure the Qt library to compile the specified driver into
+the Qt lib itself.
+
+For example, to build the PostgreSQL driver directly into the Qt
+library, configure Qt like this:
+
+ ./configure -qt-sql-psql
+
+In addition, you may need to specify an extra include path, as some
+database drivers require headers for the database they are using,
+for example:
+
+ ./configure -qt-sql-psql -I/usr/local/include
+
+If instead you need to build the PostgreSQL driver as a dynamically
+loaded plugin, configure Qt like this:
+
+ ./configure -plugin-sql-psql
+
+To compile drivers as dynamically loaded plugins, see the
+QTDIR/plugins/src/sqldrivers directory. Use 'configure -help'
+for a complete list of configure options. See the Qt documentation
+for a complete list of supported database drivers.
diff --git a/src/sql/drivers/db2/qsql_db2.cpp b/src/sql/drivers/db2/qsql_db2.cpp
new file mode 100644
index 0000000000..5e25d160ac
--- /dev/null
+++ b/src/sql/drivers/db2/qsql_db2.cpp
@@ -0,0 +1,1649 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_db2.h"
+#include <qcoreapplication.h>
+#include <qdatetime.h>
+#include <qsqlfield.h>
+#include <qsqlerror.h>
+#include <qsqlindex.h>
+#include <qsqlrecord.h>
+#include <qstringlist.h>
+#include <qvarlengtharray.h>
+#include <qvector.h>
+#include <QDebug>
+
+#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:
+ QDB2DriverPrivate(): hEnv(0), hDbc(0) {}
+ SQLHANDLE hEnv;
+ SQLHANDLE hDbc;
+ QString user;
+};
+
+class QDB2ResultPrivate
+{
+public:
+ QDB2ResultPrivate(const QDB2DriverPrivate* d): dp(d), 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();
+ }
+
+ const QDB2DriverPrivate* dp;
+ 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->dp->hEnv) + QLatin1Char(' ')
+ + qWarnDB2Handle(SQL_HANDLE_DBC, d->dp->hDbc)
+ + qWarnDB2Handle(SQL_HANDLE_STMT, d->hStmt));
+}
+
+static void qSqlWarning(const QString& message, const QDB2DriverPrivate* d)
+{
+ qWarning("%s\tError: %s", message.toLocal8Bit().constData(),
+ qDB2Warn(d).toLocal8Bit().constData());
+}
+
+static void qSqlWarning(const QString& message, const QDB2ResultPrivate* d)
+{
+ qWarning("%s\tError: %s", message.toLocal8Bit().constData(),
+ qDB2Warn(d).toLocal8Bit().constData());
+}
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
+ const QDB2DriverPrivate* p)
+{
+ return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type);
+}
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
+ const QDB2ResultPrivate* p)
+{
+ return QSqlError(QLatin1String("QDB2: ") + err, qDB2Warn(p), type);
+}
+
+static QVariant::Type qDecodeDB2Type(SQLSMALLINT sqltype)
+{
+ QVariant::Type type = QVariant::Invalid;
+ switch (sqltype) {
+ case SQL_REAL:
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ case SQL_DECIMAL:
+ case SQL_NUMERIC:
+ type = QVariant::Double;
+ break;
+ case SQL_SMALLINT:
+ case SQL_INTEGER:
+ case SQL_BIT:
+ case SQL_TINYINT:
+ type = QVariant::Int;
+ break;
+ case SQL_BIGINT:
+ type = QVariant::LongLong;
+ break;
+ case SQL_BLOB:
+ case SQL_BINARY:
+ case SQL_VARBINARY:
+ case SQL_LONGVARBINARY:
+ case SQL_CLOB:
+ case SQL_DBCLOB:
+ type = QVariant::ByteArray;
+ break;
+ case SQL_DATE:
+ case SQL_TYPE_DATE:
+ type = QVariant::Date;
+ break;
+ case SQL_TIME:
+ case SQL_TYPE_TIME:
+ type = QVariant::Time;
+ break;
+ case SQL_TIMESTAMP:
+ case SQL_TYPE_TIMESTAMP:
+ type = QVariant::DateTime;
+ break;
+ case SQL_WCHAR:
+ case SQL_WVARCHAR:
+ case SQL_WLONGVARCHAR:
+ case SQL_CHAR:
+ case SQL_VARCHAR:
+ case SQL_LONGVARCHAR:
+ type = QVariant::String;
+ break;
+ default:
+ type = QVariant::ByteArray;
+ break;
+ }
+ return type;
+}
+
+static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i)
+{
+ SQLSMALLINT colNameLen;
+ SQLSMALLINT colType;
+ SQLUINTEGER colSize;
+ SQLSMALLINT colScale;
+ SQLSMALLINT nullable;
+ SQLRETURN r = SQL_ERROR;
+ SQLTCHAR colName[COLNAMESIZE];
+ r = SQLDescribeCol(d->hStmt,
+ i+1,
+ colName,
+ (SQLSMALLINT) COLNAMESIZE,
+ &colNameLen,
+ &colType,
+ &colSize,
+ &colScale,
+ &nullable);
+
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QString::fromLatin1("qMakeFieldInfo: Unable to describe column %1").arg(i), d);
+ return QSqlField();
+ }
+ QSqlField f(qFromTChar(colName), qDecodeDB2Type(colType));
+ // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
+ if (nullable == SQL_NO_NULLS)
+ f.setRequired(true);
+ else if (nullable == SQL_NULLABLE)
+ f.setRequired(false);
+ // else required is unknown
+ f.setLength(colSize == 0 ? -1 : int(colSize));
+ f.setPrecision(colScale == 0 ? -1 : int(colScale));
+ f.setSqlType(int(colType));
+ return f;
+}
+
+static int qGetIntData(SQLHANDLE hStmt, int column, bool& isNull)
+{
+ SQLINTEGER intbuf;
+ isNull = false;
+ SQLINTEGER lengthIndicator = 0;
+ SQLRETURN r = SQLGetData(hStmt,
+ column + 1,
+ SQL_C_SLONG,
+ (SQLPOINTER) &intbuf,
+ 0,
+ &lengthIndicator);
+ if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) {
+ isNull = true;
+ return 0;
+ }
+ return int(intbuf);
+}
+
+static double qGetDoubleData(SQLHANDLE hStmt, int column, bool& isNull)
+{
+ SQLDOUBLE dblbuf;
+ isNull = false;
+ SQLINTEGER lengthIndicator = 0;
+ SQLRETURN r = SQLGetData(hStmt,
+ column+1,
+ SQL_C_DOUBLE,
+ (SQLPOINTER) &dblbuf,
+ 0,
+ &lengthIndicator);
+ if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA) {
+ isNull = true;
+ return 0.0;
+ }
+
+ return (double) dblbuf;
+}
+
+static SQLBIGINT qGetBigIntData(SQLHANDLE hStmt, int column, bool& isNull)
+{
+ SQLBIGINT lngbuf = Q_INT64_C(0);
+ isNull = false;
+ SQLINTEGER lengthIndicator = 0;
+ SQLRETURN r = SQLGetData(hStmt,
+ column+1,
+ SQL_C_SBIGINT,
+ (SQLPOINTER) &lngbuf,
+ 0,
+ &lengthIndicator);
+ if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || lengthIndicator == SQL_NULL_DATA)
+ isNull = true;
+
+ return lngbuf;
+}
+
+static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool& isNull)
+{
+ QString fieldVal;
+ SQLRETURN r = SQL_ERROR;
+ SQLINTEGER lengthIndicator = 0;
+
+ if (colSize <= 0)
+ colSize = 255;
+ else if (colSize > 65536) // limit buffer size to 64 KB
+ colSize = 65536;
+ else
+ colSize++; // make sure there is room for more than the 0 termination
+ SQLTCHAR* buf = new SQLTCHAR[colSize];
+
+ while (true) {
+ r = SQLGetData(hStmt,
+ column + 1,
+ 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->dp->hDbc,
+ &d->hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QDB2Result::reset: Unable to allocate statement handle"), d);
+ return false;
+ }
+ } else {
+ r = SQLFreeStmt(d->hStmt, SQL_CLOSE);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QDB2Result::reset: Unable to close statement handle"), d);
+ return false;
+ }
+ }
+
+ if (!setForwardOnly)
+ return true;
+
+ if (forwardOnly) {
+ r = SQLSetStmtAttr(d->hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER) SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+ } else {
+ r = SQLSetStmtAttr(d->hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER) SQL_CURSOR_STATIC,
+ SQL_IS_UINTEGER);
+ }
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ qSqlWarning(QString::fromLatin1("QDB2Result::reset: Unable to set %1 attribute.").arg(
+ forwardOnly ? QLatin1String("SQL_CURSOR_FORWARD_ONLY")
+ : QLatin1String("SQL_CURSOR_STATIC")), d);
+ return false;
+ }
+ return true;
+}
+
+QVariant QDB2Result::handle() const
+{
+ return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt);
+}
+
+/************************************/
+
+QDB2Result::QDB2Result(const QDB2Driver* dr, const QDB2DriverPrivate* dp)
+ : QSqlResult(dr)
+{
+ d = new QDB2ResultPrivate(dp);
+}
+
+QDB2Result::~QDB2Result()
+{
+ if (d->hStmt) {
+ SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QDB2Driver: Unable to free statement handle ")
+ + QString::number(r), d);
+ }
+ delete d;
+}
+
+bool QDB2Result::reset (const QString& query)
+{
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ SQLRETURN r;
+
+ d->recInf.clear();
+ d->emptyValueCache();
+
+ if (!qMakeStatement(d, isForwardOnly()))
+ return false;
+
+ r = SQLExecDirect(d->hStmt,
+ qToTChar(query),
+ (SQLINTEGER) query.length());
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
+ "Unable to execute statement"), QSqlError::StatementError, d));
+ return false;
+ }
+ SQLSMALLINT count;
+ r = SQLNumResultCols(d->hStmt, &count);
+ if (count) {
+ setSelect(true);
+ for (int i = 0; i < count; ++i) {
+ d->recInf.append(qMakeFieldInfo(d, i));
+ }
+ } else {
+ setSelect(false);
+ }
+ d->valueCache.resize(count);
+ d->valueCache.fill(NULL);
+ setActive(true);
+ return true;
+}
+
+bool QDB2Result::prepare(const QString& query)
+{
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ SQLRETURN r;
+
+ d->recInf.clear();
+ d->emptyValueCache();
+
+ if (!qMakeStatement(d, isForwardOnly()))
+ return false;
+
+ r = SQLPrepare(d->hStmt,
+ qToTChar(query),
+ (SQLINTEGER) query.length());
+
+ if (r != SQL_SUCCESS) {
+ setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
+ "Unable to prepare statement"), QSqlError::StatementError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QDB2Result::exec()
+{
+ QList<QByteArray> tmpStorage; // holds temporary ptrs
+ QVarLengthArray<SQLINTEGER, 32> indicators(boundValues().count());
+
+ memset(indicators.data(), 0, indicators.size() * sizeof(SQLINTEGER));
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ SQLRETURN r;
+
+ d->recInf.clear();
+ d->emptyValueCache();
+
+ if (!qMakeStatement(d, isForwardOnly(), false))
+ return false;
+
+
+ QVector<QVariant> &values = boundValues();
+ int i;
+ for (i = 0; i < values.count(); ++i) {
+ // bind parameters - only positional binding allowed
+ SQLINTEGER *ind = &indicators[i];
+ if (values.at(i).isNull())
+ *ind = SQL_NULL_DATA;
+ if (bindValueType(i) & QSql::Out)
+ values[i].detach();
+
+ switch (values.at(i).type()) {
+ case QVariant::Date: {
+ QByteArray ba;
+ ba.resize(sizeof(DATE_STRUCT));
+ DATE_STRUCT *dt = (DATE_STRUCT *)ba.constData();
+ QDate qdt = values.at(i).toDate();
+ dt->year = qdt.year();
+ dt->month = qdt.month();
+ dt->day = qdt.day();
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & 3],
+ SQL_C_DATE,
+ SQL_DATE,
+ 0,
+ 0,
+ (void *) dt,
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ tmpStorage.append(ba);
+ break; }
+ case QVariant::Time: {
+ QByteArray ba;
+ ba.resize(sizeof(TIME_STRUCT));
+ TIME_STRUCT *dt = (TIME_STRUCT *)ba.constData();
+ QTime qdt = values.at(i).toTime();
+ dt->hour = qdt.hour();
+ dt->minute = qdt.minute();
+ dt->second = qdt.second();
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & 3],
+ SQL_C_TIME,
+ SQL_TIME,
+ 0,
+ 0,
+ (void *) dt,
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ tmpStorage.append(ba);
+ break; }
+ case QVariant::DateTime: {
+ QByteArray ba;
+ ba.resize(sizeof(TIMESTAMP_STRUCT));
+ TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)ba.constData();
+ QDateTime qdt = values.at(i).toDateTime();
+ dt->year = qdt.date().year();
+ dt->month = qdt.date().month();
+ dt->day = qdt.date().day();
+ dt->hour = qdt.time().hour();
+ dt->minute = qdt.time().minute();
+ dt->second = qdt.time().second();
+ dt->fraction = qdt.time().msec() * 1000000;
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & 3],
+ SQL_C_TIMESTAMP,
+ SQL_TIMESTAMP,
+ 0,
+ 0,
+ (void *) dt,
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ tmpStorage.append(ba);
+ break; }
+ case QVariant::Int:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & 3],
+ SQL_C_SLONG,
+ SQL_INTEGER,
+ 0,
+ 0,
+ (void *)values.at(i).constData(),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::Double:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & 3],
+ SQL_C_DOUBLE,
+ SQL_DOUBLE,
+ 0,
+ 0,
+ (void *)values.at(i).constData(),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::ByteArray: {
+ int len = values.at(i).toByteArray().size();
+ if (*ind != SQL_NULL_DATA)
+ *ind = len;
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & 3],
+ SQL_C_BINARY,
+ SQL_LONGVARBINARY,
+ len,
+ 0,
+ (void *)values.at(i).toByteArray().constData(),
+ len,
+ ind);
+ break; }
+ case QVariant::String:
+ {
+ QString str(values.at(i).toString());
+ if (*ind != SQL_NULL_DATA)
+ *ind = str.length() * sizeof(QChar);
+ if (bindValueType(i) & QSql::Out) {
+ QByteArray ba((char*)str.utf16(), str.capacity() * sizeof(QChar));
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & 3],
+ SQL_C_WCHAR,
+ SQL_WVARCHAR,
+ str.length(),
+ 0,
+ (void *)ba.constData(),
+ ba.size(),
+ ind);
+ tmpStorage.append(ba);
+ } else {
+ void *data = (void*)str.utf16();
+ int len = str.length();
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & 3],
+ SQL_C_WCHAR,
+ SQL_WVARCHAR,
+ len,
+ 0,
+ data,
+ len * sizeof(QChar),
+ ind);
+ }
+ break;
+ }
+ default: {
+ QByteArray ba = values.at(i).toString().toAscii();
+ int len = ba.length() + 1;
+ if (*ind != SQL_NULL_DATA)
+ *ind = ba.length();
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & 3],
+ SQL_C_CHAR,
+ SQL_VARCHAR,
+ len,
+ 0,
+ (void *) ba.constData(),
+ len,
+ ind);
+ tmpStorage.append(ba);
+ break; }
+ }
+ if (r != SQL_SUCCESS) {
+ qWarning("QDB2Result::exec: unable to bind variable: %s",
+ qDB2Warn(d).toLocal8Bit().constData());
+ setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
+ "Unable to bind variable"), QSqlError::StatementError, d));
+ return false;
+ }
+ }
+
+ r = SQLExecute(d->hStmt);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ qWarning("QDB2Result::exec: Unable to execute statement: %s",
+ qDB2Warn(d).toLocal8Bit().constData());
+ setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
+ "Unable to execute statement"), QSqlError::StatementError, d));
+ return false;
+ }
+ SQLSMALLINT count;
+ r = SQLNumResultCols(d->hStmt, &count);
+ if (count) {
+ setSelect(true);
+ for (int i = 0; i < count; ++i) {
+ d->recInf.append(qMakeFieldInfo(d, i));
+ }
+ } else {
+ setSelect(false);
+ }
+ setActive(true);
+ d->valueCache.resize(count);
+ d->valueCache.fill(NULL);
+
+ //get out parameters
+ if (!hasOutValues())
+ return true;
+
+ for (i = 0; i < values.count(); ++i) {
+ switch (values[i].type()) {
+ case QVariant::Date: {
+ DATE_STRUCT ds = *((DATE_STRUCT *)tmpStorage.takeFirst().constData());
+ values[i] = QVariant(QDate(ds.year, ds.month, ds.day));
+ break; }
+ case QVariant::Time: {
+ TIME_STRUCT dt = *((TIME_STRUCT *)tmpStorage.takeFirst().constData());
+ values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second));
+ break; }
+ case QVariant::DateTime: {
+ TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT *)tmpStorage.takeFirst().constData());
+ values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day),
+ QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000)));
+ break; }
+ case QVariant::Int:
+ case QVariant::Double:
+ case QVariant::ByteArray:
+ break;
+ case QVariant::String:
+ if (bindValueType(i) & QSql::Out)
+ values[i] = QString((const QChar *)tmpStorage.takeFirst().constData());
+ break;
+ default: {
+ values[i] = QString::fromAscii(tmpStorage.takeFirst().constData());
+ break; }
+ }
+ if (indicators[i] == SQL_NULL_DATA)
+ values[i] = QVariant(values[i].type());
+ }
+ return true;
+}
+
+bool QDB2Result::fetch(int i)
+{
+ if (isForwardOnly() && i < at())
+ return false;
+ if (i == at())
+ return true;
+ d->clearValueCache();
+ int actualIdx = i + 1;
+ if (actualIdx <= 0) {
+ setAt(QSql::BeforeFirstRow);
+ return false;
+ }
+ SQLRETURN r;
+ if (isForwardOnly()) {
+ bool ok = true;
+ while (ok && i > at())
+ ok = fetchNext();
+ return ok;
+ } else {
+ r = SQLFetchScroll(d->hStmt,
+ SQL_FETCH_ABSOLUTE,
+ actualIdx);
+ }
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && 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()
+{
+ SQLRETURN r;
+ d->clearValueCache();
+ r = SQLFetchScroll(d->hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (r != SQL_NO_DATA)
+ setLastError(qMakeError(QCoreApplication::translate("QDB2Result",
+ "Unable to fetch next"), QSqlError::StatementError, d));
+ return false;
+ }
+ setAt(at() + 1);
+ return true;
+}
+
+bool QDB2Result::fetchFirst()
+{
+ if (isForwardOnly() && at() != QSql::BeforeFirstRow)
+ return false;
+ if (isForwardOnly())
+ return fetchNext();
+ d->clearValueCache();
+ SQLRETURN r;
+ r = SQLFetchScroll(d->hStmt,
+ SQL_FETCH_FIRST,
+ 0);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ 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()
+{
+ d->clearValueCache();
+
+ int i = at();
+ if (i == QSql::AfterLastRow) {
+ if (isForwardOnly()) {
+ return false;
+ } else {
+ if (!fetch(0))
+ return false;
+ i = at();
+ }
+ }
+
+ while (fetchNext())
+ ++i;
+
+ if (i == QSql::BeforeFirstRow) {
+ setAt(QSql::AfterLastRow);
+ return false;
+ }
+
+ if (!isForwardOnly())
+ return fetch(i);
+
+ setAt(i);
+ return true;
+}
+
+
+QVariant QDB2Result::data(int field)
+{
+ if (field >= d->recInf.count()) {
+ qWarning("QDB2Result::data: column %d out of range", field);
+ return QVariant();
+ }
+ SQLRETURN r = 0;
+ SQLINTEGER lengthIndicator = 0;
+ bool isNull = false;
+ const QSqlField info = d->recInf.field(field);
+
+ if (!info.isValid() || field >= d->valueCache.size())
+ return QVariant();
+
+ if (d->valueCache[field])
+ return *d->valueCache[field];
+
+
+ QVariant* v = 0;
+ switch (info.type()) {
+ case QVariant::LongLong:
+ v = new QVariant((qint64) qGetBigIntData(d->hStmt, field, isNull));
+ break;
+ case QVariant::Int:
+ v = new QVariant(qGetIntData(d->hStmt, field, isNull));
+ break;
+ case QVariant::Date: {
+ DATE_STRUCT dbuf;
+ r = SQLGetData(d->hStmt,
+ field + 1,
+ SQL_C_DATE,
+ (SQLPOINTER) &dbuf,
+ 0,
+ &lengthIndicator);
+ if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
+ v = new QVariant(QDate(dbuf.year, dbuf.month, dbuf.day));
+ } else {
+ v = new QVariant(QDate());
+ isNull = true;
+ }
+ break; }
+ case QVariant::Time: {
+ TIME_STRUCT tbuf;
+ r = SQLGetData(d->hStmt,
+ field + 1,
+ SQL_C_TIME,
+ (SQLPOINTER) &tbuf,
+ 0,
+ &lengthIndicator);
+ if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
+ v = new QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second));
+ } else {
+ v = new QVariant(QTime());
+ isNull = true;
+ }
+ break; }
+ case QVariant::DateTime: {
+ TIMESTAMP_STRUCT dtbuf;
+ r = SQLGetData(d->hStmt,
+ field + 1,
+ SQL_C_TIMESTAMP,
+ (SQLPOINTER) &dtbuf,
+ 0,
+ &lengthIndicator);
+ if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA)) {
+ v = new QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day),
+ QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000)));
+ } else {
+ v = new QVariant(QDateTime());
+ isNull = true;
+ }
+ break; }
+ case QVariant::ByteArray:
+ v = new QVariant(qGetBinaryData(d->hStmt, field, lengthIndicator, isNull));
+ break;
+ case QVariant::Double:
+ {
+ switch(numericalPrecisionPolicy()) {
+ case QSql::LowPrecisionInt32:
+ v = new QVariant(qGetIntData(d->hStmt, field, isNull));
+ break;
+ case QSql::LowPrecisionInt64:
+ v = new QVariant(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)
+{
+ if (i >= d->valueCache.size())
+ return true;
+
+ if (d->valueCache[i])
+ return d->valueCache[i]->isNull();
+ return data(i).isNull();
+}
+
+int QDB2Result::numRowsAffected()
+{
+ SQLINTEGER affectedRowCount = 0;
+ SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ return affectedRowCount;
+ else
+ qSqlWarning(QLatin1String("QDB2Result::numRowsAffected: Unable to count affected rows"), d);
+ return -1;
+}
+
+int QDB2Result::size()
+{
+ return -1;
+}
+
+QSqlRecord QDB2Result::record() const
+{
+ if (isActive())
+ return d->recInf;
+ return QSqlRecord();
+}
+
+bool QDB2Result::nextResult()
+{
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ d->recInf.clear();
+ d->emptyValueCache();
+ setSelect(false);
+
+ SQLRETURN r = SQLMoreResults(d->hStmt);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (r != SQL_NO_DATA) {
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to fetch last"), QSqlError::ConnectionError, d));
+ }
+ return false;
+ }
+
+ SQLSMALLINT fieldCount;
+ r = SQLNumResultCols(d->hStmt, &fieldCount);
+ setSelect(fieldCount > 0);
+ for (int i = 0; i < fieldCount; ++i)
+ d->recInf.append(qMakeFieldInfo(d, i));
+
+ d->valueCache.resize(fieldCount);
+ d->valueCache.fill(NULL);
+ setActive(true);
+
+ return true;
+}
+
+void QDB2Result::virtual_hook(int id, void *data)
+{
+ switch (id) {
+ case QSqlResult::NextResult:
+ Q_ASSERT(data);
+ *static_cast<bool*>(data) = nextResult();
+ break;
+ case QSqlResult::DetachFromResultSet:
+ if (d->hStmt)
+ SQLCloseCursor(d->hStmt);
+ break;
+ default:
+ QSqlResult::virtual_hook(id, data);
+ }
+}
+
+/************************************/
+
+QDB2Driver::QDB2Driver(QObject* parent)
+ : QSqlDriver(parent)
+{
+ d = new QDB2DriverPrivate;
+}
+
+QDB2Driver::QDB2Driver(Qt::HANDLE env, Qt::HANDLE con, QObject* parent)
+ : QSqlDriver(parent)
+{
+ d = new QDB2DriverPrivate;
+ d->hEnv = (SQLHANDLE)env;
+ d->hDbc = (SQLHANDLE)con;
+ if (env && con) {
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+QDB2Driver::~QDB2Driver()
+{
+ close();
+ delete d;
+}
+
+bool QDB2Driver::open(const QString& db, const QString& user, const QString& password, const QString& host, int port,
+ const QString& connOpts)
+{
+ if (isOpen())
+ close();
+ SQLRETURN r;
+ r = SQLAllocHandle(SQL_HANDLE_ENV,
+ SQL_NULL_HANDLE,
+ &d->hEnv);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ qSqlWarning(QLatin1String("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, (SQLPOINTER) v, 0);
+ } else if (opt == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) {
+ v = val.toUInt();
+ r = SQLSetConnectAttr(d->hDbc, SQL_ATTR_LOGIN_TIMEOUT, (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()
+{
+ 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, d);
+}
+
+QSqlRecord QDB2Driver::record(const QString& tableName) const
+{
+ 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
+{
+ 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
+{
+ 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:
+ 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()
+{
+ 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()
+{
+ 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)
+{
+ SQLUINTEGER ac = autoCommit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF;
+ SQLRETURN r = SQLSetConnectAttr(d->hDbc,
+ SQL_ATTR_AUTOCOMMIT,
+ (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
+{
+ 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/sql/drivers/db2/qsql_db2.h b/src/sql/drivers/db2/qsql_db2.h
new file mode 100644
index 0000000000..0be9cee913
--- /dev/null
+++ b/src/sql/drivers/db2/qsql_db2.h
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_DB2_H
+#define QSQL_DB2_H
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_DB2
+#else
+#define Q_EXPORT_SQLDRIVER_DB2 Q_SQL_EXPORT
+#endif
+
+#include <QtSql/qsqlresult.h>
+#include <QtSql/qsqldriver.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+class QDB2Driver;
+class QDB2DriverPrivate;
+class QDB2ResultPrivate;
+class QSqlRecord;
+
+class QDB2Result : public QSqlResult
+{
+public:
+ QDB2Result(const QDB2Driver* dr, const QDB2DriverPrivate* dp);
+ ~QDB2Result();
+ bool prepare(const QString& query);
+ bool exec();
+ QVariant handle() const;
+
+protected:
+ QVariant data(int field);
+ bool reset (const QString& query);
+ bool fetch(int i);
+ bool fetchNext();
+ bool fetchFirst();
+ bool fetchLast();
+ bool isNull(int i);
+ int size();
+ int numRowsAffected();
+ QSqlRecord record() const;
+ void virtual_hook(int id, void *data);
+ bool nextResult();
+
+private:
+ QDB2ResultPrivate* d;
+};
+
+class Q_EXPORT_SQLDRIVER_DB2 QDB2Driver : public QSqlDriver
+{
+ Q_OBJECT
+public:
+ explicit QDB2Driver(QObject* parent = 0);
+ QDB2Driver(Qt::HANDLE env, Qt::HANDLE con, QObject* parent = 0);
+ ~QDB2Driver();
+ bool hasFeature(DriverFeature) const;
+ void close();
+ QSqlRecord record(const QString& tableName) const;
+ QStringList tables(QSql::TableType type) const;
+ QSqlResult *createResult() const;
+ QSqlIndex primaryIndex(const QString& tablename) const;
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+ QString formatValue(const QSqlField &field, bool trimStrings) const;
+ QVariant handle() const;
+ bool open(const QString& db,
+ const QString& user,
+ const QString& password,
+ const QString& host,
+ int port,
+ const QString& connOpts);
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const;
+
+private:
+ bool setAutoCommit(bool autoCommit);
+ QDB2DriverPrivate* d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQL_DB2_H
diff --git a/src/sql/drivers/db2/qsql_db2.pri b/src/sql/drivers/db2/qsql_db2.pri
new file mode 100644
index 0000000000..16557f0e00
--- /dev/null
+++ b/src/sql/drivers/db2/qsql_db2.pri
@@ -0,0 +1,8 @@
+HEADERS += $$PWD/qsql_db2.h
+SOURCES += $$PWD/qsql_db2.cpp
+
+unix {
+ !contains(LIBS, .*db2.*):LIBS += -ldb2
+} else:!win32-borland {
+ !contains(LIBS, .*db2.*):LIBS += -ldb2cli
+}
diff --git a/src/sql/drivers/drivers.pri b/src/sql/drivers/drivers.pri
new file mode 100644
index 0000000000..87cc0b1d9e
--- /dev/null
+++ b/src/sql/drivers/drivers.pri
@@ -0,0 +1,11 @@
+contains(sql-drivers, all):sql-driver += psql mysql odbc oci tds db2 sqlite ibase
+
+contains(sql-drivers, psql):include($$PWD/psql/qsql_psql.pri)
+contains(sql-drivers, mysql):include($$PWD/mysql/qsql_mysql.pri)
+contains(sql-drivers, odbc):include($$PWD/odbc/qsql_odbc.pri)
+contains(sql-drivers, oci):include($$PWD/oci/qsql_oci.pri)
+contains(sql-drivers, tds):include($$PWD/tds/qsql_tds.pri)
+contains(sql-drivers, db2):include($$PWD/db2/qsql_db2.pri)
+contains(sql-drivers, ibase):include($$PWD/ibase/qsql_ibase.pri)
+contains(sql-drivers, sqlite2):include($$PWD/sqlite2/qsql_sqlite2.pri)
+contains(sql-drivers, sqlite):include($$PWD/sqlite/qsql_sqlite.pri)
diff --git a/src/sql/drivers/ibase/qsql_ibase.cpp b/src/sql/drivers/ibase/qsql_ibase.cpp
new file mode 100644
index 0000000000..be8f49bf42
--- /dev/null
+++ b/src/sql/drivers/ibase/qsql_ibase.cpp
@@ -0,0 +1,1886 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_ibase.h"
+#include <qcoreapplication.h>
+#include <qdatetime.h>
+#include <qvariant.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlquery.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;
+ 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;
+ // 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:
+ QIBaseDriverPrivate(QIBaseDriver *d) : q(d), ibase(0), trans(0), tc(0) {}
+
+ bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError)
+ {
+ 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:
+ QIBaseDriver* q;
+ 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
+{
+public:
+ QIBaseResultPrivate(QIBaseResult *d, const QIBaseDriver *ddb);
+ ~QIBaseResultPrivate() { cleanup(); }
+
+ void cleanup();
+ bool isError(const char *msg, QSqlError::ErrorType typ = QSqlError::UnknownError)
+ {
+ 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:
+ QIBaseResult *q;
+ const QIBaseDriver *db;
+ 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 *d, const QIBaseDriver *ddb):
+ q(d), db(ddb), trans(0), stmt(0), ibase(ddb->d->ibase), sqlda(0), inda(0), queryType(-1), tc(ddb->d->tc)
+{
+ localTransaction = (ddb->d->ibase == 0);
+}
+
+void QIBaseResultPrivate::cleanup()
+{
+ 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)
+{
+ 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 (db->d->trans) {
+ localTransaction = false;
+ trans = db->d->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(db)
+{
+ d = new QIBaseResultPrivate(this, db);
+}
+
+QIBaseResult::~QIBaseResult()
+{
+ delete d;
+}
+
+bool QIBaseResult::prepare(const QString& query)
+{
+// 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()
+{
+ 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].sqlscale < 0)
+ *((long*)d->inda->sqlvar[para].sqldata) =
+ (long)floor(0.5 + val.toDouble() * pow(10.0, d->inda->sqlvar[para].sqlscale * -1));
+ else
+ *((long*)d->inda->sqlvar[para].sqldata) = (long)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)
+{
+ 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;
+ }
+ }
+ 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()
+{
+ static char acCountInfo[] = {isc_info_sql_records};
+ char cCountType;
+
+ 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;
+ 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 (cType == cCountType) {
+ iResult = iValue;
+ break;
+ }
+ }
+ return iResult;
+}
+
+QSqlRecord QIBaseResult::record() const
+{
+ 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(new QIBaseResult(d->db));
+ 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::fromAscii(v.relname, v.relname_length).toUpper() + QLatin1String("' "
+ "AND a.RDB$FIELD_NAME = '") + QString::fromAscii(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
+{
+ return QVariant(qRegisterMetaType<isc_stmt_handle>("isc_stmt_handle"), &d->stmt);
+}
+
+/*********************************/
+
+QIBaseDriver::QIBaseDriver(QObject * parent)
+ : QSqlDriver(parent)
+{
+ d = new QIBaseDriverPrivate(this);
+}
+
+QIBaseDriver::QIBaseDriver(isc_db_handle connection, QObject *parent)
+ : QSqlDriver(parent)
+{
+ d = new QIBaseDriverPrivate(this);
+ d->ibase = connection;
+ setOpen(true);
+ setOpenError(false);
+}
+
+QIBaseDriver::~QIBaseDriver()
+{
+ delete d;
+}
+
+bool QIBaseDriver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case QuerySize:
+ case NamedPlaceholders:
+ case LastInsertId:
+ case BatchOperations:
+ case SimpleLocking:
+ case FinishQuery:
+ case MultipleResultSets:
+ 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)
+{
+ 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.resize(usr.length() + pass.length() + enc.length() + role.length() + 6);
+ int i = -1;
+ ba[++i] = isc_dpb_version1;
+ ba[++i] = isc_dpb_user_name;
+ ba[++i] = usr.length();
+ memcpy(ba.data() + ++i, usr.data(), usr.length());
+ i += usr.length();
+ ba[i] = isc_dpb_password;
+ ba[++i] = pass.length();
+ memcpy(ba.data() + ++i, pass.data(), pass.length());
+ i += pass.length();
+ ba[i] = isc_dpb_lc_ctype;
+ ba[++i] = enc.length();
+ memcpy(ba.data() + ++i, enc.data(), enc.length());
+ i += enc.length();
+
+ if (!role.isEmpty()) {
+ ba[i] = isc_dpb_sql_role_name;
+ ba[++i] = role.length();
+ memcpy(ba.data() + ++i, role.data(), role.length());
+ i += role.length();
+ }
+
+ QString ldb;
+ if (!host.isEmpty())
+ ldb += host + QLatin1Char(':');
+ ldb += db;
+ isc_attach_database(d->status, 0, const_cast<char *>(ldb.toLocal8Bit().constData()),
+ &d->ibase, i, ba.data());
+ if (d->isError(QT_TRANSLATE_NOOP("QIBaseDriver", "Error opening database"),
+ QSqlError::ConnectionError)) {
+ setOpenError(true);
+ return false;
+ }
+
+ setOpen(true);
+ return true;
+}
+
+void QIBaseDriver::close()
+{
+ 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()
+{
+ 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()
+{
+ 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()
+{
+ 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 ? true : false);
+ 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
+{
+ 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::subscribeToNotificationImplementation(const QString &name)
+{
+ 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::unsubscribeFromNotificationImplementation(const QString &name)
+{
+ 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::subscribedToNotificationsImplementation() const
+{
+ return QStringList(d->eventBuffers.keys());
+}
+
+void QIBaseDriver::qHandleEventNotification(void *updatedResultBuffer)
+{
+ 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());
+ 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 (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/sql/drivers/ibase/qsql_ibase.h b/src/sql/drivers/ibase/qsql_ibase.h
new file mode 100644
index 0000000000..8949c07e89
--- /dev/null
+++ b/src/sql/drivers/ibase/qsql_ibase.h
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_IBASE_H
+#define QSQL_IBASE_H
+
+#include <QtSql/qsqlresult.h>
+#include <QtSql/qsqldriver.h>
+#include <QtSql/private/qsqlcachedresult_p.h>
+#include <ibase.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+class QIBaseDriverPrivate;
+class QIBaseResultPrivate;
+class QIBaseDriver;
+
+class QIBaseResult : public QSqlCachedResult
+{
+ friend class QIBaseResultPrivate;
+
+public:
+ explicit QIBaseResult(const QIBaseDriver* db);
+ virtual ~QIBaseResult();
+
+ bool prepare(const QString& query);
+ bool exec();
+ QVariant handle() const;
+
+protected:
+ bool gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx);
+ bool reset (const QString& query);
+ int size();
+ int numRowsAffected();
+ QSqlRecord record() const;
+
+private:
+ QIBaseResultPrivate* d;
+};
+
+class QIBaseDriver : public QSqlDriver
+{
+ Q_OBJECT
+ friend class QIBaseDriverPrivate;
+ friend class QIBaseResultPrivate;
+public:
+ explicit QIBaseDriver(QObject *parent = 0);
+ explicit QIBaseDriver(isc_db_handle connection, QObject *parent = 0);
+ virtual ~QIBaseDriver();
+ bool hasFeature(DriverFeature f) const;
+ bool open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString & connOpts);
+ 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();
+ QSqlResult *createResult() const;
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+ QStringList tables(QSql::TableType) const;
+
+ QSqlRecord record(const QString& tablename) const;
+ QSqlIndex primaryIndex(const QString &table) const;
+
+ QString formatValue(const QSqlField &field, bool trimStrings) const;
+ QVariant handle() const;
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const;
+
+protected Q_SLOTS:
+ bool subscribeToNotificationImplementation(const QString &name);
+ bool unsubscribeFromNotificationImplementation(const QString &name);
+ QStringList subscribedToNotificationsImplementation() const;
+
+private Q_SLOTS:
+ void qHandleEventNotification(void* updatedResultBuffer);
+
+private:
+ QIBaseDriverPrivate* d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+#endif // QSQL_IBASE_H
diff --git a/src/sql/drivers/ibase/qsql_ibase.pri b/src/sql/drivers/ibase/qsql_ibase.pri
new file mode 100644
index 0000000000..33fbb0de30
--- /dev/null
+++ b/src/sql/drivers/ibase/qsql_ibase.pri
@@ -0,0 +1,11 @@
+HEADERS += $$PWD/qsql_ibase.h
+SOURCES += $$PWD/qsql_ibase.cpp
+
+unix {
+ !contains(LIBS, .*gds.*):!contains(LIBS, .*libfb.*):LIBS += -lgds
+} else {
+ !contains(LIBS, .*gds.*):!contains(LIBS, .*fbclient.*) {
+ win32-borland:LIBS += gds32.lib
+ else:LIBS += -lgds32_ms
+ }
+}
diff --git a/src/sql/drivers/mysql/qsql_mysql.cpp b/src/sql/drivers/mysql/qsql_mysql.cpp
new file mode 100644
index 0000000000..49e7f138ee
--- /dev/null
+++ b/src/sql/drivers/mysql/qsql_mysql.cpp
@@ -0,0 +1,1543 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_mysql.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 <qdebug.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:
+ QMYSQLDriverPrivate() : mysql(0),
+#ifndef QT_NO_TEXTCODEC
+ tc(QTextCodec::codecForLocale()),
+#else
+ tc(0),
+#endif
+ preparedQuerysEnabled(false) {}
+ 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 : public QObject
+{
+ Q_OBJECT
+public:
+ QMYSQLResultPrivate(const QMYSQLDriver* dp, const QMYSQLResult* d) : driver(dp), result(0), q(d),
+ rowsAffected(0), hasBlobs(false)
+#if MYSQL_VERSION_ID >= 40108
+ , stmt(0), meta(0), inBinds(0), outBinds(0)
+#endif
+ , preparedQuery(false)
+ {
+ connect(dp, SIGNAL(destroyed()), this, SLOT(driverDestroyed()));
+ }
+
+ const QMYSQLDriver* driver;
+ MYSQL_RES *result;
+ MYSQL_ROW row;
+ const QMYSQLResult* q;
+
+ 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;
+
+private Q_SLOTS:
+ void driverDestroyed() { driver = NULL; }
+};
+
+#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 :
+ case FIELD_TYPE_SHORT :
+ 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 == MYSQL_TYPE_TINY
+ || t == MYSQL_TYPE_SHORT
+ || t == MYSQL_TYPE_LONG
+ || t == MYSQL_TYPE_LONGLONG
+ || t == MYSQL_TYPE_INT24;
+}
+
+
+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 {
+ // fieldInfo->length specifies the display width, which may be too
+ // small to hold valid integer values (see
+ // http://dev.mysql.com/doc/refman/5.0/en/numeric-types.html ), so
+ // always use the MAX_BIGINT_WIDTH for integer types
+ if (qIsInteger(fieldInfo->type)) {
+ fieldInfo->length = MAX_BIGINT_WIDTH;
+ }
+ 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(db)
+{
+ d = new QMYSQLResultPrivate(db, this);
+}
+
+QMYSQLResult::~QMYSQLResult()
+{
+ cleanup();
+ delete d;
+}
+
+QVariant QMYSQLResult::handle() const
+{
+#if MYSQL_VERSION_ID >= 40108
+ if(d->preparedQuery)
+ return d->meta ? QVariant::fromValue(d->meta) : qVariantFromValue(d->stmt);
+ else
+#endif
+ return QVariant::fromValue(d->result);
+}
+
+void QMYSQLResult::cleanup()
+{
+ 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 (d->driver && d->driver->d->mysql && mysql_next_result(d->driver->d->mysql) == 0) {
+ MYSQL_RES *res = mysql_store_result(d->driver->d->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)
+{
+ if(!d->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()
+{
+ if(!d->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()
+{
+ if(!d->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)
+{
+
+ if (!isSelect() || field >= d->fields.count()) {
+ qWarning("QMYSQLResult::data: column %d out of range", field);
+ return QVariant();
+ }
+
+ if (!d->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 (f.type != QVariant::ByteArray)
+ val = toUnicode(d->driver->d->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->driver->d->tc, d->row[field], fieldLength);
+ }
+
+ switch(f.type) {
+ case QVariant::LongLong:
+ return QVariant(val.toLongLong());
+ case QVariant::ULongLong:
+ return QVariant(val.toULongLong());
+ case QVariant::Int:
+ return QVariant(val.toInt());
+ 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)
+{
+ if (d->preparedQuery)
+ return d->fields.at(field).nullIndicator;
+ else
+ return d->row[field] == NULL;
+}
+
+bool QMYSQLResult::reset (const QString& query)
+{
+ if (!driver() || !driver()->isOpen() || driver()->isOpenError() || !d->driver)
+ return false;
+
+ d->preparedQuery = false;
+
+ cleanup();
+
+ const QByteArray encQuery(fromUnicode(d->driver->d->tc, query));
+ if (mysql_real_query(d->driver->d->mysql, encQuery.data(), encQuery.length())) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"),
+ QSqlError::StatementError, d->driver->d));
+ return false;
+ }
+ d->result = mysql_store_result(d->driver->d->mysql);
+ if (!d->result && mysql_field_count(d->driver->d->mysql) > 0) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store result"),
+ QSqlError::StatementError, d->driver->d));
+ return false;
+ }
+ int numFields = mysql_field_count(d->driver->d->mysql);
+ setSelect(numFields != 0);
+ d->fields.resize(numFields);
+ d->rowsAffected = mysql_affected_rows(d->driver->d->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()
+{
+ if (d->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()
+{
+ return d->rowsAffected;
+}
+
+QVariant QMYSQLResult::lastInsertId() const
+{
+ if (!isActive() || !d->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->driver->d->mysql);
+ if (id)
+ return QVariant(id);
+ }
+ return QVariant();
+}
+
+QSqlRecord QMYSQLResult::record() const
+{
+ QSqlRecord info;
+ MYSQL_RES *res;
+ if (!isActive() || !isSelect() || !d->driver)
+ return info;
+
+#if MYSQL_VERSION_ID >= 40108
+ res = d->preparedQuery ? d->meta : d->result;
+#else
+ res = d->result;
+#endif
+
+ if (!mysql_errno(d->driver->d->mysql)) {
+ mysql_field_seek(res, 0);
+ MYSQL_FIELD* field = mysql_fetch_field(res);
+ while(field) {
+ info.append(qToField(field, d->driver->d->tc));
+ field = mysql_fetch_field(res);
+ }
+ }
+ mysql_field_seek(res, 0);
+ return info;
+}
+
+bool QMYSQLResult::nextResult()
+{
+ if(!d->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->driver->d->mysql);
+ if (status > 0) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute next query"),
+ QSqlError::StatementError, d->driver->d));
+ return false;
+ } else if (status == -1) {
+ return false; // No more result sets
+ }
+
+ d->result = mysql_store_result(d->driver->d->mysql);
+ int numFields = mysql_field_count(d->driver->d->mysql);
+ if (!d->result && numFields > 0) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"),
+ QSqlError::StatementError, d->driver->d));
+ return false;
+ }
+
+ setSelect(numFields > 0);
+ d->fields.resize(numFields);
+ d->rowsAffected = mysql_affected_rows(d->driver->d->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)
+{
+ switch (id) {
+ case QSqlResult::NextResult:
+ Q_ASSERT(data);
+ *static_cast<bool*>(data) = nextResult();
+ break;
+ default:
+ 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();
+ }
+ 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)
+{
+ if(!d->driver)
+ return false;
+#if MYSQL_VERSION_ID >= 40108
+ cleanup();
+ if (!d->driver->d->preparedQuerysEnabled)
+ return QSqlResult::prepare(query);
+
+ int r;
+
+ if (query.isEmpty())
+ return false;
+
+ if (!d->stmt)
+ d->stmt = mysql_stmt_init(d->driver->d->mysql);
+ if (!d->stmt) {
+ setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to prepare statement"),
+ QSqlError::StatementError, d->driver->d));
+ return false;
+ }
+
+ const QByteArray encQuery(fromUnicode(d->driver->d->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()
+{
+ if (!d->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:
+ case QVariant::Bool:
+ currBind->buffer_type = MYSQL_TYPE_LONG;
+ currBind->buffer = data;
+ currBind->buffer_length = sizeof(int);
+ currBind->is_unsigned = (val.type() != QVariant::Int);
+ 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->driver->d->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(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(parent)
+{
+ 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()
+{
+ d = new QMYSQLDriverPrivate();
+ d->mysql = 0;
+ qMySqlConnectionCount++;
+}
+
+QMYSQLDriver::~QMYSQLDriver()
+{
+ qMySqlConnectionCount--;
+ if (qMySqlConnectionCount == 0 && !qMySqlInitHandledByUser)
+ qLibraryEnd();
+ delete d;
+}
+
+bool QMYSQLDriver::hasFeature(DriverFeature f) const
+{
+ 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:
+ 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"))
+ optionFlags |= 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)
+{
+ 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;
+#if MYSQL_VERSION_ID >= 50000
+ my_bool reconnect=false;
+#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;
+ }
+#endif
+ 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)) &&
+ 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 (!db.isEmpty() && mysql_select_db(d->mysql, db.toLocal8Bit().constData())) {
+ setLastError(qMakeError(tr("Unable to open database '") + db +
+ QLatin1Char('\''), 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()
+{
+ 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
+{
+ 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) {
+ q.exec(QLatin1String("select table_name from information_schema.tables where table_type = 'BASE TABLE'"));
+ while(q.next())
+ tl.append(q.value(0).toString());
+ }
+ if(type & QSql::Views) {
+ q.exec(QLatin1String("select table_name from information_schema.tables where table_type = 'VIEW'"));
+ 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
+{
+ 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
+{
+ return QVariant::fromValue(d->mysql);
+}
+
+bool QMYSQLDriver::beginTransaction()
+{
+#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()
+{
+#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()
+{
+#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
+{
+ QString r;
+ if (field.isNull()) {
+ r = QLatin1String("NULL");
+ } else {
+ switch(field.type()) {
+ 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::isIdentifierEscapedImplementation(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
+
+#include "qsql_mysql.moc"
diff --git a/src/sql/drivers/mysql/qsql_mysql.h b/src/sql/drivers/mysql/qsql_mysql.h
new file mode 100644
index 0000000000..e9d21c009c
--- /dev/null
+++ b/src/sql/drivers/mysql/qsql_mysql.h
@@ -0,0 +1,143 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_MYSQL_H
+#define QSQL_MYSQL_H
+
+#include <QtSql/qsqldriver.h>
+#include <QtSql/qsqlresult.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_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QMYSQLDriverPrivate;
+class QMYSQLResultPrivate;
+class QMYSQLDriver;
+class QSqlRecordInfo;
+
+class QMYSQLResult : public QSqlResult
+{
+ friend class QMYSQLDriver;
+ friend class QMYSQLResultPrivate;
+public:
+ explicit QMYSQLResult(const QMYSQLDriver* db);
+ ~QMYSQLResult();
+
+ QVariant handle() const;
+protected:
+ void cleanup();
+ bool fetch(int i);
+ bool fetchNext();
+ bool fetchLast();
+ bool fetchFirst();
+ QVariant data(int field);
+ bool isNull(int field);
+ bool reset (const QString& query);
+ int size();
+ int numRowsAffected();
+ QVariant lastInsertId() const;
+ QSqlRecord record() const;
+ void virtual_hook(int id, void *data);
+ bool nextResult();
+
+#if MYSQL_VERSION_ID >= 40108
+ bool prepare(const QString& stmt);
+ bool exec();
+#endif
+private:
+ QMYSQLResultPrivate* d;
+};
+
+class Q_EXPORT_SQLDRIVER_MYSQL QMYSQLDriver : public QSqlDriver
+{
+ Q_OBJECT
+ friend class QMYSQLResult;
+public:
+ explicit QMYSQLDriver(QObject *parent=0);
+ explicit QMYSQLDriver(MYSQL *con, QObject * parent=0);
+ ~QMYSQLDriver();
+ bool hasFeature(DriverFeature f) const;
+ bool open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString& connOpts);
+ void close();
+ QSqlResult *createResult() const;
+ QStringList tables(QSql::TableType) const;
+ QSqlIndex primaryIndex(const QString& tablename) const;
+ QSqlRecord record(const QString& tablename) const;
+ QString formatValue(const QSqlField &field,
+ bool trimStrings) const;
+ QVariant handle() const;
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const;
+
+protected Q_SLOTS:
+ bool isIdentifierEscapedImplementation(const QString &identifier, IdentifierType type) const;
+
+protected:
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+private:
+ void init();
+ QMYSQLDriverPrivate* d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQL_MYSQL_H
diff --git a/src/sql/drivers/mysql/qsql_mysql.pri b/src/sql/drivers/mysql/qsql_mysql.pri
new file mode 100644
index 0000000000..1b9c3dd8ca
--- /dev/null
+++ b/src/sql/drivers/mysql/qsql_mysql.pri
@@ -0,0 +1,16 @@
+HEADERS += $$PWD/qsql_mysql.h
+SOURCES += $$PWD/qsql_mysql.cpp
+
+unix {
+ isEmpty(QT_LFLAGS_MYSQL) {
+ !contains(LIBS, .*mysqlclient.*):!contains(LIBS, .*mysqld.*) {
+ use_libmysqlclient_r:LIBS += -lmysqlclient_r
+ else:LIBS += -lmysqlclient
+ }
+ } else {
+ LIBS *= $$QT_LFLAGS_MYSQL
+ QMAKE_CXXFLAGS *= $$QT_CFLAGS_MYSQL
+ }
+} else {
+ !contains(LIBS, .*mysql.*):!contains(LIBS, .*mysqld.*):LIBS += -llibmysql
+}
diff --git a/src/sql/drivers/oci/qsql_oci.cpp b/src/sql/drivers/oci/qsql_oci.cpp
new file mode 100644
index 0000000000..8500d2546e
--- /dev/null
+++ b/src/sql/drivers/oci/qsql_oci.cpp
@@ -0,0 +1,2656 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_oci.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 <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_METATYPE(OCIEnv*)
+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 QOCICols;
+
+struct QOCIResultPrivate
+{
+ QOCIResultPrivate(QOCIResult *result, const QOCIDriverPrivate *driver);
+ ~QOCIResultPrivate();
+
+ QOCICols *cols;
+ QOCIResult *q;
+ 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
+ { return q->bindValueType(i) & QSql::Out; }
+ inline bool isBinaryValue(int i) const
+ { 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());
+ }
+}
+
+
+struct QOCIDriverPrivate
+{
+ QOCIDriverPrivate();
+
+ OCIEnv *env;
+ OCISvcCtx *svc;
+ OCIServer *srvhp;
+ OCISession *authp;
+ OCIError *err;
+ bool transaction;
+ int serverVersion;
+ ub4 prefetchRows;
+ ub2 prefetchMem;
+ QString user;
+
+ void allocErrorHandle();
+};
+
+QOCIDriverPrivate::QOCIDriverPrivate()
+ : env(0), svc(0), srvhp(0), authp(0), err(0), transaction(false), serverVersion(-1),
+ prefetchRows(-1), prefetchMem(QOCI_PREFETCH_MEM)
+{
+}
+
+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->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->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->numericalPrecisionPolicy() == QSql::LowPrecisionInt64)
+ type = QVariant::LongLong;
+ else if (p->q->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->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->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->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->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->setSelect(false);
+ d->q->setAt(QSql::BeforeFirstRow);
+ d->q->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->numericalPrecisionPolicy() != QSql::HighPrecision) {
+ if ((d->q->numericalPrecisionPolicy() == QSql::LowPrecisionDouble)
+ && (fld.typ == QVariant::Double)) {
+ v[index + i] = *reinterpret_cast<double *>(fld.data);
+ break;
+ } else if ((d->q->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->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 *result, const QOCIDriverPrivate *driver)
+ : cols(0), q(result), env(driver->env), err(0), svc(const_cast<OCISvcCtx*&>(driver->svc)),
+ sql(0), transaction(driver->transaction), serverVersion(driver->serverVersion),
+ prefetchRows(driver->prefetchRows), prefetchMem(driver->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, const QOCIDriverPrivate* p)
+ : QSqlCachedResult(db)
+{
+ d = new QOCIResultPrivate(this, p);
+}
+
+QOCIResult::~QOCIResult()
+{
+ if (d->sql) {
+ int r = OCIHandleFree(d->sql, OCI_HTYPE_STMT);
+ if (r != 0)
+ qWarning("~QOCIResult: unable to free statement handle");
+ }
+ delete d;
+}
+
+QVariant QOCIResult::handle() const
+{
+ 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)
+{
+ 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()
+{
+ int rowCount;
+ OCIAttrGet(d->sql,
+ OCI_HTYPE_STMT,
+ &rowCount,
+ NULL,
+ OCI_ATTR_ROW_COUNT,
+ d->err);
+ return rowCount;
+}
+
+bool QOCIResult::prepare(const QString& query)
+{
+ 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()
+{
+ 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;
+ }
+
+ if (stmtType == OCI_STMT_SELECT) {
+ iters = 0;
+ mode = OCI_DEFAULT;
+ } else {
+ iters = 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
+{
+ QSqlRecord inf;
+ if (!isActive() || !isSelect() || !d->cols)
+ return inf;
+ return d->cols->rec;
+}
+
+QVariant QOCIResult::lastInsertId() const
+{
+ 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();
+}
+
+void QOCIResult::virtual_hook(int id, void *data)
+{
+ Q_ASSERT(data);
+
+ switch (id) {
+ case QSqlResult::BatchOperation:
+ QOCICols::execBatch(d, boundValues(), *reinterpret_cast<bool *>(data));
+ break;
+ default:
+ QSqlCachedResult::virtual_hook(id, data);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+
+QOCIDriver::QOCIDriver(QObject* parent)
+ : QSqlDriver(parent)
+{
+ d = new QOCIDriverPrivate();
+
+#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(parent)
+{
+ d = new QOCIDriverPrivate();
+ d->env = env;
+ d->svc = ctx;
+
+ d->allocErrorHandle();
+
+ if (env && ctx) {
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+QOCIDriver::~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);
+
+ delete d;
+}
+
+bool QOCIDriver::hasFeature(DriverFeature f) const
+{
+ 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 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)
+{
+ 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()
+{
+ 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, d);
+}
+
+bool QOCIDriver::beginTransaction()
+{
+ 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()
+{
+ 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()
+{
+ 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;
+}
+
+QStringList QOCIDriver::tables(QSql::TableType type) const
+{
+ QStringList tl;
+ QStringList sysUsers = QStringList() << QLatin1String("MDSYS")
+ << QLatin1String("LBACSYS")
+ << QLatin1String("SYS")
+ << QLatin1String("SYSTEM")
+ << QLatin1String("WKSYS")
+ << QLatin1String("CTXSYS")
+ << QLatin1String("WMSYS");
+
+ QString user = d->user;
+ if ( isIdentifierEscaped(user, QSqlDriver::TableName))
+ user = stripDelimiters(user, QSqlDriver::TableName);
+ else
+ user = user.toUpper();
+
+ if(sysUsers.contains(user))
+ sysUsers.removeAll(user);;
+
+ if (!isOpen())
+ return tl;
+
+ QSqlQuery t(createResult());
+ t.setForwardOnly(true);
+ if (type & QSql::Tables) {
+ QString query = QLatin1String("select owner, table_name from all_tables where ");
+ QStringList whereList;
+ foreach(const QString &sysUserName, sysUsers)
+ whereList << QLatin1String("owner != '") + sysUserName + QLatin1String("' ");
+ t.exec(query + whereList.join(QLatin1String(" and ")));
+
+ 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
+ query = QLatin1String("select owner, synonym_name from all_synonyms where ");
+ t.exec(query + whereList.join(QLatin1String(" and ")));
+ 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) {
+ QString query = QLatin1String("select owner, view_name from all_views where ");
+ QStringList whereList;
+ foreach(const QString &sysUserName, sysUsers)
+ whereList << QLatin1String("owner != '") + sysUserName + QLatin1String("' ");
+ t.exec(query + whereList.join(QLatin1String(" and ")));
+ 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());
+ }
+ QString query = QLatin1String("select owner, table_name from all_tables where ");
+ QStringList whereList;
+ foreach(const QString &sysUserName, sysUsers)
+ whereList << QLatin1String("owner = '") + sysUserName + QLatin1String("' ");
+ t.exec(query + whereList.join(QLatin1String(" or ")));
+
+ 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
+ query = QLatin1String("select owner, synonym_name from all_synonyms where ");
+ t.exec(query + whereList.join(QLatin1String(" or ")));
+ while (t.next()) {
+ if (t.value(0).toString() != d->user)
+ tl.append(t.value(0).toString() + QLatin1String(".") + 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
+{
+ 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
+{
+ 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
+{
+ 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/sql/drivers/oci/qsql_oci.h b/src/sql/drivers/oci/qsql_oci.h
new file mode 100644
index 0000000000..22bd78dffd
--- /dev/null
+++ b/src/sql/drivers/oci/qsql_oci.h
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_OCI_H
+#define QSQL_OCI_H
+
+#include <QtSql/qsqlresult.h>
+#include <QtSql/qsqldriver.h>
+#include <QtSql/private/qsqlcachedresult_p.h>
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_OCI
+#else
+#define Q_EXPORT_SQLDRIVER_OCI Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_HEADER
+
+typedef struct OCIEnv OCIEnv;
+typedef struct OCISvcCtx OCISvcCtx;
+
+QT_BEGIN_NAMESPACE
+
+class QOCIDriver;
+class QOCICols;
+struct QOCIDriverPrivate;
+struct QOCIResultPrivate;
+
+class Q_EXPORT_SQLDRIVER_OCI QOCIResult : public QSqlCachedResult
+{
+ friend class QOCIDriver;
+ friend struct QOCIResultPrivate;
+ friend class QOCICols;
+public:
+ QOCIResult(const QOCIDriver * db, const QOCIDriverPrivate* p);
+ ~QOCIResult();
+ bool prepare(const QString& query);
+ bool exec();
+ QVariant handle() const;
+
+protected:
+ bool gotoNext(ValueCache &values, int index);
+ bool reset (const QString& query);
+ int size();
+ int numRowsAffected();
+ QSqlRecord record() const;
+ QVariant lastInsertId() const;
+ void virtual_hook(int id, void *data);
+
+private:
+ QOCIResultPrivate *d;
+};
+
+class Q_EXPORT_SQLDRIVER_OCI QOCIDriver : public QSqlDriver
+{
+ Q_OBJECT
+ friend struct QOCIResultPrivate;
+ friend class QOCIPrivate;
+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);
+ void close();
+ QSqlResult *createResult() const;
+ QStringList tables(QSql::TableType) const;
+ QSqlRecord record(const QString& tablename) const;
+ QSqlIndex primaryIndex(const QString& tablename) const;
+ QString formatValue(const QSqlField &field,
+ bool trimStrings) const;
+ QVariant handle() const;
+ QString escapeIdentifier(const QString &identifier, IdentifierType) const;
+
+protected:
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+private:
+ QOCIDriverPrivate *d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQL_OCI_H
diff --git a/src/sql/drivers/oci/qsql_oci.pri b/src/sql/drivers/oci/qsql_oci.pri
new file mode 100644
index 0000000000..60ccc4c227
--- /dev/null
+++ b/src/sql/drivers/oci/qsql_oci.pri
@@ -0,0 +1,9 @@
+HEADERS += $$PWD/qsql_oci.h
+SOURCES += $$PWD/qsql_oci.cpp
+
+unix {
+ !contains(LIBS, .*clnts.*):LIBS += -lclntsh
+} else {
+ LIBS *= -loci
+}
+macx:QMAKE_LFLAGS += -Wl,-flat_namespace,-U,_environ
diff --git a/src/sql/drivers/odbc/qsql_odbc.cpp b/src/sql/drivers/odbc/qsql_odbc.cpp
new file mode 100644
index 0000000000..0b534d9883
--- /dev/null
+++ b/src/sql/drivers/odbc/qsql_odbc.cpp
@@ -0,0 +1,2557 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_odbc.h"
+#include <qsqlrecord.h>
+
+#if defined (Q_OS_WIN32)
+#include <qt_windows.h>
+#endif
+#include <qcoreapplication.h>
+#include <qvariant.h>
+#include <qdatetime.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qstringlist.h>
+#include <qvarlengtharray.h>
+#include <qvector.h>
+#include <QDebug>
+#include <QSqlQuery>
+
+QT_BEGIN_NAMESPACE
+
+// undefine this to prevent initial check of the ODBC driver
+#define ODBC_CHECK_DRIVER
+
+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 " << sizeof(SQLTCHAR) << "Don't know how to handle this";
+ }
+ 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 " << sizeof(SQLTCHAR) << "Don't know how to handle this";
+ }
+ 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:
+ enum DefaultCase{Lower, Mixed, Upper, Sensitive};
+ QODBCDriverPrivate()
+ : hEnv(0), hDbc(0), unicode(false), useSchema(false), disconnectCount(0), isMySqlServer(false),
+ isMSSqlServer(false), isFreeTDSDriver(false), hasSQLFetchScroll(true),
+ hasMultiResultSets(false), isQuoteInitialized(false), quote(QLatin1Char('"'))
+ {
+ }
+
+ SQLHANDLE hEnv;
+ SQLHANDLE hDbc;
+
+ bool unicode;
+ bool useSchema;
+ int disconnectCount;
+ bool isMySqlServer;
+ bool isMSSqlServer;
+ bool isFreeTDSDriver;
+ bool hasSQLFetchScroll;
+ bool hasMultiResultSets;
+
+ bool checkDriver() const;
+ void checkUnicode();
+ void checkSqlServer();
+ void checkHasSQLFetchScroll();
+ void checkHasMultiResults();
+ void checkSchemaUsage();
+ bool setConnectionOptions(const QString& connOpts);
+ void splitTableQualifier(const QString &qualifier, QString &catalog,
+ QString &schema, QString &table);
+ DefaultCase defaultCase() const;
+ QString adjustCase(const QString&) const;
+ QChar quoteChar();
+private:
+ bool isQuoteInitialized;
+ QChar quote;
+};
+
+class QODBCPrivate
+{
+public:
+ QODBCPrivate(QODBCDriverPrivate *dpp)
+ : hStmt(0), useSchema(false), hasSQLFetchScroll(true), driverPrivate(dpp), userForwardOnly(false)
+ {
+ unicode = dpp->unicode;
+ useSchema = dpp->useSchema;
+ disconnectCount = dpp->disconnectCount;
+ hasSQLFetchScroll = dpp->hasSQLFetchScroll;
+ }
+
+ inline void clearValues()
+ { fieldCache.fill(QVariant()); fieldCacheIdx = 0; }
+
+ SQLHANDLE dpEnv() const { return driverPrivate ? driverPrivate->hEnv : 0;}
+ SQLHANDLE dpDbc() const { return driverPrivate ? driverPrivate->hDbc : 0;}
+ SQLHANDLE hStmt;
+
+ bool unicode;
+ bool useSchema;
+
+ QSqlRecord rInf;
+ QVector<QVariant> fieldCache;
+ int fieldCacheIdx;
+ int disconnectCount;
+ bool hasSQLFetchScroll;
+ QODBCDriverPrivate *driverPrivate;
+ bool userForwardOnly;
+
+ bool isStmtHandleValid(const QSqlDriver *driver);
+ void updateStmtHandleState(const QSqlDriver *driver);
+};
+
+bool QODBCPrivate::isStmtHandleValid(const QSqlDriver *driver)
+{
+ const QODBCDriver *odbcdriver = static_cast<const QODBCDriver*> (driver);
+ return disconnectCount == odbcdriver->d->disconnectCount;
+}
+
+void QODBCPrivate::updateStmtHandleState(const QSqlDriver *driver)
+{
+ const QODBCDriver *odbcdriver = static_cast<const QODBCDriver*> (driver);
+ disconnectCount = odbcdriver->d->disconnectCount;
+}
+
+static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = 0)
+{
+ SQLINTEGER nativeCode_ = 0;
+ SQLSMALLINT msgLen = 0;
+ SQLRETURN r = SQL_NO_DATA;
+ SQLTCHAR state_[SQL_SQLSTATE_SIZE+1];
+ 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_;
+ QString tmpstore;
+#ifdef UNICODE
+ tmpstore = fromSQLTCHAR(description_, msgLen);
+#else
+ tmpstore = QString::fromUtf8((const char*)description_.constData(), msgLen);
+#endif
+ if(result != tmpstore) {
+ if(!result.isEmpty())
+ result += QLatin1Char(' ');
+ result += tmpstore;
+ }
+ } else if (r == SQL_ERROR || r == SQL_INVALID_HANDLE) {
+ return result;
+ }
+ ++i;
+ } while (r != SQL_NO_DATA);
+ return result;
+}
+
+static QString qODBCWarn(const QODBCPrivate* odbc, int *nativeCode = 0)
+{
+ return QString(qWarnODBCHandle(SQL_HANDLE_ENV, odbc->dpEnv()) + QLatin1Char(' ')
+ + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->dpDbc()) + QLatin1Char(' ')
+ + qWarnODBCHandle(SQL_HANDLE_STMT, odbc->hStmt, nativeCode)).simplified();
+}
+
+static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = 0)
+{
+ return QString(qWarnODBCHandle(SQL_HANDLE_ENV, odbc->hEnv) + QLatin1Char(' ')
+ + qWarnODBCHandle(SQL_HANDLE_DBC, odbc->hDbc, nativeCode)).simplified();
+}
+
+static void qSqlWarning(const QString& message, const QODBCPrivate* odbc)
+{
+ qWarning() << message << "\tError:" << qODBCWarn(odbc);
+}
+
+static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc)
+{
+ qWarning() << message << "\tError:" << qODBCWarn(odbc);
+}
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCPrivate* p)
+{
+ int nativeCode = -1;
+ QString message = qODBCWarn(p, &nativeCode);
+ return QSqlError(QLatin1String("QODBC3: ") + err, message, type, nativeCode);
+}
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
+ const QODBCDriverPrivate* p)
+{
+ int nativeCode = -1;
+ QString message = qODBCWarn(p, &nativeCode);
+ return QSqlError(QLatin1String("QODBC3: ") + err, qODBCWarn(p), type, nativeCode);
+}
+
+template<class T>
+static QVariant::Type qDecodeODBCType(SQLSMALLINT sqltype, const T* p, bool isSigned = true)
+{
+ Q_UNUSED(p);
+ QVariant::Type type = QVariant::Invalid;
+ switch (sqltype) {
+ case SQL_DECIMAL:
+ case SQL_NUMERIC:
+ case SQL_REAL:
+ case SQL_FLOAT:
+ case SQL_DOUBLE:
+ type = QVariant::Double;
+ break;
+ case SQL_SMALLINT:
+ case SQL_INTEGER:
+ case SQL_BIT:
+ type = isSigned ? QVariant::Int : QVariant::UInt;
+ break;
+ case SQL_TINYINT:
+ type = QVariant::UInt;
+ break;
+ case SQL_BIGINT:
+ type = isSigned ? QVariant::LongLong : QVariant::ULongLong;
+ break;
+ case SQL_BINARY:
+ case SQL_VARBINARY:
+ case SQL_LONGVARBINARY:
+ type = QVariant::ByteArray;
+ break;
+ case SQL_DATE:
+ case SQL_TYPE_DATE:
+ type = QVariant::Date;
+ break;
+ case SQL_TIME:
+ case SQL_TYPE_TIME:
+ type = QVariant::Time;
+ break;
+ case SQL_TIMESTAMP:
+ case SQL_TYPE_TIMESTAMP:
+ type = QVariant::DateTime;
+ break;
+ 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 = 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 || 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/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,
+ (SQLPOINTER)(fieldVal.constData() + read),
+ colSize,
+ &lengthIndicator);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ break;
+ if (lengthIndicator == SQL_NULL_DATA)
+ return QVariant(QVariant::ByteArray);
+ if (lengthIndicator > 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);
+}
+
+// creates a QSqlField from a valid hStmt generated
+// by SQLColumns. The hStmt has to point to a valid position.
+static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate* p)
+{
+ QString fname = qGetStringData(hStmt, 3, -1, p->unicode);
+ int type = qGetIntData(hStmt, 4).toInt(); // column type
+ QSqlField f(fname, qDecodeODBCType(type, p));
+ int required = qGetIntData(hStmt, 10).toInt(); // nullable-flag
+ // required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
+ if (required == SQL_NO_NULLS)
+ f.setRequired(true);
+ else if (required == SQL_NULLABLE)
+ f.setRequired(false);
+ // else we don't know
+ QVariant var = qGetIntData(hStmt, 6);
+ f.setLength(var.isNull() ? -1 : var.toInt()); // column size
+ var = qGetIntData(hStmt, 8).toInt();
+ f.setPrecision(var.isNull() ? -1 : var.toInt()); // precision
+ f.setSqlType(type);
+ return f;
+}
+
+static QSqlField qMakeFieldInfo(const QODBCPrivate* p, int i )
+{
+ SQLSMALLINT colNameLen;
+ SQLSMALLINT colType;
+ SQLULEN colSize;
+ SQLSMALLINT colScale;
+ SQLSMALLINT nullable;
+ SQLRETURN r = SQL_ERROR;
+ QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE);
+ r = SQLDescribeCol(p->hStmt,
+ i+1,
+ colName.data(),
+ (SQLSMALLINT)COLNAMESIZE,
+ &colNameLen,
+ &colType,
+ &colSize,
+ &colScale,
+ &nullable);
+
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QString::fromLatin1("qMakeField: Unable to describe column %1").arg(i), p);
+ return QSqlField();
+ }
+
+ SQLLEN unsignedFlag = SQL_FALSE;
+ r = SQLColAttribute (p->hStmt,
+ i + 1,
+ SQL_DESC_UNSIGNED,
+ 0,
+ 0,
+ 0,
+ &unsignedFlag);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QString::fromLatin1("qMakeField: Unable to get column attributes for column %1").arg(i), p);
+ }
+
+#ifdef UNICODE
+ QString qColName(fromSQLTCHAR(colName, colNameLen));
+#else
+ QString qColName = QString::fromUtf8((const char *)colName.constData());
+#endif
+ // nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
+ int required = -1;
+ if (nullable == SQL_NO_NULLS) {
+ required = 1;
+ } else if (nullable == SQL_NULLABLE) {
+ required = 0;
+ }
+ QVariant::Type type = qDecodeODBCType(colType, p, unsignedFlag == SQL_FALSE);
+ QSqlField f(qColName, type);
+ f.setSqlType(colType);
+ f.setLength(colSize == 0 ? -1 : int(colSize));
+ f.setPrecision(colScale == 0 ? -1 : int(colScale));
+ if (nullable == SQL_NO_NULLS)
+ f.setRequired(true);
+ else if (nullable == SQL_NULLABLE)
+ f.setRequired(false);
+ // else we don't know
+ return f;
+}
+
+static int qGetODBCVersion(const QString &connOpts)
+{
+ 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)
+#ifdef UNICODE
+ quote = QChar(driverResponse[0]);
+#else
+ quote = QLatin1Char(driverResponse[0]);
+#endif
+ else
+ quote = QLatin1Char('"');
+ isQuoteInitialized = true;
+ }
+ return quote;
+}
+
+
+bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts)
+{
+ // Set any connection attributes
+ const QStringList opts(connOpts.split(QLatin1Char(';'), QString::SkipEmptyParts));
+ SQLRETURN r = SQL_SUCCESS;
+ for (int i = 0; i < opts.count(); ++i) {
+ const QString tmp(opts.at(i));
+ int idx;
+ if ((idx = tmp.indexOf(QLatin1Char('='))) == -1) {
+ qWarning() << "QODBCDriver::open: Illegal connect option value '" << tmp << '\'';
+ continue;
+ }
+ const QString opt(tmp.left(idx));
+ const QString val(tmp.mid(idx + 1).simplified());
+ SQLUINTEGER v = 0;
+
+ r = SQL_SUCCESS;
+ if (opt.toUpper() == QLatin1String("SQL_ATTR_ACCESS_MODE")) {
+ if (val.toUpper() == QLatin1String("SQL_MODE_READ_ONLY")) {
+ v = SQL_MODE_READ_ONLY;
+ } else if (val.toUpper() == QLatin1String("SQL_MODE_READ_WRITE")) {
+ v = SQL_MODE_READ_WRITE;
+ } else {
+ qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ continue;
+ }
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) v, 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CONNECTION_TIMEOUT")) {
+ v = val.toUInt();
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) v, 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_LOGIN_TIMEOUT")) {
+ v = val.toUInt();
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) v, 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CURRENT_CATALOG")) {
+ val.utf16(); // 0 terminate
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG,
+#ifdef UNICODE
+ toSQLTCHAR(val).data(),
+#else
+ (SQLCHAR*) val.toUtf8().data(),
+#endif
+ 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) v, 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_PACKET_SIZE")) {
+ v = val.toUInt();
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) v, 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_TRACEFILE")) {
+ val.utf16(); // 0 terminate
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE,
+#ifdef UNICODE
+ toSQLTCHAR(val).data(),
+#else
+ (SQLCHAR*) val.toUtf8().data(),
+#endif
+ 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) 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)v, 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_CP_MATCH")) {
+ if (val.toUpper() == QLatin1String("SQL_CP_STRICT_MATCH"))
+ v = SQL_CP_STRICT_MATCH;
+ else if (val.toUpper() == QLatin1String("SQL_CP_RELAXED_MATCH"))
+ v = SQL_CP_RELAXED_MATCH;
+ else if (val.toUpper() == QLatin1String("SQL_CP_MATCH_DEFAULT"))
+ v = SQL_CP_MATCH_DEFAULT;
+ else {
+ qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ continue;
+ }
+ r = SQLSetConnectAttr(hDbc, SQL_ATTR_CP_MATCH, (SQLPOINTER)v, 0);
+ } else if (opt.toUpper() == QLatin1String("SQL_ATTR_ODBC_VERSION")) {
+ // Already handled in QODBCDriver::open()
+ continue;
+ } else {
+ qWarning() << "QODBCDriver::open: Unknown connection attribute '" << opt << '\'';
+ }
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ qSqlWarning(QString::fromLatin1("QODBCDriver::open: Unable to set connection attribute'%1'").arg(
+ opt), this);
+ }
+ return true;
+}
+
+void QODBCDriverPrivate::splitTableQualifier(const QString & qualifier, QString &catalog,
+ QString &schema, QString &table)
+{
+ if (!useSchema) {
+ table = qualifier;
+ return;
+ }
+ QStringList l = qualifier.split(QLatin1Char('.'));
+ if (l.count() > 3)
+ return; // can't possibly be a valid table qualifier
+ int i = 0, n = l.count();
+ if (n == 1) {
+ table = qualifier;
+ } else {
+ for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) {
+ if (n == 3) {
+ if (i == 0) {
+ catalog = *it;
+ } else if (i == 1) {
+ schema = *it;
+ } else if (i == 2) {
+ table = *it;
+ }
+ } else if (n == 2) {
+ if (i == 0) {
+ schema = *it;
+ } else if (i == 1) {
+ table = *it;
+ }
+ }
+ i++;
+ }
+ }
+}
+
+QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const
+{
+ DefaultCase ret;
+ SQLUSMALLINT casing;
+ int r = SQLGetInfo(hDbc,
+ SQL_IDENTIFIER_CASE,
+ &casing,
+ sizeof(casing),
+ NULL);
+ if ( r != SQL_SUCCESS)
+ ret = Mixed;//arbitrary case if driver cannot be queried
+ else {
+ switch (casing) {
+ case (SQL_IC_UPPER):
+ ret = Upper;
+ break;
+ case (SQL_IC_LOWER):
+ ret = Lower;
+ break;
+ case (SQL_IC_SENSITIVE):
+ ret = Sensitive;
+ break;
+ case (SQL_IC_MIXED):
+ default:
+ ret = Mixed;
+ break;
+ }
+ }
+ return ret;
+}
+
+/*
+ Adjust the casing of an identifier to match what the
+ database engine would have done to it.
+*/
+QString QODBCDriverPrivate::adjustCase(const QString &identifier) const
+{
+ QString ret = identifier;
+ switch(defaultCase()) {
+ case (Lower):
+ ret = identifier.toLower();
+ break;
+ case (Upper):
+ ret = identifier.toUpper();
+ break;
+ case(Mixed):
+ case(Sensitive):
+ default:
+ ret = identifier;
+ }
+ return ret;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+QODBCResult::QODBCResult(const QODBCDriver * db, QODBCDriverPrivate* p)
+: QSqlResult(db)
+{
+ d = new QODBCPrivate(p);
+}
+
+QODBCResult::~QODBCResult()
+{
+ if (d->hStmt && d->isStmtHandleValid(driver()) && driver()->isOpen()) {
+ SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ")
+ + QString::number(r), d);
+ }
+
+ delete d;
+}
+
+bool QODBCResult::reset (const QString& query)
+{
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ d->rInf.clear();
+ d->fieldCache.clear();
+ d->fieldCacheIdx = 0;
+
+ // Always reallocate the statement handle - the statement attributes
+ // are not reset if SQLFreeStmt() is called which causes some problems.
+ SQLRETURN r;
+ if (d->hStmt && d->isStmtHandleValid(driver())) {
+ r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QODBCResult::reset: Unable to free statement handle"), d);
+ return false;
+ }
+ }
+ r = SQLAllocHandle(SQL_HANDLE_STMT,
+ d->dpDbc(),
+ &d->hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QODBCResult::reset: Unable to allocate statement handle"), d);
+ return false;
+ }
+
+ d->updateStmtHandleState(driver());
+
+ if (d->userForwardOnly) {
+ r = SQLSetStmtAttr(d->hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+ } else {
+ r = SQLSetStmtAttr(d->hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_STATIC,
+ SQL_IS_UINTEGER);
+ }
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
+ "Please check your ODBC driver configuration"), QSqlError::StatementError, d));
+ return false;
+ }
+
+#ifdef UNICODE
+ r = SQLExecDirect(d->hStmt,
+ toSQLTCHAR(query).data(),
+ (SQLINTEGER) query.length());
+#else
+ QByteArray query8 = query.toUtf8();
+ r = SQLExecDirect(d->hStmt,
+ (SQLCHAR*) query8.data(),
+ (SQLINTEGER) query8.length());
+#endif
+ 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;
+ }
+
+ if(r == SQL_NO_DATA) {
+ setSelect(false);
+ return true;
+ }
+
+ SQLINTEGER isScrollable, bufferLength;
+ r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, &bufferLength);
+ if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ QSqlResult::setForwardOnly(isScrollable==SQL_NONSCROLLABLE);
+
+ SQLSMALLINT count;
+ SQLNumResultCols(d->hStmt, &count);
+ if (count) {
+ setSelect(true);
+ for (int i = 0; i < count; ++i) {
+ d->rInf.append(qMakeFieldInfo(d, i));
+ }
+ d->fieldCache.resize(count);
+ } else {
+ setSelect(false);
+ }
+ setActive(true);
+
+ return true;
+}
+
+bool QODBCResult::fetch(int i)
+{
+ if (!driver()->isOpen())
+ return false;
+
+ if (isForwardOnly() && i < at())
+ return false;
+ if (i == at())
+ return true;
+ d->clearValues();
+ int actualIdx = i + 1;
+ if (actualIdx <= 0) {
+ setAt(QSql::BeforeFirstRow);
+ return false;
+ }
+ SQLRETURN r;
+ if (isForwardOnly()) {
+ bool ok = true;
+ while (ok && i > at())
+ ok = fetchNext();
+ return ok;
+ } else {
+ r = SQLFetchScroll(d->hStmt,
+ SQL_FETCH_ABSOLUTE,
+ actualIdx);
+ }
+ if (r != SQL_SUCCESS) {
+ if (r != SQL_NO_DATA)
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to fetch"), QSqlError::ConnectionError, d));
+ return false;
+ }
+ setAt(i);
+ return true;
+}
+
+bool QODBCResult::fetchNext()
+{
+ SQLRETURN r;
+ d->clearValues();
+
+ if (d->hasSQLFetchScroll)
+ r = SQLFetchScroll(d->hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ else
+ r = SQLFetch(d->hStmt);
+
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (r != SQL_NO_DATA)
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to fetch next"), QSqlError::ConnectionError, d));
+ return false;
+ }
+ setAt(at() + 1);
+ return true;
+}
+
+bool QODBCResult::fetchFirst()
+{
+ if (isForwardOnly() && at() != QSql::BeforeFirstRow)
+ return false;
+ SQLRETURN r;
+ d->clearValues();
+ if (isForwardOnly()) {
+ return fetchNext();
+ }
+ r = SQLFetchScroll(d->hStmt,
+ SQL_FETCH_FIRST,
+ 0);
+ if (r != SQL_SUCCESS) {
+ if (r != SQL_NO_DATA)
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to fetch first"), QSqlError::ConnectionError, d));
+ return false;
+ }
+ setAt(0);
+ return true;
+}
+
+bool QODBCResult::fetchPrevious()
+{
+ if (isForwardOnly())
+ return false;
+ SQLRETURN r;
+ d->clearValues();
+ r = SQLFetchScroll(d->hStmt,
+ SQL_FETCH_PRIOR,
+ 0);
+ if (r != SQL_SUCCESS) {
+ if (r != SQL_NO_DATA)
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to fetch previous"), QSqlError::ConnectionError, d));
+ return false;
+ }
+ setAt(at() - 1);
+ return true;
+}
+
+bool QODBCResult::fetchLast()
+{
+ SQLRETURN r;
+ d->clearValues();
+
+ if (isForwardOnly()) {
+ // cannot seek to last row in forwardOnly mode, so we have to use brute force
+ int i = at();
+ if (i == QSql::AfterLastRow)
+ return false;
+ if (i == QSql::BeforeFirstRow)
+ i = 0;
+ while (fetchNext())
+ ++i;
+ setAt(i);
+ return true;
+ }
+
+ r = SQLFetchScroll(d->hStmt,
+ SQL_FETCH_LAST,
+ 0);
+ if (r != SQL_SUCCESS) {
+ if (r != SQL_NO_DATA)
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to fetch last"), QSqlError::ConnectionError, d));
+ return false;
+ }
+ SQLINTEGER currRow;
+ r = SQLGetStmtAttr(d->hStmt,
+ SQL_ROW_NUMBER,
+ &currRow,
+ SQL_IS_INTEGER,
+ 0);
+ if (r != SQL_SUCCESS)
+ return false;
+ setAt(currRow-1);
+ return true;
+}
+
+QVariant QODBCResult::data(int field)
+{
+ if (field >= d->rInf.count() || field < 0) {
+ qWarning() << "QODBCResult::data: column" << field << "out of range";
+ return QVariant();
+ }
+ if (field < d->fieldCacheIdx)
+ return d->fieldCache.at(field);
+
+ SQLRETURN r(0);
+ 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)
+{
+ 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()
+{
+ 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)
+{
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ SQLRETURN r;
+
+ d->rInf.clear();
+ if (d->hStmt && d->isStmtHandleValid(driver())) {
+ r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to close statement"), d);
+ return false;
+ }
+ }
+ r = SQLAllocHandle(SQL_HANDLE_STMT,
+ d->dpDbc(),
+ &d->hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QODBCResult::prepare: Unable to allocate statement handle"), d);
+ return false;
+ }
+
+ d->updateStmtHandleState(driver());
+
+ if (d->userForwardOnly) {
+ r = SQLSetStmtAttr(d->hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+ } else {
+ r = SQLSetStmtAttr(d->hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_STATIC,
+ SQL_IS_UINTEGER);
+ }
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
+ "Please check your ODBC driver configuration"), QSqlError::StatementError, d));
+ return false;
+ }
+
+#ifdef UNICODE
+ r = SQLPrepare(d->hStmt,
+ toSQLTCHAR(query).data(),
+ (SQLINTEGER) query.length());
+#else
+ QByteArray query8 = query.toUtf8();
+ r = SQLPrepare(d->hStmt,
+ (SQLCHAR*) query8.data(),
+ (SQLINTEGER) query8.length());
+#endif
+
+ if (r != SQL_SUCCESS) {
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to prepare statement"), QSqlError::StatementError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QODBCResult::exec()
+{
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ d->rInf.clear();
+ d->fieldCache.clear();
+ d->fieldCacheIdx = 0;
+
+ if (!d->hStmt) {
+ qSqlWarning(QLatin1String("QODBCResult::exec: No statement handle available"), d);
+ return false;
+ }
+
+ if (isSelect())
+ SQLCloseCursor(d->hStmt);
+
+ QList<QByteArray> tmpStorage; // holds temporary buffers
+ QVarLengthArray<SQLLEN, 32> indicators(boundValues().count());
+ memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN));
+
+ // bind parameters - only positional binding allowed
+ QVector<QVariant>& values = boundValues();
+ int i;
+ SQLRETURN r;
+ for (i = 0; i < values.count(); ++i) {
+ if (bindValueType(i) & QSql::Out)
+ values[i].detach();
+ const QVariant &val = values.at(i);
+ SQLLEN *ind = &indicators[i];
+ if (val.isNull())
+ *ind = SQL_NULL_DATA;
+ switch (val.type()) {
+ case QVariant::Date: {
+ QByteArray ba;
+ ba.resize(sizeof(DATE_STRUCT));
+ DATE_STRUCT *dt = (DATE_STRUCT *)ba.constData();
+ QDate qdt = val.toDate();
+ dt->year = qdt.year();
+ dt->month = qdt.month();
+ dt->day = qdt.day();
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_DATE,
+ SQL_DATE,
+ 0,
+ 0,
+ (void *) dt,
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ tmpStorage.append(ba);
+ break; }
+ case QVariant::Time: {
+ QByteArray ba;
+ ba.resize(sizeof(TIME_STRUCT));
+ TIME_STRUCT *dt = (TIME_STRUCT *)ba.constData();
+ QTime qdt = val.toTime();
+ dt->hour = qdt.hour();
+ dt->minute = qdt.minute();
+ dt->second = qdt.second();
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_TIME,
+ SQL_TIME,
+ 0,
+ 0,
+ (void *) dt,
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ tmpStorage.append(ba);
+ break; }
+ case QVariant::DateTime: {
+ QByteArray ba;
+ ba.resize(sizeof(TIMESTAMP_STRUCT));
+ TIMESTAMP_STRUCT * dt = (TIMESTAMP_STRUCT *)ba.constData();
+ QDateTime qdt = val.toDateTime();
+ dt->year = qdt.date().year();
+ dt->month = qdt.date().month();
+ dt->day = qdt.date().day();
+ dt->hour = qdt.time().hour();
+ dt->minute = qdt.time().minute();
+ dt->second = qdt.time().second();
+ dt->fraction = qdt.time().msec() * 1000000;
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_TIMESTAMP,
+ SQL_TIMESTAMP,
+ 19,
+ 0,
+ (void *) dt,
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ tmpStorage.append(ba);
+ break; }
+ case QVariant::Int:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_SLONG,
+ SQL_INTEGER,
+ 0,
+ 0,
+ (void *) val.constData(),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::UInt:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_ULONG,
+ SQL_NUMERIC,
+ 15,
+ 0,
+ (void *) val.constData(),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::Double:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_DOUBLE,
+ SQL_DOUBLE,
+ 0,
+ 0,
+ (void *) val.constData(),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::LongLong:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_SBIGINT,
+ SQL_BIGINT,
+ 0,
+ 0,
+ (void *) val.constData(),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::ULongLong:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_UBIGINT,
+ SQL_BIGINT,
+ 0,
+ 0,
+ (void *) val.constData(),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::ByteArray:
+ if (*ind != SQL_NULL_DATA) {
+ *ind = val.toByteArray().size();
+ }
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_BINARY,
+ SQL_LONGVARBINARY,
+ val.toByteArray().size(),
+ 0,
+ (void *) val.toByteArray().constData(),
+ val.toByteArray().size(),
+ ind);
+ break;
+ case QVariant::Bool:
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_BIT,
+ SQL_BIT,
+ 0,
+ 0,
+ (void *) val.constData(),
+ 0,
+ *ind == SQL_NULL_DATA ? ind : NULL);
+ break;
+ case QVariant::String:
+ if (d->unicode) {
+ 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) {
+ QVarLengthArray<SQLTCHAR> ba(toSQLTCHAR(str));
+ ba.reserve(str.capacity());
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_TCHAR,
+ strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
+ 0, // god knows... don't change this!
+ 0,
+ (void *)ba.constData(),
+ ba.size(),
+ ind);
+ tmpStorage.append(QByteArray((const char *)ba.constData(), ba.size()*sizeof(SQLTCHAR)));
+ break;
+ }
+ QByteArray strba((const char *)toSQLTCHAR(str).constData(), str.size()*sizeof(SQLTCHAR));
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_TCHAR,
+ strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
+ strSize,
+ 0,
+ (SQLPOINTER)strba.constData(),
+ strba.size(),
+ ind);
+ tmpStorage.append(strba);
+ break;
+ }
+ else
+ {
+ QByteArray str = val.toString().toUtf8();
+ if (*ind != SQL_NULL_DATA)
+ *ind = str.length();
+ int strSize = str.length();
+
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_CHAR,
+ strSize > 254 ? SQL_LONGVARCHAR : SQL_VARCHAR,
+ strSize,
+ 0,
+ (void *)str.constData(),
+ strSize,
+ ind);
+ tmpStorage.append(str);
+ break;
+ }
+ // fall through
+ default: {
+ QByteArray ba = val.toByteArray();
+ if (*ind != SQL_NULL_DATA)
+ *ind = ba.size();
+ r = SQLBindParameter(d->hStmt,
+ i + 1,
+ qParamType[(QFlag)(bindValueType(i)) & QSql::InOut],
+ SQL_C_BINARY,
+ SQL_VARBINARY,
+ ba.length() + 1,
+ 0,
+ (void *) ba.constData(),
+ ba.length() + 1,
+ ind);
+ tmpStorage.append(ba);
+ break; }
+ }
+ if (r != SQL_SUCCESS) {
+ qWarning() << "QODBCResult::exec: unable to bind variable:" << qODBCWarn(d);
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to bind variable"), QSqlError::StatementError, d));
+ return false;
+ }
+ }
+ r = SQLExecute(d->hStmt);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ qWarning() << "QODBCResult::exec: Unable to execute statement:" << qODBCWarn(d);
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to execute statement"), QSqlError::StatementError, d));
+ return false;
+ }
+
+ SQLINTEGER isScrollable, bufferLength;
+ r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, &bufferLength);
+ if(r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ QSqlResult::setForwardOnly(isScrollable==SQL_NONSCROLLABLE);
+
+ SQLSMALLINT count;
+ SQLNumResultCols(d->hStmt, &count);
+ if (count) {
+ setSelect(true);
+ for (int i = 0; i < count; ++i) {
+ d->rInf.append(qMakeFieldInfo(d, i));
+ }
+ d->fieldCache.resize(count);
+ } else {
+ setSelect(false);
+ }
+ setActive(true);
+
+
+ //get out parameters
+ if (!hasOutValues())
+ return true;
+
+ for (i = 0; i < values.count(); ++i) {
+ switch (values.at(i).type()) {
+ case QVariant::Date: {
+ DATE_STRUCT ds = *((DATE_STRUCT *)tmpStorage.takeFirst().constData());
+ values[i] = QVariant(QDate(ds.year, ds.month, ds.day));
+ break; }
+ case QVariant::Time: {
+ TIME_STRUCT dt = *((TIME_STRUCT *)tmpStorage.takeFirst().constData());
+ values[i] = QVariant(QTime(dt.hour, dt.minute, dt.second));
+ break; }
+ case QVariant::DateTime: {
+ TIMESTAMP_STRUCT dt = *((TIMESTAMP_STRUCT*)
+ tmpStorage.takeFirst().constData());
+ values[i] = QVariant(QDateTime(QDate(dt.year, dt.month, dt.day),
+ QTime(dt.hour, dt.minute, dt.second, dt.fraction / 1000000)));
+ break; }
+ case QVariant::Bool:
+ case QVariant::Int:
+ case QVariant::UInt:
+ case QVariant::Double:
+ case QVariant::ByteArray:
+ case QVariant::LongLong:
+ case QVariant::ULongLong:
+ //nothing to do
+ break;
+ case QVariant::String:
+ if (d->unicode) {
+ if (bindValueType(i) & QSql::Out) {
+ QByteArray first = tmpStorage.takeFirst();
+ QVarLengthArray<SQLTCHAR> array;
+ array.append((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.takeFirst();
+ break; }
+ }
+ if (indicators[i] == SQL_NULL_DATA)
+ values[i] = QVariant(values[i].type());
+ }
+ return true;
+}
+
+QSqlRecord QODBCResult::record() const
+{
+ if (!isActive() || !isSelect())
+ return QSqlRecord();
+ return d->rInf;
+}
+
+QVariant QODBCResult::handle() const
+{
+ return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hStmt);
+}
+
+bool QODBCResult::nextResult()
+{
+ setActive(false);
+ setAt(QSql::BeforeFirstRow);
+ d->rInf.clear();
+ d->fieldCache.clear();
+ d->fieldCacheIdx = 0;
+ setSelect(false);
+
+ SQLRETURN r = SQLMoreResults(d->hStmt);
+ if (r != SQL_SUCCESS) {
+ if (r == SQL_SUCCESS_WITH_INFO) {
+ int nativeCode = -1;
+ QString message = qODBCWarn(d, &nativeCode);
+ qWarning() << "QODBCResult::nextResult():" << message;
+ } else {
+ if (r != SQL_NO_DATA)
+ setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
+ "Unable to fetch last"), QSqlError::ConnectionError, d));
+ return false;
+ }
+ }
+
+ SQLSMALLINT count;
+ SQLNumResultCols(d->hStmt, &count);
+ if (count) {
+ setSelect(true);
+ for (int i = 0; i < count; ++i) {
+ d->rInf.append(qMakeFieldInfo(d, i));
+ }
+ d->fieldCache.resize(count);
+ } else {
+ setSelect(false);
+ }
+ setActive(true);
+
+ return true;
+}
+
+void QODBCResult::virtual_hook(int id, void *data)
+{
+ switch (id) {
+ case QSqlResult::DetachFromResultSet:
+ if (d->hStmt)
+ SQLCloseCursor(d->hStmt);
+ break;
+ case QSqlResult::NextResult:
+ Q_ASSERT(data);
+ *static_cast<bool*>(data) = nextResult();
+ break;
+ default:
+ QSqlResult::virtual_hook(id, data);
+ }
+}
+
+void QODBCResult::setForwardOnly(bool forward)
+{
+ d->userForwardOnly = forward;
+ QSqlResult::setForwardOnly(forward);
+}
+
+////////////////////////////////////////
+
+
+QODBCDriver::QODBCDriver(QObject *parent)
+ : QSqlDriver(parent)
+{
+ init();
+}
+
+QODBCDriver::QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject * parent)
+ : QSqlDriver(parent)
+{
+ init();
+ d->hEnv = env;
+ d->hDbc = con;
+ if (env && con) {
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+void QODBCDriver::init()
+{
+ d = new QODBCDriverPrivate();
+}
+
+QODBCDriver::~QODBCDriver()
+{
+ cleanup();
+ delete d;
+}
+
+bool QODBCDriver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case Transactions: {
+ if (!d->hDbc)
+ return false;
+ SQLUSMALLINT txn;
+ SQLSMALLINT t;
+ int r = SQLGetInfo(d->hDbc,
+ (SQLUSMALLINT)SQL_TXN_CAPABLE,
+ &txn,
+ sizeof(txn),
+ &t);
+ if (r != SQL_SUCCESS || txn == SQL_TC_NONE)
+ return false;
+ else
+ return true;
+ }
+ case Unicode:
+ return d->unicode;
+ case PreparedQueries:
+ case PositionalPlaceholders:
+ case FinishQuery:
+ case LowPrecisionNumbers:
+ return true;
+ case QuerySize:
+ case NamedPlaceholders:
+ case LastInsertId:
+ case BatchOperations:
+ case SimpleLocking:
+ case EventNotifications:
+ return false;
+ case MultipleResultSets:
+ return d->hasMultiResultSets;
+ case BLOB: {
+ if(d->isMySqlServer)
+ return true;
+ else
+ return false;
+ }
+ }
+ return false;
+}
+
+bool QODBCDriver::open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString &,
+ int,
+ const QString& connOpts)
+{
+ if (isOpen())
+ close();
+ SQLRETURN r;
+ r = SQLAllocHandle(SQL_HANDLE_ENV,
+ SQL_NULL_HANDLE,
+ &d->hEnv);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate environment"), d);
+ setOpenError(true);
+ return false;
+ }
+ r = SQLSetEnvAttr(d->hEnv,
+ SQL_ATTR_ODBC_VERSION,
+ (SQLPOINTER)qGetODBCVersion(connOpts),
+ SQL_IS_UINTEGER);
+ r = SQLAllocHandle(SQL_HANDLE_DBC,
+ d->hEnv,
+ &d->hDbc);
+ if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ qSqlWarning(QLatin1String("QODBCDriver::open: Unable to allocate connection"), d);
+ setOpenError(true);
+ return false;
+ }
+
+ if (!d->setConnectionOptions(connOpts))
+ return false;
+
+ // Create the connection string
+ QString connQStr;
+ // support the "DRIVER={SQL SERVER};SERVER=blah" syntax
+ if (db.contains(QLatin1String(".dsn"), Qt::CaseInsensitive))
+ connQStr = QLatin1String("FILEDSN=") + db;
+ else if (db.contains(QLatin1String("DRIVER="), Qt::CaseInsensitive)
+ || db.contains(QLatin1String("SERVER="), Qt::CaseInsensitive))
+ connQStr = db;
+ else
+ connQStr = QLatin1String("DSN=") + db;
+
+ if (!user.isEmpty())
+ connQStr += QLatin1String(";UID=") + user;
+ if (!password.isEmpty())
+ connQStr += QLatin1String(";PWD=") + password;
+
+ SQLSMALLINT cb;
+ QVarLengthArray<SQLTCHAR> connOut(1024);
+ memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR));
+ r = SQLDriverConnect(d->hDbc,
+ NULL,
+#ifdef UNICODE
+ toSQLTCHAR(connQStr).data(),
+#else
+ (SQLCHAR*)connQStr.toUtf8().data(),
+#endif
+ (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);
+ return false;
+ }
+
+ if (!d->checkDriver()) {
+ setLastError(qMakeError(tr("Unable to connect - Driver doesn't support all "
+ "functionality required"), QSqlError::ConnectionError, d));
+ setOpenError(true);
+ return false;
+ }
+
+ d->checkUnicode();
+ d->checkSchemaUsage();
+ d->checkSqlServer();
+ d->checkHasSQLFetchScroll();
+ d->checkHasMultiResults();
+ setOpen(true);
+ setOpenError(false);
+ if(d->isMSSqlServer) {
+ QSqlQuery i(createResult());
+ i.exec(QLatin1String("SET QUOTED_IDENTIFIER ON"));
+ }
+ return true;
+}
+
+void QODBCDriver::close()
+{
+ cleanup();
+ setOpen(false);
+ setOpenError(false);
+}
+
+void QODBCDriver::cleanup()
+{
+ SQLRETURN r;
+ if (!d)
+ return;
+
+ if(d->hDbc) {
+ // Open statements/descriptors handles are automatically cleaned up by SQLDisconnect
+ if (isOpen()) {
+ r = SQLDisconnect(d->hDbc);
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QODBCDriver::disconnect: Unable to disconnect datasource"), d);
+ else
+ d->disconnectCount++;
+ }
+
+ r = SQLFreeHandle(SQL_HANDLE_DBC, d->hDbc);
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free connection handle"), d);
+ d->hDbc = 0;
+ }
+
+ if (d->hEnv) {
+ r = SQLFreeHandle(SQL_HANDLE_ENV, d->hEnv);
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QODBCDriver::cleanup: Unable to free environment handle"), d);
+ d->hEnv = 0;
+ }
+}
+
+// checks whether the server can return char, varchar and longvarchar
+// as two byte unicode characters
+void QODBCDriverPrivate::checkUnicode()
+{
+ 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::checkSqlServer()
+{
+ SQLRETURN r;
+ QVarLengthArray<SQLTCHAR> serverString(200);
+ SQLSMALLINT t;
+ memset(serverString.data(), 0, serverString.size() * sizeof(SQLTCHAR));
+
+ r = SQLGetInfo(hDbc,
+ SQL_DBMS_NAME,
+ serverString.data(),
+ serverString.size() * sizeof(SQLTCHAR),
+ &t);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ QString serverType;
+#ifdef UNICODE
+ serverType = fromSQLTCHAR(serverString, t/sizeof(SQLTCHAR));
+#else
+ serverType = QString::fromUtf8((const char *)serverString.constData(), t);
+#endif
+ isMySqlServer = serverType.contains(QLatin1String("mysql"), Qt::CaseInsensitive);
+ isMSSqlServer = serverType.contains(QLatin1String("Microsoft SQL Server"), Qt::CaseInsensitive);
+ }
+ r = SQLGetInfo(hDbc,
+ SQL_DRIVER_NAME,
+ serverString.data(),
+ serverString.size() * sizeof(SQLTCHAR),
+ &t);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ QString serverType;
+#ifdef UNICODE
+ serverType = fromSQLTCHAR(serverString, t/sizeof(SQLTCHAR));
+#else
+ serverType = QString::fromUtf8((const char *)serverString.constData(), t);
+#endif
+ 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(),
+ driverResponse.size() * sizeof(SQLTCHAR),
+ &length);
+ if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+#ifdef UNICODE
+ hasMultiResultSets = fromSQLTCHAR(driverResponse, length/sizeof(SQLTCHAR)).startsWith(QLatin1Char('Y'));
+#else
+ hasMultiResultSets = QString::fromUtf8((const char *)driverResponse.constData(), length).startsWith(QLatin1Char('Y'));
+#endif
+}
+
+QSqlResult *QODBCDriver::createResult() const
+{
+ return new QODBCResult(this, d);
+}
+
+bool QODBCDriver::beginTransaction()
+{
+ if (!isOpen()) {
+ qWarning() << "QODBCDriver::beginTransaction: Database not open";
+ return false;
+ }
+ SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
+ SQLRETURN r = SQLSetConnectAttr(d->hDbc,
+ SQL_ATTR_AUTOCOMMIT,
+ (SQLPOINTER)ac,
+ sizeof(ac));
+ if (r != SQL_SUCCESS) {
+ setLastError(qMakeError(tr("Unable to disable autocommit"),
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QODBCDriver::commitTransaction()
+{
+ if (!isOpen()) {
+ qWarning() << "QODBCDriver::commitTransaction: Database not open";
+ return false;
+ }
+ SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
+ d->hDbc,
+ SQL_COMMIT);
+ if (r != SQL_SUCCESS) {
+ setLastError(qMakeError(tr("Unable to commit transaction"),
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ return endTrans();
+}
+
+bool QODBCDriver::rollbackTransaction()
+{
+ if (!isOpen()) {
+ qWarning() << "QODBCDriver::rollbackTransaction: Database not open";
+ return false;
+ }
+ SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
+ d->hDbc,
+ SQL_ROLLBACK);
+ if (r != SQL_SUCCESS) {
+ setLastError(qMakeError(tr("Unable to rollback transaction"),
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ return endTrans();
+}
+
+bool QODBCDriver::endTrans()
+{
+ SQLUINTEGER ac(SQL_AUTOCOMMIT_ON);
+ SQLRETURN r = SQLSetConnectAttr(d->hDbc,
+ SQL_ATTR_AUTOCOMMIT,
+ (SQLPOINTER)ac,
+ sizeof(ac));
+ if (r != SQL_SUCCESS) {
+ setLastError(qMakeError(tr("Unable to enable autocommit"), QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+QStringList QODBCDriver::tables(QSql::TableType type) const
+{
+ QStringList tl;
+ if (!isOpen())
+ return tl;
+ SQLHANDLE hStmt;
+
+ SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
+ d->hDbc,
+ &hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QODBCDriver::tables: Unable to allocate handle"), d);
+ return tl;
+ }
+ r = SQLSetStmtAttr(hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+ QStringList tableType;
+ if (type & QSql::Tables)
+ tableType += QLatin1String("TABLE");
+ if (type & QSql::Views)
+ tableType += QLatin1String("VIEW");
+ if (type & QSql::SystemTables)
+ tableType += QLatin1String("SYSTEM TABLE");
+ if (tableType.isEmpty())
+ return tl;
+
+ QString joinedTableTypeString = tableType.join(QLatin1String(","));
+
+ r = SQLTables(hStmt,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ NULL,
+ 0,
+#ifdef UNICODE
+ toSQLTCHAR(joinedTableTypeString).data(),
+#else
+ (SQLCHAR*)joinedTableTypeString.toUtf8().data(),
+#endif
+ joinedTableTypeString.length() /* characters, not bytes */);
+
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QODBCDriver::tables Unable to execute table list"), d);
+
+ if (d->hasSQLFetchScroll)
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ else
+ r = SQLFetch(hStmt);
+
+ 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
+{
+ QSqlIndex index(tablename);
+ if (!isOpen())
+ return index;
+ bool usingSpecialColumns = false;
+ QSqlRecord rec = record(tablename);
+
+ SQLHANDLE hStmt;
+ SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
+ d->hDbc,
+ &hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to list primary key"), d);
+ return index;
+ }
+ QString catalog, schema, table;
+ d->splitTableQualifier(tablename, catalog, schema, table);
+
+ if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
+ catalog = stripDelimiters(catalog, QSqlDriver::TableName);
+ else
+ catalog = d->adjustCase(catalog);
+
+ if (isIdentifierEscaped(schema, QSqlDriver::TableName))
+ schema = stripDelimiters(schema, QSqlDriver::TableName);
+ else
+ schema = d->adjustCase(schema);
+
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ else
+ table = d->adjustCase(table);
+
+ r = SQLSetStmtAttr(hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+ r = SQLPrimaryKeys(hStmt,
+#ifdef UNICODE
+ catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
+#else
+ catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toUtf8().data(),
+#endif
+ catalog.length(),
+#ifdef UNICODE
+ schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
+#else
+ schema.length() == 0 ? NULL : (SQLCHAR*)schema.toUtf8().data(),
+#endif
+ schema.length(),
+#ifdef UNICODE
+ toSQLTCHAR(table).data(),
+#else
+ (SQLCHAR*)table.toUtf8().data(),
+#endif
+ table.length() /* in characters, not in bytes */);
+
+ // if the SQLPrimaryKeys() call does not succeed (e.g the driver
+ // does not support it) - try an alternative method to get hold of
+ // the primary index (e.g MS Access and FoxPro)
+ if (r != SQL_SUCCESS) {
+ r = SQLSpecialColumns(hStmt,
+ SQL_BEST_ROWID,
+#ifdef UNICODE
+ catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
+#else
+ catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toUtf8().data(),
+#endif
+ catalog.length(),
+#ifdef UNICODE
+ schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
+#else
+ schema.length() == 0 ? NULL : (SQLCHAR*)schema.toUtf8().data(),
+#endif
+ schema.length(),
+#ifdef UNICODE
+ toSQLTCHAR(table).data(),
+#else
+ (SQLCHAR*)table.toUtf8().data(),
+#endif
+ table.length(),
+ SQL_SCOPE_CURROW,
+ SQL_NULLABLE);
+
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QODBCDriver::primaryIndex: Unable to execute primary key list"), d);
+ } else {
+ usingSpecialColumns = true;
+ }
+ }
+
+ if (d->hasSQLFetchScroll)
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ else
+ r = SQLFetch(hStmt);
+
+ int fakeId = 0;
+ QString cName, idxName;
+ // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
+ while (r == SQL_SUCCESS) {
+ if (usingSpecialColumns) {
+ cName = qGetStringData(hStmt, 1, -1, d->unicode); // column name
+ idxName = QString::number(fakeId++); // invent a fake index name
+ } else {
+ cName = qGetStringData(hStmt, 3, -1, d->unicode); // column name
+ idxName = qGetStringData(hStmt, 5, -1, d->unicode); // pk index name
+ }
+ index.append(rec.field(cName));
+ index.setName(idxName);
+
+ if (d->hasSQLFetchScroll)
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ else
+ r = SQLFetch(hStmt);
+
+ }
+ r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ if (r!= SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle") + QString::number(r), d);
+ return index;
+}
+
+QSqlRecord QODBCDriver::record(const QString& tablename) const
+{
+ QSqlRecord fil;
+ if (!isOpen())
+ return fil;
+
+ SQLHANDLE hStmt;
+ QString catalog, schema, table;
+ d->splitTableQualifier(tablename, catalog, schema, table);
+
+ if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
+ catalog = stripDelimiters(catalog, QSqlDriver::TableName);
+ else
+ catalog = d->adjustCase(catalog);
+
+ if (isIdentifierEscaped(schema, QSqlDriver::TableName))
+ schema = stripDelimiters(schema, QSqlDriver::TableName);
+ else
+ schema = d->adjustCase(schema);
+
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ else
+ table = d->adjustCase(table);
+
+ SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
+ d->hDbc,
+ &hStmt);
+ if (r != SQL_SUCCESS) {
+ qSqlWarning(QLatin1String("QODBCDriver::record: Unable to allocate handle"), d);
+ return fil;
+ }
+ r = SQLSetStmtAttr(hStmt,
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+ r = SQLColumns(hStmt,
+#ifdef UNICODE
+ catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
+#else
+ catalog.length() == 0 ? NULL : (SQLCHAR*)catalog.toUtf8().data(),
+#endif
+ catalog.length(),
+#ifdef UNICODE
+ schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
+#else
+ schema.length() == 0 ? NULL : (SQLCHAR*)schema.toUtf8().data(),
+#endif
+ schema.length(),
+#ifdef UNICODE
+ toSQLTCHAR(table).data(),
+#else
+ (SQLCHAR*)table.toUtf8().data(),
+#endif
+ table.length(),
+ NULL,
+ 0);
+ if (r != SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QODBCDriver::record: Unable to execute column list"), d);
+
+ if (d->hasSQLFetchScroll)
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ else
+ r = SQLFetch(hStmt);
+
+ // Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
+ while (r == SQL_SUCCESS) {
+
+ fil.append(qMakeFieldInfo(hStmt, d));
+
+ if (d->hasSQLFetchScroll)
+ r = SQLFetchScroll(hStmt,
+ SQL_FETCH_NEXT,
+ 0);
+ else
+ r = SQLFetch(hStmt);
+ }
+
+ r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
+ if (r!= SQL_SUCCESS)
+ qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") + QString::number(r), d);
+
+ return fil;
+}
+
+QString QODBCDriver::formatValue(const QSqlField &field,
+ bool trimStrings) const
+{
+ QString r;
+ if (field.isNull()) {
+ r = QLatin1String("NULL");
+ } else if (field.type() == QVariant::DateTime) {
+ // Use an escape sequence for the datetime fields
+ if (field.value().toDateTime().isValid()){
+ QDate dt = field.value().toDateTime().date();
+ QTime tm = field.value().toDateTime().time();
+ // Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10
+ r = QLatin1String("{ ts '") +
+ QString::number(dt.year()) + QLatin1Char('-') +
+ QString::number(dt.month()).rightJustified(2, QLatin1Char('0'), true) +
+ QLatin1Char('-') +
+ QString::number(dt.day()).rightJustified(2, QLatin1Char('0'), true) +
+ QLatin1Char(' ') +
+ tm.toString() +
+ QLatin1String("' }");
+ } else
+ r = QLatin1String("NULL");
+ } else if (field.type() == QVariant::ByteArray) {
+ QByteArray ba = field.value().toByteArray();
+ QString res;
+ static const char hexchars[] = "0123456789abcdef";
+ for (int i = 0; i < ba.size(); ++i) {
+ uchar s = (uchar) ba[i];
+ res += QLatin1Char(hexchars[s >> 4]);
+ res += QLatin1Char(hexchars[s & 0x0f]);
+ }
+ r = QLatin1String("0x") + res;
+ } else {
+ r = QSqlDriver::formatValue(field, trimStrings);
+ }
+ return r;
+}
+
+QVariant QODBCDriver::handle() const
+{
+ return QVariant(qRegisterMetaType<SQLHANDLE>("SQLHANDLE"), &d->hDbc);
+}
+
+QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
+{
+ QChar quote = d->quoteChar();
+ QString res = identifier;
+ if(!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) {
+ res.replace(quote, QString(quote)+QString(quote));
+ res.prepend(quote).append(quote);
+ res.replace(QLatin1Char('.'), QString(quote)+QLatin1Char('.')+QString(quote));
+ }
+ return res;
+}
+
+bool QODBCDriver::isIdentifierEscapedImplementation(const QString &identifier, IdentifierType) const
+{
+ QChar quote = d->quoteChar();
+ return identifier.size() > 2
+ && identifier.startsWith(quote) //left delimited
+ && identifier.endsWith(quote); //right delimited
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/drivers/odbc/qsql_odbc.h b/src/sql/drivers/odbc/qsql_odbc.h
new file mode 100644
index 0000000000..3854b8e888
--- /dev/null
+++ b/src/sql/drivers/odbc/qsql_odbc.h
@@ -0,0 +1,158 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_ODBC_H
+#define QSQL_ODBC_H
+
+#include <QtSql/qsqldriver.h>
+#include <QtSql/qsqlresult.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_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QODBCPrivate;
+class QODBCDriverPrivate;
+class QODBCDriver;
+class QSqlRecordInfo;
+
+class QODBCResult : public QSqlResult
+{
+public:
+ QODBCResult(const QODBCDriver * db, QODBCDriverPrivate* p);
+ virtual ~QODBCResult();
+
+ bool prepare(const QString& query);
+ bool exec();
+
+ QVariant handle() const;
+ virtual void setForwardOnly(bool forward);
+
+protected:
+ bool fetchNext();
+ bool fetchFirst();
+ bool fetchLast();
+ bool fetchPrevious();
+ bool fetch(int i);
+ bool reset (const QString& query);
+ QVariant data(int field);
+ bool isNull(int field);
+ int size();
+ int numRowsAffected();
+ QSqlRecord record() const;
+ void virtual_hook(int id, void *data);
+ bool nextResult();
+
+private:
+ QODBCPrivate *d;
+};
+
+class Q_EXPORT_SQLDRIVER_ODBC QODBCDriver : public QSqlDriver
+{
+ Q_OBJECT
+public:
+ explicit QODBCDriver(QObject *parent=0);
+ QODBCDriver(SQLHANDLE env, SQLHANDLE con, QObject * parent=0);
+ virtual ~QODBCDriver();
+ bool hasFeature(DriverFeature f) const;
+ void close();
+ QSqlResult *createResult() const;
+ QStringList tables(QSql::TableType) const;
+ QSqlRecord record(const QString& tablename) const;
+ QSqlIndex primaryIndex(const QString& tablename) const;
+ QVariant handle() const;
+ QString formatValue(const QSqlField &field,
+ bool trimStrings) const;
+ bool open(const QString& db,
+ const QString& user,
+ const QString& password,
+ const QString& host,
+ int port,
+ const QString& connOpts);
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const;
+
+protected Q_SLOTS:
+ bool isIdentifierEscapedImplementation(const QString &identifier, IdentifierType type) const;
+
+protected:
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+
+private:
+ void init();
+ bool endTrans();
+ void cleanup();
+ QODBCDriverPrivate* d;
+ friend class QODBCPrivate;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQL_ODBC_H
diff --git a/src/sql/drivers/odbc/qsql_odbc.pri b/src/sql/drivers/odbc/qsql_odbc.pri
new file mode 100644
index 0000000000..66a8d51af0
--- /dev/null
+++ b/src/sql/drivers/odbc/qsql_odbc.pri
@@ -0,0 +1,13 @@
+HEADERS += $$PWD/qsql_odbc.h
+SOURCES += $$PWD/qsql_odbc.cpp
+
+unix {
+ DEFINES += UNICODE
+ !contains(LIBS, .*odbc.*) {
+ macx:LIBS += -liodbc
+ else:LIBS += $$QT_LFLAGS_ODBC
+ }
+} else {
+ win32-borland:LIBS *= $(BCB)/lib/PSDK/odbc32.lib
+ else:LIBS *= -lodbc32
+}
diff --git a/src/sql/drivers/psql/qsql_psql.cpp b/src/sql/drivers/psql/qsql_psql.cpp
new file mode 100644
index 0000000000..34277b3afa
--- /dev/null
+++ b/src/sql/drivers/psql/qsql_psql.cpp
@@ -0,0 +1,1372 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_psql.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 <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
+
+/* 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_METATYPE(PGconn*)
+Q_DECLARE_METATYPE(PGresult*)
+
+QT_BEGIN_NAMESPACE
+
+inline void qPQfreemem(void *buffer)
+{
+ PQfreemem(buffer);
+}
+
+class QPSQLDriverPrivate
+{
+public:
+ QPSQLDriverPrivate() : connection(0), isUtf8(false), pro(QPSQLDriver::Version6), sn(0) {}
+ PGconn *connection;
+ bool isUtf8;
+ QPSQLDriver::Protocol pro;
+ QSocketNotifier *sn;
+ QStringList seid;
+
+ void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
+};
+
+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));
+ }
+}
+
+class QPSQLResultPrivate
+{
+public:
+ QPSQLResultPrivate(QPSQLResult *qq): q(qq), driver(0), result(0), currentSize(-1), preparedQueriesEnabled(false) {}
+
+ QPSQLResult *q;
+ const QPSQLDriverPrivate *driver;
+ PGresult *result;
+ int currentSize;
+ bool preparedQueriesEnabled;
+ QString preparedStmtId;
+
+ bool processResults();
+};
+
+static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
+ const QPSQLDriverPrivate *p)
+{
+ const char *s = PQerrorMessage(p->connection);
+ QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
+ return QSqlError(QLatin1String("QPSQL: ") + err, msg, type);
+}
+
+bool QPSQLResultPrivate::processResults()
+{
+ 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, driver));
+ 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;
+}
+
+static void qDeallocatePreparedStmt(QPSQLResultPrivate *d)
+{
+ const QString stmt = QLatin1String("DEALLOCATE ") + d->preparedStmtId;
+ PGresult *result = PQexec(d->driver->connection,
+ d->driver->isUtf8 ? stmt.toUtf8().constData()
+ : stmt.toLocal8Bit().constData());
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ qWarning("Unable to free statement: %s", PQerrorMessage(d->driver->connection));
+ PQclear(result);
+ d->preparedStmtId.clear();
+}
+
+QPSQLResult::QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p)
+ : QSqlResult(db)
+{
+ d = new QPSQLResultPrivate(this);
+ d->driver = p;
+ d->preparedQueriesEnabled = db->hasFeature(QSqlDriver::PreparedQueries);
+}
+
+QPSQLResult::~QPSQLResult()
+{
+ cleanup();
+
+ if (d->preparedQueriesEnabled && !d->preparedStmtId.isNull())
+ qDeallocatePreparedStmt(d);
+
+ delete d;
+}
+
+QVariant QPSQLResult::handle() const
+{
+ return QVariant::fromValue(d->result);
+}
+
+void QPSQLResult::cleanup()
+{
+ if (d->result)
+ PQclear(d->result);
+ d->result = 0;
+ setAt(QSql::BeforeFirstRow);
+ d->currentSize = -1;
+ setActive(false);
+}
+
+bool QPSQLResult::fetch(int i)
+{
+ 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()
+{
+ return fetch(PQntuples(d->result) - 1);
+}
+
+QVariant QPSQLResult::data(int i)
+{
+ 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->driver->isUtf8 ? QString::fromUtf8(val) : QString::fromAscii(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::fromAscii(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::fromAscii(val);
+ }
+ return QString::fromAscii(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());
+ if (str.at(str.length() - 3) == QLatin1Char('+') || str.at(str.length() - 3) == QLatin1Char('-'))
+ // strip the timezone
+ // TODO: fix this when timestamp support comes into QDateTime
+ return QVariant(QTime::fromString(str.left(str.length() - 3), Qt::ISODate));
+ 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());
+ // remove the timezone
+ // TODO: fix this when timestamp support comes into QDateTime
+ if (dtval.at(dtval.length() - 3) == QLatin1Char('+') || dtval.at(dtval.length() - 3) == QLatin1Char('-'))
+ dtval.chop(3);
+ // milliseconds are sometimes returned with 2 digits only
+ if (dtval.at(dtval.length() - 3).isPunct())
+ dtval += QLatin1Char('0');
+ if (dtval.isEmpty())
+ return QVariant(QDateTime());
+ else
+ return QVariant(QDateTime::fromString(dtval, Qt::ISODate));
+#else
+ return QVariant(dtval);
+#endif
+ }
+ case QVariant::ByteArray: {
+ size_t len;
+ unsigned char *data = PQunescapeBytea((unsigned char*)val, &len);
+ QByteArray ba((const char*)data, len);
+ qPQfreemem(data);
+ return QVariant(ba);
+ }
+ default:
+ case QVariant::Invalid:
+ qWarning("QPSQLResult::data: unknown data type");
+ }
+ return QVariant();
+}
+
+bool QPSQLResult::isNull(int field)
+{
+ PQgetvalue(d->result, at(), field);
+ return PQgetisnull(d->result, at(), field);
+}
+
+bool QPSQLResult::reset (const QString& query)
+{
+ cleanup();
+ if (!driver())
+ return false;
+ if (!driver()->isOpen() || driver()->isOpenError())
+ return false;
+ d->result = PQexec(d->driver->connection,
+ d->driver->isUtf8 ? query.toUtf8().constData()
+ : query.toLocal8Bit().constData());
+ return d->processResults();
+}
+
+int QPSQLResult::size()
+{
+ return d->currentSize;
+}
+
+int QPSQLResult::numRowsAffected()
+{
+ return QString::fromLatin1(PQcmdTuples(d->result)).toInt();
+}
+
+QVariant QPSQLResult::lastInsertId() const
+{
+ if (isActive()) {
+ Oid id = PQoidValue(d->result);
+ if (id != InvalidOid)
+ return QVariant(id);
+ }
+ return QVariant();
+}
+
+QSqlRecord QPSQLResult::record() const
+{
+ QSqlRecord info;
+ if (!isActive() || !isSelect())
+ return info;
+
+ int count = PQnfields(d->result);
+ for (int i = 0; i < count; ++i) {
+ QSqlField f;
+ if (d->driver->isUtf8)
+ f.setName(QString::fromUtf8(PQfname(d->result, i)));
+ else
+ f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
+ f.setType(qDecodePSQLType(PQftype(d->result, i)));
+ int len = PQfsize(d->result, i);
+ int precision = PQfmod(d->result, i);
+ // swap length and precision if length == -1
+ if (len == -1 && precision > -1) {
+ len = precision - 4;
+ precision = -1;
+ }
+ f.setLength(len);
+ f.setPrecision(precision);
+ f.setSqlType(PQftype(d->result, i));
+ info.append(f);
+ }
+ return info;
+}
+
+void QPSQLResult::virtual_hook(int id, void *data)
+{
+ Q_ASSERT(data);
+
+ switch (id) {
+ default:
+ QSqlResult::virtual_hook(id, data);
+ }
+}
+
+static QString qReplacePlaceholderMarkers(const QString &query)
+{
+ const int originalLength = query.length();
+ bool inQuote = false;
+ int markerIdx = 0;
+ QString result;
+ result.reserve(originalLength + 23);
+ for (int i = 0; i < originalLength; ++i) {
+ const QChar ch = query.at(i);
+ if (ch == QLatin1Char('?') && !inQuote) {
+ result += QLatin1Char('$');
+ result += QString::number(++markerIdx);
+ } else {
+ if (ch == QLatin1Char('\''))
+ inQuote = !inQuote;
+ result += ch;
+ }
+ }
+
+ result.squeeze();
+ return result;
+}
+
+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)
+{
+ if (!d->preparedQueriesEnabled)
+ return QSqlResult::prepare(query);
+
+ cleanup();
+
+ if (!d->preparedStmtId.isEmpty())
+ qDeallocatePreparedStmt(d);
+
+ const QString stmtId = qMakePreparedStmtId();
+ const QString stmt = QString::fromLatin1("PREPARE %1 AS ").arg(stmtId).append(qReplacePlaceholderMarkers(query));
+
+ PGresult *result = PQexec(d->driver->connection,
+ d->driver->isUtf8 ? stmt.toUtf8().constData()
+ : stmt.toLocal8Bit().constData());
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+ setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
+ "Unable to prepare statement"), QSqlError::StatementError, d->driver));
+ PQclear(result);
+ d->preparedStmtId.clear();
+ return false;
+ }
+
+ PQclear(result);
+ d->preparedStmtId = stmtId;
+ return true;
+}
+
+bool QPSQLResult::exec()
+{
+ if (!d->preparedQueriesEnabled)
+ return QSqlResult::exec();
+
+ cleanup();
+
+ QString stmt;
+ const QString params = qCreateParamString(boundValues(), d->q->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 = PQexec(d->driver->connection,
+ d->driver->isUtf8 ? stmt.toUtf8().constData()
+ : stmt.toLocal8Bit().constData());
+
+ return d->processResults();
+}
+
+///////////////////////////////////////////////////////////////////
+
+static bool setEncodingUtf8(PGconn* connection)
+{
+ PGresult* result = PQexec(connection, "SET CLIENT_ENCODING TO 'UNICODE'");
+ int status = PQresultStatus(result);
+ PQclear(result);
+ return status == PGRES_COMMAND_OK;
+}
+
+static void setDatestyle(PGconn* connection)
+{
+ PGresult* result = PQexec(connection, "SET DATESTYLE TO 'ISO'");
+ int status = PQresultStatus(result);
+ if (status != PGRES_COMMAND_OK)
+ qWarning("%s", PQerrorMessage(connection));
+ 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;
+}
+
+static QPSQLDriver::Protocol getPSQLVersion(PGconn* connection)
+{
+ QPSQLDriver::Protocol serverVersion = QPSQLDriver::Version6;
+ PGresult* result = PQexec(connection, "select version()");
+ int status = PQresultStatus(result);
+ if (status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK) {
+ QString val = QString::fromAscii(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);
+#ifdef PG_MAJORVERSION
+ if (rx.indexIn(QLatin1String(PG_MAJORVERSION)) != -1) {
+ 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.
+ result = PQexec(connection, "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.");
+ }
+#endif
+ }
+ }
+ 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(parent)
+{
+ init();
+}
+
+QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent)
+ : QSqlDriver(parent)
+{
+ init();
+ d->connection = conn;
+ if (conn) {
+ d->pro = getPSQLVersion(d->connection);
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+void QPSQLDriver::init()
+{
+ d = new QPSQLDriverPrivate();
+}
+
+QPSQLDriver::~QPSQLDriver()
+{
+ if (d->connection)
+ PQfinish(d->connection);
+ delete d;
+}
+
+QVariant QPSQLDriver::handle() const
+{
+ return QVariant::fromValue(d->connection);
+}
+
+bool QPSQLDriver::hasFeature(DriverFeature f) const
+{
+ 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:
+ 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)
+{
+ 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 = getPSQLVersion(d->connection);
+ d->isUtf8 = setEncodingUtf8(d->connection);
+ setDatestyle(d->connection);
+
+ setOpen(true);
+ setOpenError(false);
+ return true;
+}
+
+void QPSQLDriver::close()
+{
+ 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, d);
+}
+
+bool QPSQLDriver::beginTransaction()
+{
+ if (!isOpen()) {
+ qWarning("QPSQLDriver::beginTransaction: Database not open");
+ return false;
+ }
+ PGresult* res = PQexec(d->connection, "BEGIN");
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
+ PQclear(res);
+ setLastError(qMakeError(tr("Could not begin transaction"),
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ PQclear(res);
+ return true;
+}
+
+bool QPSQLDriver::commitTransaction()
+{
+ if (!isOpen()) {
+ qWarning("QPSQLDriver::commitTransaction: Database not open");
+ return false;
+ }
+ PGresult* res = PQexec(d->connection, "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) {
+ PQclear(res);
+ setLastError(qMakeError(tr("Could not commit transaction"),
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ PQclear(res);
+ return true;
+}
+
+bool QPSQLDriver::rollbackTransaction()
+{
+ if (!isOpen()) {
+ qWarning("QPSQLDriver::rollbackTransaction: Database not open");
+ return false;
+ }
+ PGresult* res = PQexec(d->connection, "ROLLBACK");
+ if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) {
+ setLastError(qMakeError(tr("Could not rollback transaction"),
+ QSqlError::TransactionError, d));
+ PQclear(res);
+ return false;
+ }
+ PQclear(res);
+ return true;
+}
+
+QStringList QPSQLDriver::tables(QSql::TableType type) const
+{
+ QStringList tl;
+ if (!isOpen())
+ return tl;
+ QSqlQuery t(createResult());
+ t.setForwardOnly(true);
+
+ if (type & QSql::Tables)
+ d->appendTables(tl, t, QLatin1Char('r'));
+ if (type & QSql::Views)
+ 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
+{
+ 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
+{
+ 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;
+}
+
+QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
+{
+ QString r;
+ if (field.isNull()) {
+ r = QLatin1String("NULL");
+ } else {
+ switch (field.type()) {
+ case QVariant::DateTime:
+#ifndef QT_NO_DATESTRING
+ if (field.value().toDateTime().isValid()) {
+ QDate dt = field.value().toDateTime().date();
+ QTime tm = field.value().toDateTime().time();
+ // msecs need to be right aligned otherwise psql
+ // interpretes them wrong
+ r = QLatin1Char('\'') + QString::number(dt.year()) + QLatin1Char('-')
+ + QString::number(dt.month()) + QLatin1Char('-')
+ + QString::number(dt.day()) + QLatin1Char(' ')
+ + tm.toString() + QLatin1Char('.')
+ + QString::number(tm.msec()).rightJustified(3, QLatin1Char('0'))
+ + 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:
+ {
+ // Escape '\' characters
+ r = QSqlDriver::formatValue(field, trimStrings);
+ 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, (unsigned char*)ba.constData(), ba.size(), &len);
+#else
+ unsigned char *data = PQescapeBytea((unsigned char*)ba.constData(), ba.size(), &len);
+#endif
+ r += QLatin1Char('\'');
+ r += QLatin1String((const char*)data);
+ r += QLatin1Char('\'');
+ qPQfreemem(data);
+ break;
+ }
+ case QVariant::Double: {
+ double val = field.value().toDouble();
+ if (isnan(val))
+ r = QLatin1String("'NaN'");
+ else {
+ int res = isinf(val);
+ if (res == 1)
+ r = QLatin1String("'Infinity'");
+ else if (res == -1)
+ r = QLatin1String("'-Infinity'");
+ else
+ r = QSqlDriver::formatValue(field, trimStrings);
+ }
+ 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
+{
+ return PQstatus(d->connection) == CONNECTION_OK;
+}
+
+QPSQLDriver::Protocol QPSQLDriver::protocol() const
+{
+ return d->pro;
+}
+
+bool QPSQLDriver::subscribeToNotificationImplementation(const QString &name)
+{
+ 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) {
+ QString query = QLatin1String("LISTEN ") + escapeIdentifier(name, QSqlDriver::TableName);
+ if (PQresultStatus(PQexec(d->connection,
+ d->isUtf8 ? query.toUtf8().constData()
+ : query.toLocal8Bit().constData())
+ ) != PGRES_COMMAND_OK) {
+ setLastError(qMakeError(tr("Unable to subscribe"), QSqlError::StatementError, d));
+ return false;
+ }
+
+ if (!d->sn) {
+ d->sn = new QSocketNotifier(socket, QSocketNotifier::Read);
+ connect(d->sn, SIGNAL(activated(int)), this, SLOT(_q_handleNotification(int)));
+ }
+ }
+
+ d->seid << name;
+ return true;
+}
+
+bool QPSQLDriver::unsubscribeFromNotificationImplementation(const QString &name)
+{
+ 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);
+ if (PQresultStatus(PQexec(d->connection,
+ d->isUtf8 ? query.toUtf8().constData()
+ : query.toLocal8Bit().constData())
+ ) != PGRES_COMMAND_OK) {
+ setLastError(qMakeError(tr("Unable to unsubscribe"), QSqlError::StatementError, d));
+ return false;
+ }
+
+ 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::subscribedToNotificationsImplementation() const
+{
+ return d->seid;
+}
+
+void QPSQLDriver::_q_handleNotification(int)
+{
+ PQconsumeInput(d->connection);
+
+ PGnotify *notify = 0;
+ while((notify = PQnotifies(d->connection)) != 0) {
+ QString name(QLatin1String(notify->relname));
+ if (d->seid.contains(name))
+ emit notification(name);
+ else
+ qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.",
+ qPrintable(name));
+
+ qPQfreemem(notify);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/drivers/psql/qsql_psql.h b/src/sql/drivers/psql/qsql_psql.h
new file mode 100644
index 0000000000..284a122b18
--- /dev/null
+++ b/src/sql/drivers/psql/qsql_psql.h
@@ -0,0 +1,159 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_PSQL_H
+#define QSQL_PSQL_H
+
+#include <QtSql/qsqlresult.h>
+#include <QtSql/qsqldriver.h>
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_PSQL
+#else
+#define Q_EXPORT_SQLDRIVER_PSQL Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_HEADER
+
+typedef struct pg_conn PGconn;
+typedef struct pg_result PGresult;
+
+QT_BEGIN_NAMESPACE
+
+class QPSQLResultPrivate;
+class QPSQLDriverPrivate;
+class QPSQLDriver;
+class QSqlRecordInfo;
+
+class QPSQLResult : public QSqlResult
+{
+ friend class QPSQLResultPrivate;
+public:
+ QPSQLResult(const QPSQLDriver* db, const QPSQLDriverPrivate* p);
+ ~QPSQLResult();
+
+ QVariant handle() const;
+ void virtual_hook(int id, void *data);
+
+protected:
+ void cleanup();
+ bool fetch(int i);
+ bool fetchFirst();
+ bool fetchLast();
+ QVariant data(int i);
+ bool isNull(int field);
+ bool reset (const QString& query);
+ int size();
+ int numRowsAffected();
+ QSqlRecord record() const;
+ QVariant lastInsertId() const;
+ bool prepare(const QString& query);
+ bool exec();
+
+private:
+ QPSQLResultPrivate *d;
+};
+
+class Q_EXPORT_SQLDRIVER_PSQL QPSQLDriver : public QSqlDriver
+{
+ 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;
+ bool open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString& connOpts);
+ bool isOpen() const;
+ void close();
+ QSqlResult *createResult() const;
+ QStringList tables(QSql::TableType) const;
+ QSqlIndex primaryIndex(const QString& tablename) const;
+ QSqlRecord record(const QString& tablename) const;
+
+ Protocol protocol() const;
+ QVariant handle() const;
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const;
+ QString formatValue(const QSqlField &field, bool trimStrings) const;
+
+protected:
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+
+protected Q_SLOTS:
+ bool subscribeToNotificationImplementation(const QString &name);
+ bool unsubscribeFromNotificationImplementation(const QString &name);
+ QStringList subscribedToNotificationsImplementation() const;
+
+private Q_SLOTS:
+ void _q_handleNotification(int);
+
+private:
+ void init();
+ QPSQLDriverPrivate *d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQL_PSQL_H
diff --git a/src/sql/drivers/psql/qsql_psql.pri b/src/sql/drivers/psql/qsql_psql.pri
new file mode 100644
index 0000000000..6da3540104
--- /dev/null
+++ b/src/sql/drivers/psql/qsql_psql.pri
@@ -0,0 +1,10 @@
+HEADERS += $$PWD/qsql_psql.h
+SOURCES += $$PWD/qsql_psql.cpp
+
+unix|win32-g++* {
+ LIBS *= $$QT_LFLAGS_PSQL
+ !contains(LIBS, .*pq.*):LIBS += -lpq
+ QMAKE_CXXFLAGS *= $$QT_CFLAGS_PSQL
+} else {
+ !contains(LIBS, .*pq.*):LIBS += -llibpq -lws2_32 -ladvapi32
+}
diff --git a/src/sql/drivers/sqlite/qsql_sqlite.cpp b/src/sql/drivers/sqlite/qsql_sqlite.cpp
new file mode 100644
index 0000000000..727c4f9bcc
--- /dev/null
+++ b/src/sql/drivers/sqlite/qsql_sqlite.cpp
@@ -0,0 +1,709 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_sqlite.h"
+
+#include <qcoreapplication.h>
+#include <qvariant.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlquery.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_METATYPE(sqlite3*)
+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.startsWith(QLatin1String("numeric")))
+ return QVariant::Double;
+ if (typeName == QLatin1String("blob"))
+ return QVariant::ByteArray;
+ 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, errorCode);
+}
+
+class QSQLiteDriverPrivate
+{
+public:
+ inline QSQLiteDriverPrivate() : access(0) {}
+ sqlite3 *access;
+};
+
+
+class QSQLiteResultPrivate
+{
+public:
+ QSQLiteResultPrivate(QSQLiteResult *res);
+ void cleanup();
+ bool fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch);
+ // initializes the recordInfo and the cache
+ void initColumns(bool emptyResultset);
+ void finalize();
+
+ QSQLiteResult* q;
+ sqlite3 *access;
+
+ 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* res) : q(res), access(0),
+ stmt(0), skippedStatus(false), skipRow(false)
+{
+}
+
+void QSQLiteResultPrivate::cleanup()
+{
+ 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)
+{
+ 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)));
+
+ int dotIdx = colName.lastIndexOf(QLatin1Char('.'));
+ QSqlField fld(colName.mid(dotIdx == -1 ? 0 : dotIdx + 1), qGetColumnType(typeName));
+
+ // sqlite3_column_type is documented to have undefined behavior if the result set is empty
+ int stp = emptyResultset ? -1 : sqlite3_column_type(stmt, i);
+ fld.setSqlType(stp);
+ rInf.append(fld);
+ }
+}
+
+bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch)
+{
+ 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(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(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(db)
+{
+ d = new QSQLiteResultPrivate(this);
+ d->access = db->d->access;
+}
+
+QSQLiteResult::~QSQLiteResult()
+{
+ d->cleanup();
+ delete d;
+}
+
+void QSQLiteResult::virtual_hook(int id, void *data)
+{
+ switch (id) {
+ case QSqlResult::DetachFromResultSet:
+ if (d->stmt)
+ sqlite3_reset(d->stmt);
+ break;
+ default:
+ QSqlCachedResult::virtual_hook(id, data);
+ }
+}
+
+bool QSQLiteResult::reset(const QString &query)
+{
+ if (!prepare(query))
+ return false;
+ return exec();
+}
+
+bool QSQLiteResult::prepare(const QString &query)
+{
+ if (!driver() || !driver()->isOpen() || driver()->isOpenError())
+ return false;
+
+ d->cleanup();
+
+ setSelect(false);
+
+#if (SQLITE_VERSION_NUMBER >= 3003011)
+ int res = sqlite3_prepare16_v2(d->access, query.constData(), (query.size() + 1) * sizeof(QChar),
+ &d->stmt, 0);
+#else
+ int res = sqlite3_prepare16(d->access, query.constData(), (query.size() + 1) * sizeof(QChar),
+ &d->stmt, 0);
+#endif
+
+ if (res != SQLITE_OK) {
+ setLastError(qMakeError(d->access, QCoreApplication::translate("QSQLiteResult",
+ "Unable to execute statement"), QSqlError::StatementError, res));
+ d->finalize();
+ return false;
+ }
+ return true;
+}
+
+bool QSQLiteResult::exec()
+{
+ 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->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:
+ 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::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->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)
+{
+ return d->fetchNext(row, idx, false);
+}
+
+int QSQLiteResult::size()
+{
+ return -1;
+}
+
+int QSQLiteResult::numRowsAffected()
+{
+ return sqlite3_changes(d->access);
+}
+
+QVariant QSQLiteResult::lastInsertId() const
+{
+ if (isActive()) {
+ qint64 id = sqlite3_last_insert_rowid(d->access);
+ if (id)
+ return id;
+ }
+ return QVariant();
+}
+
+QSqlRecord QSQLiteResult::record() const
+{
+ if (!isActive() || !isSelect())
+ return QSqlRecord();
+ return d->rInf;
+}
+
+QVariant QSQLiteResult::handle() const
+{
+ return QVariant::fromValue(d->stmt);
+}
+
+/////////////////////////////////////////////////////////
+
+QSQLiteDriver::QSQLiteDriver(QObject * parent)
+ : QSqlDriver(parent)
+{
+ d = new QSQLiteDriverPrivate();
+}
+
+QSQLiteDriver::QSQLiteDriver(sqlite3 *connection, QObject *parent)
+ : QSqlDriver(parent)
+{
+ d = new QSQLiteDriverPrivate();
+ d->access = connection;
+ setOpen(true);
+ setOpenError(false);
+}
+
+
+QSQLiteDriver::~QSQLiteDriver()
+{
+ delete d;
+}
+
+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:
+ 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)
+{
+ if (isOpen())
+ close();
+
+ if (db.isEmpty())
+ return false;
+ bool sharedCache = false;
+ int openMode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, timeOut=5000;
+ QStringList opts=QString(conOpts).remove(QLatin1Char(' ')).split(QLatin1Char(';'));
+ foreach(const QString &option, opts) {
+ if (option.startsWith(QLatin1String("QSQLITE_BUSY_TIMEOUT="))) {
+ bool ok;
+ int nt = option.mid(21).toInt(&ok);
+ if (ok)
+ timeOut = nt;
+ }
+ if (option == QLatin1String("QSQLITE_OPEN_READONLY"))
+ openMode = SQLITE_OPEN_READONLY;
+ if (option == QLatin1String("QSQLITE_ENABLE_SHARED_CACHE"))
+ sharedCache = true;
+ }
+
+ 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 {
+ setLastError(qMakeError(d->access, tr("Error opening database"),
+ QSqlError::ConnectionError));
+ setOpenError(true);
+ return false;
+ }
+}
+
+void QSQLiteDriver::close()
+{
+ if (isOpen()) {
+ 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) + QLatin1String(")"));
+
+ 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
+{
+ 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/sql/drivers/sqlite/qsql_sqlite.h b/src/sql/drivers/sqlite/qsql_sqlite.h
new file mode 100644
index 0000000000..53f2755a45
--- /dev/null
+++ b/src/sql/drivers/sqlite/qsql_sqlite.h
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_SQLITE_H
+#define QSQL_SQLITE_H
+
+#include <QtSql/qsqldriver.h>
+#include <QtSql/qsqlresult.h>
+#include <QtSql/private/qsqlcachedresult_p.h>
+
+struct sqlite3;
+
+#ifdef QT_PLUGIN
+#define Q_EXPORT_SQLDRIVER_SQLITE
+#else
+#define Q_EXPORT_SQLDRIVER_SQLITE Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+class QSQLiteDriverPrivate;
+class QSQLiteResultPrivate;
+class QSQLiteDriver;
+
+class QSQLiteResult : public QSqlCachedResult
+{
+ friend class QSQLiteDriver;
+ friend class QSQLiteResultPrivate;
+public:
+ explicit QSQLiteResult(const QSQLiteDriver* db);
+ ~QSQLiteResult();
+ QVariant handle() const;
+
+protected:
+ bool gotoNext(QSqlCachedResult::ValueCache& row, int idx);
+ bool reset(const QString &query);
+ bool prepare(const QString &query);
+ bool exec();
+ int size();
+ int numRowsAffected();
+ QVariant lastInsertId() const;
+ QSqlRecord record() const;
+ void virtual_hook(int id, void *data);
+
+private:
+ QSQLiteResultPrivate* d;
+};
+
+class Q_EXPORT_SQLDRIVER_SQLITE QSQLiteDriver : public QSqlDriver
+{
+ Q_OBJECT
+ friend class QSQLiteResult;
+public:
+ explicit QSQLiteDriver(QObject *parent = 0);
+ explicit QSQLiteDriver(sqlite3 *connection, QObject *parent = 0);
+ ~QSQLiteDriver();
+ bool hasFeature(DriverFeature f) const;
+ bool open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString & connOpts);
+ void close();
+ QSqlResult *createResult() const;
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+ QStringList tables(QSql::TableType) const;
+
+ QSqlRecord record(const QString& tablename) const;
+ QSqlIndex primaryIndex(const QString &table) const;
+ QVariant handle() const;
+ QString escapeIdentifier(const QString &identifier, IdentifierType) const;
+
+private:
+ QSQLiteDriverPrivate* d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQL_SQLITE_H
diff --git a/src/sql/drivers/sqlite/qsql_sqlite.pri b/src/sql/drivers/sqlite/qsql_sqlite.pri
new file mode 100644
index 0000000000..78a4e49979
--- /dev/null
+++ b/src/sql/drivers/sqlite/qsql_sqlite.pri
@@ -0,0 +1,11 @@
+HEADERS += $$PWD/qsql_sqlite.h
+SOURCES += $$PWD/qsql_sqlite.cpp
+
+symbian:include($$QT_SOURCE_TREE/src/plugins/sqldrivers/sqlite_symbian/sqlite_symbian.pri)
+
+!system-sqlite:!contains(LIBS, .*sqlite3.*) {
+ include($$PWD/../../../3rdparty/sqlite.pri)
+} else {
+ LIBS *= $$QT_LFLAGS_SQLITE
+ QMAKE_CXXFLAGS *= $$QT_CFLAGS_SQLITE
+}
diff --git a/src/sql/drivers/sqlite2/qsql_sqlite2.cpp b/src/sql/drivers/sqlite2/qsql_sqlite2.cpp
new file mode 100644
index 0000000000..2f3c780370
--- /dev/null
+++ b/src/sql/drivers/sqlite2/qsql_sqlite2.cpp
@@ -0,0 +1,575 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsql_sqlite2.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 <qstringlist.h>
+#include <qvector.h>
+
+#if !defined Q_WS_WIN32
+# include <unistd.h>
+#endif
+#include <sqlite.h>
+
+typedef struct sqlite_vm sqlite_vm;
+
+Q_DECLARE_METATYPE(sqlite_vm*)
+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:
+ QSQLite2DriverPrivate();
+ sqlite *access;
+ bool utf8;
+};
+
+QSQLite2DriverPrivate::QSQLite2DriverPrivate() : access(0)
+{
+ utf8 = (qstrcmp(sqlite_encoding, "UTF-8") == 0);
+}
+
+class QSQLite2ResultPrivate
+{
+public:
+ QSQLite2ResultPrivate(QSQLite2Result *res);
+ 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();
+
+ QSQLite2Result* q;
+ sqlite *access;
+
+ // 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()?
+ bool utf8;
+ QSqlRecord rInf;
+ QVector<QVariant> firstRow;
+};
+
+static const uint initial_cache_size = 128;
+
+QSQLite2ResultPrivate::QSQLite2ResultPrivate(QSQLite2Result* res) : q(res), access(0), currentTail(0),
+ currentMachine(0), skippedStatus(false), skipRow(false), utf8(false)
+{
+}
+
+void QSQLite2ResultPrivate::cleanup()
+{
+ finalize();
+ rInf.clear();
+ currentTail = 0;
+ currentMachine = 0;
+ skippedStatus = false;
+ skipRow = false;
+ q->setAt(QSql::BeforeFirstRow);
+ q->setActive(false);
+ q->cleanup();
+}
+
+void QSQLite2ResultPrivate::finalize()
+{
+ 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::fromAscii(err),
+ QSqlError::StatementError, res));
+ sqlite_freemem(err);
+ }
+ currentMachine = 0;
+}
+
+// called on first fetch
+void QSQLite2ResultPrivate::init(const char **cnames, int numCols)
+{
+ 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::fromAscii(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::fromAscii(cnames[i+numCols]))));
+ }
+}
+
+bool QSQLite2ResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int idx, bool initialFetch)
+{
+ // 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_WS_WIN32
+ 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] = utf8 ? QString::fromUtf8(fvals[i]) : QString::fromAscii(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(db)
+{
+ d = new QSQLite2ResultPrivate(this);
+ d->access = db->d->access;
+ d->utf8 = db->d->utf8;
+}
+
+QSQLite2Result::~QSQLite2Result()
+{
+ d->cleanup();
+ delete d;
+}
+
+void QSQLite2Result::virtual_hook(int id, void *data)
+{
+ switch (id) {
+ case QSqlResult::DetachFromResultSet:
+ d->finalize();
+ break;
+ default:
+ QSqlCachedResult::virtual_hook(id, data);
+ }
+}
+
+/*
+ Execute \a query.
+*/
+bool QSQLite2Result::reset (const QString& query)
+{
+ // 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->access,
+ d->utf8 ? query.toUtf8().constData()
+ : query.toAscii().constData(),
+ &(d->currentTail),
+ &(d->currentMachine),
+ &err);
+ if (res != SQLITE_OK || err) {
+ setLastError(QSqlError(QCoreApplication::translate("QSQLite2Result",
+ "Unable to execute statement"), QString::fromAscii(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)
+{
+ return d->fetchNext(row, idx, false);
+}
+
+int QSQLite2Result::size()
+{
+ return -1;
+}
+
+int QSQLite2Result::numRowsAffected()
+{
+ return sqlite_changes(d->access);
+}
+
+QSqlRecord QSQLite2Result::record() const
+{
+ if (!isActive() || !isSelect())
+ return QSqlRecord();
+ return d->rInf;
+}
+
+QVariant QSQLite2Result::handle() const
+{
+ return QVariant::fromValue(d->currentMachine);
+}
+
+/////////////////////////////////////////////////////////
+
+QSQLite2Driver::QSQLite2Driver(QObject * parent)
+ : QSqlDriver(parent)
+{
+ d = new QSQLite2DriverPrivate();
+}
+
+QSQLite2Driver::QSQLite2Driver(sqlite *connection, QObject *parent)
+ : QSqlDriver(parent)
+{
+ d = new QSQLite2DriverPrivate();
+ d->access = connection;
+ setOpen(true);
+ setOpenError(false);
+}
+
+
+QSQLite2Driver::~QSQLite2Driver()
+{
+ delete d;
+}
+
+bool QSQLite2Driver::hasFeature(DriverFeature f) const
+{
+ 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 &)
+{
+ 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::fromAscii(err),
+ QSqlError::ConnectionError));
+ sqlite_freemem(err);
+ err = 0;
+ }
+
+ if (d->access) {
+ setOpen(true);
+ setOpenError(false);
+ return true;
+ }
+ setOpenError(true);
+ return false;
+}
+
+void QSQLite2Driver::close()
+{
+ if (isOpen()) {
+ sqlite_close(d->access);
+ d->access = 0;
+ setOpen(false);
+ setOpenError(false);
+ }
+}
+
+QSqlResult *QSQLite2Driver::createResult() const
+{
+ return new QSQLite2Result(this);
+}
+
+bool QSQLite2Driver::beginTransaction()
+{
+ 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::fromAscii(err), QSqlError::TransactionError, res));
+ sqlite_freemem(err);
+ return false;
+}
+
+bool QSQLite2Driver::commitTransaction()
+{
+ 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::fromAscii(err), QSqlError::TransactionError, res));
+ sqlite_freemem(err);
+ return false;
+}
+
+bool QSQLite2Driver::rollbackTransaction()
+{
+ 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::fromAscii(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
+{
+ 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/sql/drivers/sqlite2/qsql_sqlite2.h b/src/sql/drivers/sqlite2/qsql_sqlite2.h
new file mode 100644
index 0000000000..d8c13dc1da
--- /dev/null
+++ b/src/sql/drivers/sqlite2/qsql_sqlite2.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_SQLITE2_H
+#define QSQL_SQLITE2_H
+
+#include <QtSql/qsqldriver.h>
+#include <QtSql/qsqlresult.h>
+#include <QtSql/qsqlrecord.h>
+#include <QtSql/qsqlindex.h>
+#include <QtSql/private/qsqlcachedresult_p.h>
+
+#if defined (Q_OS_WIN32)
+# include <QtCore/qt_windows.h>
+#endif
+
+struct sqlite;
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QSQLite2DriverPrivate;
+class QSQLite2ResultPrivate;
+class QSQLite2Driver;
+
+class QSQLite2Result : public QSqlCachedResult
+{
+ friend class QSQLite2Driver;
+ friend class QSQLite2ResultPrivate;
+public:
+ explicit QSQLite2Result(const QSQLite2Driver* db);
+ ~QSQLite2Result();
+ QVariant handle() const;
+
+protected:
+ bool gotoNext(QSqlCachedResult::ValueCache& row, int idx);
+ bool reset (const QString& query);
+ int size();
+ int numRowsAffected();
+ QSqlRecord record() const;
+ void virtual_hook(int id, void *data);
+
+private:
+ QSQLite2ResultPrivate* d;
+};
+
+class QSQLite2Driver : public QSqlDriver
+{
+ Q_OBJECT
+ friend class QSQLite2Result;
+public:
+ explicit QSQLite2Driver(QObject *parent = 0);
+ explicit QSQLite2Driver(sqlite *connection, QObject *parent = 0);
+ ~QSQLite2Driver();
+ bool hasFeature(DriverFeature f) const;
+ bool open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString & connOpts);
+ 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();
+ QSqlResult *createResult() const;
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+ QStringList tables(QSql::TableType) const;
+
+ QSqlRecord record(const QString& tablename) const;
+ QSqlIndex primaryIndex(const QString &table) const;
+ QVariant handle() const;
+ QString escapeIdentifier(const QString &identifier, IdentifierType) const;
+
+private:
+ QSQLite2DriverPrivate* d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQL_SQLITE2_H
diff --git a/src/sql/drivers/sqlite2/qsql_sqlite2.pri b/src/sql/drivers/sqlite2/qsql_sqlite2.pri
new file mode 100644
index 0000000000..9a9f6cdf9e
--- /dev/null
+++ b/src/sql/drivers/sqlite2/qsql_sqlite2.pri
@@ -0,0 +1,4 @@
+HEADERS += $$PWD/qsql_sqlite2.h
+SOURCES += $$PWD/qsql_sqlite2.cpp
+
+!contains(LIBS, .*sqlite.*):LIBS += -lsqlite
diff --git a/src/sql/drivers/tds/qsql_tds.cpp b/src/sql/drivers/tds/qsql_tds.cpp
new file mode 100644
index 0000000000..9e7eb859fb
--- /dev/null
+++ b/src/sql/drivers/tds/qsql_tds.cpp
@@ -0,0 +1,832 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <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 "qsql_tds.h"
+
+#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 <qstringlist.h>
+#include <qvector.h>
+
+#include <stdlib.h>
+
+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:
+ QTDSDriverPrivate(): login(0) {}
+ LOGINREC* login; // login information
+ QString hostName;
+ QString db;
+};
+
+
+class QTDSResultPrivate
+{
+public:
+ QTDSResultPrivate():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<void *> 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::fromAscii(msgtext))
+ .arg(msgno)
+ .arg(severity)
+ .arg(msgstate)
+ .arg(QString::fromAscii(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(db)
+{
+ d = new QTDSResultPrivate();
+ d->login = db->d->login;
+
+ d->dbproc = dbopen(d->login, const_cast<char*>(db->d->hostName.toLatin1().constData()));
+ if (!d->dbproc)
+ return;
+ if (dbuse(d->dbproc, const_cast<char*>(db->d->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()
+{
+ cleanup();
+ if (d->dbproc)
+ dbclose(d->dbproc);
+ errs()->remove(d->dbproc);
+ delete d;
+}
+
+void QTDSResult::cleanup()
+{
+ d->clearErrorMsgs();
+ d->rec.clear();
+ for (int i = 0; i < d->buffer.size() / 2; ++i)
+ free(d->buffer.at(i * 2));
+ d->buffer.clear();
+ // "can" stands for "cancel"... very clever.
+ dbcanquery(d->dbproc);
+ dbfreebuf(d->dbproc);
+
+ QSqlCachedResult::cleanup();
+}
+
+QVariant QTDSResult::handle() const
+{
+ return QVariant(qRegisterMetaType<DBPROCESS *>("DBPROCESS*"), &d->dbproc);
+}
+
+static inline bool qIsNull(const void *ind)
+{
+ return *reinterpret_cast<const DBINT *>(&ind) == -1;
+}
+
+bool QTDSResult::gotoNext(QSqlCachedResult::ValueCache &values, int index)
+{
+ 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 * 2 + 1))) {
+ values[idx] = QVariant(QVariant::DateTime);
+ } else {
+ DBDATETIME *bdt = (DBDATETIME*) d->buffer.at(i * 2);
+ 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 * 2 + 1)))
+ values[idx] = QVariant(QVariant::Int);
+ else
+ values[idx] = *((int*)d->buffer.at(i * 2));
+ break;
+ case QVariant::Double:
+ case QVariant::String:
+ if (qIsNull(d->buffer.at(i * 2 + 1)))
+ values[idx] = QVariant(QVariant::String);
+ else
+ values[idx] = QString::fromLocal8Bit((const char*)d->buffer.at(i * 2)).trimmed();
+ break;
+ case QVariant::ByteArray: {
+ if (qIsNull(d->buffer.at(i * 2 + 1)))
+ values[idx] = QVariant(QVariant::ByteArray);
+ else
+ values[idx] = QByteArray((const char*)d->buffer.at(i * 2));
+ 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)
+{
+ 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 * 2);
+ init(numCols);
+ }
+ for (int i = 0; i < numCols; ++i) {
+ int dbType = dbcoltype(d->dbproc, i+1);
+ QVariant::Type vType = qDecodeTDSType(dbType);
+ QSqlField f(QString::fromAscii(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 * 2] = p;
+ ret = dbnullbind(d->dbproc, i+1, (DBINT*)(&d->buffer[i * 2 + 1]));
+ } else {
+ d->buffer[i * 2] = 0;
+ d->buffer[i * 2 + 1] = 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()
+{
+#ifdef DBNTWIN32
+ if (dbiscount(d->dbproc)) {
+ return DBCOUNT(d->dbproc);
+ }
+ return -1;
+#else
+ return DBCOUNT(d->dbproc);
+#endif
+}
+
+QSqlRecord QTDSResult::record() const
+{
+ return d->rec;
+}
+
+///////////////////////////////////////////////////////////////////
+
+QTDSDriver::QTDSDriver(QObject* parent)
+ : QSqlDriver(parent)
+{
+ init();
+}
+
+QTDSDriver::QTDSDriver(LOGINREC* rec, const QString& host, const QString &db, QObject* parent)
+ : QSqlDriver(parent)
+{
+ init();
+ d->login = rec;
+ d->hostName = host;
+ d->db = db;
+ if (rec) {
+ setOpen(true);
+ setOpenError(false);
+ }
+}
+
+QVariant QTDSDriver::handle() const
+{
+ return QVariant(qRegisterMetaType<LOGINREC *>("LOGINREC*"), &d->login);
+}
+
+void QTDSDriver::init()
+{
+ d = new QTDSDriverPrivate();
+ // 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();
+ delete d;
+}
+
+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*/)
+{
+ if (isOpen())
+ close();
+ if (!dbinit()) {
+ 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()
+{
+ 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
+{
+ 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/sql/drivers/tds/qsql_tds.h b/src/sql/drivers/tds/qsql_tds.h
new file mode 100644
index 0000000000..3ca8449472
--- /dev/null
+++ b/src/sql/drivers/tds/qsql_tds.h
@@ -0,0 +1,137 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_TDS_H
+#define QSQL_TDS_H
+
+#include <QtSql/qsqlresult.h>
+#include <QtSql/qsqldriver.h>
+#include <QtSql/private/qsqlcachedresult_p.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_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QTDSDriverPrivate;
+class QTDSResultPrivate;
+class QTDSDriver;
+
+class QTDSResult : public QSqlCachedResult
+{
+public:
+ explicit QTDSResult(const QTDSDriver* db);
+ ~QTDSResult();
+ QVariant handle() const;
+
+protected:
+ void cleanup();
+ bool reset (const QString& query);
+ int size();
+ int numRowsAffected();
+ bool gotoNext(QSqlCachedResult::ValueCache &values, int index);
+ QSqlRecord record() const;
+
+private:
+ QTDSResultPrivate* d;
+};
+
+class Q_EXPORT_SQLDRIVER_TDS QTDSDriver : public QSqlDriver
+{
+ Q_OBJECT
+ friend class QTDSResult;
+public:
+ explicit QTDSDriver(QObject* parent = 0);
+ QTDSDriver(LOGINREC* rec, const QString& host, const QString &db, QObject* parent = 0);
+ ~QTDSDriver();
+ bool hasFeature(DriverFeature f) const;
+ bool open(const QString & db,
+ const QString & user,
+ const QString & password,
+ const QString & host,
+ int port,
+ const QString& connOpts);
+ void close();
+ QStringList tables(QSql::TableType) const;
+ QSqlResult *createResult() const;
+ QSqlRecord record(const QString& tablename) const;
+ QSqlIndex primaryIndex(const QString& tablename) const;
+
+ QString formatValue(const QSqlField &field,
+ bool trimStrings) const;
+ QVariant handle() const;
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const;
+
+protected:
+ bool beginTransaction();
+ bool commitTransaction();
+ bool rollbackTransaction();
+private:
+ void init();
+ QTDSDriverPrivate *d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQL_TDS_H
diff --git a/src/sql/drivers/tds/qsql_tds.pri b/src/sql/drivers/tds/qsql_tds.pri
new file mode 100644
index 0000000000..521c06b799
--- /dev/null
+++ b/src/sql/drivers/tds/qsql_tds.pri
@@ -0,0 +1,12 @@
+HEADERS += $$PWD/qsql_tds.h
+SOURCES += $$PWD/qsql_tds.cpp
+
+unix|win32-g++*: {
+ LIBS *= $$QT_LFLAGS_TDS
+ !contains(LIBS, .*sybdb.*):LIBS += -lsybdb
+ QMAKE_CXXFLAGS *= $$QT_CFLAGS_TDS
+} else:win32-borland {
+ LIBS *= $(BCB)/lib/PSDK/NTWDBLIB.LIB
+} else {
+ LIBS *= -lNTWDBLIB
+}
diff --git a/src/sql/kernel/kernel.pri b/src/sql/kernel/kernel.pri
new file mode 100644
index 0000000000..c6fe404737
--- /dev/null
+++ b/src/sql/kernel/kernel.pri
@@ -0,0 +1,24 @@
+HEADERS += kernel/qsql.h \
+ kernel/qsqlquery.h \
+ kernel/qsqldatabase.h \
+ kernel/qsqlfield.h \
+ kernel/qsqlrecord.h \
+ kernel/qsqldriver.h \
+ kernel/qsqlnulldriver_p.h \
+ kernel/qsqldriverplugin.h \
+ kernel/qsqlerror.h \
+ kernel/qsqlresult.h \
+ kernel/qsqlcachedresult_p.h \
+ kernel/qsqlindex.h
+
+SOURCES += kernel/qsqlquery.cpp \
+ kernel/qsqldatabase.cpp \
+ kernel/qsqlfield.cpp \
+ kernel/qsqlrecord.cpp \
+ kernel/qsqldriver.cpp \
+ kernel/qsqldriverplugin.cpp \
+ kernel/qsqlerror.cpp \
+ kernel/qsqlresult.cpp \
+ kernel/qsqlindex.cpp \
+ kernel/qsqlcachedresult.cpp
+
diff --git a/src/sql/kernel/qsql.h b/src/sql/kernel/qsql.h
new file mode 100644
index 0000000000..0cfe3880ed
--- /dev/null
+++ b/src/sql/kernel/qsql.h
@@ -0,0 +1,113 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQL_H
+#define QSQL_H
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+namespace QSql
+{
+ enum Location
+ {
+ BeforeFirstRow = -1,
+ AfterLastRow = -2
+#ifdef QT3_SUPPORT
+ , BeforeFirst = BeforeFirstRow,
+ AfterLast = AfterLastRow
+#endif
+ };
+
+ enum ParamTypeFlag
+ {
+ In = 0x00000001,
+ Out = 0x00000002,
+ InOut = In | Out,
+ Binary = 0x00000004
+ };
+ Q_DECLARE_FLAGS(ParamType, ParamTypeFlag)
+
+ enum TableType
+ {
+ Tables = 0x01,
+ SystemTables = 0x02,
+ Views = 0x04,
+ AllTables = 0xff
+ };
+
+ enum NumericalPrecisionPolicy
+ {
+ LowPrecisionInt32 = 0x01,
+ LowPrecisionInt64 = 0x02,
+ LowPrecisionDouble = 0x04,
+
+ HighPrecision = 0
+ };
+
+#ifdef QT3_SUPPORT
+ enum Op {
+ None = -1,
+ Insert = 0,
+ Update = 1,
+ Delete = 2
+ };
+
+ enum Confirm {
+ Cancel = -1,
+ No = 0,
+ Yes = 1
+ };
+#endif
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(QSql::ParamType)
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQL_H
diff --git a/src/sql/kernel/qsql.qdoc b/src/sql/kernel/qsql.qdoc
new file mode 100644
index 0000000000..3a2cf245a0
--- /dev/null
+++ b/src/sql/kernel/qsql.qdoc
@@ -0,0 +1,125 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Free Documentation License
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of this
+** file.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \namespace QSql
+ \inmodule QtSql
+ \brief The QSql namespace contains miscellaneous identifiers used throughout
+ the Qt SQL library.
+
+ \inheaderfile QtSql
+ \ingroup database
+
+
+ \sa {QtSql Module}
+*/
+
+/*!
+ \enum QSql::Confirm
+ \compat
+
+ This enum type describes edit confirmations.
+
+ \value Yes
+ \value No
+ \value Cancel
+*/
+
+/*!
+ \enum QSql::Op
+ \compat
+
+ This enum type describes edit operations.
+
+ \value None
+ \value Insert
+ \value Update
+ \value Delete
+*/
+
+
+/*!
+ \enum QSql::Location
+
+ This enum type describes special SQL navigation locations:
+
+ \value BeforeFirstRow Before the first record.
+ \value AfterLastRow After the last record.
+
+ \omitvalue BeforeFirst
+ \omitvalue AfterLast
+
+ \sa QSqlQuery::at()
+*/
+
+/*!
+ \enum QSql::ParamTypeFlag
+
+ This enum is used to specify the type of a bind parameter.
+
+ \value In The bind parameter is used to put data into the database.
+ \value Out The bind parameter is used to receive data from the database.
+ \value InOut The bind parameter is used to put data into the
+ database; it will be overwritten with output data on executing
+ a query.
+ \value Binary This must be OR'd with one of the other flags if
+ you want to indicate that the data being transferred is
+ raw binary data.
+*/
+
+/*!
+ \enum QSql::TableType
+
+ This enum type describes types of SQL tables.
+
+ \value Tables All the tables visible to the user.
+ \value SystemTables Internal tables used by the database.
+ \value Views All the views visible to the user.
+ \value AllTables All of the above.
+*/
+
+/*!
+ \enum QSql::NumericalPrecisionPolicy
+
+ This enum type describes at which precision levels numercial values are read from
+ a database.
+
+ Some databases support numerical values with a precision that is not storable in a
+ C++ basic data type. The default behavior is to bind these values as a QString.
+ This enum can be used to override this behavior.
+
+ \value LowPrecisionInt32 Force 32bit integer values. In case of floating point numbers,
+ the fractional part is silently discarded.
+ \value LowPrecisionInt64 Force 64bit integer values. In case of floating point numbers,
+ the fractional part is silently discarded.
+ \value LowPrecisionDouble Force \c double values.
+ \value HighPrecision The default behavior - try to preserve maximum precision.
+
+ Note: The actual behaviour if an overflow occurs is driver specific. The Oracle database
+ just returns an error in this case.
+*/
+
diff --git a/src/sql/kernel/qsqlcachedresult.cpp b/src/sql/kernel/qsqlcachedresult.cpp
new file mode 100644
index 0000000000..47ed5bf1f8
--- /dev/null
+++ b/src/sql/kernel/qsqlcachedresult.cpp
@@ -0,0 +1,318 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "private/qsqlcachedresult_p.h"
+
+#include <qvariant.h>
+#include <qdatetime.h>
+#include <qvector.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ QSqlCachedResult is a convenience class for databases that only allow
+ forward only fetching. It will cache all the results so we can iterate
+ backwards over the results again.
+
+ All you need to do is to inherit from QSqlCachedResult and reimplement
+ gotoNext(). gotoNext() will have a reference to the internal cache and
+ will give you an index where you can start filling in your data. Special
+ case: If the user actually wants a forward-only query, idx will be -1
+ to indicate that we are not interested in the actual values.
+*/
+
+static const uint initial_cache_size = 128;
+
+class QSqlCachedResultPrivate
+{
+public:
+ QSqlCachedResultPrivate();
+ bool canSeek(int i) const;
+ inline int cacheCount() const;
+ void init(int count, bool fo);
+ void cleanup();
+ int nextIndex();
+ void revertLast();
+
+ QSqlCachedResult::ValueCache cache;
+ int rowCacheEnd;
+ int colCount;
+ bool forwardOnly;
+ bool atEnd;
+};
+
+QSqlCachedResultPrivate::QSqlCachedResultPrivate():
+ rowCacheEnd(0), colCount(0), forwardOnly(false), atEnd(false)
+{
+}
+
+void QSqlCachedResultPrivate::cleanup()
+{
+ cache.clear();
+ forwardOnly = false;
+ atEnd = false;
+ colCount = 0;
+ rowCacheEnd = 0;
+}
+
+void QSqlCachedResultPrivate::init(int count, bool fo)
+{
+ Q_ASSERT(count);
+ cleanup();
+ forwardOnly = fo;
+ colCount = count;
+ if (fo) {
+ cache.resize(count);
+ rowCacheEnd = count;
+ } else {
+ cache.resize(initial_cache_size * count);
+ }
+}
+
+int QSqlCachedResultPrivate::nextIndex()
+{
+ if (forwardOnly)
+ return 0;
+ int newIdx = rowCacheEnd;
+ if (newIdx + colCount > cache.size())
+ cache.resize(qMin(cache.size() * 2, cache.size() + 10000));
+ rowCacheEnd += colCount;
+
+ return newIdx;
+}
+
+bool QSqlCachedResultPrivate::canSeek(int i) const
+{
+ if (forwardOnly || i < 0)
+ return false;
+ return rowCacheEnd >= (i + 1) * colCount;
+}
+
+void QSqlCachedResultPrivate::revertLast()
+{
+ if (forwardOnly)
+ return;
+ rowCacheEnd -= colCount;
+}
+
+inline int QSqlCachedResultPrivate::cacheCount() const
+{
+ Q_ASSERT(!forwardOnly);
+ Q_ASSERT(colCount);
+ return rowCacheEnd / colCount;
+}
+
+//////////////
+
+QSqlCachedResult::QSqlCachedResult(const QSqlDriver * db): QSqlResult (db)
+{
+ d = new QSqlCachedResultPrivate();
+}
+
+QSqlCachedResult::~QSqlCachedResult()
+{
+ delete d;
+}
+
+void QSqlCachedResult::init(int colCount)
+{
+ d->init(colCount, isForwardOnly());
+}
+
+bool QSqlCachedResult::fetch(int i)
+{
+ if ((!isActive()) || (i < 0))
+ return false;
+ if (at() == i)
+ return true;
+ if (d->forwardOnly) {
+ // speed hack - do not copy values if not needed
+ if (at() > i || at() == QSql::AfterLastRow)
+ return false;
+ while(at() < i - 1) {
+ if (!gotoNext(d->cache, -1))
+ return false;
+ setAt(at() + 1);
+ }
+ if (!gotoNext(d->cache, 0))
+ return false;
+ setAt(at() + 1);
+ return true;
+ }
+ if (d->canSeek(i)) {
+ setAt(i);
+ return true;
+ }
+ if (d->rowCacheEnd > 0)
+ setAt(d->cacheCount());
+ while (at() < i + 1) {
+ if (!cacheNext()) {
+ if (d->canSeek(i))
+ break;
+ return false;
+ }
+ }
+ setAt(i);
+
+ return true;
+}
+
+bool QSqlCachedResult::fetchNext()
+{
+ if (d->canSeek(at() + 1)) {
+ setAt(at() + 1);
+ return true;
+ }
+ return cacheNext();
+}
+
+bool QSqlCachedResult::fetchPrevious()
+{
+ return fetch(at() - 1);
+}
+
+bool QSqlCachedResult::fetchFirst()
+{
+ if (d->forwardOnly && at() != QSql::BeforeFirstRow) {
+ return false;
+ }
+ if (d->canSeek(0)) {
+ setAt(0);
+ return true;
+ }
+ return cacheNext();
+}
+
+bool QSqlCachedResult::fetchLast()
+{
+ if (d->atEnd) {
+ if (d->forwardOnly)
+ return false;
+ else
+ return fetch(d->cacheCount() - 1);
+ }
+
+ int i = at();
+ while (fetchNext())
+ ++i; /* brute force */
+ if (d->forwardOnly && at() == QSql::AfterLastRow) {
+ setAt(i);
+ return true;
+ } else {
+ return fetch(i);
+ }
+}
+
+QVariant QSqlCachedResult::data(int i)
+{
+ int idx = d->forwardOnly ? i : at() * d->colCount + i;
+ if (i >= d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd)
+ return QVariant();
+
+ return d->cache.at(idx);
+}
+
+bool QSqlCachedResult::isNull(int i)
+{
+ int idx = d->forwardOnly ? i : at() * d->colCount + i;
+ if (i > d->colCount || i < 0 || at() < 0 || idx >= d->rowCacheEnd)
+ return true;
+
+ return d->cache.at(idx).isNull();
+}
+
+void QSqlCachedResult::cleanup()
+{
+ setAt(QSql::BeforeFirstRow);
+ setActive(false);
+ d->cleanup();
+}
+
+void QSqlCachedResult::clearValues()
+{
+ setAt(QSql::BeforeFirstRow);
+ d->rowCacheEnd = 0;
+ d->atEnd = false;
+}
+
+bool QSqlCachedResult::cacheNext()
+{
+ if (d->atEnd)
+ return false;
+
+ if(isForwardOnly()) {
+ d->cache.clear();
+ d->cache.resize(d->colCount);
+ }
+
+ if (!gotoNext(d->cache, d->nextIndex())) {
+ d->revertLast();
+ d->atEnd = true;
+ return false;
+ }
+ setAt(at() + 1);
+ return true;
+}
+
+int QSqlCachedResult::colCount() const
+{
+ return d->colCount;
+}
+
+QSqlCachedResult::ValueCache &QSqlCachedResult::cache()
+{
+ return d->cache;
+}
+
+void QSqlCachedResult::virtual_hook(int id, void *data)
+{
+ switch (id) {
+ case QSqlResult::DetachFromResultSet:
+ case QSqlResult::SetNumericalPrecision:
+ cleanup();
+ break;
+ default:
+ QSqlResult::virtual_hook(id, data);
+ }
+}
+
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqlcachedresult_p.h b/src/sql/kernel/qsqlcachedresult_p.h
new file mode 100644
index 0000000000..a76ca0bbbc
--- /dev/null
+++ b/src/sql/kernel/qsqlcachedresult_p.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLCACHEDRESULT_P_H
+#define QSQLCACHEDRESULT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "QtSql/qsqlresult.h"
+
+QT_BEGIN_NAMESPACE
+
+class QVariant;
+template <typename T> class QVector;
+
+class QSqlCachedResultPrivate;
+
+class Q_SQL_EXPORT QSqlCachedResult: public QSqlResult
+{
+public:
+ virtual ~QSqlCachedResult();
+
+ typedef QVector<QVariant> ValueCache;
+
+protected:
+ QSqlCachedResult(const QSqlDriver * db);
+
+ void init(int colCount);
+ void cleanup();
+ void clearValues();
+
+ virtual bool gotoNext(ValueCache &values, int index) = 0;
+
+ QVariant data(int i);
+ bool isNull(int i);
+ bool fetch(int i);
+ bool fetchNext();
+ bool fetchPrevious();
+ bool fetchFirst();
+ bool fetchLast();
+
+ int colCount() const;
+ ValueCache &cache();
+
+ void virtual_hook(int id, void *data);
+private:
+ bool cacheNext();
+ QSqlCachedResultPrivate *d;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQLCACHEDRESULT_P_H
diff --git a/src/sql/kernel/qsqldatabase.cpp b/src/sql/kernel/qsqldatabase.cpp
new file mode 100644
index 0000000000..b0ae8d9ea6
--- /dev/null
+++ b/src/sql/kernel/qsqldatabase.cpp
@@ -0,0 +1,1547 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqldatabase.h"
+#include "qsqlquery.h"
+
+#ifdef Q_OS_WIN32
+// Conflicting declarations of LPCBYTE in sqlfront.h and winscard.h
+#define _WINSCARD_H_
+#endif
+
+#ifdef QT_SQL_PSQL
+#include "../drivers/psql/qsql_psql.h"
+#endif
+#ifdef QT_SQL_MYSQL
+#include "../drivers/mysql/qsql_mysql.h"
+#endif
+#ifdef QT_SQL_ODBC
+#include "../drivers/odbc/qsql_odbc.h"
+#endif
+#ifdef QT_SQL_OCI
+#include "../drivers/oci/qsql_oci.h"
+#endif
+#ifdef QT_SQL_TDS
+// conflicting RETCODE typedef between odbc and freetds
+#define RETCODE DBRETCODE
+#include "../drivers/tds/qsql_tds.h"
+#undef RETCODE
+#endif
+#ifdef QT_SQL_DB2
+#include "../drivers/db2/qsql_db2.h"
+#endif
+#ifdef QT_SQL_SQLITE
+#include "../drivers/sqlite/qsql_sqlite.h"
+#endif
+#ifdef QT_SQL_SQLITE2
+#include "../drivers/sqlite2/qsql_sqlite2.h"
+#endif
+#ifdef QT_SQL_IBASE
+#undef SQL_FLOAT // avoid clash with ODBC
+#undef SQL_DOUBLE
+#undef SQL_TIMESTAMP
+#undef SQL_TYPE_TIME
+#undef SQL_TYPE_DATE
+#undef SQL_DATE
+#define SCHAR IBASE_SCHAR // avoid clash with ODBC (older versions of ibase.h with Firebird)
+#include "../drivers/ibase/qsql_ibase.h"
+#undef SCHAR
+#endif
+
+#include "qdebug.h"
+#include "qcoreapplication.h"
+#include "qreadwritelock.h"
+#include "qsqlresult.h"
+#include "qsqldriver.h"
+#include "qsqldriverplugin.h"
+#include "qsqlindex.h"
+#include "private/qfactoryloader_p.h"
+#include "private/qsqlnulldriver_p.h"
+#include "qmutex.h"
+#include "qhash.h"
+#include <stdlib.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_LIBRARY
+Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,
+ (QSqlDriverFactoryInterface_iid,
+ QLatin1String("/sqldrivers")))
+#endif
+
+QT_STATIC_CONST_IMPL char *QSqlDatabase::defaultConnection = "qt_sql_default_connection";
+
+typedef QHash<QString, QSqlDriverCreatorBase*> DriverDict;
+
+class QConnectionDict: public QHash<QString, QSqlDatabase>
+{
+public:
+ inline bool contains_ts(const QString &key)
+ {
+ QReadLocker locker(&lock);
+ return contains(key);
+ }
+ inline QStringList keys_ts() const
+ {
+ QReadLocker locker(&lock);
+ return keys();
+ }
+
+ mutable QReadWriteLock lock;
+};
+Q_GLOBAL_STATIC(QConnectionDict, dbDict)
+
+class QSqlDatabasePrivate
+{
+public:
+ QSqlDatabasePrivate(QSqlDatabase *d, QSqlDriver *dr = 0):
+ q(d),
+ driver(dr),
+ port(-1)
+ {
+ ref = 1;
+ if(driver)
+ precisionPolicy = driver->numericalPrecisionPolicy();
+ else
+ precisionPolicy= QSql::LowPrecisionDouble;
+ }
+ QSqlDatabasePrivate(const QSqlDatabasePrivate &other);
+ ~QSqlDatabasePrivate();
+ void init(const QString& type);
+ void copy(const QSqlDatabasePrivate *other);
+ void disable();
+
+ QAtomicInt ref;
+ QSqlDatabase *q;
+ QSqlDriver* driver;
+ QString dbname;
+ QString uname;
+ QString pword;
+ QString hname;
+ QString drvName;
+ int port;
+ QString connOptions;
+ QString connName;
+ QSql::NumericalPrecisionPolicy precisionPolicy;
+
+ static QSqlDatabasePrivate *shared_null();
+ static QSqlDatabase database(const QString& name, bool open);
+ static void addDatabase(const QSqlDatabase &db, const QString & name);
+ static void removeDatabase(const QString& name);
+ static void invalidateDb(const QSqlDatabase &db, const QString &name, bool doWarn = true);
+ static DriverDict &driverDict();
+ static void cleanConnections();
+};
+
+QSqlDatabasePrivate::QSqlDatabasePrivate(const QSqlDatabasePrivate &other)
+{
+ ref = 1;
+ q = other.q;
+ dbname = other.dbname;
+ uname = other.uname;
+ pword = other.pword;
+ hname = other.hname;
+ drvName = other.drvName;
+ port = other.port;
+ connOptions = other.connOptions;
+ driver = other.driver;
+ precisionPolicy = other.precisionPolicy;
+}
+
+QSqlDatabasePrivate::~QSqlDatabasePrivate()
+{
+ if (driver != shared_null()->driver)
+ delete driver;
+}
+
+void QSqlDatabasePrivate::cleanConnections()
+{
+ QConnectionDict *dict = dbDict();
+ Q_ASSERT(dict);
+ QWriteLocker locker(&dict->lock);
+
+ QConnectionDict::iterator it = dict->begin();
+ while (it != dict->end()) {
+ invalidateDb(it.value(), it.key(), false);
+ ++it;
+ }
+ dict->clear();
+}
+
+static bool qDriverDictInit = false;
+static void cleanDriverDict()
+{
+ qDeleteAll(QSqlDatabasePrivate::driverDict());
+ QSqlDatabasePrivate::driverDict().clear();
+ QSqlDatabasePrivate::cleanConnections();
+ qDriverDictInit = false;
+}
+
+DriverDict &QSqlDatabasePrivate::driverDict()
+{
+ static DriverDict dict;
+ if (!qDriverDictInit) {
+ qDriverDictInit = true;
+ qAddPostRoutine(cleanDriverDict);
+ }
+ return dict;
+}
+
+QSqlDatabasePrivate *QSqlDatabasePrivate::shared_null()
+{
+ static QSqlNullDriver dr;
+ static QSqlDatabasePrivate n(NULL, &dr);
+ return &n;
+}
+
+void QSqlDatabasePrivate::invalidateDb(const QSqlDatabase &db, const QString &name, bool doWarn)
+{
+ if (db.d->ref != 1 && doWarn) {
+ qWarning("QSqlDatabasePrivate::removeDatabase: connection '%s' is still in use, "
+ "all queries will cease to work.", name.toLocal8Bit().constData());
+ db.d->disable();
+ db.d->connName.clear();
+ }
+}
+
+void QSqlDatabasePrivate::removeDatabase(const QString &name)
+{
+ QConnectionDict *dict = dbDict();
+ Q_ASSERT(dict);
+ QWriteLocker locker(&dict->lock);
+
+ if (!dict->contains(name))
+ return;
+
+ invalidateDb(dict->take(name), name);
+}
+
+void QSqlDatabasePrivate::addDatabase(const QSqlDatabase &db, const QString &name)
+{
+ QConnectionDict *dict = dbDict();
+ Q_ASSERT(dict);
+ QWriteLocker locker(&dict->lock);
+
+ if (dict->contains(name)) {
+ invalidateDb(dict->take(name), name);
+ qWarning("QSqlDatabasePrivate::addDatabase: duplicate connection name '%s', old "
+ "connection removed.", name.toLocal8Bit().data());
+ }
+ dict->insert(name, db);
+ db.d->connName = name;
+}
+
+/*! \internal
+*/
+QSqlDatabase QSqlDatabasePrivate::database(const QString& name, bool open)
+{
+ const QConnectionDict *dict = dbDict();
+ Q_ASSERT(dict);
+
+ dict->lock.lockForRead();
+ QSqlDatabase db = dict->value(name);
+ dict->lock.unlock();
+ if (db.isValid() && !db.isOpen() && open) {
+ if (!db.open())
+ qWarning() << "QSqlDatabasePrivate::database: unable to open database:" << db.lastError().text();
+
+ }
+ return db;
+}
+
+
+/*! \internal
+ Copies the connection data from \a other.
+*/
+void QSqlDatabasePrivate::copy(const QSqlDatabasePrivate *other)
+{
+ q = other->q;
+ dbname = other->dbname;
+ uname = other->uname;
+ pword = other->pword;
+ hname = other->hname;
+ drvName = other->drvName;
+ port = other->port;
+ connOptions = other->connOptions;
+ precisionPolicy = other->precisionPolicy;
+}
+
+void QSqlDatabasePrivate::disable()
+{
+ if (driver != shared_null()->driver) {
+ delete driver;
+ driver = shared_null()->driver;
+ }
+}
+
+/*!
+ \class QSqlDriverCreatorBase
+ \brief The QSqlDriverCreatorBase class is the base class for
+ SQL driver factories.
+
+ \ingroup database
+ \inmodule QtSql
+
+ Reimplement createObject() to return an instance of the specific
+ QSqlDriver subclass that you want to provide.
+
+ See QSqlDatabase::registerSqlDriver() for details.
+
+ \sa QSqlDriverCreator
+*/
+
+/*!
+ \fn QSqlDriverCreatorBase::~QSqlDriverCreatorBase()
+
+ Destroys the SQL driver creator object.
+*/
+
+/*!
+ \fn QSqlDriver *QSqlDriverCreatorBase::createObject() const
+
+ Reimplement this function to returns a new instance of a
+ QSqlDriver subclass.
+*/
+
+/*!
+ \class QSqlDriverCreator
+ \brief The QSqlDriverCreator class is a template class that
+ provides a SQL driver factory for a specific driver type.
+
+ \ingroup database
+ \inmodule QtSql
+
+ QSqlDriverCreator<T> instantiates objects of type T, where T is a
+ QSqlDriver subclass.
+
+ See QSqlDatabase::registerSqlDriver() for details.
+*/
+
+/*!
+ \fn QSqlDriver *QSqlDriverCreator::createObject() const
+ \reimp
+*/
+
+/*!
+ \class QSqlDatabase
+ \brief The QSqlDatabase class represents a connection to
+ a database.
+
+ \ingroup database
+
+ \inmodule QtSql
+
+ The QSqlDatabase class provides an interface for accessing a
+ database through a connection. An instance of QSqlDatabase
+ represents the connection. The connection provides access to the
+ database via one of the \l{SQL Database Drivers#Supported
+ Databases} {supported database drivers}, which are derived from
+ QSqlDriver. Alternatively, you can subclass your own database
+ driver from QSqlDriver. See \l{How to Write Your Own Database
+ Driver} for more information.
+
+ Create a connection (i.e., an instance of QSqlDatabase) by calling
+ one of the static addDatabase() functions, where you specify
+ \l{SQL Database Drivers#Supported Databases} {the driver or type
+ of driver} to use (i.e., what kind of database will you access?)
+ and a connection name. A connection is known by its own name,
+ \e{not} by the name of the database it connects to. You can have
+ multiple connections to one database. QSqlDatabase also supports
+ the concept of a \e{default} connection, which is the unnamed
+ connection. To create the default connection, don't pass the
+ connection name argument when you call addDatabase().
+ Subsequently, when you call any static member function that takes
+ the connection name argument, if you don't pass the connection
+ name argument, the default connection is assumed. The following
+ snippet shows how to create and open a default connection to a
+ PostgreSQL database:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 0
+
+ Once the QSqlDatabase object has been created, set the connection
+ parameters with setDatabaseName(), setUserName(), setPassword(),
+ setHostName(), setPort(), and setConnectOptions(). Then call
+ open() to activate the physical connection to the database. The
+ connection is not usable until you open it.
+
+ The connection defined above will be the \e{default} connection,
+ because we didn't give a connection name to \l{QSqlDatabase::}
+ {addDatabase()}. Subsequently, you can get the default connection
+ by calling database() without the connection name argument:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 1
+
+ QSqlDatabase is a value class. Changes made to a database
+ connection via one instance of QSqlDatabase will affect other
+ instances of QSqlDatabase that represent the same connection. Use
+ cloneDatabase() to create an independent database connection based
+ on an existing one.
+
+ If you create multiple database connections, specify a unique
+ connection name for each one, when you call addDatabase(). Use
+ database() with a connection name to get that connection. Use
+ removeDatabase() with a connection name to remove a connection.
+ QSqlDatabase outputs a warning if you try to remove a connection
+ referenced by other QSqlDatabase objects. Use contains() to see if
+ a given connection name is in the list of connections.
+
+ Once a connection is established, you can call tables() to get the
+ list of tables in the database, call primaryIndex() to get a
+ table's primary index, and call record() to get meta-information
+ about a table's fields (e.g., field names).
+
+ \note QSqlDatabase::exec() is deprecated. Use QSqlQuery::exec()
+ instead.
+
+ If the driver supports transactions, use transaction() to start a
+ transaction, and commit() or rollback() to complete it. Use
+ \l{QSqlDriver::} {hasFeature()} to ask if the driver supports
+ transactions. \note When using transactions, you must start the
+ transaction before you create your query.
+
+ If an error occurrs, lastError() will return information about it.
+
+ Get the names of the available SQL drivers with drivers(). Check
+ for the presence of a particular driver with isDriverAvailable().
+ If you have created your own custom driver, you must register it
+ with registerSqlDriver().
+
+ \sa QSqlDriver, QSqlQuery, {QtSql Module}, {Threads and the SQL Module}
+*/
+
+/*! \fn QSqlDatabase QSqlDatabase::addDatabase(const QString &type, const QString &connectionName)
+ \threadsafe
+
+ Adds a database to the list of database connections using the
+ driver \a type and the connection name \a connectionName. If
+ there already exists a database connection called \a
+ connectionName, that connection is removed.
+
+ The database connection is referred to by \a connectionName. The
+ newly added database connection is returned.
+
+ If \a type is not available or could not be loaded, isValid() returns false.
+
+ If \a connectionName is not specified, the new connection becomes
+ the default connection for the application, and subsequent calls
+ to database() without the connection name argument will return the
+ default connection. If a \a connectionName is provided here, use
+ database(\a connectionName) to retrieve the connection.
+
+ \warning If you add a connection with the same name as an existing
+ connection, the new connection replaces the old one. If you call
+ this function more than once without specifying \a connectionName,
+ the default connection will be the one replaced.
+
+ Before using the connection, it must be initialized. e.g., call
+ some or all of setDatabaseName(), setUserName(), setPassword(),
+ setHostName(), setPort(), and setConnectOptions(), and, finally,
+ open().
+
+ \sa database() removeDatabase() {Threads and the SQL Module}
+*/
+QSqlDatabase QSqlDatabase::addDatabase(const QString &type, const QString &connectionName)
+{
+ QSqlDatabase db(type);
+ QSqlDatabasePrivate::addDatabase(db, connectionName);
+ return db;
+}
+
+/*!
+ \threadsafe
+
+ Returns the database connection called \a connectionName. The
+ database connection must have been previously added with
+ addDatabase(). If \a open is true (the default) and the database
+ connection is not already open it is opened now. If no \a
+ connectionName is specified the default connection is used. If \a
+ connectionName does not exist in the list of databases, an invalid
+ connection is returned.
+
+ \sa isOpen() {Threads and the SQL Module}
+*/
+
+QSqlDatabase QSqlDatabase::database(const QString& connectionName, bool open)
+{
+ return QSqlDatabasePrivate::database(connectionName, open);
+}
+
+/*!
+ \threadsafe
+
+ Removes the database connection \a connectionName from the list of
+ database connections.
+
+ \warning There should be no open queries on the database
+ connection when this function is called, otherwise a resource leak
+ will occur.
+
+ Example:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 0
+
+ The correct way to do it:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 1
+
+ To remove the default connection, which may have been created with a
+ call to addDatabase() not specifying a connection name, you can
+ retrieve the default connection name by calling connectionName() on
+ the database returned by database(). Note that if a default database
+ hasn't been created an invalid database will be returned.
+
+ \sa database() connectionName() {Threads and the SQL Module}
+*/
+
+void QSqlDatabase::removeDatabase(const QString& connectionName)
+{
+ QSqlDatabasePrivate::removeDatabase(connectionName);
+}
+
+/*!
+ Returns a list of all the available database drivers.
+
+ \sa registerSqlDriver()
+*/
+
+QStringList QSqlDatabase::drivers()
+{
+ QStringList list;
+
+#ifdef QT_SQL_PSQL
+ list << QLatin1String("QPSQL7");
+ list << QLatin1String("QPSQL");
+#endif
+#ifdef QT_SQL_MYSQL
+ list << QLatin1String("QMYSQL3");
+ list << QLatin1String("QMYSQL");
+#endif
+#ifdef QT_SQL_ODBC
+ list << QLatin1String("QODBC3");
+ list << QLatin1String("QODBC");
+#endif
+#ifdef QT_SQL_OCI
+ list << QLatin1String("QOCI8");
+ list << QLatin1String("QOCI");
+#endif
+#ifdef QT_SQL_TDS
+ list << QLatin1String("QTDS7");
+ list << QLatin1String("QTDS");
+#endif
+#ifdef QT_SQL_DB2
+ list << QLatin1String("QDB2");
+#endif
+#ifdef QT_SQL_SQLITE
+ list << QLatin1String("QSQLITE");
+#endif
+#ifdef QT_SQL_SQLITE2
+ list << QLatin1String("QSQLITE2");
+#endif
+#ifdef QT_SQL_IBASE
+ list << QLatin1String("QIBASE");
+#endif
+
+#ifndef QT_NO_LIBRARY
+ if (QFactoryLoader *fl = loader()) {
+ QStringList keys = fl->keys();
+ for (QStringList::const_iterator i = keys.constBegin(); i != keys.constEnd(); ++i) {
+ if (!list.contains(*i))
+ list << *i;
+ }
+ }
+#endif
+
+ DriverDict dict = QSqlDatabasePrivate::driverDict();
+ for (DriverDict::const_iterator i = dict.constBegin(); i != dict.constEnd(); ++i) {
+ if (!list.contains(i.key()))
+ list << i.key();
+ }
+
+ return list;
+}
+
+/*!
+ This function registers a new SQL driver called \a name, within
+ the SQL framework. This is useful if you have a custom SQL driver
+ and don't want to compile it as a plugin.
+
+ Example:
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 2
+
+ QSqlDatabase takes ownership of the \a creator pointer, so you
+ mustn't delete it yourself.
+
+ \sa drivers()
+*/
+void QSqlDatabase::registerSqlDriver(const QString& name, QSqlDriverCreatorBase *creator)
+{
+ delete QSqlDatabasePrivate::driverDict().take(name);
+ if (creator)
+ QSqlDatabasePrivate::driverDict().insert(name, creator);
+}
+
+/*!
+ \threadsafe
+
+ Returns true if the list of database connections contains \a
+ connectionName; otherwise returns false.
+
+ \sa connectionNames(), database(), {Threads and the SQL Module}
+*/
+
+bool QSqlDatabase::contains(const QString& connectionName)
+{
+ return dbDict()->contains_ts(connectionName);
+}
+
+/*!
+ \threadsafe
+
+ Returns a list containing the names of all connections.
+
+ \sa contains(), database(), {Threads and the SQL Module}
+*/
+QStringList QSqlDatabase::connectionNames()
+{
+ return dbDict()->keys_ts();
+}
+
+/*!
+ \overload
+
+ Creates a QSqlDatabase connection that uses the driver referred
+ to by \a type. If the \a type is not recognized, the database
+ connection will have no functionality.
+
+ The currently available driver types are:
+
+ \table
+ \header \i Driver Type \i Description
+ \row \i QDB2 \i IBM DB2
+ \row \i QIBASE \i Borland InterBase Driver
+ \row \i QMYSQL \i MySQL Driver
+ \row \i QOCI \i Oracle Call Interface Driver
+ \row \i QODBC \i ODBC Driver (includes Microsoft SQL Server)
+ \row \i QPSQL \i PostgreSQL Driver
+ \row \i QSQLITE \i SQLite version 3 or above
+ \row \i QSQLITE2 \i SQLite version 2
+ \row \i QTDS \i Sybase Adaptive Server
+ \endtable
+
+ Additional third party drivers, including your own custom
+ drivers, can be loaded dynamically.
+
+ \sa {SQL Database Drivers}, registerSqlDriver(), drivers()
+*/
+
+QSqlDatabase::QSqlDatabase(const QString &type)
+{
+ d = new QSqlDatabasePrivate(this);
+ d->init(type);
+}
+
+/*!
+ \overload
+
+ Creates a database connection using the given \a driver.
+*/
+
+QSqlDatabase::QSqlDatabase(QSqlDriver *driver)
+{
+ d = new QSqlDatabasePrivate(this, driver);
+}
+
+/*!
+ Creates an empty, invalid QSqlDatabase object. Use addDatabase(),
+ removeDatabase(), and database() to get valid QSqlDatabase
+ objects.
+*/
+QSqlDatabase::QSqlDatabase()
+{
+ d = QSqlDatabasePrivate::shared_null();
+ d->ref.ref();
+}
+
+/*!
+ Creates a copy of \a other.
+*/
+QSqlDatabase::QSqlDatabase(const QSqlDatabase &other)
+{
+ d = other.d;
+ d->ref.ref();
+}
+
+/*!
+ Assigns \a other to this object.
+*/
+QSqlDatabase &QSqlDatabase::operator=(const QSqlDatabase &other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ \internal
+
+ Create the actual driver instance \a type.
+*/
+
+void QSqlDatabasePrivate::init(const QString &type)
+{
+ drvName = type;
+
+ if (!driver) {
+#ifdef QT_SQL_PSQL
+ if (type == QLatin1String("QPSQL") || type == QLatin1String("QPSQL7"))
+ driver = new QPSQLDriver();
+#endif
+#ifdef QT_SQL_MYSQL
+ if (type == QLatin1String("QMYSQL") || type == QLatin1String("QMYSQL3"))
+ driver = new QMYSQLDriver();
+#endif
+#ifdef QT_SQL_ODBC
+ if (type == QLatin1String("QODBC") || type == QLatin1String("QODBC3"))
+ driver = new QODBCDriver();
+#endif
+#ifdef QT_SQL_OCI
+ if (type == QLatin1String("QOCI") || type == QLatin1String("QOCI8"))
+ driver = new QOCIDriver();
+#endif
+#ifdef QT_SQL_TDS
+ if (type == QLatin1String("QTDS") || type == QLatin1String("QTDS7"))
+ driver = new QTDSDriver();
+#endif
+#ifdef QT_SQL_DB2
+ if (type == QLatin1String("QDB2"))
+ driver = new QDB2Driver();
+#endif
+#ifdef QT_SQL_SQLITE
+ if (type == QLatin1String("QSQLITE"))
+ driver = new QSQLiteDriver();
+#endif
+#ifdef QT_SQL_SQLITE2
+ if (type == QLatin1String("QSQLITE2"))
+ driver = new QSQLite2Driver();
+#endif
+#ifdef QT_SQL_IBASE
+ if (type == QLatin1String("QIBASE"))
+ driver = new QIBaseDriver();
+#endif
+ }
+
+ if (!driver) {
+ DriverDict dict = QSqlDatabasePrivate::driverDict();
+ for (DriverDict::const_iterator it = dict.constBegin();
+ it != dict.constEnd() && !driver; ++it) {
+ if (type == it.key()) {
+ driver = ((QSqlDriverCreatorBase*)(*it))->createObject();
+ }
+ }
+ }
+
+#ifndef QT_NO_LIBRARY
+ if (!driver && loader()) {
+ if (QSqlDriverFactoryInterface *factory = qobject_cast<QSqlDriverFactoryInterface*>(loader()->instance(type)))
+ driver = factory->create(type);
+ }
+#endif // QT_NO_LIBRARY
+
+ if (!driver) {
+ qWarning("QSqlDatabase: %s driver not loaded", type.toLatin1().data());
+ qWarning("QSqlDatabase: available drivers: %s",
+ QSqlDatabase::drivers().join(QLatin1String(" ")).toLatin1().data());
+ if (QCoreApplication::instance() == 0)
+ qWarning("QSqlDatabase: an instance of QCoreApplication is required for loading driver plugins");
+ driver = shared_null()->driver;
+ }
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+
+ If this is the last QSqlDatabase object that uses a certain
+ database connection, the database connection is automatically closed.
+
+ \sa close()
+*/
+
+QSqlDatabase::~QSqlDatabase()
+{
+ if (!d->ref.deref()) {
+ close();
+ delete d;
+ }
+}
+
+/*!
+ Executes a SQL statement on the database and returns a QSqlQuery
+ object. Use lastError() to retrieve error information. If \a
+ query is empty, an empty, invalid query is returned and
+ lastError() is not affected.
+
+ \sa QSqlQuery, lastError()
+*/
+
+QSqlQuery QSqlDatabase::exec(const QString & query) const
+{
+ QSqlQuery r(d->driver->createResult());
+ if (!query.isEmpty()) {
+ r.exec(query);
+ d->driver->setLastError(r.lastError());
+ }
+ return r;
+}
+
+/*!
+ Opens the database connection using the current connection
+ values. Returns true on success; otherwise returns false. Error
+ information can be retrieved using lastError().
+
+ \sa lastError() setDatabaseName() setUserName() setPassword()
+ \sa setHostName() setPort() setConnectOptions()
+*/
+
+bool QSqlDatabase::open()
+{
+ return d->driver->open(d->dbname, d->uname, d->pword, d->hname,
+ d->port, d->connOptions);
+}
+
+/*!
+ \overload
+
+ Opens the database connection using the given \a user name and \a
+ password. Returns true on success; otherwise returns false. Error
+ information can be retrieved using the lastError() function.
+
+ This function does not store the password it is given. Instead,
+ the password is passed directly to the driver for opening the
+ connection and it is then discarded.
+
+ \sa lastError()
+*/
+
+bool QSqlDatabase::open(const QString& user, const QString& password)
+{
+ setUserName(user);
+ return d->driver->open(d->dbname, user, password, d->hname,
+ d->port, d->connOptions);
+}
+
+/*!
+ Closes the database connection, freeing any resources acquired, and
+ invalidating any existing QSqlQuery objects that are used with the
+ database.
+
+ This will also affect copies of this QSqlDatabase object.
+
+ \sa removeDatabase()
+*/
+
+void QSqlDatabase::close()
+{
+ d->driver->close();
+}
+
+/*!
+ Returns true if the database connection is currently open;
+ otherwise returns false.
+*/
+
+bool QSqlDatabase::isOpen() const
+{
+ return d->driver->isOpen();
+}
+
+/*!
+ Returns true if there was an error opening the database
+ connection; otherwise returns false. Error information can be
+ retrieved using the lastError() function.
+*/
+
+bool QSqlDatabase::isOpenError() const
+{
+ return d->driver->isOpenError();
+}
+
+/*!
+ Begins a transaction on the database if the driver supports
+ transactions. Returns \c{true} if the operation succeeded.
+ Otherwise it returns \c{false}.
+
+ \sa QSqlDriver::hasFeature(), commit(), rollback()
+*/
+bool QSqlDatabase::transaction()
+{
+ if (!d->driver->hasFeature(QSqlDriver::Transactions))
+ return false;
+ return d->driver->beginTransaction();
+}
+
+/*!
+ Commits a transaction to the database if the driver supports
+ transactions and a transaction() has been started. Returns \c{true}
+ if the operation succeeded. Otherwise it returns \c{false}.
+
+ \note For some databases, the commit will fail and return \c{false}
+ if there is an \l{QSqlQuery::isActive()} {active query} using the
+ database for a \c{SELECT}. Make the query \l{QSqlQuery::isActive()}
+ {inactive} before doing the commit.
+
+ Call lastError() to get information about errors.
+
+ \sa QSqlQuery::isActive() QSqlDriver::hasFeature() rollback()
+*/
+bool QSqlDatabase::commit()
+{
+ if (!d->driver->hasFeature(QSqlDriver::Transactions))
+ return false;
+ return d->driver->commitTransaction();
+}
+
+/*!
+ Rolls back a transaction on the database, if the driver supports
+ transactions and a transaction() has been started. Returns \c{true}
+ if the operation succeeded. Otherwise it returns \c{false}.
+
+ \note For some databases, the rollback will fail and return
+ \c{false} if there is an \l{QSqlQuery::isActive()} {active query}
+ using the database for a \c{SELECT}. Make the query
+ \l{QSqlQuery::isActive()} {inactive} before doing the rollback.
+
+ Call lastError() to get information about errors.
+
+ \sa QSqlQuery::isActive() QSqlDriver::hasFeature() commit()
+*/
+bool QSqlDatabase::rollback()
+{
+ if (!d->driver->hasFeature(QSqlDriver::Transactions))
+ return false;
+ return d->driver->rollbackTransaction();
+}
+
+/*!
+ Sets the connection's database name to \a name. To have effect,
+ the database name must be set \e{before} the connection is
+ \l{open()} {opened}. Alternatively, you can close() the
+ connection, set the database name, and call open() again. \note
+ The \e{database name} is not the \e{connection name}. The
+ connection name must be passed to addDatabase() at connection
+ object create time.
+
+ For the QOCI (Oracle) driver, the database name is the TNS
+ Service Name.
+
+ For the QODBC driver, the \a name can either be a DSN, a DSN
+ filename (in which case the file must have a \c .dsn extension),
+ or a connection string.
+
+ For example, Microsoft Access users can use the following
+ connection string to open an \c .mdb file directly, instead of
+ having to create a DSN entry in the ODBC manager:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 3
+
+ There is no default value.
+
+ \sa databaseName() setUserName() setPassword() setHostName()
+ \sa setPort() setConnectOptions() open()
+*/
+
+void QSqlDatabase::setDatabaseName(const QString& name)
+{
+ if (isValid())
+ d->dbname = name;
+}
+
+/*!
+ Sets the connection's user name to \a name. To have effect, the
+ user name must be set \e{before} the connection is \l{open()}
+ {opened}. Alternatively, you can close() the connection, set the
+ user name, and call open() again.
+
+ There is no default value.
+
+ \sa userName() setDatabaseName() setPassword() setHostName()
+ \sa setPort() setConnectOptions() open()
+*/
+
+void QSqlDatabase::setUserName(const QString& name)
+{
+ if (isValid())
+ d->uname = name;
+}
+
+/*!
+ Sets the connection's password to \a password. To have effect, the
+ password must be set \e{before} the connection is \l{open()}
+ {opened}. Alternatively, you can close() the connection, set the
+ password, and call open() again.
+
+ There is no default value.
+
+ \warning This function stores the password in plain text within
+ Qt. Use the open() call that takes a password as parameter to
+ avoid this behavior.
+
+ \sa password() setUserName() setDatabaseName() setHostName()
+ \sa setPort() setConnectOptions() open()
+*/
+
+void QSqlDatabase::setPassword(const QString& password)
+{
+ if (isValid())
+ d->pword = password;
+}
+
+/*!
+ Sets the connection's host name to \a host. To have effect, the
+ host name must be set \e{before} the connection is \l{open()}
+ {opened}. Alternatively, you can close() the connection, set the
+ host name, and call open() again.
+
+ There is no default value.
+
+ \sa hostName() setUserName() setPassword() setDatabaseName()
+ \sa setPort() setConnectOptions() open()
+*/
+
+void QSqlDatabase::setHostName(const QString& host)
+{
+ if (isValid())
+ d->hname = host;
+}
+
+/*!
+ Sets the connection's port number to \a port. To have effect, the
+ port number must be set \e{before} the connection is \l{open()}
+ {opened}. Alternatively, you can close() the connection, set the
+ port number, and call open() again..
+
+ There is no default value.
+
+ \sa port() setUserName() setPassword() setHostName()
+ \sa setDatabaseName() setConnectOptions() open()
+*/
+
+void QSqlDatabase::setPort(int port)
+{
+ if (isValid())
+ d->port = port;
+}
+
+/*!
+ Returns the connection's database name, which may be empty.
+ \note The database name is not the connection name.
+
+ \sa setDatabaseName()
+*/
+QString QSqlDatabase::databaseName() const
+{
+ return d->dbname;
+}
+
+/*!
+ Returns the connection's user name; it may be empty.
+
+ \sa setUserName()
+*/
+QString QSqlDatabase::userName() const
+{
+ return d->uname;
+}
+
+/*!
+ Returns the connection's password. If the password was not set
+ with setPassword(), and if the password was given in the open()
+ call, or if no password was used, an empty string is returned.
+*/
+QString QSqlDatabase::password() const
+{
+ return d->pword;
+}
+
+/*!
+ Returns the connection's host name; it may be empty.
+
+ \sa setHostName()
+*/
+QString QSqlDatabase::hostName() const
+{
+ return d->hname;
+}
+
+/*!
+ Returns the connection's driver name.
+
+ \sa addDatabase(), driver()
+*/
+QString QSqlDatabase::driverName() const
+{
+ return d->drvName;
+}
+
+/*!
+ Returns the connection's port number. The value is undefined if
+ the port number has not been set.
+
+ \sa setPort()
+*/
+int QSqlDatabase::port() const
+{
+ return d->port;
+}
+
+/*!
+ Returns the database driver used to access the database
+ connection.
+
+ \sa addDatabase() drivers()
+*/
+
+QSqlDriver* QSqlDatabase::driver() const
+{
+ return d->driver;
+}
+
+/*!
+ Returns information about the last error that occurred on the
+ database.
+
+ Failures that occur in conjunction with an individual query are
+ reported by QSqlQuery::lastError().
+
+ \sa QSqlError, QSqlQuery::lastError()
+*/
+
+QSqlError QSqlDatabase::lastError() const
+{
+ return d->driver->lastError();
+}
+
+
+/*!
+ Returns a list of the database's tables, system tables and views,
+ as specified by the parameter \a type.
+
+ \sa primaryIndex(), record()
+*/
+
+QStringList QSqlDatabase::tables(QSql::TableType type) const
+{
+ return d->driver->tables(type);
+}
+
+/*!
+ Returns the primary index for table \a tablename. If no primary
+ index exists an empty QSqlIndex is returned.
+
+ \sa tables(), record()
+*/
+
+QSqlIndex QSqlDatabase::primaryIndex(const QString& tablename) const
+{
+ return d->driver->primaryIndex(tablename);
+}
+
+
+/*!
+ Returns a QSqlRecord populated with the names of all the fields in
+ the table (or view) called \a tablename. The order in which the
+ fields appear in the record is undefined. If no such table (or
+ view) exists, an empty record is returned.
+*/
+
+QSqlRecord QSqlDatabase::record(const QString& tablename) const
+{
+ return d->driver->record(tablename);
+}
+
+
+/*!
+ Sets database-specific \a options. This must be done before the
+ connection is opened or it has no effect (or you can close() the
+ connection, call this function and open() the connection again).
+
+ The format of the \a options string is a semicolon separated list
+ of option names or option=value pairs. The options depend on the
+ database client used:
+
+ \table
+ \header \i ODBC \i MySQL \i PostgreSQL
+ \row
+
+ \i
+ \list
+ \i SQL_ATTR_ACCESS_MODE
+ \i SQL_ATTR_LOGIN_TIMEOUT
+ \i SQL_ATTR_CONNECTION_TIMEOUT
+ \i SQL_ATTR_CURRENT_CATALOG
+ \i SQL_ATTR_METADATA_ID
+ \i SQL_ATTR_PACKET_SIZE
+ \i SQL_ATTR_TRACEFILE
+ \i SQL_ATTR_TRACE
+ \i SQL_ATTR_CONNECTION_POOLING
+ \i SQL_ATTR_ODBC_VERSION
+ \endlist
+
+ \i
+ \list
+ \i CLIENT_COMPRESS
+ \i CLIENT_FOUND_ROWS
+ \i CLIENT_IGNORE_SPACE
+ \i CLIENT_SSL
+ \i CLIENT_ODBC
+ \i CLIENT_NO_SCHEMA
+ \i CLIENT_INTERACTIVE
+ \i UNIX_SOCKET
+ \i MYSQL_OPT_RECONNECT
+ \endlist
+
+ \i
+ \list
+ \i connect_timeout
+ \i options
+ \i tty
+ \i requiressl
+ \i service
+ \endlist
+
+ \header \i DB2 \i OCI \i TDS
+ \row
+
+ \i
+ \list
+ \i SQL_ATTR_ACCESS_MODE
+ \i SQL_ATTR_LOGIN_TIMEOUT
+ \endlist
+
+ \i
+ \list
+ \i OCI_ATTR_PREFETCH_ROWS
+ \i OCI_ATTR_PREFETCH_MEMORY
+ \endlist
+
+ \i
+ \e none
+
+ \header \i SQLite \i Interbase
+ \row
+
+ \i
+ \list
+ \i QSQLITE_BUSY_TIMEOUT
+ \i QSQLITE_OPEN_READONLY
+ \i QSQLITE_ENABLE_SHARED_CACHE
+ \endlist
+
+ \i
+ \list
+ \i ISC_DPB_LC_CTYPE
+ \i ISC_DPB_SQL_ROLE_NAME
+ \endlist
+
+ \endtable
+
+ Examples:
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 4
+
+ Refer to the client library documentation for more information
+ about the different options.
+
+ \sa connectOptions()
+*/
+
+void QSqlDatabase::setConnectOptions(const QString &options)
+{
+ if (isValid())
+ d->connOptions = options;
+}
+
+/*!
+ Returns the connection options string used for this connection.
+ The string may be empty.
+
+ \sa setConnectOptions()
+ */
+QString QSqlDatabase::connectOptions() const
+{
+ return d->connOptions;
+}
+
+/*!
+ Returns true if a driver called \a name is available; otherwise
+ returns false.
+
+ \sa drivers()
+*/
+
+bool QSqlDatabase::isDriverAvailable(const QString& name)
+{
+ return drivers().contains(name);
+}
+
+/*! \fn QSqlDatabase QSqlDatabase::addDatabase(QSqlDriver* driver, const QString& connectionName)
+
+ This overload is useful when you want to create a database
+ connection with a \l{QSqlDriver} {driver} you instantiated
+ yourself. It might be your own database driver, or you might just
+ need to instantiate one of the Qt drivers yourself. If you do
+ this, it is recommended that you include the driver code in your
+ application. For example, you can create a PostgreSQL connection
+ with your own QPSQL driver like this:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 5
+ \codeline
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 6
+
+ The above code sets up a PostgreSQL connection and instantiates a
+ QPSQLDriver object. Next, addDatabase() is called to add the
+ connection to the known connections so that it can be used by the
+ Qt SQL classes. When a driver is instantiated with a connection
+ handle (or set of handles), Qt assumes that you have already
+ opened the database connection.
+
+ \note We assume that \c qtdir is the directory where Qt is
+ installed. This will pull in the code that is needed to use the
+ PostgreSQL client library and to instantiate a QPSQLDriver object,
+ assuming that you have the PostgreSQL headers somewhere in your
+ include search path.
+
+ Remember that you must link your application against the database
+ client library. Make sure the client library is in your linker's
+ search path, and add lines like these to your \c{.pro} file:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 7
+
+ The method described works for all the supplied drivers. The only
+ difference will be in the driver constructor arguments. Here is a
+ table of the drivers included with Qt, their source code files,
+ and their constructor arguments:
+
+ \table
+ \header \i Driver \i Class name \i Constructor arguments \i File to include
+ \row
+ \i QPSQL
+ \i QPSQLDriver
+ \i PGconn *connection
+ \i \c qsql_psql.cpp
+ \row
+ \i QMYSQL
+ \i QMYSQLDriver
+ \i MYSQL *connection
+ \i \c qsql_mysql.cpp
+ \row
+ \i QOCI
+ \i QOCIDriver
+ \i OCIEnv *environment, OCISvcCtx *serviceContext
+ \i \c qsql_oci.cpp
+ \row
+ \i QODBC
+ \i QODBCDriver
+ \i SQLHANDLE environment, SQLHANDLE connection
+ \i \c qsql_odbc.cpp
+ \row
+ \i QDB2
+ \i QDB2
+ \i SQLHANDLE environment, SQLHANDLE connection
+ \i \c qsql_db2.cpp
+ \row
+ \i QTDS
+ \i QTDSDriver
+ \i LOGINREC *loginRecord, DBPROCESS *dbProcess, const QString &hostName
+ \i \c qsql_tds.cpp
+ \row
+ \i QSQLITE
+ \i QSQLiteDriver
+ \i sqlite *connection
+ \i \c qsql_sqlite.cpp
+ \row
+ \i QIBASE
+ \i QIBaseDriver
+ \i isc_db_handle connection
+ \i \c qsql_ibase.cpp
+ \endtable
+
+ The host name (or service name) is needed when constructing the
+ QTDSDriver for creating new connections for internal queries. This
+ is to prevent blocking when several QSqlQuery objects are used
+ simultaneously.
+
+ \warning Adding a database connection with the same connection
+ name as an existing connection, causes the existing connection to
+ be replaced by the new one.
+
+ \warning The SQL framework takes ownership of the \a driver. It
+ must not be deleted. To remove the connection, use
+ removeDatabase().
+
+ \sa drivers()
+*/
+QSqlDatabase QSqlDatabase::addDatabase(QSqlDriver* driver, const QString& connectionName)
+{
+ QSqlDatabase db(driver);
+ QSqlDatabasePrivate::addDatabase(db, connectionName);
+ return db;
+}
+
+/*!
+ Returns true if the QSqlDatabase has a valid driver.
+
+ Example:
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldatabase.cpp 8
+*/
+bool QSqlDatabase::isValid() const
+{
+ return d->driver && d->driver != d->shared_null()->driver;
+}
+
+#ifdef QT3_SUPPORT
+/*!
+ Use query.record() instead.
+*/
+QSqlRecord QSqlDatabase::record(const QSqlQuery& query) const
+{ return query.record(); }
+
+/*!
+ Use query.record() instead.
+*/
+QSqlRecord QSqlDatabase::recordInfo(const QSqlQuery& query) const
+{ return query.record(); }
+
+/*!
+ \fn QSqlRecord QSqlDatabase::recordInfo(const QString& tablename) const
+
+ Use record() instead.
+*/
+#endif
+
+/*!
+ Clones the database connection \a other and and stores it as \a
+ connectionName. All the settings from the original database, e.g.
+ databaseName(), hostName(), etc., are copied across. Does nothing
+ if \a other is an invalid database. Returns the newly created
+ database connection.
+
+ \note The new connection has not been opened. Before using the new
+ connection, you must call open().
+*/
+QSqlDatabase QSqlDatabase::cloneDatabase(const QSqlDatabase &other, const QString &connectionName)
+{
+ if (!other.isValid())
+ return QSqlDatabase();
+
+ QSqlDatabase db(other.driverName());
+ db.d->copy(other.d);
+ QSqlDatabasePrivate::addDatabase(db, connectionName);
+ return db;
+}
+
+/*!
+ \since 4.4
+
+ Returns the connection name, which may be empty. \note The
+ connection name is not the \l{databaseName()} {database name}.
+
+ \sa addDatabase()
+*/
+QString QSqlDatabase::connectionName() const
+{
+ return d->connName;
+}
+
+/*!
+ \since 4.6
+
+ Sets the default numerical precision policy used by queries created
+ on this database connection to \a precisionPolicy.
+
+ Note: Drivers that don't support fetching numerical values with low
+ precision will ignore the precision policy. You can use
+ QSqlDriver::hasFeature() to find out whether a driver supports this
+ feature.
+
+ Note: Setting the default precision policy to \a precisionPolicy
+ doesn't affect any currently active queries.
+
+ \sa QSql::NumericalPrecisionPolicy, numericalPrecisionPolicy(),
+ QSqlQuery::setNumericalPrecisionPolicy(), QSqlQuery::numericalPrecisionPolicy()
+*/
+void QSqlDatabase::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy)
+{
+ if(driver())
+ driver()->setNumericalPrecisionPolicy(precisionPolicy);
+ d->precisionPolicy = precisionPolicy;
+}
+
+/*!
+ \since 4.6
+
+ Returns the current default precision policy for the database connection.
+
+ \sa QSql::NumericalPrecisionPolicy, setNumericalPrecisionPolicy(),
+ QSqlQuery::numericalPrecisionPolicy(), QSqlQuery::setNumericalPrecisionPolicy()
+*/
+QSql::NumericalPrecisionPolicy QSqlDatabase::numericalPrecisionPolicy() const
+{
+ if(driver())
+ return driver()->numericalPrecisionPolicy();
+ else
+ return d->precisionPolicy;
+}
+
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QSqlDatabase &d)
+{
+ if (!d.isValid()) {
+ dbg.nospace() << "QSqlDatabase(invalid)";
+ return dbg.space();
+ }
+
+ dbg.nospace() << "QSqlDatabase(driver=\"" << d.driverName() << "\", database=\""
+ << d.databaseName() << "\", host=\"" << d.hostName() << "\", port=" << d.port()
+ << ", user=\"" << d.userName() << "\", open=" << d.isOpen() << ")";
+ return dbg.space();
+}
+#endif
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqldatabase.h b/src/sql/kernel/qsqldatabase.h
new file mode 100644
index 0000000000..00fccd6161
--- /dev/null
+++ b/src/sql/kernel/qsqldatabase.h
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLDATABASE_H
+#define QSQLDATABASE_H
+
+#include <QtCore/qstring.h>
+#include <QtSql/qsql.h>
+#ifdef QT3_SUPPORT
+#include <QtSql/qsqlrecord.h>
+#endif
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QSqlError;
+class QSqlDriver;
+class QSqlIndex;
+class QSqlRecord;
+class QSqlQuery;
+class QSqlDatabasePrivate;
+
+class Q_SQL_EXPORT QSqlDriverCreatorBase
+{
+public:
+ virtual ~QSqlDriverCreatorBase() {}
+ virtual QSqlDriver *createObject() const = 0;
+};
+
+template <class T>
+class QSqlDriverCreator : public QSqlDriverCreatorBase
+{
+public:
+ QSqlDriver *createObject() const { return new T; }
+};
+
+class Q_SQL_EXPORT QSqlDatabase
+{
+public:
+ QSqlDatabase();
+ QSqlDatabase(const QSqlDatabase &other);
+ ~QSqlDatabase();
+
+ QSqlDatabase &operator=(const QSqlDatabase &other);
+
+ bool open();
+ bool open(const QString& user, const QString& password);
+ void close();
+ bool isOpen() const;
+ bool isOpenError() const;
+ QStringList tables(QSql::TableType type = QSql::Tables) const;
+ QSqlIndex primaryIndex(const QString& tablename) const;
+ QSqlRecord record(const QString& tablename) const;
+#ifdef QT3_SUPPORT
+ QT3_SUPPORT QSqlRecord record(const QSqlQuery& query) const;
+ inline QT3_SUPPORT QSqlRecord recordInfo(const QString& tablename) const
+ { return record(tablename); }
+ QT3_SUPPORT QSqlRecord recordInfo(const QSqlQuery& query) const;
+#endif
+ QSqlQuery exec(const QString& query = QString()) const;
+ QSqlError lastError() const;
+ bool isValid() const;
+
+ bool transaction();
+ bool commit();
+ bool rollback();
+
+ void setDatabaseName(const QString& name);
+ void setUserName(const QString& name);
+ void setPassword(const QString& password);
+ void setHostName(const QString& host);
+ void setPort(int p);
+ void setConnectOptions(const QString& options = QString());
+ QString databaseName() const;
+ QString userName() const;
+ QString password() const;
+ QString hostName() const;
+ QString driverName() const;
+ int port() const;
+ QString connectOptions() const;
+ QString connectionName() const;
+ void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy);
+ QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const;
+
+ QSqlDriver* driver() const;
+
+ QT_STATIC_CONST char *defaultConnection;
+
+ static QSqlDatabase addDatabase(const QString& type,
+ const QString& connectionName = QLatin1String(defaultConnection));
+ static QSqlDatabase addDatabase(QSqlDriver* driver,
+ const QString& connectionName = QLatin1String(defaultConnection));
+ static QSqlDatabase cloneDatabase(const QSqlDatabase &other, const QString& connectionName);
+ static QSqlDatabase database(const QString& connectionName = QLatin1String(defaultConnection),
+ bool open = true);
+ static void removeDatabase(const QString& connectionName);
+ static bool contains(const QString& connectionName = QLatin1String(defaultConnection));
+ static QStringList drivers();
+ static QStringList connectionNames();
+ static void registerSqlDriver(const QString &name, QSqlDriverCreatorBase *creator);
+ static bool isDriverAvailable(const QString &name);
+
+protected:
+ explicit QSqlDatabase(const QString& type);
+ explicit QSqlDatabase(QSqlDriver* driver);
+
+private:
+ friend class QSqlDatabasePrivate;
+ QSqlDatabasePrivate *d;
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_SQL_EXPORT QDebug operator<<(QDebug, const QSqlDatabase &);
+#endif
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLDATABASE_H
diff --git a/src/sql/kernel/qsqldriver.cpp b/src/sql/kernel/qsqldriver.cpp
new file mode 100644
index 0000000000..bbec21dc0a
--- /dev/null
+++ b/src/sql/kernel/qsqldriver.cpp
@@ -0,0 +1,948 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqldriver.h"
+
+#include "qdatetime.h"
+#include "qsqlerror.h"
+#include "qsqlfield.h"
+#include "qsqlindex.h"
+#include "private/qobject_p.h"
+
+QT_BEGIN_NAMESPACE
+
+static QString prepareIdentifier(const QString &identifier,
+ QSqlDriver::IdentifierType type, const QSqlDriver *driver)
+{
+ Q_ASSERT( driver != NULL );
+ QString ret = identifier;
+ if (!driver->isIdentifierEscaped(identifier, type)) {
+ ret = driver->escapeIdentifier(identifier, type);
+ }
+ return ret;
+}
+
+class QSqlDriverPrivate : public QObjectPrivate
+{
+public:
+ QSqlDriverPrivate();
+ virtual ~QSqlDriverPrivate();
+
+public:
+ // @CHECK: this member is never used. It was named q, which expanded to q_func().
+ QSqlDriver *q_func();
+ uint isOpen : 1;
+ uint isOpenError : 1;
+ QSqlError error;
+ QSql::NumericalPrecisionPolicy precisionPolicy;
+};
+
+inline QSqlDriverPrivate::QSqlDriverPrivate()
+ : QObjectPrivate(), isOpen(false), isOpenError(false), precisionPolicy(QSql::LowPrecisionDouble)
+{
+}
+
+QSqlDriverPrivate::~QSqlDriverPrivate()
+{
+}
+
+/*!
+ \class QSqlDriver
+ \brief The QSqlDriver class is an abstract base class for accessing
+ specific SQL databases.
+
+ \ingroup database
+ \inmodule QtSql
+
+ This class should not be used directly. Use QSqlDatabase instead.
+
+ If you want to create your own SQL drivers, you can subclass this
+ class and reimplement its pure virtual functions and those
+ virtual functions that you need. See \l{How to Write Your Own
+ Database Driver} for more information.
+
+ \sa QSqlDatabase, QSqlResult
+*/
+
+/*!
+ Constructs a new driver with the given \a parent.
+*/
+
+QSqlDriver::QSqlDriver(QObject *parent)
+ : QObject(*new QSqlDriverPrivate, parent)
+{
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+
+QSqlDriver::~QSqlDriver()
+{
+}
+
+/*!
+ \since 4.4
+
+ \fn QSqlDriver::notification(const QString &name)
+
+ This signal is emitted when the database posts an event notification
+ that the driver subscribes to. \a name identifies the event notification.
+
+ \sa subscribeToNotification()
+*/
+
+/*!
+ \fn bool QSqlDriver::open(const QString &db, const QString &user, const QString& password,
+ const QString &host, int port, const QString &options)
+
+ Derived classes must reimplement this pure virtual function to
+ open a database connection on database \a db, using user name \a
+ user, password \a password, host \a host, port \a port and
+ connection options \a options.
+
+ The function must return true on success and false on failure.
+
+ \sa setOpen()
+*/
+
+/*!
+ \fn bool QSqlDriver::close()
+
+ Derived classes must reimplement this pure virtual function in
+ order to close the database connection. Return true on success,
+ false on failure.
+
+ \sa open(), setOpen()
+*/
+
+/*!
+ \fn QSqlResult *QSqlDriver::createResult() const
+
+ Creates an empty SQL result on the database. Derived classes must
+ reimplement this function and return a QSqlResult object
+ appropriate for their database to the caller.
+*/
+
+/*!
+ Returns true if the database connection is open; otherwise returns
+ false.
+*/
+
+bool QSqlDriver::isOpen() const
+{
+ return d_func()->isOpen;
+}
+
+/*!
+ Returns true if the there was an error opening the database
+ connection; otherwise returns false.
+*/
+
+bool QSqlDriver::isOpenError() const
+{
+ return d_func()->isOpenError;
+}
+
+/*!
+ \enum QSqlDriver::DriverFeature
+
+ This enum contains a list of features a driver might support. Use
+ hasFeature() to query whether a feature is supported or not.
+
+ \value Transactions Whether the driver supports SQL transactions.
+ \value QuerySize Whether the database is capable of reporting the size
+ of a query. Note that some databases do not support returning the size
+ (i.e. number of rows returned) of a query, in which case
+ QSqlQuery::size() will return -1.
+ \value BLOB Whether the driver supports Binary Large Object fields.
+ \value Unicode Whether the driver supports Unicode strings if the
+ database server does.
+ \value PreparedQueries Whether the driver supports prepared query execution.
+ \value NamedPlaceholders Whether the driver supports the use of named placeholders.
+ \value PositionalPlaceholders Whether the driver supports the use of positional placeholders.
+ \value LastInsertId Whether the driver supports returning the Id of the last touched row.
+ \value BatchOperations Whether the driver supports batched operations, see QSqlQuery::execBatch()
+ \value SimpleLocking Whether the driver disallows a write lock on a table while other queries have a read lock on it.
+ \value LowPrecisionNumbers Whether the driver allows fetching numerical values with low precision.
+ \value EventNotifications Whether the driver supports database event notifications.
+ \value FinishQuery Whether the driver can do any low-level resource cleanup when QSqlQuery::finish() is called.
+ \value MultipleResultSets Whether the driver can access multiple result sets returned from batched statements or stored procedures.
+
+ More information about supported features can be found in the
+ \l{sql-driver.html}{Qt SQL driver} documentation.
+
+ \sa hasFeature()
+*/
+
+/*!
+ \enum QSqlDriver::StatementType
+
+ This enum contains a list of SQL statement (or clause) types the
+ driver can create.
+
+ \value WhereStatement An SQL \c WHERE statement (e.g., \c{WHERE f = 5}).
+ \value SelectStatement An SQL \c SELECT statement (e.g., \c{SELECT f FROM t}).
+ \value UpdateStatement An SQL \c UPDATE statement (e.g., \c{UPDATE TABLE t set f = 1}).
+ \value InsertStatement An SQL \c INSERT statement (e.g., \c{INSERT INTO t (f) values (1)}).
+ \value DeleteStatement An SQL \c DELETE statement (e.g., \c{DELETE FROM t}).
+
+ \sa sqlStatement()
+*/
+
+/*!
+ \enum QSqlDriver::IdentifierType
+
+ This enum contains a list of SQL identifier types.
+
+ \value FieldName A SQL field name
+ \value TableName A SQL table name
+*/
+
+/*!
+ \fn bool QSqlDriver::hasFeature(DriverFeature feature) const
+
+ Returns true if the driver supports feature \a feature; otherwise
+ returns false.
+
+ Note that some databases need to be open() before this can be
+ determined.
+
+ \sa DriverFeature
+*/
+
+/*!
+ This function sets the open state of the database to \a open.
+ Derived classes can use this function to report the status of
+ open().
+
+ \sa open(), setOpenError()
+*/
+
+void QSqlDriver::setOpen(bool open)
+{
+ d_func()->isOpen = open;
+}
+
+/*!
+ This function sets the open error state of the database to \a
+ error. Derived classes can use this function to report the status
+ of open(). Note that if \a error is true the open state of the
+ database is set to closed (i.e., isOpen() returns false).
+
+ \sa open(), setOpen()
+*/
+
+void QSqlDriver::setOpenError(bool error)
+{
+ d_func()->isOpenError = error;
+ if (error)
+ d_func()->isOpen = false;
+}
+
+/*!
+ This function is called to begin a transaction. If successful,
+ return true, otherwise return false. The default implementation
+ does nothing and returns false.
+
+ \sa commitTransaction(), rollbackTransaction()
+*/
+
+bool QSqlDriver::beginTransaction()
+{
+ return false;
+}
+
+/*!
+ This function is called to commit a transaction. If successful,
+ return true, otherwise return false. The default implementation
+ does nothing and returns false.
+
+ \sa beginTransaction(), rollbackTransaction()
+*/
+
+bool QSqlDriver::commitTransaction()
+{
+ return false;
+}
+
+/*!
+ This function is called to rollback a transaction. If successful,
+ return true, otherwise return false. The default implementation
+ does nothing and returns false.
+
+ \sa beginTransaction(), commitTransaction()
+*/
+
+bool QSqlDriver::rollbackTransaction()
+{
+ return false;
+}
+
+/*!
+ This function is used to set the value of the last error, \a error,
+ that occurred on the database.
+
+ \sa lastError()
+*/
+
+void QSqlDriver::setLastError(const QSqlError &error)
+{
+ d_func()->error = error;
+}
+
+/*!
+ Returns a QSqlError object which contains information about the
+ last error that occurred on the database.
+*/
+
+QSqlError QSqlDriver::lastError() const
+{
+ return d_func()->error;
+}
+
+/*!
+ Returns a list of the names of the tables in the database. The
+ default implementation returns an empty list.
+
+ The \a tableType argument describes what types of tables
+ should be returned. Due to binary compatibility, the string
+ contains the value of the enum QSql::TableTypes as text.
+ An empty string should be treated as QSql::Tables for
+ backward compatibility.
+*/
+
+QStringList QSqlDriver::tables(QSql::TableType) const
+{
+ return QStringList();
+}
+
+/*!
+ Returns the primary index for table \a tableName. Returns an empty
+ QSqlIndex if the table doesn't have a primary index. The default
+ implementation returns an empty index.
+*/
+
+QSqlIndex QSqlDriver::primaryIndex(const QString&) const
+{
+ return QSqlIndex();
+}
+
+
+/*!
+ Returns a QSqlRecord populated with the names of the fields in
+ table \a tableName. If no such table exists, an empty record is
+ returned. The default implementation returns an empty record.
+*/
+
+QSqlRecord QSqlDriver::record(const QString & /* tableName */) const
+{
+ return QSqlRecord();
+}
+
+/*!
+ Returns the \a identifier escaped according to the database rules.
+ \a identifier can either be a table name or field name, dependent
+ on \a type.
+
+ The default implementation does nothing.
+ \sa isIdentifierEscaped()
+ */
+QString QSqlDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
+{
+ return identifier;
+}
+
+/*!
+ Returns whether \a identifier is escaped according to the database rules.
+ \a identifier can either be a table name or field name, dependent
+ on \a type.
+
+ \warning Because of binary compatibility constraints, this function is not virtual.
+ If you want to provide your own implementation in your QSqlDriver subclass,
+ reimplement the isIdentifierEscapedImplementation() slot in your subclass instead.
+ The isIdentifierEscapedFunction() will dynamically detect the slot and call it.
+
+ \sa stripDelimiters(), escapeIdentifier()
+ */
+bool QSqlDriver::isIdentifierEscaped(const QString &identifier, IdentifierType type) const
+{
+ bool result;
+ QMetaObject::invokeMethod(const_cast<QSqlDriver*>(this),
+ "isIdentifierEscapedImplementation", Qt::DirectConnection,
+ Q_RETURN_ARG(bool, result),
+ Q_ARG(QString, identifier),
+ Q_ARG(IdentifierType, type));
+ return result;
+}
+
+/*!
+ Returns the \a identifier with the leading and trailing delimiters removed,
+ \a identifier can either be a table name or field name,
+ dependent on \a type. If \a identifier does not have leading
+ and trailing delimiter characters, \a identifier is returned without
+ modification.
+
+ \warning Because of binary compatibility constraints, this function is not virtual,
+ If you want to provide your own implementation in your QSqlDriver subclass,
+ reimplement the stripDelimitersImplementation() slot in your subclass instead.
+ The stripDelimiters() function will dynamically detect the slot and call it.
+
+ \since 4.5
+ \sa isIdentifierEscaped()
+ */
+QString QSqlDriver::stripDelimiters(const QString &identifier, IdentifierType type) const
+{
+ QString result;
+ QMetaObject::invokeMethod(const_cast<QSqlDriver*>(this),
+ "stripDelimitersImplementation", Qt::DirectConnection,
+ Q_RETURN_ARG(QString, result),
+ Q_ARG(QString, identifier),
+ Q_ARG(IdentifierType, type));
+ return result;
+}
+
+/*!
+ Returns a SQL statement of type \a type for the table \a tableName
+ with the values from \a rec. If \a preparedStatement is true, the
+ string will contain placeholders instead of values.
+
+ This method can be used to manipulate tables without having to worry
+ about database-dependent SQL dialects. For non-prepared statements,
+ the values will be properly escaped.
+*/
+QString QSqlDriver::sqlStatement(StatementType type, const QString &tableName,
+ const QSqlRecord &rec, bool preparedStatement) const
+{
+ int i;
+ QString s;
+ s.reserve(128);
+ switch (type) {
+ case SelectStatement:
+ for (i = 0; i < rec.count(); ++i) {
+ if (rec.isGenerated(i))
+ s.append(prepareIdentifier(rec.fieldName(i), QSqlDriver::FieldName, this)).append(QLatin1String(", "));
+ }
+ if (s.isEmpty())
+ return s;
+ s.chop(2);
+ s.prepend(QLatin1String("SELECT ")).append(QLatin1String(" FROM ")).append(tableName);
+ break;
+ case WhereStatement:
+ if (preparedStatement) {
+ for (int i = 0; i < rec.count(); ++i) {
+ s.append(prepareIdentifier(rec.fieldName(i), FieldName,this));
+ if (rec.isNull(i))
+ s.append(QLatin1String(" IS NULL"));
+ else
+ s.append(QLatin1String(" = ?"));
+ s.append(QLatin1String(" AND "));
+ }
+ } else {
+ for (i = 0; i < rec.count(); ++i) {
+ s.append(prepareIdentifier(rec.fieldName(i), QSqlDriver::FieldName, this));
+ QString val = formatValue(rec.field(i));
+ if (val == QLatin1String("NULL"))
+ s.append(QLatin1String(" IS NULL"));
+ else
+ s.append(QLatin1String(" = ")).append(val);
+ s.append(QLatin1String(" AND "));
+ }
+ }
+ if (!s.isEmpty()) {
+ s.prepend(QLatin1String("WHERE "));
+ s.chop(5); // remove tailing AND
+ }
+ break;
+ case UpdateStatement:
+ s.append(QLatin1String("UPDATE ")).append(tableName).append(
+ QLatin1String(" SET "));
+ for (i = 0; i < rec.count(); ++i) {
+ if (!rec.isGenerated(i))
+ continue;
+ s.append(prepareIdentifier(rec.fieldName(i), QSqlDriver::FieldName, this)).append(QLatin1Char('='));
+ if (preparedStatement)
+ s.append(QLatin1Char('?'));
+ else
+ s.append(formatValue(rec.field(i)));
+ s.append(QLatin1String(", "));
+ }
+ if (s.endsWith(QLatin1String(", ")))
+ s.chop(2);
+ else
+ s.clear();
+ break;
+ case DeleteStatement:
+ s.append(QLatin1String("DELETE FROM ")).append(tableName);
+ break;
+ case InsertStatement: {
+ s.append(QLatin1String("INSERT INTO ")).append(tableName).append(QLatin1String(" ("));
+ QString vals;
+ for (i = 0; i < rec.count(); ++i) {
+ if (!rec.isGenerated(i))
+ continue;
+ s.append(prepareIdentifier(rec.fieldName(i), QSqlDriver::FieldName, this)).append(QLatin1String(", "));
+ if (preparedStatement)
+ vals.append(QLatin1Char('?'));
+ else
+ vals.append(formatValue(rec.field(i)));
+ vals.append(QLatin1String(", "));
+ }
+ if (vals.isEmpty()) {
+ s.clear();
+ } else {
+ vals.chop(2); // remove trailing comma
+ s[s.length() - 2] = QLatin1Char(')');
+ s.append(QLatin1String("VALUES (")).append(vals).append(QLatin1Char(')'));
+ }
+ break; }
+ }
+ return s;
+}
+
+/*!
+ Returns a string representation of the \a field value for the
+ database. This is used, for example, when constructing INSERT and
+ UPDATE statements.
+
+ The default implementation returns the value formatted as a string
+ according to the following rules:
+
+ \list
+
+ \i If \a field is character data, the value is returned enclosed
+ in single quotation marks, which is appropriate for many SQL
+ databases. Any embedded single-quote characters are escaped
+ (replaced with two single-quote characters). If \a trimStrings is
+ true (the default is false), all trailing whitespace is trimmed
+ from the field.
+
+ \i If \a field is date/time data, the value is formatted in ISO
+ format and enclosed in single quotation marks. If the date/time
+ data is invalid, "NULL" is returned.
+
+ \i If \a field is \link QByteArray bytearray\endlink data, and the
+ driver can edit binary fields, the value is formatted as a
+ hexadecimal string.
+
+ \i For any other field type, toString() is called on its value
+ and the result of this is returned.
+
+ \endlist
+
+ \sa QVariant::toString()
+
+*/
+QString QSqlDriver::formatValue(const QSqlField &field, bool trimStrings) const
+{
+ const QLatin1String nullTxt("NULL");
+
+ QString r;
+ if (field.isNull())
+ r = nullTxt;
+ else {
+ switch (field.type()) {
+ case QVariant::Int:
+ case QVariant::UInt:
+ if (field.value().type() == QVariant::Bool)
+ r = field.value().toBool() ? QLatin1String("1") : QLatin1String("0");
+ else
+ r = field.value().toString();
+ break;
+#ifndef QT_NO_DATESTRING
+ case QVariant::Date:
+ if (field.value().toDate().isValid())
+ r = QLatin1Char('\'') + field.value().toDate().toString(Qt::ISODate)
+ + QLatin1Char('\'');
+ else
+ r = nullTxt;
+ break;
+ case QVariant::Time:
+ if (field.value().toTime().isValid())
+ r = QLatin1Char('\'') + field.value().toTime().toString(Qt::ISODate)
+ + QLatin1Char('\'');
+ else
+ r = nullTxt;
+ break;
+ case QVariant::DateTime:
+ if (field.value().toDateTime().isValid())
+ r = QLatin1Char('\'') +
+ field.value().toDateTime().toString(Qt::ISODate) + QLatin1Char('\'');
+ else
+ r = nullTxt;
+ break;
+#endif
+ case QVariant::String:
+ case QVariant::Char:
+ {
+ QString result = field.value().toString();
+ if (trimStrings) {
+ int end = result.length();
+ while (end && result.at(end-1).isSpace()) /* skip white space from end */
+ end--;
+ result.truncate(end);
+ }
+ /* escape the "'" character */
+ result.replace(QLatin1Char('\''), QLatin1String("''"));
+ r = QLatin1Char('\'') + result + QLatin1Char('\'');
+ break;
+ }
+ case QVariant::Bool:
+ r = QString::number(field.value().toBool());
+ break;
+ case QVariant::ByteArray : {
+ if (hasFeature(BLOB)) {
+ 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 = QLatin1Char('\'') + res + QLatin1Char('\'');
+ break;
+ }
+ }
+ default:
+ r = field.value().toString();
+ break;
+ }
+ }
+ return r;
+}
+
+/*!
+ Returns the low-level database handle wrapped in a QVariant or an
+ invalid variant if there is no handle.
+
+ \warning Use this with uttermost care and only if you know what you're doing.
+
+ \warning The handle returned here can become a stale pointer if the connection
+ is modified (for example, if you close the connection).
+
+ \warning The handle can be NULL if the connection is not open yet.
+
+ The handle returned here is database-dependent, you should query the type
+ name of the variant before accessing it.
+
+ This example retrieves the handle for a connection to sqlite:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldriver.cpp 0
+
+ This snippet returns the handle for PostgreSQL or MySQL:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqldriver.cpp 1
+
+ \sa QSqlResult::handle()
+*/
+QVariant QSqlDriver::handle() const
+{
+ return QVariant();
+}
+
+/*!
+ \fn QSqlRecord QSqlDriver::record(const QSqlQuery& query) const
+
+ Use query.record() instead.
+*/
+
+/*!
+ \fn QSqlRecord QSqlDriver::recordInfo(const QString& tablename) const
+
+ Use record() instead.
+*/
+
+/*!
+ \fn QSqlRecord QSqlDriver::recordInfo(const QSqlQuery& query) const
+
+ Use query.record() instead.
+*/
+
+/*!
+ \fn QString QSqlDriver::nullText() const
+
+ sqlStatement() is now used to generate SQL. Use tr("NULL") for example, instead.
+*/
+
+/*!
+ \fn QString QSqlDriver::formatValue(const QSqlField *field, bool trimStrings) const
+
+ Use the other formatValue() overload instead.
+*/
+
+/*!
+ This function is called to subscribe to event notifications from the database.
+ \a name identifies the event notification.
+
+ If successful, return true, otherwise return false.
+
+ The database must be open when this function is called. When the database is closed
+ by calling close() all subscribed event notifications are automatically unsubscribed.
+ Note that calling open() on an already open database may implicitly cause close() to
+ be called, which will cause the driver to unsubscribe from all event notifications.
+
+ When an event notification identified by \a name is posted by the database the
+ notification() signal is emitted.
+
+ \warning Because of binary compatibility constraints, this function is not virtual.
+ If you want to provide event notification support in your own QSqlDriver subclass,
+ reimplement the subscribeToNotificationImplementation() slot in your subclass instead.
+ The subscribeToNotification() function will dynamically detect the slot and call it.
+
+ \since 4.4
+ \sa unsubscribeFromNotification() subscribedToNotifications() QSqlDriver::hasFeature()
+*/
+bool QSqlDriver::subscribeToNotification(const QString &name)
+{
+ bool result;
+ QMetaObject::invokeMethod(const_cast<QSqlDriver *>(this),
+ "subscribeToNotificationImplementation", Qt::DirectConnection,
+ Q_RETURN_ARG(bool, result),
+ Q_ARG(QString, name));
+ return result;
+}
+
+/*!
+ This function is called to unsubscribe from event notifications from the database.
+ \a name identifies the event notification.
+
+ If successful, return true, otherwise return false.
+
+ The database must be open when this function is called. All subscribed event
+ notifications are automatically unsubscribed from when the close() function is called.
+
+ After calling \e this function the notification() signal will no longer be emitted
+ when an event notification identified by \a name is posted by the database.
+
+ \warning Because of binary compatibility constraints, this function is not virtual.
+ If you want to provide event notification support in your own QSqlDriver subclass,
+ reimplement the unsubscribeFromNotificationImplementation() slot in your subclass instead.
+ The unsubscribeFromNotification() function will dynamically detect the slot and call it.
+
+ \since 4.4
+ \sa subscribeToNotification() subscribedToNotifications()
+*/
+bool QSqlDriver::unsubscribeFromNotification(const QString &name)
+{
+ bool result;
+ QMetaObject::invokeMethod(const_cast<QSqlDriver *>(this),
+ "unsubscribeFromNotificationImplementation", Qt::DirectConnection,
+ Q_RETURN_ARG(bool, result),
+ Q_ARG(QString, name));
+ return result;
+}
+
+/*!
+ Returns a list of the names of the event notifications that are currently subscribed to.
+
+ \warning Because of binary compatibility constraints, this function is not virtual.
+ If you want to provide event notification support in your own QSqlDriver subclass,
+ reimplement the subscribedToNotificationsImplementation() slot in your subclass instead.
+ The subscribedToNotifications() function will dynamically detect the slot and call it.
+
+ \since 4.4
+ \sa subscribeToNotification() unsubscribeFromNotification()
+*/
+QStringList QSqlDriver::subscribedToNotifications() const
+{
+ QStringList result;
+ QMetaObject::invokeMethod(const_cast<QSqlDriver *>(this),
+ "subscribedToNotificationsImplementation", Qt::DirectConnection,
+ Q_RETURN_ARG(QStringList, result));
+ return result;
+}
+
+/*!
+ This slot is called to subscribe to event notifications from the database.
+ \a name identifies the event notification.
+
+ If successful, return true, otherwise return false.
+
+ The database must be open when this \e slot is called. When the database is closed
+ by calling close() all subscribed event notifications are automatically unsubscribed.
+ Note that calling open() on an already open database may implicitly cause close() to
+ be called, which will cause the driver to unsubscribe from all event notifications.
+
+ When an event notification identified by \a name is posted by the database the
+ notification() signal is emitted.
+
+ Reimplement this slot to provide your own QSqlDriver subclass with event notification
+ support; because of binary compatibility constraints, the subscribeToNotification()
+ function (introduced in Qt 4.4) is not virtual. Instead, subscribeToNotification()
+ will dynamically detect and call \e this slot. The default implementation does nothing
+ and returns false.
+
+ \since 4.4
+ \sa subscribeToNotification()
+*/
+bool QSqlDriver::subscribeToNotificationImplementation(const QString &name)
+{
+ Q_UNUSED(name);
+ return false;
+}
+
+/*!
+ This slot is called to unsubscribe from event notifications from the database.
+ \a name identifies the event notification.
+
+ If successful, return true, otherwise return false.
+
+ The database must be open when \e this slot is called. All subscribed event
+ notifications are automatically unsubscribed from when the close() function is called.
+
+ After calling \e this slot the notification() signal will no longer be emitted
+ when an event notification identified by \a name is posted by the database.
+
+ Reimplement this slot to provide your own QSqlDriver subclass with event notification
+ support; because of binary compatibility constraints, the unsubscribeFromNotification()
+ function (introduced in Qt 4.4) is not virtual. Instead, unsubscribeFromNotification()
+ will dynamically detect and call \e this slot. The default implementation does nothing
+ and returns false.
+
+ \since 4.4
+ \sa unsubscribeFromNotification()
+*/
+bool QSqlDriver::unsubscribeFromNotificationImplementation(const QString &name)
+{
+ Q_UNUSED(name);
+ return false;
+}
+
+/*!
+ Returns a list of the names of the event notifications that are currently subscribed to.
+
+ Reimplement this slot to provide your own QSqlDriver subclass with event notification
+ support; because of binary compatibility constraints, the subscribedToNotifications()
+ function (introduced in Qt 4.4) is not virtual. Instead, subscribedToNotifications()
+ will dynamically detect and call \e this slot. The default implementation simply
+ returns an empty QStringList.
+
+ \since 4.4
+ \sa subscribedToNotifications()
+*/
+QStringList QSqlDriver::subscribedToNotificationsImplementation() const
+{
+ return QStringList();
+}
+
+/*!
+ \since 4.6
+
+ This slot returns whether \a identifier is escaped according to the database rules.
+ \a identifier can either be a table name or field name, dependent
+ on \a type.
+
+ Because of binary compatibility constraints, isIdentifierEscaped() function
+ (introduced in Qt 4.5) is not virtual. Instead, isIdentifierEscaped() will
+ dynamically detect and call \e this slot. The default implementation
+ assumes the escape/delimiter character is a double quote. Reimplement this
+ slot in your own QSqlDriver if your database engine uses a different
+ delimiter character.
+
+ \sa isIdentifierEscaped()
+ */
+bool QSqlDriver::isIdentifierEscapedImplementation(const QString &identifier, IdentifierType type) const
+{
+ Q_UNUSED(type);
+ return identifier.size() > 2
+ && identifier.startsWith(QLatin1Char('"')) //left delimited
+ && identifier.endsWith(QLatin1Char('"')); //right delimited
+}
+
+/*!
+ \since 4.6
+
+ This slot returns \a identifier with the leading and trailing delimiters removed,
+ \a identifier can either be a tablename or field name, dependent on \a type.
+ If \a identifier does not have leading and trailing delimiter characters, \a
+ identifier is returned without modification.
+
+ Because of binary compatibility constraints, the stripDelimiters() function
+ (introduced in Qt 4.5) is not virtual. Instead, stripDelimiters() will
+ dynamically detect and call \e this slot. It generally unnecessary
+ to reimplement this slot.
+
+ \sa stripDelimiters()
+ */
+QString QSqlDriver::stripDelimitersImplementation(const QString &identifier, IdentifierType type) const
+{
+ QString ret;
+ if (this->isIdentifierEscaped(identifier, type)) {
+ ret = identifier.mid(1);
+ ret.chop(1);
+ } else {
+ ret = identifier;
+ }
+ return ret;
+}
+
+/*!
+ \since 4.6
+
+ Sets the default numerical precision policy used by queries created
+ by this driver to \a precisionPolicy.
+
+ Note: Setting the default precision policy to \a precisionPolicy
+ doesn't affect any currently active queries.
+
+ \sa QSql::NumericalPrecisionPolicy, numericalPrecisionPolicy(),
+ QSqlQuery::setNumericalPrecisionPolicy(), QSqlQuery::numericalPrecisionPolicy()
+*/
+void QSqlDriver::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy)
+{
+ d_func()->precisionPolicy = precisionPolicy;
+}
+
+/*!
+ \since 4.6
+
+ Returns the current default precision policy for the database connection.
+
+ \sa QSql::NumericalPrecisionPolicy, setNumericalPrecisionPolicy(),
+ QSqlQuery::numericalPrecisionPolicy(), QSqlQuery::setNumericalPrecisionPolicy()
+*/
+QSql::NumericalPrecisionPolicy QSqlDriver::numericalPrecisionPolicy() const
+{
+ return d_func()->precisionPolicy;
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqldriver.h b/src/sql/kernel/qsqldriver.h
new file mode 100644
index 0000000000..8adffcf25d
--- /dev/null
+++ b/src/sql/kernel/qsqldriver.h
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLDRIVER_H
+#define QSQLDRIVER_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qstringlist.h>
+#include <QtSql/qsql.h>
+#ifdef QT3_SUPPORT
+#include <QtSql/qsqlquery.h>
+#endif
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QSqlDatabase;
+class QSqlDriverPrivate;
+class QSqlError;
+class QSqlField;
+class QSqlIndex;
+class QSqlRecord;
+class QSqlResult;
+class QVariant;
+
+class Q_SQL_EXPORT QSqlDriver : public QObject
+{
+ friend class QSqlDatabase;
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QSqlDriver)
+
+public:
+ enum DriverFeature { Transactions, QuerySize, BLOB, Unicode, PreparedQueries,
+ NamedPlaceholders, PositionalPlaceholders, LastInsertId,
+ BatchOperations, SimpleLocking, LowPrecisionNumbers,
+ EventNotifications, FinishQuery, MultipleResultSets };
+
+ enum StatementType { WhereStatement, SelectStatement, UpdateStatement,
+ InsertStatement, DeleteStatement };
+
+ enum IdentifierType { FieldName, TableName };
+
+ explicit QSqlDriver(QObject *parent=0);
+ ~QSqlDriver();
+ virtual bool isOpen() const;
+ bool isOpenError() const;
+
+ virtual bool beginTransaction();
+ virtual bool commitTransaction();
+ virtual bool rollbackTransaction();
+ virtual QStringList tables(QSql::TableType tableType) const;
+ virtual QSqlIndex primaryIndex(const QString &tableName) const;
+ virtual QSqlRecord record(const QString &tableName) const;
+#ifdef QT3_SUPPORT
+ inline QT3_SUPPORT QSqlRecord record(const QSqlQuery& query) const
+ { return query.record(); }
+ inline QT3_SUPPORT QSqlRecord recordInfo(const QString& tablename) const
+ { return record(tablename); }
+ inline QT3_SUPPORT QSqlRecord recordInfo(const QSqlQuery& query) const
+ { return query.record(); }
+ inline QT3_SUPPORT QString nullText() const { return QLatin1String("NULL"); }
+ inline QT3_SUPPORT QString formatValue(const QSqlField *field, bool trimStrings = false) const
+ { return field ? formatValue(*field, trimStrings) : QString(); }
+#endif
+ virtual QString formatValue(const QSqlField& field, bool trimStrings = false) const;
+
+ virtual QString escapeIdentifier(const QString &identifier, IdentifierType type) const;
+ virtual QString sqlStatement(StatementType type, const QString &tableName,
+ const QSqlRecord &rec, bool preparedStatement) const;
+
+ QSqlError lastError() const;
+
+ virtual QVariant handle() const;
+ virtual bool hasFeature(DriverFeature f) const = 0;
+ virtual void close() = 0;
+ virtual QSqlResult *createResult() const = 0;
+
+ virtual bool open(const QString& db,
+ const QString& user = QString(),
+ const QString& password = QString(),
+ const QString& host = QString(),
+ int port = -1,
+ const QString& connOpts = QString()) = 0;
+ bool subscribeToNotification(const QString &name); // ### Qt 5: make virtual
+ bool unsubscribeFromNotification(const QString &name); // ### Qt 5: make virtual
+ QStringList subscribedToNotifications() const; // ### Qt 5: make virtual
+
+ bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const; // ### Qt 5: make virtual
+ QString stripDelimiters(const QString &identifier, IdentifierType type) const; // ### Qt 5: make virtual
+
+ void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy);
+ QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const;
+
+Q_SIGNALS:
+ void notification(const QString &name);
+
+protected:
+ virtual void setOpen(bool o);
+ virtual void setOpenError(bool e);
+ virtual void setLastError(const QSqlError& e);
+
+protected Q_SLOTS:
+ bool subscribeToNotificationImplementation(const QString &name); // ### Qt 5: eliminate, see subscribeToNotification()
+ bool unsubscribeFromNotificationImplementation(const QString &name); // ### Qt 5: eliminate, see unsubscribeFromNotification()
+ QStringList subscribedToNotificationsImplementation() const; // ### Qt 5: eliminate, see subscribedNotifications()
+
+ bool isIdentifierEscapedImplementation(const QString &identifier, IdentifierType type) const; // ### Qt 5: eliminate, see isIdentifierEscaped()
+ QString stripDelimitersImplementation(const QString &identifier, IdentifierType type) const; // ### Qt 5: eliminate, see stripDelimiters()
+
+private:
+ Q_DISABLE_COPY(QSqlDriver)
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLDRIVER_H
diff --git a/src/sql/kernel/qsqldriverplugin.cpp b/src/sql/kernel/qsqldriverplugin.cpp
new file mode 100644
index 0000000000..b1edfb9d19
--- /dev/null
+++ b/src/sql/kernel/qsqldriverplugin.cpp
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqldriverplugin.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QSqlDriverPlugin
+ \brief The QSqlDriverPlugin class provides an abstract base for custom QSqlDriver plugins.
+
+ \ingroup plugins
+ \inmodule QtSql
+
+ The SQL driver plugin is a simple plugin interface that makes it
+ easy to create your own SQL driver plugins that can be loaded
+ dynamically by Qt.
+
+ Writing a SQL plugin is achieved by subclassing this base class,
+ reimplementing the pure virtual functions keys() and create(), and
+ exporting the class with the Q_EXPORT_PLUGIN2() macro. See the SQL
+ plugins that come with Qt for example implementations (in the
+ \c{plugins/src/sqldrivers} subdirectory of the source
+ distribution).
+
+ \sa {How to Create Qt Plugins}
+*/
+
+/*!
+ \fn QStringList QSqlDriverPlugin::keys() const
+
+ Returns the list of drivers (keys) this plugin supports.
+
+ These keys are usually the class names of the custom drivers that
+ are implemented in the plugin.
+
+ \sa create()
+*/
+
+/*!
+ \fn QSqlDriver *QSqlDriverPlugin::create(const QString& key)
+
+ Creates and returns a QSqlDriver object for the driver called \a
+ key. The driver key is usually the class name of the required
+ driver. Keys are case sensitive.
+
+ \sa keys()
+*/
+
+/*!
+ Constructs a SQL driver plugin and sets the parent to \a parent.
+ This is invoked automatically by the Q_EXPORT_PLUGIN2() macro.
+*/
+
+QSqlDriverPlugin::QSqlDriverPlugin(QObject *parent)
+ : QObject(parent)
+{
+}
+
+/*!
+ Destroys the SQL driver plugin.
+
+ You never have to call this explicitly. Qt destroys a plugin
+ automatically when it is no longer used.
+*/
+QSqlDriverPlugin::~QSqlDriverPlugin()
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqldriverplugin.h b/src/sql/kernel/qsqldriverplugin.h
new file mode 100644
index 0000000000..4783a211b8
--- /dev/null
+++ b/src/sql/kernel/qsqldriverplugin.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLDRIVERPLUGIN_H
+#define QSQLDRIVERPLUGIN_H
+
+#include <QtCore/qplugin.h>
+#include <QtCore/qfactoryinterface.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QSqlDriver;
+
+struct Q_SQL_EXPORT QSqlDriverFactoryInterface : public QFactoryInterface
+{
+ virtual QSqlDriver *create(const QString &name) = 0;
+};
+
+#define QSqlDriverFactoryInterface_iid "com.trolltech.Qt.QSqlDriverFactoryInterface"
+Q_DECLARE_INTERFACE(QSqlDriverFactoryInterface, QSqlDriverFactoryInterface_iid)
+
+class Q_SQL_EXPORT QSqlDriverPlugin : public QObject, public QSqlDriverFactoryInterface
+{
+ Q_OBJECT
+ Q_INTERFACES(QSqlDriverFactoryInterface:QFactoryInterface)
+public:
+ explicit QSqlDriverPlugin(QObject *parent = 0);
+ ~QSqlDriverPlugin();
+
+ virtual QStringList keys() const = 0;
+ virtual QSqlDriver *create(const QString &key) = 0;
+
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLDRIVERPLUGIN_H
diff --git a/src/sql/kernel/qsqlerror.cpp b/src/sql/kernel/qsqlerror.cpp
new file mode 100644
index 0000000000..526d32e452
--- /dev/null
+++ b/src/sql/kernel/qsqlerror.cpp
@@ -0,0 +1,253 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqlerror.h"
+#include "qdebug.h"
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QSqlError &s)
+{
+ dbg.nospace() << "QSqlError(" << s.number() << ", " << s.driverText() <<
+ ", " << s.databaseText() << ')';
+ return dbg.space();
+}
+#endif
+
+/*!
+ \class QSqlError
+ \brief The QSqlError class provides SQL database error information.
+
+ \ingroup database
+ \inmodule QtSql
+
+ A QSqlError object can provide database-specific error data,
+ including the driverText() and databaseText() messages (or both
+ concatenated together as text()), and the error number() and
+ type(). The functions all have setters so that you can create and
+ return QSqlError objects from your own classes, for example from
+ your own SQL drivers.
+
+ \sa QSqlDatabase::lastError(), QSqlQuery::lastError()
+*/
+
+/*!
+ \enum QSqlError::ErrorType
+
+ This enum type describes the context in which the error occurred, e.g., a connection error, a statement error, etc.
+
+ \value NoError No error occurred.
+ \value ConnectionError Connection error.
+ \value StatementError SQL statement syntax error.
+ \value TransactionError Transaction failed error.
+ \value UnknownError Unknown error.
+
+ \omitvalue None
+ \omitvalue Connection
+ \omitvalue Statement
+ \omitvalue Transaction
+ \omitvalue Unknown
+*/
+
+/*!
+ Constructs an error containing the driver error text \a
+ driverText, the database-specific error text \a databaseText, the
+ type \a type and the optional error number \a number.
+*/
+
+QSqlError::QSqlError(const QString& driverText, const QString& databaseText, ErrorType type,
+ int number)
+ : driverError(driverText), databaseError(databaseText), errorType(type), errorNumber(number)
+{
+}
+
+/*!
+ Creates a copy of \a other.
+*/
+QSqlError::QSqlError(const QSqlError& other)
+ : driverError(other.driverError), databaseError(other.databaseError),
+ errorType(other.errorType),
+ errorNumber(other.errorNumber)
+{
+}
+
+/*!
+ Assigns the \a other error's values to this error.
+*/
+
+QSqlError& QSqlError::operator=(const QSqlError& other)
+{
+ driverError = other.driverError;
+ databaseError = other.databaseError;
+ errorType = other.errorType;
+ errorNumber = other.errorNumber;
+ return *this;
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+
+QSqlError::~QSqlError()
+{
+}
+
+/*!
+ Returns the text of the error as reported by the driver. This may
+ contain database-specific descriptions. It may also be empty.
+
+ \sa setDriverText() databaseText() text()
+*/
+QString QSqlError::driverText() const
+{
+ return driverError;
+}
+
+/*!
+ Sets the driver error text to the value of \a driverText.
+
+ \sa driverText() setDatabaseText() text()
+*/
+
+void QSqlError::setDriverText(const QString& driverText)
+{
+ driverError = driverText;
+}
+
+/*!
+ Returns the text of the error as reported by the database. This
+ may contain database-specific descriptions; it may be empty.
+
+ \sa setDatabaseText() driverText() text()
+*/
+
+QString QSqlError::databaseText() const
+{
+ return databaseError;
+}
+
+/*!
+ Sets the database error text to the value of \a databaseText.
+
+ \sa databaseText() setDriverText() text()
+*/
+
+void QSqlError::setDatabaseText(const QString& databaseText)
+{
+ databaseError = databaseText;
+}
+
+/*!
+ Returns the error type, or -1 if the type cannot be determined.
+
+ \sa setType()
+*/
+
+QSqlError::ErrorType QSqlError::type() const
+{
+ return errorType;
+}
+
+/*!
+ Sets the error type to the value of \a type.
+
+ \sa type()
+*/
+
+void QSqlError::setType(ErrorType type)
+{
+ errorType = type;
+}
+
+/*!
+ Returns the database-specific error number, or -1 if it cannot be
+ determined.
+
+ \sa setNumber()
+*/
+
+int QSqlError::number() const
+{
+ return errorNumber;
+}
+
+/*!
+ Sets the database-specific error number to \a number.
+
+ \sa number()
+*/
+
+void QSqlError::setNumber(int number)
+{
+ errorNumber = number;
+}
+
+/*!
+ This is a convenience function that returns databaseText() and
+ driverText() concatenated into a single string.
+
+ \sa driverText() databaseText()
+*/
+
+QString QSqlError::text() const
+{
+ QString result = databaseError;
+ if (!databaseError.endsWith(QLatin1String("\n")))
+ result += QLatin1Char(' ');
+ result += driverError;
+ return result;
+}
+
+/*!
+ Returns true if an error is set, otherwise false.
+
+ Example:
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqlerror.cpp 0
+
+ \sa type()
+*/
+bool QSqlError::isValid() const
+{
+ return errorType != NoError;
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqlerror.h b/src/sql/kernel/qsqlerror.h
new file mode 100644
index 0000000000..5375e94127
--- /dev/null
+++ b/src/sql/kernel/qsqlerror.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLERROR_H
+#define QSQLERROR_H
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class Q_SQL_EXPORT QSqlError
+{
+public:
+ enum ErrorType {
+ NoError,
+ ConnectionError,
+ StatementError,
+ TransactionError,
+ UnknownError
+#ifdef QT3_SUPPORT
+ , None = NoError,
+ Connection = ConnectionError,
+ Statement = StatementError,
+ Transaction = TransactionError,
+ Unknown = UnknownError
+#endif
+ };
+ QSqlError( const QString& driverText = QString(),
+ const QString& databaseText = QString(),
+ ErrorType type = NoError,
+ int number = -1);
+ QSqlError(const QSqlError& other);
+ QSqlError& operator=(const QSqlError& other);
+ ~QSqlError();
+
+ QString driverText() const;
+ void setDriverText(const QString& driverText);
+ QString databaseText() const;
+ void setDatabaseText(const QString& databaseText);
+ ErrorType type() const;
+ void setType(ErrorType type);
+ int number() const;
+ void setNumber(int number);
+ QString text() const;
+ bool isValid() const;
+
+private:
+ QString driverError;
+ QString databaseError;
+ ErrorType errorType;
+ int errorNumber;
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_SQL_EXPORT QDebug operator<<(QDebug, const QSqlError &);
+#endif
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLERROR_H
diff --git a/src/sql/kernel/qsqlfield.cpp b/src/sql/kernel/qsqlfield.cpp
new file mode 100644
index 0000000000..b7e58b79d9
--- /dev/null
+++ b/src/sql/kernel/qsqlfield.cpp
@@ -0,0 +1,560 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqlfield.h"
+#include "qatomic.h"
+#include "qdebug.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSqlFieldPrivate
+{
+public:
+ QSqlFieldPrivate(const QString &name,
+ QVariant::Type type) :
+ ref(1), nm(name), ro(false), type(type), req(QSqlField::Unknown),
+ len(-1), prec(-1), tp(-1), gen(true), autoval(false)
+ {
+ }
+
+ QSqlFieldPrivate(const QSqlFieldPrivate &other)
+ : ref(1),
+ nm(other.nm),
+ ro(other.ro),
+ type(other.type),
+ req(other.req),
+ len(other.len),
+ prec(other.prec),
+ def(other.def),
+ tp(other.tp),
+ gen(other.gen),
+ autoval(other.autoval)
+ {}
+
+ bool operator==(const QSqlFieldPrivate& other) const
+ {
+ return (nm == other.nm
+ && ro == other.ro
+ && type == other.type
+ && req == other.req
+ && len == other.len
+ && prec == other.prec
+ && def == other.def
+ && gen == other.gen
+ && autoval == other.autoval);
+ }
+
+ QAtomicInt ref;
+ QString nm;
+ uint ro: 1;
+ QVariant::Type type;
+ QSqlField::RequiredStatus req;
+ int len;
+ int prec;
+ QVariant def;
+ int tp;
+ uint gen: 1;
+ uint autoval: 1;
+};
+
+
+/*!
+ \class QSqlField
+ \brief The QSqlField class manipulates the fields in SQL database tables
+ and views.
+
+ \ingroup database
+ \ingroup shared
+ \inmodule QtSql
+
+ QSqlField represents the characteristics of a single column in a
+ database table or view, such as the data type and column name. A
+ field also contains the value of the database column, which can be
+ viewed or changed.
+
+ Field data values are stored as QVariants. Using an incompatible
+ type is not permitted. For example:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 2
+
+ However, the field will attempt to cast certain data types to the
+ field data type where possible:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 3
+
+ QSqlField objects are rarely created explicitly in application
+ code. They are usually accessed indirectly through \l{QSqlRecord}s
+ that already contain a list of fields. For example:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 4
+ \dots
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 5
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 6
+
+ A QSqlField object can provide some meta-data about the field, for
+ example, its name(), variant type(), length(), precision(),
+ defaultValue(), typeID(), and its requiredStatus(),
+ isGenerated() and isReadOnly(). The field's data can be
+ checked to see if it isNull(), and its value() retrieved. When
+ editing the data can be set with setValue() or set to NULL with
+ clear().
+
+ \sa QSqlRecord
+*/
+
+/*!
+ \enum QSqlField::RequiredStatus
+
+ Specifies whether the field is required or optional.
+
+ \value Required The field must be specified when inserting records.
+ \value Optional The fields doesn't have to be specified when inserting records.
+ \value Unknown The database driver couldn't determine whether the field is required or
+ optional.
+
+ \sa requiredStatus()
+*/
+
+/*!
+ Constructs an empty field called \a fieldName of variant type \a
+ type.
+
+ \sa setRequiredStatus() setLength() setPrecision() setDefaultValue() setGenerated() setReadOnly()
+*/
+QSqlField::QSqlField(const QString& fieldName, QVariant::Type type)
+{
+ d = new QSqlFieldPrivate(fieldName, type);
+ val = QVariant(type);
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+
+QSqlField::QSqlField(const QSqlField& other)
+{
+ d = other.d;
+ d->ref.ref();
+ val = other.val;
+}
+
+/*!
+ Sets the field equal to \a other.
+*/
+
+QSqlField& QSqlField::operator=(const QSqlField& other)
+{
+ qAtomicAssign(d, other.d);
+ val = other.val;
+ return *this;
+}
+
+
+/*! \fn bool QSqlField::operator!=(const QSqlField &other) const
+ Returns true if the field is unequal to \a other; otherwise returns
+ false.
+*/
+
+/*!
+ Returns true if the field is equal to \a other; otherwise returns
+ false.
+*/
+bool QSqlField::operator==(const QSqlField& other) const
+{
+ return ((d == other.d || *d == *other.d)
+ && val == other.val);
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+
+QSqlField::~QSqlField()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ Sets the required status of this field to \a required.
+
+ \sa requiredStatus() setType() setLength() setPrecision() setDefaultValue() setGenerated() setReadOnly()
+*/
+void QSqlField::setRequiredStatus(RequiredStatus required)
+{
+ detach();
+ d->req = required;
+}
+
+/*! \fn void QSqlField::setRequired(bool required)
+
+ Sets the required status of this field to \l Required if \a
+ required is true; otherwise sets it to \l Optional.
+
+ \sa setRequiredStatus(), requiredStatus()
+*/
+
+/*!
+ Sets the field's length to \a fieldLength. For strings this is the
+ maximum number of characters the string can hold; the meaning
+ varies for other types.
+
+ \sa length() setType() setRequiredStatus() setPrecision() setDefaultValue() setGenerated() setReadOnly()
+*/
+void QSqlField::setLength(int fieldLength)
+{
+ detach();
+ d->len = fieldLength;
+}
+
+/*!
+ Sets the field's \a precision. This only affects numeric fields.
+
+ \sa precision() setType() setRequiredStatus() setLength() setDefaultValue() setGenerated() setReadOnly()
+*/
+void QSqlField::setPrecision(int precision)
+{
+ detach();
+ d->prec = precision;
+}
+
+/*!
+ Sets the default value used for this field to \a value.
+
+ \sa defaultValue() value() setType() setRequiredStatus() setLength() setPrecision() setGenerated() setReadOnly()
+*/
+void QSqlField::setDefaultValue(const QVariant &value)
+{
+ detach();
+ d->def = value;
+}
+
+/*!
+ \internal
+*/
+void QSqlField::setSqlType(int type)
+{
+ detach();
+ d->tp = type;
+}
+
+/*!
+ Sets the generated state. If \a gen is false, no SQL will
+ be generated for this field; otherwise, Qt classes such as
+ QSqlQueryModel and QSqlTableModel will generate SQL for this
+ field.
+
+ \sa isGenerated() setType() setRequiredStatus() setLength() setPrecision() setDefaultValue() setReadOnly()
+*/
+void QSqlField::setGenerated(bool gen)
+{
+ detach();
+ d->gen = gen;
+}
+
+
+/*!
+ Sets the value of the field to \a value. If the field is read-only
+ (isReadOnly() returns true), nothing happens.
+
+ If the data type of \a value differs from the field's current
+ data type, an attempt is made to cast it to the proper type. This
+ preserves the data type of the field in the case of assignment,
+ e.g. a QString to an integer data type.
+
+ To set the value to NULL, use clear().
+
+ \sa value() isReadOnly() defaultValue()
+*/
+
+void QSqlField::setValue(const QVariant& value)
+{
+ if (isReadOnly())
+ return;
+ val = value;
+}
+
+/*!
+ Clears the value of the field and sets it to NULL.
+ If the field is read-only, nothing happens.
+
+ \sa setValue() isReadOnly() requiredStatus()
+*/
+
+void QSqlField::clear()
+{
+ if (isReadOnly())
+ return;
+ val = QVariant(type());
+}
+
+/*!
+ Sets the name of the field to \a name.
+
+ \sa name()
+*/
+
+void QSqlField::setName(const QString& name)
+{
+ detach();
+ d->nm = name;
+}
+
+/*!
+ Sets the read only flag of the field's value to \a readOnly. A
+ read-only field cannot have its value set with setValue() and
+ cannot be cleared to NULL with clear().
+*/
+void QSqlField::setReadOnly(bool readOnly)
+{
+ detach();
+ d->ro = readOnly;
+}
+
+/*!
+ \fn QVariant QSqlField::value() const
+
+ Returns the value of the field as a QVariant.
+
+ Use isNull() to check if the field's value is NULL.
+*/
+
+/*!
+ Returns the name of the field.
+
+ \sa setName()
+*/
+QString QSqlField::name() const
+{
+ return d->nm;
+}
+
+/*!
+ Returns the field's type as stored in the database.
+ Note that the actual value might have a different type,
+ Numerical values that are too large to store in a long
+ int or double are usually stored as strings to prevent
+ precision loss.
+
+ \sa setType()
+*/
+QVariant::Type QSqlField::type() const
+{
+ return d->type;
+}
+
+/*!
+ Set's the field's variant type to \a type.
+
+ \sa type() setRequiredStatus() setLength() setPrecision() setDefaultValue() setGenerated() setReadOnly()
+*/
+void QSqlField::setType(QVariant::Type type)
+{
+ detach();
+ d->type = type;
+ if (!val.isValid())
+ val = QVariant(type);
+}
+
+
+/*!
+ Returns true if the field's value is read-only; otherwise returns
+ false.
+
+ \sa setReadOnly() type() requiredStatus() length() precision() defaultValue() isGenerated()
+*/
+bool QSqlField::isReadOnly() const
+{ return d->ro; }
+
+/*!
+ Returns true if the field's value is NULL; otherwise returns
+ false.
+
+ \sa value()
+*/
+bool QSqlField::isNull() const
+{ return val.isNull(); }
+
+/*! \internal
+*/
+void QSqlField::detach()
+{
+ qAtomicDetach(d);
+}
+
+/*!
+ Returns true if this is a required field; otherwise returns false.
+ An \c INSERT will fail if a required field does not have a value.
+
+ \sa setRequiredStatus() type() length() precision() defaultValue() isGenerated()
+*/
+QSqlField::RequiredStatus QSqlField::requiredStatus() const
+{
+ return d->req;
+}
+
+/*!
+ Returns the field's length.
+
+ If the returned value is negative, it means that the information
+ is not available from the database.
+
+ \sa setLength() type() requiredStatus() precision() defaultValue() isGenerated()
+*/
+int QSqlField::length() const
+{
+ return d->len;
+}
+
+/*!
+ Returns the field's precision; this is only meaningful for numeric
+ types.
+
+ If the returned value is negative, it means that the information
+ is not available from the database.
+
+ \sa setPrecision() type() requiredStatus() length() defaultValue() isGenerated()
+*/
+int QSqlField::precision() const
+{
+ return d->prec;
+}
+
+/*!
+ Returns the field's default value (which may be NULL).
+
+ \sa setDefaultValue() type() requiredStatus() length() precision() isGenerated()
+*/
+QVariant QSqlField::defaultValue() const
+{
+ return d->def;
+}
+
+/*!
+ \internal
+
+ Returns the type ID for the field.
+
+ If the returned value is negative, it means that the information
+ is not available from the database.
+*/
+int QSqlField::typeID() const
+{
+ return d->tp;
+}
+
+/*!
+ Returns true if the field is generated; otherwise returns
+ false.
+
+ \sa setGenerated() type() requiredStatus() length() precision() defaultValue()
+*/
+bool QSqlField::isGenerated() const
+{
+ return d->gen;
+}
+
+/*!
+ Returns true if the field's variant type is valid; otherwise
+ returns false.
+*/
+bool QSqlField::isValid() const
+{
+ return d->type != QVariant::Invalid;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QSqlField &f)
+{
+#ifndef Q_BROKEN_DEBUG_STREAM
+ dbg.nospace() << "QSqlField(" << f.name() << ", " << QVariant::typeToName(f.type());
+ if (f.length() >= 0)
+ dbg.nospace() << ", length: " << f.length();
+ if (f.precision() >= 0)
+ dbg.nospace() << ", precision: " << f.precision();
+ if (f.requiredStatus() != QSqlField::Unknown)
+ dbg.nospace() << ", required: "
+ << (f.requiredStatus() == QSqlField::Required ? "yes" : "no");
+ dbg.nospace() << ", generated: " << (f.isGenerated() ? "yes" : "no");
+ if (f.typeID() >= 0)
+ dbg.nospace() << ", typeID: " << f.typeID();
+ if (!f.defaultValue().isNull())
+ dbg.nospace() << ", auto-value: \"" << f.defaultValue() << '\"';
+ dbg.nospace() << ')';
+ return dbg.space();
+#else
+ qWarning("This compiler doesn't support streaming QSqlField to QDebug");
+ return dbg;
+ Q_UNUSED(f);
+#endif
+}
+#endif
+
+/*!
+ \fn void QSqlField::setNull()
+
+ Use clear() instead.
+*/
+
+/*!
+ Returns true if the value is auto-generated by the database,
+ for example auto-increment primary key values.
+
+ \sa setAutoValue()
+*/
+bool QSqlField::isAutoValue() const
+{
+ return d->autoval;
+}
+
+/*!
+ Marks the field as an auto-generated value if \a autoVal
+ is true.
+
+ \sa isAutoValue()
+ */
+void QSqlField::setAutoValue(bool autoVal)
+{
+ detach();
+ d->autoval = autoVal;
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqlfield.h b/src/sql/kernel/qsqlfield.h
new file mode 100644
index 0000000000..68d506d8cd
--- /dev/null
+++ b/src/sql/kernel/qsqlfield.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLFIELD_H
+#define QSQLFIELD_H
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QSqlFieldPrivate;
+
+class Q_SQL_EXPORT QSqlField
+{
+public:
+ enum RequiredStatus { Unknown = -1, Optional = 0, Required = 1 };
+
+ QSqlField(const QString& fieldName = QString(),
+ QVariant::Type type = QVariant::Invalid);
+
+ QSqlField(const QSqlField& other);
+ QSqlField& operator=(const QSqlField& other);
+ bool operator==(const QSqlField& other) const;
+ inline bool operator!=(const QSqlField &other) const { return !operator==(other); }
+ ~QSqlField();
+
+ void setValue(const QVariant& value);
+ inline QVariant value() const
+ { return val; }
+ void setName(const QString& name);
+ QString name() const;
+ bool isNull() const;
+ void setReadOnly(bool readOnly);
+ bool isReadOnly() const;
+ void clear();
+ QVariant::Type type() const;
+ bool isAutoValue() const;
+
+ void setType(QVariant::Type type);
+ void setRequiredStatus(RequiredStatus status);
+ inline void setRequired(bool required)
+ { setRequiredStatus(required ? Required : Optional); }
+ void setLength(int fieldLength);
+ void setPrecision(int precision);
+ void setDefaultValue(const QVariant &value);
+ void setSqlType(int type);
+ void setGenerated(bool gen);
+ void setAutoValue(bool autoVal);
+
+ RequiredStatus requiredStatus() const;
+ int length() const;
+ int precision() const;
+ QVariant defaultValue() const;
+ int typeID() const;
+ bool isGenerated() const;
+ bool isValid() const;
+
+#ifdef QT3_SUPPORT
+ inline QT3_SUPPORT void setNull() { clear(); }
+#endif
+
+private:
+ void detach();
+ QVariant val;
+ QSqlFieldPrivate* d;
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_SQL_EXPORT QDebug operator<<(QDebug, const QSqlField &);
+#endif
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLFIELD_H
diff --git a/src/sql/kernel/qsqlindex.cpp b/src/sql/kernel/qsqlindex.cpp
new file mode 100644
index 0000000000..63158c6c29
--- /dev/null
+++ b/src/sql/kernel/qsqlindex.cpp
@@ -0,0 +1,256 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqlindex.h"
+
+#include "qsqlfield.h"
+#include "qstringlist.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QSqlIndex
+ \brief The QSqlIndex class provides functions to manipulate and
+ describe database indexes.
+
+ \ingroup database
+ \inmodule QtSql
+
+ An \e index refers to a single table or view in a database.
+ Information about the fields that comprise the index can be used
+ to generate SQL statements.
+*/
+
+/*!
+ Constructs an empty index using the cursor name \a cursorname and
+ index name \a name.
+*/
+
+QSqlIndex::QSqlIndex(const QString& cursorname, const QString& name)
+ : cursor(cursorname), nm(name)
+{
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+
+QSqlIndex::QSqlIndex(const QSqlIndex& other)
+ : QSqlRecord(other), cursor(other.cursor), nm(other.nm), sorts(other.sorts)
+{
+}
+
+/*!
+ Sets the index equal to \a other.
+*/
+
+QSqlIndex& QSqlIndex::operator=(const QSqlIndex& other)
+{
+ cursor = other.cursor;
+ nm = other.nm;
+ sorts = other.sorts;
+ QSqlRecord::operator=(other);
+ return *this;
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+
+QSqlIndex::~QSqlIndex()
+{
+
+}
+
+/*!
+ Sets the name of the index to \a name.
+*/
+
+void QSqlIndex::setName(const QString& name)
+{
+ nm = name;
+}
+
+/*!
+ \fn QString QSqlIndex::name() const
+
+ Returns the name of the index.
+*/
+
+/*!
+ Appends the field \a field to the list of indexed fields. The
+ field is appended with an ascending sort order.
+*/
+
+void QSqlIndex::append(const QSqlField& field)
+{
+ append(field, false);
+}
+
+/*!
+ \overload
+
+ Appends the field \a field to the list of indexed fields. The
+ field is appended with an ascending sort order, unless \a desc is
+ true.
+*/
+
+void QSqlIndex::append(const QSqlField& field, bool desc)
+{
+ sorts.append(desc);
+ QSqlRecord::append(field);
+}
+
+
+/*!
+ Returns true if field \a i in the index is sorted in descending
+ order; otherwise returns false.
+*/
+
+bool QSqlIndex::isDescending(int i) const
+{
+ if (i >= 0 && i < sorts.size())
+ return sorts[i];
+ return false;
+}
+
+/*!
+ If \a desc is true, field \a i is sorted in descending order.
+ Otherwise, field \a i is sorted in ascending order (the default).
+ If the field does not exist, nothing happens.
+*/
+
+void QSqlIndex::setDescending(int i, bool desc)
+{
+ if (i >= 0 && i < sorts.size())
+ sorts[i] = desc;
+}
+
+#ifdef QT3_SUPPORT
+
+/*!
+ Returns a comma-separated list of all the index's field names as a
+ string. This string is suitable, for example, for generating a
+ SQL SELECT statement. Only generated fields are included in the
+ list (see \l{isGenerated()}). If a \a prefix is specified, e.g. a
+ table name, it is prepended before all field names in the form:
+
+ "\a{prefix}.<fieldname>"
+
+ If \a sep is specified, each field is separated by \a sep. If \a
+ verbose is true (the default), each field contains a suffix
+ indicating an ASCending or DESCending sort order.
+*/
+
+QString QSqlIndex::toString(const QString& prefix, const QString& sep, bool verbose) const
+{
+ QString s;
+ bool comma = false;
+ for (int i = 0; i < count(); ++i) {
+ if(comma)
+ s += sep + QLatin1Char(' ');
+ s += createField(i, prefix, verbose);
+ comma = true;
+ }
+ return s;
+}
+
+/*!
+ Returns a list of all the index's field names. Only generated
+ fields are included in the list (see \l{isGenerated()}). If a \a
+ prefix is specified, e.g. a table name, all fields are prefixed in
+ the form:
+
+ "\a{prefix}.<fieldname>"
+
+ If \a verbose is true (the default), each field contains a suffix
+ indicating an ASCending or DESCending sort order.
+
+ Note that if you want to iterate over the list, you should iterate
+ over a copy, e.g.
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqlindex.cpp 0
+
+*/
+QStringList QSqlIndex::toStringList(const QString& prefix, bool verbose) const
+{
+ QStringList s;
+ for (int i = 0; i < count(); ++i)
+ s += createField(i, prefix, verbose);
+ return s;
+}
+#endif
+
+/*! \internal
+
+ Creates a string representing the field number \a i using prefix \a
+ prefix. If \a verbose is true, ASC or DESC is included in the field
+ description if the field is sorted in ASCending or DESCending order.
+*/
+
+QString QSqlIndex::createField(int i, const QString& prefix, bool verbose) const
+{
+ QString f;
+ if (!prefix.isEmpty())
+ f += prefix + QLatin1Char('.');
+ f += field(i).name();
+ if (verbose)
+ f += QLatin1Char(' ') + QString((isDescending(i)
+ ? QLatin1String("DESC") : QLatin1String("ASC")));
+ return f;
+}
+
+/*!
+ \fn QString QSqlIndex::cursorName() const
+
+ Returns the name of the cursor which the index is associated with.
+*/
+
+
+/*!
+ Sets the name of the cursor that the index is associated with to
+ \a cursorName.
+*/
+void QSqlIndex::setCursorName(const QString& cursorName)
+{
+ cursor = cursorName;
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqlindex.h b/src/sql/kernel/qsqlindex.h
new file mode 100644
index 0000000000..df43a320f4
--- /dev/null
+++ b/src/sql/kernel/qsqlindex.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLINDEX_H
+#define QSQLINDEX_H
+
+#include <QtSql/qsqlrecord.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qlist.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class Q_SQL_EXPORT QSqlIndex : public QSqlRecord
+{
+public:
+ QSqlIndex(const QString &cursorName = QString(), const QString &name = QString());
+ QSqlIndex(const QSqlIndex &other);
+ ~QSqlIndex();
+ QSqlIndex &operator=(const QSqlIndex &other);
+ void setCursorName(const QString &cursorName);
+ inline QString cursorName() const { return cursor; }
+ void setName(const QString& name);
+ inline QString name() const { return nm; }
+
+ void append(const QSqlField &field);
+ void append(const QSqlField &field, bool desc);
+
+ bool isDescending(int i) const;
+ void setDescending(int i, bool desc);
+
+#ifdef QT3_SUPPORT
+ QT3_SUPPORT QString toString(const QString &prefix = QString(),
+ const QString &sep = QLatin1String(","),
+ bool verbose = true) const;
+ QT3_SUPPORT QStringList toStringList(const QString& prefix = QString(),
+ bool verbose = true) const;
+#endif
+
+private:
+ QString createField(int i, const QString& prefix, bool verbose) const;
+ QString cursor;
+ QString nm;
+ QList<bool> sorts;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLINDEX_H
diff --git a/src/sql/kernel/qsqlnulldriver_p.h b/src/sql/kernel/qsqlnulldriver_p.h
new file mode 100644
index 0000000000..ca1f516f29
--- /dev/null
+++ b/src/sql/kernel/qsqlnulldriver_p.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLNULLDRIVER_P_H
+#define QSQLNULLDRIVER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. This header file may
+// change from version to version without notice, or even be
+// removed.
+//
+// We mean it.
+//
+
+#include "QtCore/qvariant.h"
+#include "QtSql/qsqldriver.h"
+#include "QtSql/qsqlerror.h"
+#include "QtSql/qsqlresult.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSqlNullResult : public QSqlResult
+{
+public:
+ inline explicit QSqlNullResult(const QSqlDriver* d): QSqlResult(d)
+ { QSqlResult::setLastError(
+ QSqlError(QLatin1String("Driver not loaded"), QLatin1String("Driver not loaded"), QSqlError::ConnectionError)); }
+protected:
+ inline QVariant data(int) { return QVariant(); }
+ inline bool reset (const QString&) { return false; }
+ inline bool fetch(int) { return false; }
+ inline bool fetchFirst() { return false; }
+ inline bool fetchLast() { return false; }
+ inline bool isNull(int) { return false; }
+ inline int size() { return -1; }
+ inline int numRowsAffected() { return 0; }
+
+ inline void setAt(int) {}
+ inline void setActive(bool) {}
+ inline void setLastError(const QSqlError&) {}
+ inline void setQuery(const QString&) {}
+ inline void setSelect(bool) {}
+ inline void setForwardOnly(bool) {}
+
+ inline bool exec() { return false; }
+ inline bool prepare(const QString&) { return false; }
+ inline bool savePrepare(const QString&) { return false; }
+ inline void bindValue(int, const QVariant&, QSql::ParamType) {}
+ inline void bindValue(const QString&, const QVariant&, QSql::ParamType) {}
+};
+
+class QSqlNullDriver : public QSqlDriver
+{
+public:
+ inline QSqlNullDriver(): QSqlDriver()
+ { QSqlDriver::setLastError(
+ QSqlError(QLatin1String("Driver not loaded"), QLatin1String("Driver not loaded"), QSqlError::ConnectionError)); }
+ inline bool hasFeature(DriverFeature) const { return false; }
+ inline bool open(const QString &, const QString & , const QString & ,
+ const QString &, int, const QString&)
+ { return false; }
+ inline void close() {}
+ inline QSqlResult *createResult() const { return new QSqlNullResult(this); }
+
+protected:
+ inline void setOpen(bool) {}
+ inline void setOpenError(bool) {}
+ inline void setLastError(const QSqlError&) {}
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQLNULLDRIVER_P_H
diff --git a/src/sql/kernel/qsqlquery.cpp b/src/sql/kernel/qsqlquery.cpp
new file mode 100644
index 0000000000..3cdc8b147f
--- /dev/null
+++ b/src/sql/kernel/qsqlquery.cpp
@@ -0,0 +1,1236 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqlquery.h"
+
+//#define QT_DEBUG_SQL
+
+#include "qatomic.h"
+#include "qsqlrecord.h"
+#include "qsqlresult.h"
+#include "qsqldriver.h"
+#include "qsqldatabase.h"
+#include "private/qsqlnulldriver_p.h"
+#include "qvector.h"
+#include "qmap.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSqlQueryPrivate
+{
+public:
+ QSqlQueryPrivate(QSqlResult* result);
+ ~QSqlQueryPrivate();
+ QAtomicInt ref;
+ QSqlResult* sqlResult;
+
+ static QSqlQueryPrivate* shared_null();
+};
+
+Q_GLOBAL_STATIC_WITH_ARGS(QSqlQueryPrivate, nullQueryPrivate, (0))
+Q_GLOBAL_STATIC(QSqlNullDriver, nullDriver)
+Q_GLOBAL_STATIC_WITH_ARGS(QSqlNullResult, nullResult, (nullDriver()))
+
+QSqlQueryPrivate* QSqlQueryPrivate::shared_null()
+{
+ QSqlQueryPrivate *null = nullQueryPrivate();
+ null->ref.ref();
+ return null;
+}
+
+/*!
+\internal
+*/
+QSqlQueryPrivate::QSqlQueryPrivate(QSqlResult* result)
+ : ref(1), sqlResult(result)
+{
+ if (!sqlResult)
+ sqlResult = nullResult();
+}
+
+QSqlQueryPrivate::~QSqlQueryPrivate()
+{
+ QSqlResult *nr = nullResult();
+ if (!nr || sqlResult == nr)
+ return;
+ delete sqlResult;
+}
+
+/*!
+ \class QSqlQuery
+ \brief The QSqlQuery class provides a means of executing and
+ manipulating SQL statements.
+
+ \ingroup database
+ \ingroup shared
+
+ \inmodule QtSql
+
+ QSqlQuery encapsulates the functionality involved in creating,
+ navigating and retrieving data from SQL queries which are
+ executed on a \l QSqlDatabase. It can be used to execute DML
+ (data manipulation language) statements, such as \c SELECT, \c
+ INSERT, \c UPDATE and \c DELETE, as well as DDL (data definition
+ language) statements, such as \c{CREATE} \c{TABLE}. It can also
+ be used to execute database-specific commands which are not
+ standard SQL (e.g. \c{SET DATESTYLE=ISO} for PostgreSQL).
+
+ Successfully executed SQL statements set the query's state to
+ active so that isActive() returns true. Otherwise the query's
+ state is set to inactive. In either case, when executing a new SQL
+ statement, the query is positioned on an invalid record. An active
+ query must be navigated to a valid record (so that isValid()
+ returns true) before values can be retrieved.
+
+ For some databases, if an active query that is a \c{SELECT}
+ statement exists when you call \l{QSqlDatabase::}{commit()} or
+ \l{QSqlDatabase::}{rollback()}, the commit or rollback will
+ fail. See isActive() for details.
+
+ \target QSqlQuery examples
+
+ Navigating records is performed with the following functions:
+
+ \list
+ \o next()
+ \o previous()
+ \o first()
+ \o last()
+ \o seek()
+ \endlist
+
+ These functions allow the programmer to move forward, backward
+ or arbitrarily through the records returned by the query. If you
+ only need to move forward through the results (e.g., by using
+ next()), you can use setForwardOnly(), which will save a
+ significant amount of memory overhead and improve performance on
+ some databases. Once an active query is positioned on a valid
+ record, data can be retrieved using value(). All data is
+ transferred from the SQL backend using QVariants.
+
+ For example:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 7
+
+ To access the data returned by a query, use value(int). Each
+ field in the data returned by a \c SELECT statement is accessed
+ by passing the field's position in the statement, starting from
+ 0. This makes using \c{SELECT *} queries inadvisable because the
+ order of the fields returned is indeterminate.
+
+ For the sake of efficiency, there are no functions to access a
+ field by name (unless you use prepared queries with names, as
+ explained below). To convert a field name into an index, use
+ record().\l{QSqlRecord::indexOf()}{indexOf()}, for example:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 8
+
+ QSqlQuery supports prepared query execution and the binding of
+ parameter values to placeholders. Some databases don't support
+ these features, so for those, Qt emulates the required
+ functionality. For example, the Oracle and ODBC drivers have
+ proper prepared query support, and Qt makes use of it; but for
+ databases that don't have this support, Qt implements the feature
+ itself, e.g. by replacing placeholders with actual values when a
+ query is executed. Use numRowsAffected() to find out how many rows
+ were affected by a non-\c SELECT query, and size() to find how
+ many were retrieved by a \c SELECT.
+
+ Oracle databases identify placeholders by using a colon-name
+ syntax, e.g \c{:name}. ODBC simply uses \c ? characters. Qt
+ supports both syntaxes, with the restriction that you can't mix
+ them in the same query.
+
+ You can retrieve the values of all the fields in a single variable
+ (a map) using boundValues().
+
+ \section1 Approaches to Binding Values
+
+ Below we present the same example using each of the four
+ different binding approaches, as well as one example of binding
+ values to a stored procedure.
+
+ \bold{Named binding using named placeholders:}
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 9
+
+ \bold{Positional binding using named placeholders:}
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 10
+
+ \bold{Binding values using positional placeholders (version 1):}
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 11
+
+ \bold{Binding values using positional placeholders (version 2):}
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 12
+
+ \bold{Binding values to a stored procedure:}
+
+ This code calls a stored procedure called \c AsciiToInt(), passing
+ it a character through its in parameter, and taking its result in
+ the out parameter.
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 13
+
+ Note that unbound parameters will retain their values.
+
+ Stored procedures that uses the return statement to return values,
+ or return multiple result sets, are not fully supported. For specific
+ details see \l{SQL Database Drivers}.
+
+ \warning You must load the SQL driver and open the connection before a
+ QSqlQuery is created. Also, the connection must remain open while the
+ query exists; otherwise, the behavior of QSqlQuery is undefined.
+
+ \sa QSqlDatabase, QSqlQueryModel, QSqlTableModel, QVariant
+*/
+
+/*!
+ Constructs a QSqlQuery object which uses the QSqlResult \a result
+ to communicate with a database.
+*/
+
+QSqlQuery::QSqlQuery(QSqlResult *result)
+{
+ d = new QSqlQueryPrivate(result);
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+
+QSqlQuery::~QSqlQuery()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ Constructs a copy of \a other.
+*/
+
+QSqlQuery::QSqlQuery(const QSqlQuery& other)
+{
+ d = other.d;
+ d->ref.ref();
+}
+
+/*!
+ \internal
+*/
+static void qInit(QSqlQuery *q, const QString& query, QSqlDatabase db)
+{
+ QSqlDatabase database = db;
+ if (!database.isValid())
+ database = QSqlDatabase::database(QLatin1String(QSqlDatabase::defaultConnection), false);
+ if (database.isValid()) {
+ *q = QSqlQuery(database.driver()->createResult());
+ }
+ if (!query.isEmpty())
+ q->exec(query);
+}
+
+/*!
+ Constructs a QSqlQuery object using the SQL \a query and the
+ database \a db. If \a db is not specified, or is invalid, the application's
+ default database is used. If \a query is not an empty string, it
+ will be executed.
+
+ \sa QSqlDatabase
+*/
+QSqlQuery::QSqlQuery(const QString& query, QSqlDatabase db)
+{
+ d = QSqlQueryPrivate::shared_null();
+ qInit(this, query, db);
+}
+
+/*!
+ Constructs a QSqlQuery object using the database \a db.
+ If \a db is invalid, the application's default database will be used.
+
+ \sa QSqlDatabase
+*/
+
+QSqlQuery::QSqlQuery(QSqlDatabase db)
+{
+ d = QSqlQueryPrivate::shared_null();
+ qInit(this, QString(), db);
+}
+
+
+/*!
+ Assigns \a other to this object.
+*/
+
+QSqlQuery& QSqlQuery::operator=(const QSqlQuery& other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Returns true if the query is \l{isActive()}{active} and positioned
+ on a valid record and the \a field is NULL; otherwise returns
+ false. Note that for some drivers, isNull() will not return accurate
+ information until after an attempt is made to retrieve data.
+
+ \sa isActive(), isValid(), value()
+*/
+
+bool QSqlQuery::isNull(int field) const
+{
+ if (d->sqlResult->isActive() && d->sqlResult->isValid())
+ return d->sqlResult->isNull(field);
+ return true;
+}
+
+/*!
+
+ Executes the SQL in \a query. Returns true and sets the query state
+ to \l{isActive()}{active} if the query was successful; otherwise
+ returns false. The \a query string must use syntax appropriate for
+ the SQL database being queried (for example, standard SQL).
+
+ After the query is executed, the query is positioned on an \e
+ invalid record and must be navigated to a valid record before data
+ values can be retrieved (for example, using next()).
+
+ Note that the last error for this query is reset when exec() is
+ called.
+
+ Example:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 34
+
+ \sa isActive(), isValid(), next(), previous(), first(), last(),
+ seek()
+*/
+
+bool QSqlQuery::exec(const QString& query)
+{
+ if (d->ref != 1) {
+ bool fo = isForwardOnly();
+ *this = QSqlQuery(driver()->createResult());
+ d->sqlResult->setNumericalPrecisionPolicy(d->sqlResult->numericalPrecisionPolicy());
+ setForwardOnly(fo);
+ } else {
+ d->sqlResult->clear();
+ d->sqlResult->setActive(false);
+ d->sqlResult->setLastError(QSqlError());
+ d->sqlResult->setAt(QSql::BeforeFirstRow);
+ d->sqlResult->setNumericalPrecisionPolicy(d->sqlResult->numericalPrecisionPolicy());
+ }
+ d->sqlResult->setQuery(query.trimmed());
+ if (!driver()->isOpen() || driver()->isOpenError()) {
+ qWarning("QSqlQuery::exec: database not open");
+ return false;
+ }
+ if (query.isEmpty()) {
+ qWarning("QSqlQuery::exec: empty query");
+ return false;
+ }
+#ifdef QT_DEBUG_SQL
+ qDebug("\n QSqlQuery: %s", query.toLocal8Bit().constData());
+#endif
+ return d->sqlResult->reset(query);
+}
+
+/*!
+ Returns the value of field \a index in the current record.
+
+ The fields are numbered from left to right using the text of the
+ \c SELECT statement, e.g. in
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqlquery.cpp 0
+
+ field 0 is \c forename and field 1 is \c
+ surname. Using \c{SELECT *} is not recommended because the order
+ of the fields in the query is undefined.
+
+ An invalid QVariant is returned if field \a index does not
+ exist, if the query is inactive, or if the query is positioned on
+ an invalid record.
+
+ \sa previous() next() first() last() seek() isActive() isValid()
+*/
+
+QVariant QSqlQuery::value(int index) const
+{
+ if (isActive() && isValid() && (index > QSql::BeforeFirstRow))
+ return d->sqlResult->data(index);
+ qWarning("QSqlQuery::value: not positioned on a valid record");
+ return QVariant();
+}
+
+/*!
+ Returns the current internal position of the query. The first
+ record is at position zero. If the position is invalid, the
+ function returns QSql::BeforeFirstRow or
+ QSql::AfterLastRow, which are special negative values.
+
+ \sa previous() next() first() last() seek() isActive() isValid()
+*/
+
+int QSqlQuery::at() const
+{
+ return d->sqlResult->at();
+}
+
+/*!
+ Returns the text of the current query being used, or an empty
+ string if there is no current query text.
+
+ \sa executedQuery()
+*/
+
+QString QSqlQuery::lastQuery() const
+{
+ return d->sqlResult->lastQuery();
+}
+
+/*!
+ Returns the database driver associated with the query.
+*/
+
+const QSqlDriver *QSqlQuery::driver() const
+{
+ return d->sqlResult->driver();
+}
+
+/*!
+ Returns the result associated with the query.
+*/
+
+const QSqlResult* QSqlQuery::result() const
+{
+ return d->sqlResult;
+}
+
+/*!
+ Retrieves the record at position \a index, if available, and
+ positions the query on the retrieved record. The first record is at
+ position 0. Note that the query must be in an \l{isActive()}
+ {active} state and isSelect() must return true before calling this
+ function.
+
+ If \a relative is false (the default), the following rules apply:
+
+ \list
+
+ \o If \a index is negative, the result is positioned before the
+ first record and false is returned.
+
+ \o Otherwise, an attempt is made to move to the record at position
+ \a index. If the record at position \a index could not be retrieved,
+ the result is positioned after the last record and false is
+ returned. If the record is successfully retrieved, true is returned.
+
+ \endlist
+
+ If \a relative is true, the following rules apply:
+
+ \list
+
+ \o If the result is currently positioned before the first record or
+ on the first record, and \a index is negative, there is no change,
+ and false is returned.
+
+ \o If the result is currently located after the last record, and \a
+ index is positive, there is no change, and false is returned.
+
+ \o If the result is currently located somewhere in the middle, and
+ the relative offset \a index moves the result below zero, the result
+ is positioned before the first record and false is returned.
+
+ \o Otherwise, an attempt is made to move to the record \a index
+ records ahead of the current record (or \a index records behind the
+ current record if \a index is negative). If the record at offset \a
+ index could not be retrieved, the result is positioned after the
+ last record if \a index >= 0, (or before the first record if \a
+ index is negative), and false is returned. If the record is
+ successfully retrieved, true is returned.
+
+ \endlist
+
+ \sa next() previous() first() last() at() isActive() isValid()
+*/
+bool QSqlQuery::seek(int index, bool relative)
+{
+ if (!isSelect() || !isActive())
+ return false;
+ int actualIdx;
+ if (!relative) { // arbitrary seek
+ if (index < 0) {
+ d->sqlResult->setAt(QSql::BeforeFirstRow);
+ return false;
+ }
+ actualIdx = index;
+ } else {
+ switch (at()) { // relative seek
+ case QSql::BeforeFirstRow:
+ if (index > 0)
+ actualIdx = index;
+ else {
+ return false;
+ }
+ break;
+ case QSql::AfterLastRow:
+ if (index < 0) {
+ d->sqlResult->fetchLast();
+ actualIdx = at() + index;
+ } else {
+ return false;
+ }
+ break;
+ default:
+ if ((at() + index) < 0) {
+ d->sqlResult->setAt(QSql::BeforeFirstRow);
+ return false;
+ }
+ actualIdx = at() + index;
+ break;
+ }
+ }
+ // let drivers optimize
+ if (isForwardOnly() && actualIdx < at()) {
+ qWarning("QSqlQuery::seek: cannot seek backwards in a forward only query");
+ return false;
+ }
+ if (actualIdx == (at() + 1) && at() != QSql::BeforeFirstRow) {
+ if (!d->sqlResult->fetchNext()) {
+ d->sqlResult->setAt(QSql::AfterLastRow);
+ return false;
+ }
+ return true;
+ }
+ if (actualIdx == (at() - 1)) {
+ if (!d->sqlResult->fetchPrevious()) {
+ d->sqlResult->setAt(QSql::BeforeFirstRow);
+ return false;
+ }
+ return true;
+ }
+ if (!d->sqlResult->fetch(actualIdx)) {
+ d->sqlResult->setAt(QSql::AfterLastRow);
+ return false;
+ }
+ return true;
+}
+
+/*!
+
+ Retrieves the next record in the result, if available, and positions
+ the query on the retrieved record. Note that the result must be in
+ the \l{isActive()}{active} state and isSelect() must return true
+ before calling this function or it will do nothing and return false.
+
+ The following rules apply:
+
+ \list
+
+ \o If the result is currently located before the first record,
+ e.g. immediately after a query is executed, an attempt is made to
+ retrieve the first record.
+
+ \o If the result is currently located after the last record, there
+ is no change and false is returned.
+
+ \o If the result is located somewhere in the middle, an attempt is
+ made to retrieve the next record.
+
+ \endlist
+
+ If the record could not be retrieved, the result is positioned after
+ the last record and false is returned. If the record is successfully
+ retrieved, true is returned.
+
+ \sa previous() first() last() seek() at() isActive() isValid()
+*/
+bool QSqlQuery::next()
+{
+ if (!isSelect() || !isActive())
+ return false;
+ bool b = false;
+ switch (at()) {
+ case QSql::BeforeFirstRow:
+ b = d->sqlResult->fetchFirst();
+ return b;
+ case QSql::AfterLastRow:
+ return false;
+ default:
+ if (!d->sqlResult->fetchNext()) {
+ d->sqlResult->setAt(QSql::AfterLastRow);
+ return false;
+ }
+ return true;
+ }
+}
+
+/*!
+
+ Retrieves the previous record in the result, if available, and
+ positions the query on the retrieved record. Note that the result
+ must be in the \l{isActive()}{active} state and isSelect() must
+ return true before calling this function or it will do nothing and
+ return false.
+
+ The following rules apply:
+
+ \list
+
+ \o If the result is currently located before the first record, there
+ is no change and false is returned.
+
+ \o If the result is currently located after the last record, an
+ attempt is made to retrieve the last record.
+
+ \o If the result is somewhere in the middle, an attempt is made to
+ retrieve the previous record.
+
+ \endlist
+
+ If the record could not be retrieved, the result is positioned
+ before the first record and false is returned. If the record is
+ successfully retrieved, true is returned.
+
+ \sa next() first() last() seek() at() isActive() isValid()
+*/
+bool QSqlQuery::previous()
+{
+ if (!isSelect() || !isActive())
+ return false;
+ if (isForwardOnly()) {
+ qWarning("QSqlQuery::seek: cannot seek backwards in a forward only query");
+ return false;
+ }
+
+ bool b = false;
+ switch (at()) {
+ case QSql::BeforeFirstRow:
+ return false;
+ case QSql::AfterLastRow:
+ b = d->sqlResult->fetchLast();
+ return b;
+ default:
+ if (!d->sqlResult->fetchPrevious()) {
+ d->sqlResult->setAt(QSql::BeforeFirstRow);
+ return false;
+ }
+ return true;
+ }
+}
+
+/*!
+ Retrieves the first record in the result, if available, and
+ positions the query on the retrieved record. Note that the result
+ must be in the \l{isActive()}{active} state and isSelect() must
+ return true before calling this function or it will do nothing and
+ return false. Returns true if successful. If unsuccessful the query
+ position is set to an invalid position and false is returned.
+
+ \sa next() previous() last() seek() at() isActive() isValid()
+ */
+bool QSqlQuery::first()
+{
+ if (!isSelect() || !isActive())
+ return false;
+ if (isForwardOnly() && at() > QSql::BeforeFirstRow) {
+ qWarning("QSqlQuery::seek: cannot seek backwards in a forward only query");
+ return false;
+ }
+ bool b = false;
+ b = d->sqlResult->fetchFirst();
+ return b;
+}
+
+/*!
+
+ Retrieves the last record in the result, if available, and positions
+ the query on the retrieved record. Note that the result must be in
+ the \l{isActive()}{active} state and isSelect() must return true
+ before calling this function or it will do nothing and return false.
+ Returns true if successful. If unsuccessful the query position is
+ set to an invalid position and false is returned.
+
+ \sa next() previous() first() seek() at() isActive() isValid()
+*/
+
+bool QSqlQuery::last()
+{
+ if (!isSelect() || !isActive())
+ return false;
+ bool b = false;
+ b = d->sqlResult->fetchLast();
+ return b;
+}
+
+/*!
+ Returns the size of the result (number of rows returned), or -1 if
+ the size cannot be determined or if the database does not support
+ reporting information about query sizes. Note that for non-\c SELECT
+ statements (isSelect() returns false), size() will return -1. If the
+ query is not active (isActive() returns false), -1 is returned.
+
+ To determine the number of rows affected by a non-\c SELECT
+ statement, use numRowsAffected().
+
+ \sa isActive() numRowsAffected() QSqlDriver::hasFeature()
+*/
+int QSqlQuery::size() const
+{
+ if (isActive() && d->sqlResult->driver()->hasFeature(QSqlDriver::QuerySize))
+ return d->sqlResult->size();
+ return -1;
+}
+
+/*!
+ Returns the number of rows affected by the result's SQL statement,
+ or -1 if it cannot be determined. Note that for \c SELECT
+ statements, the value is undefined; use size() instead. If the query
+ is not \l{isActive()}{active}, -1 is returned.
+
+ \sa size() QSqlDriver::hasFeature()
+*/
+
+int QSqlQuery::numRowsAffected() const
+{
+ if (isActive())
+ return d->sqlResult->numRowsAffected();
+ return -1;
+}
+
+/*!
+ Returns error information about the last error (if any) that
+ occurred with this query.
+
+ \sa QSqlError, QSqlDatabase::lastError()
+*/
+
+QSqlError QSqlQuery::lastError() const
+{
+ return d->sqlResult->lastError();
+}
+
+/*!
+ Returns true if the query is currently positioned on a valid
+ record; otherwise returns false.
+*/
+
+bool QSqlQuery::isValid() const
+{
+ return d->sqlResult->isValid();
+}
+
+/*!
+
+ Returns true if the query is \e{active}. An active QSqlQuery is one
+ that has been \l{QSqlQuery::exec()} {exec()'d} successfully but not
+ yet finished with. When you are finished with an active query, you
+ can make make the query inactive by calling finish() or clear(), or
+ you can delete the QSqlQuery instance.
+
+ \note Of particular interest is an active query that is a \c{SELECT}
+ statement. For some databases that support transactions, an active
+ query that is a \c{SELECT} statement can cause a \l{QSqlDatabase::}
+ {commit()} or a \l{QSqlDatabase::} {rollback()} to fail, so before
+ committing or rolling back, you should make your active \c{SELECT}
+ statement query inactive using one of the ways listed above.
+
+ \sa isSelect()
+ */
+bool QSqlQuery::isActive() const
+{
+ return d->sqlResult->isActive();
+}
+
+/*!
+ Returns true if the current query is a \c SELECT statement;
+ otherwise returns false.
+*/
+
+bool QSqlQuery::isSelect() const
+{
+ return d->sqlResult->isSelect();
+}
+
+/*!
+ Returns true if you can only scroll forward through a result set;
+ otherwise returns false.
+
+ \sa setForwardOnly(), next()
+*/
+bool QSqlQuery::isForwardOnly() const
+{
+ return d->sqlResult->isForwardOnly();
+}
+
+/*!
+ Sets forward only mode to \a forward. If \a forward is true, only
+ next() and seek() with positive values, are allowed for navigating
+ the results.
+
+ Forward only mode can be (depending on the driver) more memory
+ efficient since results do not need to be cached. It will also
+ improve performance on some databases. For this to be true, you must
+ call \c setForwardOnly() before the query is prepared or executed.
+ Note that the constructor that takes a query and a database may
+ execute the query.
+
+ Forward only mode is off by default.
+
+ Setting forward only to false is a suggestion to the database engine,
+ which has the final say on whether a result set is forward only or
+ scrollable. isForwardOnly() will always return the correct status of
+ the result set.
+
+ \note Calling setForwardOnly after execution of the query will result
+ in unexpected results at best, and crashes at worst.
+
+ \sa isForwardOnly(), next(), seek(), QSqlResult::setForwardOnly()
+*/
+void QSqlQuery::setForwardOnly(bool forward)
+{
+ d->sqlResult->setForwardOnly(forward);
+}
+
+/*!
+ Returns a QSqlRecord containing the field information for the
+ current query. If the query points to a valid row (isValid() returns
+ true), the record is populated with the row's values. An empty
+ record is returned when there is no active query (isActive() returns
+ false).
+
+ To retrieve values from a query, value() should be used since
+ its index-based lookup is faster.
+
+ In the following example, a \c{SELECT * FROM} query is executed.
+ Since the order of the columns is not defined, QSqlRecord::indexOf()
+ is used to obtain the index of a column.
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqlquery.cpp 1
+
+ \sa value()
+*/
+QSqlRecord QSqlQuery::record() const
+{
+ QSqlRecord rec = d->sqlResult->record();
+
+ if (isValid()) {
+ for (int i = 0; i < rec.count(); ++i)
+ rec.setValue(i, value(i));
+ }
+ return rec;
+}
+
+/*!
+ Clears the result set and releases any resources held by the
+ query. Sets the query state to inactive. You should rarely if ever
+ need to call this function.
+*/
+void QSqlQuery::clear()
+{
+ *this = QSqlQuery(driver()->createResult());
+}
+
+/*!
+ Prepares the SQL query \a query for execution. Returns true if the
+ query is prepared successfully; otherwise returns false.
+
+ The query may contain placeholders for binding values. Both Oracle
+ style colon-name (e.g., \c{:surname}), and ODBC style (\c{?})
+ placeholders are supported; but they cannot be mixed in the same
+ query. See the \l{QSqlQuery examples}{Detailed Description} for
+ examples.
+
+ Portability note: Some databases choose to delay preparing a query
+ until it is executed the first time. In this case, preparing a
+ syntactically wrong query succeeds, but every consecutive exec()
+ will fail.
+
+ Example:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 9
+
+ \sa exec(), bindValue(), addBindValue()
+*/
+bool QSqlQuery::prepare(const QString& query)
+{
+ if (d->ref != 1) {
+ bool fo = isForwardOnly();
+ *this = QSqlQuery(driver()->createResult());
+ setForwardOnly(fo);
+ d->sqlResult->setNumericalPrecisionPolicy(d->sqlResult->numericalPrecisionPolicy());
+ } else {
+ d->sqlResult->setActive(false);
+ d->sqlResult->setLastError(QSqlError());
+ d->sqlResult->setAt(QSql::BeforeFirstRow);
+ d->sqlResult->setNumericalPrecisionPolicy(d->sqlResult->numericalPrecisionPolicy());
+ }
+ if (!driver()) {
+ qWarning("QSqlQuery::prepare: no driver");
+ return false;
+ }
+ if (!driver()->isOpen() || driver()->isOpenError()) {
+ qWarning("QSqlQuery::prepare: database not open");
+ return false;
+ }
+ if (query.isEmpty()) {
+ qWarning("QSqlQuery::prepare: empty query");
+ return false;
+ }
+#ifdef QT_DEBUG_SQL
+ qDebug("\n QSqlQuery::prepare: %s", query.toLocal8Bit().constData());
+#endif
+ return d->sqlResult->savePrepare(query);
+}
+
+/*!
+ Executes a previously prepared SQL query. Returns true if the query
+ executed successfully; otherwise returns false.
+
+ Note that the last error for this query is reset when exec() is
+ called.
+
+ \sa prepare() bindValue() addBindValue() boundValue() boundValues()
+*/
+bool QSqlQuery::exec()
+{
+ d->sqlResult->resetBindCount();
+
+ if (d->sqlResult->lastError().isValid())
+ d->sqlResult->setLastError(QSqlError());
+
+ return d->sqlResult->exec();
+}
+
+/*! \enum QSqlQuery::BatchExecutionMode
+
+ \value ValuesAsRows - Updates multiple rows. Treats every entry in a QVariantList as a value for updating the next row.
+ \value ValuesAsColumns - Updates a single row. Treats every entry in a QVariantList as a single value of an array type.
+*/
+
+/*!
+ \since 4.2
+
+ Executes a previously prepared SQL query in a batch. All the bound
+ parameters have to be lists of variants. If the database doesn't
+ support batch executions, the driver will simulate it using
+ conventional exec() calls.
+
+ Returns true if the query is executed successfully; otherwise
+ returns false.
+
+ Example:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqlquery.cpp 2
+
+ The example above inserts four new rows into \c myTable:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqlquery.cpp 3
+
+ To bind NULL values, a null QVariant of the relevant type has to be
+ added to the bound QVariantList; for example, \c
+ {QVariant(QVariant::String)} should be used if you are using
+ strings.
+
+ \note Every bound QVariantList must contain the same amount of
+ variants.
+
+ \note The type of the QVariants in a list must not change. For
+ example, you cannot mix integer and string variants within a
+ QVariantList.
+
+ The \a mode parameter indicates how the bound QVariantList will be
+ interpreted. If \a mode is \c ValuesAsRows, every variant within
+ the QVariantList will be interpreted as a value for a new row. \c
+ ValuesAsColumns is a special case for the Oracle driver. In this
+ mode, every entry within a QVariantList will be interpreted as
+ array-value for an IN or OUT value within a stored procedure. Note
+ that this will only work if the IN or OUT value is a table-type
+ consisting of only one column of a basic type, for example \c{TYPE
+ myType IS TABLE OF VARCHAR(64) INDEX BY BINARY_INTEGER;}
+
+ \sa prepare(), bindValue(), addBindValue()
+*/
+bool QSqlQuery::execBatch(BatchExecutionMode mode)
+{
+ return d->sqlResult->execBatch(mode == ValuesAsColumns);
+}
+
+/*!
+ Set the placeholder \a placeholder to be bound to value \a val in
+ the prepared statement. Note that the placeholder mark (e.g \c{:})
+ must be included when specifying the placeholder name. If \a
+ paramType is QSql::Out or QSql::InOut, the placeholder will be
+ overwritten with data from the database after the exec() call.
+ In this case, sufficient space must be pre-allocated to store
+ the result into.
+
+ To bind a NULL value, use a null QVariant; for example, use
+ \c {QVariant(QVariant::String)} if you are binding a string.
+
+ Values cannot be bound to multiple locations in the query, eg:
+ \code
+ INSERT INTO testtable (id, name, samename) VALUES (:id, :name, :name)
+ \endcode
+ Binding to name will bind to the first :name, but not the second.
+
+ \sa addBindValue(), prepare(), exec(), boundValue() boundValues()
+*/
+void QSqlQuery::bindValue(const QString& placeholder, const QVariant& val,
+ QSql::ParamType paramType
+)
+{
+ d->sqlResult->bindValue(placeholder, val, paramType);
+}
+
+/*!
+ Set the placeholder in position \a pos to be bound to value \a val
+ in the prepared statement. Field numbering starts at 0. If \a
+ paramType is QSql::Out or QSql::InOut, the placeholder will be
+ overwritten with data from the database after the exec() call.
+*/
+void QSqlQuery::bindValue(int pos, const QVariant& val, QSql::ParamType paramType)
+{
+ d->sqlResult->bindValue(pos, val, paramType);
+}
+
+/*!
+ Adds the value \a val to the list of values when using positional
+ value binding. The order of the addBindValue() calls determines
+ which placeholder a value will be bound to in the prepared query.
+ If \a paramType is QSql::Out or QSql::InOut, the placeholder will be
+ overwritten with data from the database after the exec() call.
+
+ To bind a NULL value, use a null QVariant; for example, use \c
+ {QVariant(QVariant::String)} if you are binding a string.
+
+ \sa bindValue(), prepare(), exec(), boundValue() boundValues()
+*/
+void QSqlQuery::addBindValue(const QVariant& val, QSql::ParamType paramType)
+{
+ d->sqlResult->addBindValue(val, paramType);
+}
+
+/*!
+ Returns the value for the \a placeholder.
+
+ \sa boundValues() bindValue() addBindValue()
+*/
+QVariant QSqlQuery::boundValue(const QString& placeholder) const
+{
+ return d->sqlResult->boundValue(placeholder);
+}
+
+/*!
+ Returns the value for the placeholder at position \a pos.
+*/
+QVariant QSqlQuery::boundValue(int pos) const
+{
+ return d->sqlResult->boundValue(pos);
+}
+
+/*!
+ Returns a map of the bound values.
+
+ With named binding, the bound values can be examined in the
+ following ways:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 14
+
+ With positional binding, the code becomes:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 15
+
+ \sa boundValue() bindValue() addBindValue()
+*/
+QMap<QString,QVariant> QSqlQuery::boundValues() const
+{
+ QMap<QString,QVariant> map;
+
+ const QVector<QVariant> values(d->sqlResult->boundValues());
+ for (int i = 0; i < values.count(); ++i)
+ map[d->sqlResult->boundValueName(i)] = values.at(i);
+ return map;
+}
+
+/*!
+ Returns the last query that was successfully executed.
+
+ In most cases this function returns the same string as lastQuery().
+ If a prepared query with placeholders is executed on a DBMS that
+ does not support it, the preparation of this query is emulated. The
+ placeholders in the original query are replaced with their bound
+ values to form a new query. This function returns the modified
+ query. It is mostly useful for debugging purposes.
+
+ \sa lastQuery()
+*/
+QString QSqlQuery::executedQuery() const
+{
+ return d->sqlResult->executedQuery();
+}
+
+/*!
+ \fn bool QSqlQuery::prev()
+
+ Use previous() instead.
+*/
+
+/*!
+ Returns the object ID of the most recent inserted row if the
+ database supports it. An invalid QVariant will be returned if the
+ query did not insert any value or if the database does not report
+ the id back. If more than one row was touched by the insert, the
+ behavior is undefined.
+
+ For MySQL databases the row's auto-increment field will be returned.
+
+ \note For this function to work in PSQL, the table table must
+ contain OIDs, which may not have been created by default. Check the
+ \c default_with_oids configuration variable to be sure.
+
+ \sa QSqlDriver::hasFeature()
+*/
+QVariant QSqlQuery::lastInsertId() const
+{
+ return d->sqlResult->lastInsertId();
+}
+
+/*!
+
+ Instruct the database driver to return numerical values with a
+ precision specified by \a precisionPolicy.
+
+ The Oracle driver, for example, can retrieve numerical values as
+ strings to prevent the loss of precision. If high precision doesn't
+ matter, use this method to increase execution speed by bypassing
+ string conversions.
+
+ Note: Drivers that don't support fetching numerical values with low
+ precision will ignore the precision policy. You can use
+ QSqlDriver::hasFeature() to find out whether a driver supports this
+ feature.
+
+ Note: Setting the precision policy doesn't affect the currently
+ active query. Call \l{exec()}{exec(QString)} or prepare() in order
+ to activate the policy.
+
+ \sa QSql::NumericalPrecisionPolicy, numericalPrecisionPolicy()
+*/
+void QSqlQuery::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy)
+{
+ d->sqlResult->setNumericalPrecisionPolicy(precisionPolicy);
+}
+
+/*!
+ Returns the current precision policy.
+
+ \sa QSql::NumericalPrecisionPolicy, setNumericalPrecisionPolicy()
+*/
+QSql::NumericalPrecisionPolicy QSqlQuery::numericalPrecisionPolicy() const
+{
+ return d->sqlResult->numericalPrecisionPolicy();
+}
+
+/*!
+ \since 4.3.2
+
+ Instruct the database driver that no more data will be fetched from
+ this query until it is re-executed. There is normally no need to
+ call this function, but it may be helpful in order to free resources
+ such as locks or cursors if you intend to re-use the query at a
+ later time.
+
+ Sets the query to inactive. Bound values retain their values.
+
+ \sa prepare() exec() isActive()
+*/
+void QSqlQuery::finish()
+{
+ if (isActive()) {
+ d->sqlResult->setLastError(QSqlError());
+ d->sqlResult->setAt(QSql::BeforeFirstRow);
+ d->sqlResult->detachFromResultSet();
+ d->sqlResult->setActive(false);
+ }
+}
+
+/*!
+ \since 4.4
+
+ Discards the current result set and navigates to the next if available.
+
+ Some databases are capable of returning multiple result sets for
+ stored procedures or SQL batches (a query strings that contains
+ multiple statements). If multiple result sets are available after
+ executing a query this function can be used to navigate to the next
+ result set(s).
+
+ If a new result set is available this function will return true.
+ The query will be repositioned on an \e invalid record in the new
+ result set and must be navigated to a valid record before data
+ values can be retrieved. If a new result set isn't available the
+ function returns false and the query is set to inactive. In any
+ case the old result set will be discarded.
+
+ When one of the statements is a non-select statement a count of
+ affected rows may be available instead of a result set.
+
+ Note that some databases, i.e. Microsoft SQL Server, requires
+ non-scrollable cursors when working with multiple result sets. Some
+ databases may execute all statements at once while others may delay
+ the execution until the result set is actually accessed, and some
+ databases may have restrictions on which statements are allowed to
+ be used in a SQL batch.
+
+ \sa QSqlDriver::hasFeature() setForwardOnly() next() isSelect() numRowsAffected() isActive() lastError()
+*/
+bool QSqlQuery::nextResult()
+{
+ if (isActive())
+ return d->sqlResult->nextResult();
+ return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqlquery.h b/src/sql/kernel/qsqlquery.h
new file mode 100644
index 0000000000..4c547c4a32
--- /dev/null
+++ b/src/sql/kernel/qsqlquery.h
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLQUERY_H
+#define QSQLQUERY_H
+
+#include <QtSql/qsql.h>
+#include <QtSql/qsqldatabase.h>
+#include <QtCore/qstring.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QVariant;
+class QSqlDriver;
+class QSqlError;
+class QSqlResult;
+class QSqlRecord;
+template <class Key, class T> class QMap;
+class QSqlQueryPrivate;
+
+class Q_SQL_EXPORT QSqlQuery
+{
+public:
+ QSqlQuery(QSqlResult *r);
+ QSqlQuery(const QString& query = QString(), QSqlDatabase db = QSqlDatabase());
+ explicit QSqlQuery(QSqlDatabase db);
+ QSqlQuery(const QSqlQuery& other);
+ QSqlQuery& operator=(const QSqlQuery& other);
+ ~QSqlQuery();
+
+ bool isValid() const;
+ bool isActive() const;
+ bool isNull(int field) const;
+ int at() const;
+ QString lastQuery() const;
+ int numRowsAffected() const;
+ QSqlError lastError() const;
+ bool isSelect() const;
+ int size() const;
+ const QSqlDriver* driver() const;
+ const QSqlResult* result() const;
+ bool isForwardOnly() const;
+ QSqlRecord record() const;
+
+ void setForwardOnly(bool forward);
+ bool exec(const QString& query);
+ QVariant value(int i) const;
+
+ void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy precisionPolicy);
+ QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const;
+
+ bool seek(int i, bool relative = false);
+ bool next();
+ bool previous();
+#ifdef QT3_SUPPORT
+ inline QT3_SUPPORT bool prev() { return previous(); }
+#endif
+ bool first();
+ bool last();
+
+ void clear();
+
+ // prepared query support
+ bool exec();
+ enum BatchExecutionMode { ValuesAsRows, ValuesAsColumns };
+ bool execBatch(BatchExecutionMode mode = ValuesAsRows);
+ bool prepare(const QString& query);
+ void bindValue(const QString& placeholder, const QVariant& val,
+ QSql::ParamType type = QSql::In);
+ void bindValue(int pos, const QVariant& val, QSql::ParamType type = QSql::In);
+ void addBindValue(const QVariant& val, QSql::ParamType type = QSql::In);
+ QVariant boundValue(const QString& placeholder) const;
+ QVariant boundValue(int pos) const;
+ QMap<QString, QVariant> boundValues() const;
+ QString executedQuery() const;
+ QVariant lastInsertId() const;
+ void finish();
+ bool nextResult();
+
+private:
+ QSqlQueryPrivate* d;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLQUERY_H
diff --git a/src/sql/kernel/qsqlrecord.cpp b/src/sql/kernel/qsqlrecord.cpp
new file mode 100644
index 0000000000..635e5e4e62
--- /dev/null
+++ b/src/sql/kernel/qsqlrecord.cpp
@@ -0,0 +1,605 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqlrecord.h"
+
+#include "qdebug.h"
+#include "qstringlist.h"
+#include "qatomic.h"
+#include "qsqlfield.h"
+#include "qstring.h"
+#include "qvector.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSqlRecordPrivate
+{
+public:
+ QSqlRecordPrivate();
+ QSqlRecordPrivate(const QSqlRecordPrivate &other);
+
+ inline bool contains(int index) { return index >= 0 && index < fields.count(); }
+ QString createField(int index, const QString &prefix) const;
+
+ QVector<QSqlField> fields;
+ QAtomicInt ref;
+};
+
+QSqlRecordPrivate::QSqlRecordPrivate()
+{
+ ref = 1;
+}
+
+QSqlRecordPrivate::QSqlRecordPrivate(const QSqlRecordPrivate &other): fields(other.fields)
+{
+ ref = 1;
+}
+
+/*! \internal
+ Just for compat
+*/
+QString QSqlRecordPrivate::createField(int index, const QString &prefix) const
+{
+ QString f;
+ if (!prefix.isEmpty())
+ f = prefix + QLatin1Char('.');
+ f += fields.at(index).name();
+ return f;
+}
+
+/*!
+ \class QSqlRecord
+ \brief The QSqlRecord class encapsulates a database record.
+
+ \ingroup database
+ \ingroup shared
+ \inmodule QtSql
+
+ The QSqlRecord class encapsulates the functionality and
+ characteristics of a database record (usually a row in a table or
+ view within the database). QSqlRecord supports adding and
+ removing fields as well as setting and retrieving field values.
+
+ The values of a record's fields' can be set by name or position
+ with setValue(); if you want to set a field to null use
+ setNull(). To find the position of a field by name use indexOf(),
+ and to find the name of a field at a particular position use
+ fieldName(). Use field() to retrieve a QSqlField object for a
+ given field. Use contains() to see if the record contains a
+ particular field name.
+
+ When queries are generated to be executed on the database only
+ those fields for which isGenerated() is true are included in the
+ generated SQL.
+
+ A record can have fields added with append() or insert(), replaced
+ with replace(), and removed with remove(). All the fields can be
+ removed with clear(). The number of fields is given by count();
+ all their values can be cleared (to null) using clearValues().
+
+ \sa QSqlField, QSqlQuery::record()
+*/
+
+
+/*!
+ Constructs an empty record.
+
+ \sa isEmpty(), append(), insert()
+*/
+
+QSqlRecord::QSqlRecord()
+{
+ d = new QSqlRecordPrivate();
+}
+
+/*!
+ Constructs a copy of \a other.
+
+ QSqlRecord is \l{implicitly shared}. This means you can make copies
+ of a record in \l{constant time}.
+*/
+
+QSqlRecord::QSqlRecord(const QSqlRecord& other)
+{
+ d = other.d;
+ d->ref.ref();
+}
+
+/*!
+ Sets the record equal to \a other.
+
+ QSqlRecord is \l{implicitly shared}. This means you can make copies
+ of a record in \l{constant time}.
+*/
+
+QSqlRecord& QSqlRecord::operator=(const QSqlRecord& other)
+{
+ qAtomicAssign(d, other.d);
+ return *this;
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+
+QSqlRecord::~QSqlRecord()
+{
+ if (!d->ref.deref())
+ delete d;
+}
+
+/*!
+ \fn bool QSqlRecord::operator!=(const QSqlRecord &other) const
+
+ Returns true if this object is not identical to \a other;
+ otherwise returns false.
+
+ \sa operator==()
+*/
+
+/*!
+ Returns true if this object is identical to \a other (i.e., has
+ the same fields in the same order); otherwise returns false.
+
+ \sa operator!=()
+*/
+bool QSqlRecord::operator==(const QSqlRecord &other) const
+{
+ return d->fields == other.d->fields;
+}
+
+/*!
+ Returns the value of the field located at position \a index in
+ the record. If \a index is out of bounds, an invalid QVariant
+ is returned.
+
+ \sa fieldName() isNull()
+*/
+
+QVariant QSqlRecord::value(int index) const
+{
+ return d->fields.value(index).value();
+}
+
+/*!
+ \overload
+
+ Returns the value of the field called \a name in the record. If
+ field \a name does not exist an invalid variant is returned.
+
+ \sa indexOf()
+*/
+
+QVariant QSqlRecord::value(const QString& name) const
+{
+ return value(indexOf(name));
+}
+
+/*!
+ Returns the name of the field at position \a index. If the field
+ does not exist, an empty string is returned.
+
+ \sa indexOf()
+*/
+
+QString QSqlRecord::fieldName(int index) const
+{
+ return d->fields.value(index).name();
+}
+
+/*!
+ Returns the position of the field called \a name within the
+ record, or -1 if it cannot be found. Field names are not
+ case-sensitive. If more than one field matches, the first one is
+ returned.
+
+ \sa fieldName()
+*/
+
+int QSqlRecord::indexOf(const QString& name) const
+{
+ QString nm = name.toUpper();
+ for (int i = 0; i < count(); ++i) {
+ if (d->fields.at(i).name().toUpper() == nm) // TODO: case-insensitive comparison
+ return i;
+ }
+ return -1;
+}
+
+#ifdef QT3_SUPPORT
+/*!
+ \obsolete
+ Use field() instead
+*/
+const QSqlField* QSqlRecord::fieldPtr(int index) const
+{
+ if (!d->contains(index))
+ return 0;
+
+ return &d->fields.at(index);
+}
+
+/*!
+ \obsolete
+ Use field() instead
+*/
+
+const QSqlField* QSqlRecord::fieldPtr(const QString& name) const
+{
+ int i = indexOf(name);
+ if (!d->contains(i))
+ return 0;
+
+ return &d->fields.at(i);
+}
+#endif //QT3_SUPPORT
+
+/*!
+ Returns the field at position \a index. If the position is out of
+ range, an empty field is returned.
+ */
+QSqlField QSqlRecord::field(int index) const
+{
+ return d->fields.value(index);
+}
+
+/*! \overload
+ Returns the field called \a name.
+ */
+QSqlField QSqlRecord::field(const QString &name) const
+{
+ return field(indexOf(name));
+}
+
+
+/*!
+ Append a copy of field \a field to the end of the record.
+
+ \sa insert() replace() remove()
+*/
+
+void QSqlRecord::append(const QSqlField& field)
+{
+ detach();
+ d->fields.append(field);
+}
+
+/*!
+ Inserts the field \a field at position \a pos in the record.
+
+ \sa append() replace() remove()
+ */
+void QSqlRecord::insert(int pos, const QSqlField& field)
+{
+ detach();
+ d->fields.insert(pos, field);
+}
+
+/*!
+ Replaces the field at position \a pos with the given \a field. If
+ \a pos is out of range, nothing happens.
+
+ \sa append() insert() remove()
+*/
+
+void QSqlRecord::replace(int pos, const QSqlField& field)
+{
+ if (!d->contains(pos))
+ return;
+
+ detach();
+ d->fields[pos] = field;
+}
+
+/*!
+ Removes the field at position \a pos. If \a pos is out of range,
+ nothing happens.
+
+ \sa append() insert() replace()
+*/
+
+void QSqlRecord::remove(int pos)
+{
+ if (!d->contains(pos))
+ return;
+
+ detach();
+ d->fields.remove(pos);
+}
+
+/*!
+ Removes all the record's fields.
+
+ \sa clearValues() isEmpty()
+*/
+
+void QSqlRecord::clear()
+{
+ detach();
+ d->fields.clear();
+}
+
+/*!
+ Returns true if there are no fields in the record; otherwise
+ returns false.
+
+ \sa append() insert() clear()
+*/
+
+bool QSqlRecord::isEmpty() const
+{
+ return d->fields.isEmpty();
+}
+
+
+/*!
+ Returns true if there is a field in the record called \a name;
+ otherwise returns false.
+*/
+
+bool QSqlRecord::contains(const QString& name) const
+{
+ return indexOf(name) >= 0;
+}
+
+/*!
+ Clears the value of all fields in the record and sets each field
+ to null.
+
+ \sa setValue()
+*/
+
+void QSqlRecord::clearValues()
+{
+ detach();
+ int count = d->fields.count();
+ for (int i = 0; i < count; ++i)
+ d->fields[i].clear();
+}
+
+/*!
+ Sets the generated flag for the field called \a name to \a
+ generated. If the field does not exist, nothing happens. Only
+ fields that have \a generated set to true are included in the SQL
+ that is generated by QSqlQueryModel for example.
+
+ \sa isGenerated()
+*/
+
+void QSqlRecord::setGenerated(const QString& name, bool generated)
+{
+ setGenerated(indexOf(name), generated);
+}
+
+/*!
+ \overload
+
+ Sets the generated flag for the field \a index to \a generated.
+
+ \sa isGenerated()
+*/
+
+void QSqlRecord::setGenerated(int index, bool generated)
+{
+ if (!d->contains(index))
+ return;
+ detach();
+ d->fields[index].setGenerated(generated);
+}
+
+/*!
+ \overload
+
+ Returns true if the field \a index is null or if there is no field at
+ position \a index; otherwise returns false.
+*/
+bool QSqlRecord::isNull(int index) const
+{
+ return d->fields.value(index).isNull();
+}
+
+/*!
+ Returns true if the field called \a name is null or if there is no
+ field called \a name; otherwise returns false.
+
+ \sa setNull()
+*/
+bool QSqlRecord::isNull(const QString& name) const
+{
+ return isNull(indexOf(name));
+}
+
+/*!
+ Sets the value of field \a index to null. If the field does not exist,
+ nothing happens.
+
+ \sa setValue()
+*/
+void QSqlRecord::setNull(int index)
+{
+ if (!d->contains(index))
+ return;
+ detach();
+ d->fields[index].clear();
+}
+
+/*!
+ \overload
+
+ Sets the value of the field called \a name to null. If the field
+ does not exist, nothing happens.
+*/
+void QSqlRecord::setNull(const QString& name)
+{
+ setNull(indexOf(name));
+}
+
+
+/*!
+ Returns true if the record has a field called \a name and this
+ field is to be generated (the default); otherwise returns false.
+
+ \sa setGenerated()
+*/
+bool QSqlRecord::isGenerated(const QString& name) const
+{
+ return isGenerated(indexOf(name));
+}
+
+/*! \overload
+
+ Returns true if the record has a field at position \a index and this
+ field is to be generated (the default); otherwise returns false.
+
+ \sa setGenerated()
+*/
+bool QSqlRecord::isGenerated(int index) const
+{
+ return d->fields.value(index).isGenerated();
+}
+
+#ifdef QT3_SUPPORT
+/*!
+ Returns a list of all the record's field names as a string
+ separated by \a sep.
+
+ In the unlikely event that you used this function in Qt 3, you
+ can simulate it using the rest of the QSqlRecord public API.
+*/
+
+QString QSqlRecord::toString(const QString& prefix, const QString& sep) const
+{
+ QString pflist;
+ bool comma = false;
+ for (int i = 0; i < count(); ++i) {
+ if (!d->fields.value(i).isGenerated()) {
+ if (comma)
+ pflist += sep + QLatin1Char(' ');
+ pflist += d->createField(i, prefix);
+ comma = true;
+ }
+ }
+ return pflist;
+}
+
+/*!
+ Returns a list of all the record's field names, each having the
+ prefix \a prefix.
+
+ In the unlikely event that you used this function in Qt 3, you
+ can simulate it using the rest of the QSqlRecord public API.
+*/
+
+QStringList QSqlRecord::toStringList(const QString& prefix) const
+{
+ QStringList s;
+ for (int i = 0; i < count(); ++i) {
+ if (!d->fields.value(i).isGenerated())
+ s += d->createField(i, prefix);
+ }
+ return s;
+}
+#endif // QT3_SUPPORT
+
+/*!
+ Returns the number of fields in the record.
+
+ \sa isEmpty()
+*/
+
+int QSqlRecord::count() const
+{
+ return d->fields.count();
+}
+
+/*!
+ Sets the value of the field at position \a index to \a val. If the
+ field does not exist, nothing happens.
+
+ \sa setNull()
+*/
+
+void QSqlRecord::setValue(int index, const QVariant& val)
+{
+ if (!d->contains(index))
+ return;
+ detach();
+ d->fields[index].setValue(val);
+}
+
+
+/*!
+ \overload
+
+ Sets the value of the field called \a name to \a val. If the field
+ does not exist, nothing happens.
+*/
+
+void QSqlRecord::setValue(const QString& name, const QVariant& val)
+{
+ setValue(indexOf(name), val);
+}
+
+
+/*! \internal
+*/
+void QSqlRecord::detach()
+{
+ qAtomicDetach(d);
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug dbg, const QSqlRecord &r)
+{
+ dbg << "QSqlRecord(" << r.count() << ')';
+ for (int i = 0; i < r.count(); ++i)
+ dbg << '\n' << QString::fromLatin1("%1:").arg(i, 2) << r.field(i) << r.value(i).toString();
+ return dbg;
+}
+#endif
+
+/*!
+ \fn int QSqlRecord::position(const QString& name) const
+
+ Use indexOf() instead.
+*/
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqlrecord.h b/src/sql/kernel/qsqlrecord.h
new file mode 100644
index 0000000000..ed79dd8a02
--- /dev/null
+++ b/src/sql/kernel/qsqlrecord.h
@@ -0,0 +1,123 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLRECORD_H
+#define QSQLRECORD_H
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QSqlField;
+class QStringList;
+class QVariant;
+class QSqlRecordPrivate;
+
+class Q_SQL_EXPORT QSqlRecord
+{
+public:
+ QSqlRecord();
+ QSqlRecord(const QSqlRecord& other);
+ QSqlRecord& operator=(const QSqlRecord& other);
+ ~QSqlRecord();
+
+ bool operator==(const QSqlRecord &other) const;
+ inline bool operator!=(const QSqlRecord &other) const { return !operator==(other); }
+
+ QVariant value(int i) const;
+ QVariant value(const QString& name) const;
+ void setValue(int i, const QVariant& val);
+ void setValue(const QString& name, const QVariant& val);
+
+ void setNull(int i);
+ void setNull(const QString& name);
+ bool isNull(int i) const;
+ bool isNull(const QString& name) const;
+
+ int indexOf(const QString &name) const;
+ QString fieldName(int i) const;
+
+ QSqlField field(int i) const;
+ QSqlField field(const QString &name) const;
+
+ bool isGenerated(int i) const;
+ bool isGenerated(const QString& name) const;
+ void setGenerated(const QString& name, bool generated);
+ void setGenerated(int i, bool generated);
+
+#ifdef QT3_SUPPORT
+ QT3_SUPPORT const QSqlField* fieldPtr(int i) const;
+ QT3_SUPPORT const QSqlField* fieldPtr(const QString& name) const;
+ inline QT3_SUPPORT int position(const QString& name) const { return indexOf(name); }
+ QT3_SUPPORT QString toString(const QString& prefix = QString(),
+ const QString& sep = QLatin1String(",")) const;
+ QT3_SUPPORT QStringList toStringList(const QString& prefix = QString()) const;
+#endif
+
+ void append(const QSqlField& field);
+ void replace(int pos, const QSqlField& field);
+ void insert(int pos, const QSqlField& field);
+ void remove(int pos);
+
+ bool isEmpty() const;
+ bool contains(const QString& name) const;
+ void clear();
+ void clearValues();
+ int count() const;
+
+private:
+ void detach();
+ QSqlRecordPrivate* d;
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+Q_SQL_EXPORT QDebug operator<<(QDebug, const QSqlRecord &);
+#endif
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLRECORD_H
diff --git a/src/sql/kernel/qsqlresult.cpp b/src/sql/kernel/qsqlresult.cpp
new file mode 100644
index 0000000000..e8dcc044d2
--- /dev/null
+++ b/src/sql/kernel/qsqlresult.cpp
@@ -0,0 +1,1040 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qvariant.h"
+#include "qhash.h"
+#include "qregexp.h"
+#include "qsqlerror.h"
+#include "qsqlfield.h"
+#include "qsqlrecord.h"
+#include "qsqlresult.h"
+#include "qvector.h"
+#include "qsqldriver.h"
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+
+struct QHolder {
+ QHolder(const QString& hldr = QString(), int index = -1): holderName(hldr), holderPos(index) {}
+ bool operator==(const QHolder& h) const { return h.holderPos == holderPos && h.holderName == holderName; }
+ bool operator!=(const QHolder& h) const { return h.holderPos != holderPos || h.holderName != holderName; }
+ QString holderName;
+ int holderPos;
+};
+
+class QSqlResultPrivate
+{
+public:
+ QSqlResultPrivate(QSqlResult* d)
+ : q(d), sqldriver(0), idx(QSql::BeforeFirstRow), active(false),
+ isSel(false), forwardOnly(false), precisionPolicy(QSql::LowPrecisionDouble), bindCount(0), binds(QSqlResult::PositionalBinding)
+ {}
+
+ void clearValues()
+ {
+ values.clear();
+ bindCount = 0;
+ }
+
+ void resetBindCount()
+ {
+ bindCount = 0;
+ }
+
+ void clearIndex()
+ {
+ indexes.clear();
+ holders.clear();
+ types.clear();
+ }
+
+ void clear()
+ {
+ clearValues();
+ clearIndex();;
+ }
+
+ QString positionalToNamedBinding();
+ QString namedToPositionalBinding();
+ QString holderAt(int index) const;
+
+public:
+ QSqlResult* q;
+ const QSqlDriver* sqldriver;
+ int idx;
+ QString sql;
+ bool active;
+ bool isSel;
+ QSqlError error;
+ bool forwardOnly;
+ QSql::NumericalPrecisionPolicy precisionPolicy;
+
+ int bindCount;
+ QSqlResult::BindingSyntax binds;
+
+ QString executedQuery;
+ QHash<int, QSql::ParamType> types;
+ QVector<QVariant> values;
+ typedef QHash<QString, int> IndexMap;
+ IndexMap indexes;
+
+ typedef QVector<QHolder> QHolderVector;
+ QHolderVector holders;
+};
+
+QString QSqlResultPrivate::holderAt(int index) const
+{
+ return indexes.key(index);
+}
+
+// return a unique id for bound names
+static QString qFieldSerial(int i)
+{
+ ushort arr[] = { ':', 'f', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ ushort *ptr = &arr[1];
+
+ while (i > 0) {
+ *(++ptr) = 'a' + i % 16;
+ i >>= 4;
+ }
+
+ return QString(reinterpret_cast<const QChar *>(arr), int(ptr - arr) + 1);
+}
+
+static bool qIsAlnum(QChar ch)
+{
+ uint u = uint(ch.unicode());
+ // matches [a-zA-Z0-9_]
+ return u - 'a' < 26 || u - 'A' < 26 || u - '0' < 10 || u == '_';
+}
+
+QString QSqlResultPrivate::positionalToNamedBinding()
+{
+ int n = sql.size();
+
+ QString result;
+ result.reserve(n * 5 / 4);
+ bool inQuote = false;
+ int count = 0;
+
+ for (int i = 0; i < n; ++i) {
+ QChar ch = sql.at(i);
+ if (ch == QLatin1Char('?') && !inQuote) {
+ result += qFieldSerial(count++);
+ } else {
+ if (ch == QLatin1Char('\''))
+ inQuote = !inQuote;
+ result += ch;
+ }
+ }
+ result.squeeze();
+ return result;
+}
+
+QString QSqlResultPrivate::namedToPositionalBinding()
+{
+ int n = sql.size();
+
+ QString result;
+ result.reserve(n);
+ bool inQuote = false;
+ int count = 0;
+ int i = 0;
+
+ while (i < n) {
+ QChar ch = sql.at(i);
+ if (ch == QLatin1Char(':') && !inQuote
+ && (i == 0 || sql.at(i - 1) != QLatin1Char(':'))
+ && (i < n - 1 && qIsAlnum(sql.at(i + 1)))) {
+ int pos = i + 2;
+ while (pos < n && qIsAlnum(sql.at(pos)))
+ ++pos;
+ indexes[sql.mid(i, pos - i)] = count++;
+ result += QLatin1Char('?');
+ i = pos;
+ } else {
+ if (ch == QLatin1Char('\''))
+ inQuote = !inQuote;
+ result += ch;
+ ++i;
+ }
+ }
+ result.squeeze();
+ return result;
+}
+
+/*!
+ \class QSqlResult
+ \brief The QSqlResult class provides an abstract interface for
+ accessing data from specific SQL databases.
+
+ \ingroup database
+ \inmodule QtSql
+
+ Normally, you would use QSqlQuery instead of QSqlResult, since
+ QSqlQuery provides a generic wrapper for database-specific
+ implementations of QSqlResult.
+
+ If you are implementing your own SQL driver (by subclassing
+ QSqlDriver), you will need to provide your own QSqlResult
+ subclass that implements all the pure virtual functions and other
+ virtual functions that you need.
+
+ \sa QSqlDriver
+*/
+
+/*!
+ \enum QSqlResult::BindingSyntax
+
+ This enum type specifies the different syntaxes for specifying
+ placeholders in prepared queries.
+
+ \value PositionalBinding Use the ODBC-style positional syntax, with "?" as placeholders.
+ \value NamedBinding Use the Oracle-style syntax with named placeholders (e.g., ":id")
+ \omitvalue BindByPosition
+ \omitvalue BindByName
+
+ \sa bindingSyntax()
+*/
+
+/*!
+ \enum QSqlResult::VirtualHookOperation
+ \internal
+*/
+
+/*!
+ Creates a QSqlResult using database driver \a db. The object is
+ initialized to an inactive state.
+
+ \sa isActive(), driver()
+*/
+
+QSqlResult::QSqlResult(const QSqlDriver *db)
+{
+ d = new QSqlResultPrivate(this);
+ d->sqldriver = db;
+ if(db) {
+ setNumericalPrecisionPolicy(db->numericalPrecisionPolicy());
+ }
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+
+QSqlResult::~QSqlResult()
+{
+ delete d;
+}
+
+/*!
+ Sets the current query for the result to \a query. You must call
+ reset() to execute the query on the database.
+
+ \sa reset(), lastQuery()
+*/
+
+void QSqlResult::setQuery(const QString& query)
+{
+ d->sql = query;
+}
+
+/*!
+ Returns the current SQL query text, or an empty string if there
+ isn't one.
+
+ \sa setQuery()
+*/
+
+QString QSqlResult::lastQuery() const
+{
+ return d->sql;
+}
+
+/*!
+ Returns the current (zero-based) row position of the result. May
+ return the special values QSql::BeforeFirstRow or
+ QSql::AfterLastRow.
+
+ \sa setAt(), isValid()
+*/
+int QSqlResult::at() const
+{
+ return d->idx;
+}
+
+
+/*!
+ Returns true if the result is positioned on a valid record (that
+ is, the result is not positioned before the first or after the
+ last record); otherwise returns false.
+
+ \sa at()
+*/
+
+bool QSqlResult::isValid() const
+{
+ return d->idx != QSql::BeforeFirstRow && d->idx != QSql::AfterLastRow;
+}
+
+/*!
+ \fn bool QSqlResult::isNull(int index)
+
+ Returns true if the field at position \a index in the current row
+ is null; otherwise returns false.
+*/
+
+/*!
+ Returns true if the result has records to be retrieved; otherwise
+ returns false.
+*/
+
+bool QSqlResult::isActive() const
+{
+ return d->active;
+}
+
+/*!
+ This function is provided for derived classes to set the
+ internal (zero-based) row position to \a index.
+
+ \sa at()
+*/
+
+void QSqlResult::setAt(int index)
+{
+ d->idx = index;
+}
+
+
+/*!
+ This function is provided for derived classes to indicate whether
+ or not the current statement is a SQL \c SELECT statement. The \a
+ select parameter should be true if the statement is a \c SELECT
+ statement; otherwise it should be false.
+
+ \sa isSelect()
+*/
+
+void QSqlResult::setSelect(bool select)
+{
+ d->isSel = select;
+}
+
+/*!
+ Returns true if the current result is from a \c SELECT statement;
+ otherwise returns false.
+
+ \sa setSelect()
+*/
+
+bool QSqlResult::isSelect() const
+{
+ return d->isSel;
+}
+
+/*!
+ Returns the driver associated with the result. This is the object
+ that was passed to the constructor.
+*/
+
+const QSqlDriver *QSqlResult::driver() const
+{
+ return d->sqldriver;
+}
+
+
+/*!
+ This function is provided for derived classes to set the internal
+ active state to \a active.
+
+ \sa isActive()
+*/
+
+void QSqlResult::setActive(bool active)
+{
+ if (active && d->executedQuery.isEmpty())
+ d->executedQuery = d->sql;
+
+ d->active = active;
+}
+
+/*!
+ This function is provided for derived classes to set the last
+ error to \a error.
+
+ \sa lastError()
+*/
+
+void QSqlResult::setLastError(const QSqlError &error)
+{
+ d->error = error;
+}
+
+
+/*!
+ Returns the last error associated with the result.
+*/
+
+QSqlError QSqlResult::lastError() const
+{
+ return d->error;
+}
+
+/*!
+ \fn int QSqlResult::size()
+
+ Returns the size of the \c SELECT result, or -1 if it cannot be
+ determined or if the query is not a \c SELECT statement.
+
+ \sa numRowsAffected()
+*/
+
+/*!
+ \fn int QSqlResult::numRowsAffected()
+
+ Returns the number of rows affected by the last query executed, or
+ -1 if it cannot be determined or if the query is a \c SELECT
+ statement.
+
+ \sa size()
+*/
+
+/*!
+ \fn QVariant QSqlResult::data(int index)
+
+ Returns the data for field \a index in the current row as
+ a QVariant. This function is only called if the result is in
+ an active state and is positioned on a valid record and \a index is
+ non-negative. Derived classes must reimplement this function and
+ return the value of field \a index, or QVariant() if it cannot be
+ determined.
+*/
+
+/*!
+ \fn bool QSqlResult::reset(const QString &query)
+
+ Sets the result to use the SQL statement \a query for subsequent
+ data retrieval.
+
+ Derived classes must reimplement this function and apply the \a
+ query to the database. This function is only called after the
+ result is set to an inactive state and is positioned before the
+ first record of the new result. Derived classes should return
+ true if the query was successful and ready to be used, or false
+ otherwise.
+
+ \sa setQuery()
+*/
+
+/*!
+ \fn bool QSqlResult::fetch(int index)
+
+ Positions the result to an arbitrary (zero-based) row \a index.
+
+ This function is only called if the result is in an active state.
+ Derived classes must reimplement this function and position the
+ result to the row \a index, and call setAt() with an appropriate
+ value. Return true to indicate success, or false to signify
+ failure.
+
+ \sa isActive(), fetchFirst(), fetchLast(), fetchNext(), fetchPrevious()
+*/
+
+/*!
+ \fn bool QSqlResult::fetchFirst()
+
+ Positions the result to the first record (row 0) in the result.
+
+ This function is only called if the result is in an active state.
+ Derived classes must reimplement this function and position the
+ result to the first record, and call setAt() with an appropriate
+ value. Return true to indicate success, or false to signify
+ failure.
+
+ \sa fetch(), fetchLast()
+*/
+
+/*!
+ \fn bool QSqlResult::fetchLast()
+
+ Positions the result to the last record (last row) in the result.
+
+ This function is only called if the result is in an active state.
+ Derived classes must reimplement this function and position the
+ result to the last record, and call setAt() with an appropriate
+ value. Return true to indicate success, or false to signify
+ failure.
+
+ \sa fetch(), fetchFirst()
+*/
+
+/*!
+ Positions the result to the next available record (row) in the
+ result.
+
+ This function is only called if the result is in an active
+ state. The default implementation calls fetch() with the next
+ index. Derived classes can reimplement this function and position
+ the result to the next record in some other way, and call setAt()
+ with an appropriate value. Return true to indicate success, or
+ false to signify failure.
+
+ \sa fetch(), fetchPrevious()
+*/
+
+bool QSqlResult::fetchNext()
+{
+ return fetch(at() + 1);
+}
+
+/*!
+ Positions the result to the previous record (row) in the result.
+
+ This function is only called if the result is in an active state.
+ The default implementation calls fetch() with the previous index.
+ Derived classes can reimplement this function and position the
+ result to the next record in some other way, and call setAt()
+ with an appropriate value. Return true to indicate success, or
+ false to signify failure.
+*/
+
+bool QSqlResult::fetchPrevious()
+{
+ return fetch(at() - 1);
+}
+
+/*!
+ Returns true if you can only scroll forward through the result
+ set; otherwise returns false.
+
+ \sa setForwardOnly()
+*/
+bool QSqlResult::isForwardOnly() const
+{
+ return d->forwardOnly;
+}
+
+/*!
+ Sets forward only mode to \a forward. If \a forward is true, only
+ fetchNext() is allowed for navigating the results. Forward only
+ mode needs much less memory since results do not have to be
+ cached. By default, this feature is disabled.
+
+ Setting forward only to false is a suggestion to the database engine,
+ which has the final say on whether a result set is forward only or
+ scrollable. isForwardOnly() will always return the correct status of
+ the result set.
+
+ \note Calling setForwardOnly after execution of the query will result
+ in unexpected results at best, and crashes at worst.
+
+ \sa isForwardOnly(), fetchNext(), QSqlQuery::setForwardOnly()
+*/
+void QSqlResult::setForwardOnly(bool forward)
+{
+ d->forwardOnly = forward;
+}
+
+/*!
+ Prepares the given \a query, using the underlying database
+ functionality where possible. Returns true if the query is
+ prepared successfully; otherwise returns false.
+
+ \sa prepare()
+*/
+bool QSqlResult::savePrepare(const QString& query)
+{
+ if (!driver())
+ return false;
+ d->clear();
+ d->sql = query;
+ if (!driver()->hasFeature(QSqlDriver::PreparedQueries))
+ return prepare(query);
+
+ if (driver()->hasFeature(QSqlDriver::NamedPlaceholders)) {
+ // parse the query to memorize parameter location
+ d->namedToPositionalBinding();
+ d->executedQuery = d->positionalToNamedBinding();
+ } else {
+ d->executedQuery = d->namedToPositionalBinding();
+ }
+ return prepare(d->executedQuery);
+}
+
+/*!
+ Prepares the given \a query for execution; the query will normally
+ use placeholders so that it can be executed repeatedly. Returns
+ true if the query is prepared successfully; otherwise returns false.
+
+ \sa exec()
+*/
+bool QSqlResult::prepare(const QString& query)
+{
+ int n = query.size();
+
+ bool inQuote = false;
+ int i = 0;
+
+ while (i < n) {
+ QChar ch = query.at(i);
+ if (ch == QLatin1Char(':') && !inQuote
+ && (i == 0 || query.at(i - 1) != QLatin1Char(':'))
+ && (i < n - 1 && qIsAlnum(query.at(i + 1)))) {
+ int pos = i + 2;
+ while (pos < n && qIsAlnum(query.at(pos)))
+ ++pos;
+
+ d->holders.append(QHolder(query.mid(i, pos - i), i));
+ i = pos;
+ } else {
+ if (ch == QLatin1Char('\''))
+ inQuote = !inQuote;
+ ++i;
+ }
+ }
+ d->sql = query;
+ return true; // fake prepares should always succeed
+}
+
+/*!
+ Executes the query, returning true if successful; otherwise returns
+ false.
+
+ \sa prepare()
+*/
+bool QSqlResult::exec()
+{
+ bool ret;
+ // fake preparation - just replace the placeholders..
+ QString query = lastQuery();
+ if (d->binds == NamedBinding) {
+ int i;
+ QVariant val;
+ QString holder;
+ for (i = d->holders.count() - 1; i >= 0; --i) {
+ holder = d->holders.at(i).holderName;
+ val = d->values.value(d->indexes.value(holder));
+ QSqlField f(QLatin1String(""), val.type());
+ f.setValue(val);
+ query = query.replace(d->holders.at(i).holderPos,
+ holder.length(), driver()->formatValue(f));
+ }
+ } else {
+ QString val;
+ int i = 0;
+ int idx = 0;
+ for (idx = 0; idx < d->values.count(); ++idx) {
+ i = query.indexOf(QLatin1Char('?'), i);
+ if (i == -1)
+ continue;
+ QVariant var = d->values.value(idx);
+ QSqlField f(QLatin1String(""), var.type());
+ if (var.isNull())
+ f.clear();
+ else
+ f.setValue(var);
+ val = driver()->formatValue(f);
+ query = query.replace(i, 1, driver()->formatValue(f));
+ i += val.length();
+ }
+ }
+
+ // have to retain the original query with placeholders
+ QString orig = lastQuery();
+ ret = reset(query);
+ d->executedQuery = query;
+ setQuery(orig);
+ d->resetBindCount();
+ return ret;
+}
+
+/*!
+ Binds the value \a val of parameter type \a paramType to position \a index
+ in the current record (row).
+
+ \sa addBindValue()
+*/
+void QSqlResult::bindValue(int index, const QVariant& val, QSql::ParamType paramType)
+{
+ d->binds = PositionalBinding;
+ d->indexes[qFieldSerial(index)] = index;
+ if (d->values.count() <= index)
+ d->values.resize(index + 1);
+ d->values[index] = val;
+ if (paramType != QSql::In || !d->types.isEmpty())
+ d->types[index] = paramType;
+}
+
+/*!
+ \overload
+
+ Binds the value \a val of parameter type \a paramType to the \a
+ placeholder name in the current record (row).
+
+ Values cannot be bound to multiple locations in the query, eg:
+ \code
+ INSERT INTO testtable (id, name, samename) VALUES (:id, :name, :name)
+ \endcode
+ Binding to name will bind to the first :name, but not the second.
+
+ \note Binding an undefined placeholder will result in undefined behavior.
+
+ \sa QSqlQuery::bindValue()
+*/
+void QSqlResult::bindValue(const QString& placeholder, const QVariant& val,
+ QSql::ParamType paramType)
+{
+ d->binds = NamedBinding;
+ // if the index has already been set when doing emulated named
+ // bindings - don't reset it
+ int idx = d->indexes.value(placeholder, -1);
+ if (idx >= 0) {
+ if (d->values.count() <= idx)
+ d->values.resize(idx + 1);
+ d->values[idx] = val;
+ } else {
+ d->values.append(val);
+ idx = d->values.count() - 1;
+ d->indexes[placeholder] = idx;
+ }
+
+ if (paramType != QSql::In || !d->types.isEmpty())
+ d->types[idx] = paramType;
+}
+
+/*!
+ Binds the value \a val of parameter type \a paramType to the next
+ available position in the current record (row).
+
+ \sa bindValue()
+*/
+void QSqlResult::addBindValue(const QVariant& val, QSql::ParamType paramType)
+{
+ d->binds = PositionalBinding;
+ bindValue(d->bindCount, val, paramType);
+ ++d->bindCount;
+}
+
+/*!
+ Returns the value bound at position \a index in the current record
+ (row).
+
+ \sa bindValue(), boundValues()
+*/
+QVariant QSqlResult::boundValue(int index) const
+{
+ return d->values.value(index);
+}
+
+/*!
+ \overload
+
+ Returns the value bound by the given \a placeholder name in the
+ current record (row).
+
+ \sa bindValueType()
+*/
+QVariant QSqlResult::boundValue(const QString& placeholder) const
+{
+ int idx = d->indexes.value(placeholder, -1);
+ return d->values.value(idx);
+}
+
+/*!
+ Returns the parameter type for the value bound at position \a index.
+
+ \sa boundValue()
+*/
+QSql::ParamType QSqlResult::bindValueType(int index) const
+{
+ return d->types.value(index, QSql::In);
+}
+
+/*!
+ \overload
+
+ Returns the parameter type for the value bound with the given \a
+ placeholder name.
+*/
+QSql::ParamType QSqlResult::bindValueType(const QString& placeholder) const
+{
+ return d->types.value(d->indexes.value(placeholder, -1), QSql::In);
+}
+
+/*!
+ Returns the number of bound values in the result.
+
+ \sa boundValues()
+*/
+int QSqlResult::boundValueCount() const
+{
+ return d->values.count();
+}
+
+/*!
+ Returns a vector of the result's bound values for the current
+ record (row).
+
+ \sa boundValueCount()
+*/
+QVector<QVariant>& QSqlResult::boundValues() const
+{
+ return d->values;
+}
+
+/*!
+ Returns the binding syntax used by prepared queries.
+*/
+QSqlResult::BindingSyntax QSqlResult::bindingSyntax() const
+{
+ return d->binds;
+}
+
+/*!
+ Clears the entire result set and releases any associated
+ resources.
+*/
+void QSqlResult::clear()
+{
+ d->clear();
+}
+
+/*!
+ Returns the query that was actually executed. This may differ from
+ the query that was passed, for example if bound values were used
+ with a prepared query and the underlying database doesn't support
+ prepared queries.
+
+ \sa exec(), setQuery()
+*/
+QString QSqlResult::executedQuery() const
+{
+ return d->executedQuery;
+}
+
+void QSqlResult::resetBindCount()
+{
+ d->resetBindCount();
+}
+
+/*!
+ Returns the name of the bound value at position \a index in the
+ current record (row).
+
+ \sa boundValue()
+*/
+QString QSqlResult::boundValueName(int index) const
+{
+ return d->holderAt(index);
+}
+
+/*!
+ Returns true if at least one of the query's bound values is a \c
+ QSql::Out or a QSql::InOut; otherwise returns false.
+
+ \sa bindValueType()
+*/
+bool QSqlResult::hasOutValues() const
+{
+ if (d->types.isEmpty())
+ return false;
+ QHash<int, QSql::ParamType>::ConstIterator it;
+ for (it = d->types.constBegin(); it != d->types.constEnd(); ++it) {
+ if (it.value() != QSql::In)
+ return true;
+ }
+ return false;
+}
+
+/*!
+ Returns the current record if the query is active; otherwise
+ returns an empty QSqlRecord.
+
+ The default implementation always returns an empty QSqlRecord.
+
+ \sa isActive()
+*/
+QSqlRecord QSqlResult::record() const
+{
+ return QSqlRecord();
+}
+
+/*!
+ Returns the object ID of the most recent inserted row if the
+ database supports it.
+ An invalid QVariant will be returned if the query did not
+ insert any value or if the database does not report the id back.
+ If more than one row was touched by the insert, the behavior is
+ undefined.
+
+ Note that for Oracle databases the row's ROWID will be returned,
+ while for MySQL databases the row's auto-increment field will
+ be returned.
+
+ \sa QSqlDriver::hasFeature()
+*/
+QVariant QSqlResult::lastInsertId() const
+{
+ return QVariant();
+}
+
+/*! \internal
+*/
+void QSqlResult::virtual_hook(int, void *)
+{
+}
+
+/*! \internal
+ \since 4.2
+
+ Executes a prepared query in batch mode if the driver supports it,
+ otherwise emulates a batch execution using bindValue() and exec().
+ QSqlDriver::hasFeature() can be used to find out whether a driver
+ supports batch execution.
+
+ Batch execution can be faster for large amounts of data since it
+ reduces network roundtrips.
+
+ For batch executions, bound values have to be provided as lists
+ of variants (QVariantList).
+
+ Each list must contain values of the same type. All lists must
+ contain equal amount of values (rows).
+
+ NULL values are passed in as typed QVariants, for example
+ \c {QVariant(QVariant::Int)} for an integer NULL value.
+
+ Example:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqlresult.cpp 0
+
+ Here, we insert two rows into a SQL table, with each row containing three values.
+
+ \sa exec(), QSqlDriver::hasFeature()
+*/
+bool QSqlResult::execBatch(bool arrayBind)
+{
+ if (driver()->hasFeature(QSqlDriver::BatchOperations)) {
+ virtual_hook(BatchOperation, &arrayBind);
+ d->resetBindCount();
+ return d->error.type() == QSqlError::NoError;
+ } else {
+ QVector<QVariant> values = d->values;
+ if (values.count() == 0)
+ return false;
+ for (int i = 0; i < values.at(0).toList().count(); ++i) {
+ for (int j = 0; j < values.count(); ++j)
+ bindValue(j, values.at(j).toList().at(i), QSql::In);
+ if (!exec())
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+/*! \internal
+ */
+void QSqlResult::detachFromResultSet()
+{
+ if (driver()->hasFeature(QSqlDriver::FinishQuery)
+ || driver()->hasFeature(QSqlDriver::SimpleLocking))
+ virtual_hook(DetachFromResultSet, 0);
+}
+
+/*! \internal
+ */
+void QSqlResult::setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy)
+{
+ d->precisionPolicy = policy;
+ virtual_hook(SetNumericalPrecision, &policy);
+}
+
+/*! \internal
+ */
+QSql::NumericalPrecisionPolicy QSqlResult::numericalPrecisionPolicy() const
+{
+ return d->precisionPolicy;
+}
+
+/*! \internal
+*/
+bool QSqlResult::nextResult()
+{
+ if (driver()->hasFeature(QSqlDriver::MultipleResultSets)) {
+ bool result = false;
+ virtual_hook(NextResult, &result);
+ return result;
+ }
+ return false;
+}
+
+/*!
+ Returns the low-level database handle for this result set
+ wrapped in a QVariant or an invalid QVariant if there is no handle.
+
+ \warning Use this with uttermost care and only if you know what you're doing.
+
+ \warning The handle returned here can become a stale pointer if the result
+ is modified (for example, if you clear it).
+
+ \warning The handle can be NULL if the result was not executed yet.
+
+ The handle returned here is database-dependent, you should query the type
+ name of the variant before accessing it.
+
+ This example retrieves the handle for a sqlite result:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqlresult.cpp 1
+
+ This snippet returns the handle for PostgreSQL or MySQL:
+
+ \snippet doc/src/snippets/code/src_sql_kernel_qsqlresult.cpp 2
+
+ \sa QSqlDriver::handle()
+*/
+QVariant QSqlResult::handle() const
+{
+ return QVariant();
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/kernel/qsqlresult.h b/src/sql/kernel/qsqlresult.h
new file mode 100644
index 0000000000..d216eac062
--- /dev/null
+++ b/src/sql/kernel/qsqlresult.h
@@ -0,0 +1,153 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLRESULT_H
+#define QSQLRESULT_H
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qvector.h>
+#include <QtSql/qsql.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QString;
+class QSqlRecord;
+template <typename T> class QVector;
+class QVariant;
+class QSqlDriver;
+class QSqlError;
+class QSqlResultPrivate;
+
+class Q_SQL_EXPORT QSqlResult
+{
+ friend class QSqlQuery;
+ friend class QSqlTableModelPrivate;
+ friend class QSqlResultPrivate;
+
+public:
+ virtual ~QSqlResult();
+ virtual QVariant handle() const;
+
+protected:
+ enum BindingSyntax {
+ PositionalBinding,
+ NamedBinding
+#ifdef QT3_SUPPORT
+ , BindByPosition = PositionalBinding,
+ BindByName = NamedBinding
+#endif
+ };
+
+ explicit QSqlResult(const QSqlDriver * db);
+ int at() const;
+ QString lastQuery() const;
+ QSqlError lastError() const;
+ bool isValid() const;
+ bool isActive() const;
+ bool isSelect() const;
+ bool isForwardOnly() const;
+ const QSqlDriver* driver() const;
+ virtual void setAt(int at);
+ virtual void setActive(bool a);
+ virtual void setLastError(const QSqlError& e);
+ virtual void setQuery(const QString& query);
+ virtual void setSelect(bool s);
+ virtual void setForwardOnly(bool forward);
+
+ // prepared query support
+ virtual bool exec();
+ virtual bool prepare(const QString& query);
+ virtual bool savePrepare(const QString& sqlquery);
+ virtual void bindValue(int pos, const QVariant& val, QSql::ParamType type);
+ virtual void bindValue(const QString& placeholder, const QVariant& val,
+ QSql::ParamType type);
+ void addBindValue(const QVariant& val, QSql::ParamType type);
+ QVariant boundValue(const QString& placeholder) const;
+ QVariant boundValue(int pos) const;
+ QSql::ParamType bindValueType(const QString& placeholder) const;
+ QSql::ParamType bindValueType(int pos) const;
+ int boundValueCount() const;
+ QVector<QVariant>& boundValues() const;
+ QString executedQuery() const;
+ QString boundValueName(int pos) const;
+ void clear();
+ bool hasOutValues() const;
+
+ BindingSyntax bindingSyntax() const;
+
+ virtual QVariant data(int i) = 0;
+ virtual bool isNull(int i) = 0;
+ virtual bool reset(const QString& sqlquery) = 0;
+ virtual bool fetch(int i) = 0;
+ virtual bool fetchNext();
+ virtual bool fetchPrevious();
+ virtual bool fetchFirst() = 0;
+ virtual bool fetchLast() = 0;
+ virtual int size() = 0;
+ virtual int numRowsAffected() = 0;
+ virtual QSqlRecord record() const;
+ virtual QVariant lastInsertId() const;
+
+ enum VirtualHookOperation { BatchOperation, DetachFromResultSet, SetNumericalPrecision, NextResult };
+ virtual void virtual_hook(int id, void *data);
+ bool execBatch(bool arrayBind = false);
+ void detachFromResultSet();
+ void setNumericalPrecisionPolicy(QSql::NumericalPrecisionPolicy policy);
+ QSql::NumericalPrecisionPolicy numericalPrecisionPolicy() const;
+ bool nextResult();
+
+private:
+ QSqlResultPrivate* d;
+ void resetBindCount(); // HACK
+
+private:
+ Q_DISABLE_COPY(QSqlResult)
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLRESULT_H
diff --git a/src/sql/models/models.pri b/src/sql/models/models.pri
new file mode 100644
index 0000000000..972cf2f3f2
--- /dev/null
+++ b/src/sql/models/models.pri
@@ -0,0 +1,12 @@
+HEADERS += models/qsqlquerymodel.h \
+ models/qsqlquerymodel_p.h \
+ models/qsqltablemodel.h \
+ models/qsqltablemodel_p.h \
+ models/qsqlrelationaldelegate.h \
+ models/qsqlrelationaltablemodel.h
+
+SOURCES += models/qsqlquerymodel.cpp \
+ models/qsqltablemodel.cpp \
+ models/qsqlrelationaldelegate.cpp \
+ models/qsqlrelationaltablemodel.cpp
+
diff --git a/src/sql/models/qsqlquerymodel.cpp b/src/sql/models/qsqlquerymodel.cpp
new file mode 100644
index 0000000000..d1051dedab
--- /dev/null
+++ b/src/sql/models/qsqlquerymodel.cpp
@@ -0,0 +1,600 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqlquerymodel.h"
+
+#include <qdebug.h>
+#include <qsqldriver.h>
+#include <qsqlfield.h>
+
+#include "qsqlquerymodel_p.h"
+
+QT_BEGIN_NAMESPACE
+
+#define QSQL_PREFETCH 255
+
+void QSqlQueryModelPrivate::prefetch(int limit)
+{
+ Q_Q(QSqlQueryModel);
+
+ if (atEnd || limit <= bottom.row() || bottom.column() == -1)
+ return;
+
+ QModelIndex newBottom;
+ const int oldBottomRow = qMax(bottom.row(), 0);
+
+ // try to seek directly
+ if (query.seek(limit)) {
+ newBottom = q->createIndex(limit, bottom.column());
+ } else {
+ // have to seek back to our old position for MS Access
+ int i = oldBottomRow;
+ if (query.seek(i)) {
+ while (query.next())
+ ++i;
+ newBottom = q->createIndex(i, bottom.column());
+ } else {
+ // empty or invalid query
+ newBottom = q->createIndex(-1, bottom.column());
+ }
+ atEnd = true; // this is the end.
+ }
+ if (newBottom.row() >= 0 && newBottom.row() > bottom.row()) {
+ q->beginInsertRows(QModelIndex(), bottom.row() + 1, newBottom.row());
+ bottom = newBottom;
+ q->endInsertRows();
+ } else {
+ bottom = newBottom;
+ }
+}
+
+QSqlQueryModelPrivate::~QSqlQueryModelPrivate()
+{
+}
+
+void QSqlQueryModelPrivate::initColOffsets(int size)
+{
+ colOffsets.resize(size);
+ memset(colOffsets.data(), 0, colOffsets.size() * sizeof(int));
+}
+
+/*!
+ \class QSqlQueryModel
+ \brief The QSqlQueryModel class provides a read-only data model for SQL
+ result sets.
+
+ \ingroup database
+ \inmodule QtSql
+
+ QSqlQueryModel is a high-level interface for executing SQL
+ statements and traversing the result set. It is built on top of
+ the lower-level QSqlQuery and can be used to provide data to
+ view classes such as QTableView. For example:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 16
+
+ We set the model's query, then we set up the labels displayed in
+ the view header.
+
+ QSqlQueryModel can also be used to access a database
+ programmatically, without binding it to a view:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 21
+
+ The code snippet above extracts the \c salary field from record 4 in
+ the result set of the query \c{SELECT * from employee}. Assuming
+ that \c salary is column 2, we can rewrite the last line as follows:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 22
+
+ The model is read-only by default. To make it read-write, you
+ must subclass it and reimplement setData() and flags(). Another
+ option is to use QSqlTableModel, which provides a read-write
+ model based on a single database table.
+
+ The \l{sql/querymodel} example illustrates how to use
+ QSqlQueryModel to display the result of a query. It also shows
+ how to subclass QSqlQueryModel to customize the contents of the
+ data before showing it to the user, and how to create a
+ read-write model based on QSqlQueryModel.
+
+ If the database doesn't return the amount of selected rows in
+ a query, the model will fetch rows incrementally.
+ See fetchMore() for more information.
+
+ \sa QSqlTableModel, QSqlRelationalTableModel, QSqlQuery,
+ {Model/View Programming}, {Query Model Example}
+*/
+
+/*!
+ Creates an empty QSqlQueryModel with the given \a parent.
+ */
+QSqlQueryModel::QSqlQueryModel(QObject *parent)
+ : QAbstractTableModel(*new QSqlQueryModelPrivate, parent)
+{
+}
+
+/*! \internal
+ */
+QSqlQueryModel::QSqlQueryModel(QSqlQueryModelPrivate &dd, QObject *parent)
+ : QAbstractTableModel(dd, parent)
+{
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+
+ \sa clear()
+*/
+QSqlQueryModel::~QSqlQueryModel()
+{
+}
+
+/*!
+ \since 4.1
+
+ Fetches more rows from a database.
+ This only affects databases that don't report back the size of a query
+ (see QSqlDriver::hasFeature()).
+
+ To force fetching of the entire database, you can use the following:
+
+ \snippet doc/src/snippets/code/src_sql_models_qsqlquerymodel.cpp 0
+
+ \a parent should always be an invalid QModelIndex.
+
+ \sa canFetchMore()
+*/
+void QSqlQueryModel::fetchMore(const QModelIndex &parent)
+{
+ Q_D(QSqlQueryModel);
+ if (parent.isValid())
+ return;
+ d->prefetch(qMax(d->bottom.row(), 0) + QSQL_PREFETCH);
+}
+
+/*!
+ \since 4.1
+
+ Returns true if it is possible to read more rows from the database.
+ This only affects databases that don't report back the size of a query
+ (see QSqlDriver::hasFeature()).
+
+ \a parent should always be an invalid QModelIndex.
+
+ \sa fetchMore()
+ */
+bool QSqlQueryModel::canFetchMore(const QModelIndex &parent) const
+{
+ Q_D(const QSqlQueryModel);
+ return (!parent.isValid() && !d->atEnd);
+}
+
+/*! \fn int QSqlQueryModel::rowCount(const QModelIndex &parent) const
+ \since 4.1
+
+ If the database supports returning the size of a query
+ (see QSqlDriver::hasFeature()), the amount of rows of the current
+ query is returned. Otherwise, returns the amount of rows
+ currently cached on the client.
+
+ \a parent should always be an invalid QModelIndex.
+
+ \sa canFetchMore(), QSqlDriver::hasFeature()
+ */
+int QSqlQueryModel::rowCount(const QModelIndex &index) const
+{
+ Q_D(const QSqlQueryModel);
+ return index.isValid() ? 0 : d->bottom.row() + 1;
+}
+
+/*! \reimp
+ */
+int QSqlQueryModel::columnCount(const QModelIndex &index) const
+{
+ Q_D(const QSqlQueryModel);
+ return index.isValid() ? 0 : d->rec.count();
+}
+
+/*!
+ Returns the value for the specified \a item and \a role.
+
+ If \a item is out of bounds or if an error occurred, an invalid
+ QVariant is returned.
+
+ \sa lastError()
+*/
+QVariant QSqlQueryModel::data(const QModelIndex &item, int role) const
+{
+ Q_D(const QSqlQueryModel);
+ if (!item.isValid())
+ return QVariant();
+
+ QVariant v;
+ if (role & ~(Qt::DisplayRole | Qt::EditRole))
+ return v;
+
+ if (!d->rec.isGenerated(item.column()))
+ return v;
+ QModelIndex dItem = indexInQuery(item);
+ if (dItem.row() > d->bottom.row())
+ const_cast<QSqlQueryModelPrivate *>(d)->prefetch(dItem.row());
+
+ if (!d->query.seek(dItem.row())) {
+ d->error = d->query.lastError();
+ return v;
+ }
+
+ return d->query.value(dItem.column());
+}
+
+/*!
+ Returns the header data for the given \a role in the \a section
+ of the header with the specified \a orientation.
+*/
+QVariant QSqlQueryModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ Q_D(const QSqlQueryModel);
+ if (orientation == Qt::Horizontal) {
+ QVariant val = d->headers.value(section).value(role);
+ if (role == Qt::DisplayRole && !val.isValid())
+ val = d->headers.value(section).value(Qt::EditRole);
+ if (val.isValid())
+ return val;
+
+ // See if it's an inserted column (iiq.column() != -1)
+ QModelIndex dItem = indexInQuery(createIndex(0, section));
+
+ if (role == Qt::DisplayRole && d->rec.count() > section && dItem.column() != -1)
+ return d->rec.fieldName(section);
+ }
+ return QAbstractItemModel::headerData(section, orientation, role);
+}
+
+/*!
+ This virtual function is called whenever the query changes. The
+ default implementation does nothing.
+
+ query() returns the new query.
+
+ \sa query(), setQuery()
+ */
+void QSqlQueryModel::queryChange()
+{
+ // do nothing
+}
+
+/*!
+ Resets the model and sets the data provider to be the given \a
+ query. Note that the query must be active and must not be
+ isForwardOnly().
+
+ lastError() can be used to retrieve verbose information if there
+ was an error setting the query.
+
+ \note Calling setQuery() will remove any inserted columns.
+
+ \sa query(), QSqlQuery::isActive(), QSqlQuery::setForwardOnly(), lastError()
+*/
+void QSqlQueryModel::setQuery(const QSqlQuery &query)
+{
+ Q_D(QSqlQueryModel);
+ QSqlRecord newRec = query.record();
+ bool columnsChanged = (newRec != d->rec);
+ bool hasQuerySize = query.driver()->hasFeature(QSqlDriver::QuerySize);
+ bool hasNewData = (newRec != QSqlRecord()) || !query.lastError().isValid();
+
+ if (d->colOffsets.size() != newRec.count() || columnsChanged)
+ d->initColOffsets(newRec.count());
+
+ bool mustClearModel = d->bottom.isValid();
+ if (mustClearModel) {
+ d->atEnd = true;
+ beginRemoveRows(QModelIndex(), 0, qMax(d->bottom.row(), 0));
+ d->bottom = QModelIndex();
+ }
+
+ d->error = QSqlError();
+ d->query = query;
+ d->rec = newRec;
+
+ if (mustClearModel)
+ endRemoveRows();
+
+ d->atEnd = false;
+
+ if (columnsChanged && hasNewData)
+ reset();
+
+ if (!query.isActive() || query.isForwardOnly()) {
+ d->atEnd = true;
+ d->bottom = QModelIndex();
+ if (query.isForwardOnly())
+ d->error = QSqlError(QLatin1String("Forward-only queries "
+ "cannot be used in a data model"),
+ QString(), QSqlError::ConnectionError);
+ else
+ d->error = query.lastError();
+ return;
+ }
+ QModelIndex newBottom;
+ if (hasQuerySize && d->query.size() > 0) {
+ newBottom = createIndex(d->query.size() - 1, d->rec.count() - 1);
+ beginInsertRows(QModelIndex(), 0, qMax(0, newBottom.row()));
+ d->bottom = createIndex(d->query.size() - 1, columnsChanged ? 0 : d->rec.count() - 1);
+ d->atEnd = true;
+ endInsertRows();
+ } else {
+ newBottom = createIndex(-1, d->rec.count() - 1);
+ }
+ d->bottom = newBottom;
+
+ queryChange();
+
+ // fetchMore does the rowsInserted stuff for incremental models
+ fetchMore();
+}
+
+/*! \overload
+
+ Executes the query \a query for the given database connection \a
+ db. If no database (or an invalid database) is specified, the
+ default connection is used.
+
+ lastError() can be used to retrieve verbose information if there
+ was an error setting the query.
+
+ Example:
+ \snippet doc/src/snippets/code/src_sql_models_qsqlquerymodel.cpp 1
+
+ \sa query(), queryChange(), lastError()
+*/
+void QSqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db)
+{
+ setQuery(QSqlQuery(query, db));
+}
+
+/*!
+ Clears the model and releases any acquired resource.
+*/
+void QSqlQueryModel::clear()
+{
+ Q_D(QSqlQueryModel);
+ d->error = QSqlError();
+ d->atEnd = true;
+ d->query.clear();
+ d->rec.clear();
+ d->colOffsets.clear();
+ d->bottom = QModelIndex();
+ d->headers.clear();
+}
+
+/*!
+ Sets the caption for a horizontal header for the specified \a role to
+ \a value. This is useful if the model is used to
+ display data in a view (e.g., QTableView).
+
+ Returns true if \a orientation is Qt::Horizontal and
+ the \a section refers to a valid section; otherwise returns
+ false.
+
+ Note that this function cannot be used to modify values in the
+ database since the model is read-only.
+
+ \sa data()
+ */
+bool QSqlQueryModel::setHeaderData(int section, Qt::Orientation orientation,
+ const QVariant &value, int role)
+{
+ Q_D(QSqlQueryModel);
+ if (orientation != Qt::Horizontal || section < 0 || columnCount() <= section)
+ return false;
+
+ if (d->headers.size() <= section)
+ d->headers.resize(qMax(section + 1, 16));
+ d->headers[section][role] = value;
+ emit headerDataChanged(orientation, section, section);
+ return true;
+}
+
+/*!
+ Returns the QSqlQuery associated with this model.
+
+ \sa setQuery()
+*/
+QSqlQuery QSqlQueryModel::query() const
+{
+ Q_D(const QSqlQueryModel);
+ return d->query;
+}
+
+/*!
+ Returns information about the last error that occurred on the
+ database.
+
+ \sa query()
+*/
+QSqlError QSqlQueryModel::lastError() const
+{
+ Q_D(const QSqlQueryModel);
+ return d->error;
+}
+
+/*!
+ Protected function which allows derived classes to set the value of
+ the last error that occurred on the database to \a error.
+
+ \sa lastError()
+*/
+void QSqlQueryModel::setLastError(const QSqlError &error)
+{
+ Q_D(QSqlQueryModel);
+ d->error = error;
+}
+
+/*!
+ Returns the record containing information about the fields of the
+ current query. If \a row is the index of a valid row, the record
+ will be populated with values from that row.
+
+ If the model is not initialized, an empty record will be
+ returned.
+
+ \sa QSqlRecord::isEmpty()
+*/
+QSqlRecord QSqlQueryModel::record(int row) const
+{
+ Q_D(const QSqlQueryModel);
+ if (row < 0)
+ return d->rec;
+
+ QSqlRecord rec = d->rec;
+ for (int i = 0; i < rec.count(); ++i)
+ rec.setValue(i, data(createIndex(row, i), Qt::EditRole));
+ return rec;
+}
+
+/*! \overload
+
+ Returns an empty record containing information about the fields
+ of the current query.
+
+ If the model is not initialized, an empty record will be
+ returned.
+
+ \sa QSqlRecord::isEmpty()
+ */
+QSqlRecord QSqlQueryModel::record() const
+{
+ Q_D(const QSqlQueryModel);
+ return d->rec;
+}
+
+/*!
+ Inserts \a count columns into the model at position \a column. The
+ \a parent parameter must always be an invalid QModelIndex, since
+ the model does not support parent-child relationships.
+
+ Returns true if \a column is within bounds; otherwise returns false.
+
+ By default, inserted columns are empty. To fill them with data,
+ reimplement data() and handle any inserted column separately:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 23
+
+ \sa removeColumns()
+*/
+bool QSqlQueryModel::insertColumns(int column, int count, const QModelIndex &parent)
+{
+ Q_D(QSqlQueryModel);
+ if (count <= 0 || parent.isValid() || column < 0 || column > d->rec.count())
+ return false;
+
+ beginInsertColumns(parent, column, column + count - 1);
+ for (int c = 0; c < count; ++c) {
+ QSqlField field;
+ field.setReadOnly(true);
+ field.setGenerated(false);
+ d->rec.insert(column, field);
+ if (d->colOffsets.size() < d->rec.count()) {
+ int nVal = d->colOffsets.isEmpty() ? 0 : d->colOffsets[d->colOffsets.size() - 1];
+ d->colOffsets.append(nVal);
+ Q_ASSERT(d->colOffsets.size() >= d->rec.count());
+ }
+ for (int i = column + 1; i < d->colOffsets.count(); ++i)
+ ++d->colOffsets[i];
+ }
+ endInsertColumns();
+ return true;
+}
+
+/*!
+ Removes \a count columns from the model starting from position \a
+ column. The \a parent parameter must always be an invalid
+ QModelIndex, since the model does not support parent-child
+ relationships.
+
+ Removing columns effectively hides them. It does not affect the
+ underlying QSqlQuery.
+
+ Returns true if the columns were removed; otherwise returns false.
+ */
+bool QSqlQueryModel::removeColumns(int column, int count, const QModelIndex &parent)
+{
+ Q_D(QSqlQueryModel);
+ if (count <= 0 || parent.isValid() || column < 0 || column >= d->rec.count())
+ return false;
+
+ beginRemoveColumns(parent, column, column + count - 1);
+
+ int i;
+ for (i = 0; i < count; ++i)
+ d->rec.remove(column);
+ for (i = column; i < d->colOffsets.count(); ++i)
+ d->colOffsets[i] -= count;
+
+ endRemoveColumns();
+ return true;
+}
+
+/*!
+ Returns the index of the value in the database result set for the
+ given \a item in the model.
+
+ The return value is identical to \a item if no columns or rows
+ have been inserted, removed, or moved around.
+
+ Returns an invalid model index if \a item is out of bounds or if
+ \a item does not point to a value in the result set.
+
+ \sa QSqlTableModel::indexInQuery(), insertColumns(), removeColumns()
+*/
+QModelIndex QSqlQueryModel::indexInQuery(const QModelIndex &item) const
+{
+ Q_D(const QSqlQueryModel);
+ if (item.column() < 0 || item.column() >= d->rec.count()
+ || !d->rec.isGenerated(item.column()))
+ return QModelIndex();
+ return createIndex(item.row(), item.column() - d->colOffsets[item.column()],
+ item.internalPointer());
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/models/qsqlquerymodel.h b/src/sql/models/qsqlquerymodel.h
new file mode 100644
index 0000000000..b4acfa114f
--- /dev/null
+++ b/src/sql/models/qsqlquerymodel.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLQUERYMODEL_H
+#define QSQLQUERYMODEL_H
+
+#include <QtCore/qabstractitemmodel.h>
+#include <QtSql/qsqldatabase.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QSqlQueryModelPrivate;
+class QSqlError;
+class QSqlRecord;
+class QSqlQuery;
+
+class Q_SQL_EXPORT QSqlQueryModel: public QAbstractTableModel
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QSqlQueryModel)
+
+public:
+ explicit QSqlQueryModel(QObject *parent = 0);
+ virtual ~QSqlQueryModel();
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QSqlRecord record(int row) const;
+ QSqlRecord record() const;
+
+ QVariant data(const QModelIndex &item, int role = Qt::DisplayRole) const;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value,
+ int role = Qt::EditRole);
+
+ bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex());
+ bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex());
+
+ void setQuery(const QSqlQuery &query);
+ void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase());
+ QSqlQuery query() const;
+
+ virtual void clear();
+
+ QSqlError lastError() const;
+
+ void fetchMore(const QModelIndex &parent = QModelIndex());
+ bool canFetchMore(const QModelIndex &parent = QModelIndex()) const;
+
+protected:
+ virtual void queryChange();
+
+ QModelIndex indexInQuery(const QModelIndex &item) const;
+ void setLastError(const QSqlError &error);
+ QSqlQueryModel(QSqlQueryModelPrivate &dd, QObject *parent = 0);
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLQUERYMODEL_H
diff --git a/src/sql/models/qsqlquerymodel_p.h b/src/sql/models/qsqlquerymodel_p.h
new file mode 100644
index 0000000000..f77a0c94a5
--- /dev/null
+++ b/src/sql/models/qsqlquerymodel_p.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLQUERYMODEL_P_H
+#define QSQLQUERYMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of qsql*model.h . This header file may change from version to version
+// without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "private/qabstractitemmodel_p.h"
+#include "QtSql/qsqlerror.h"
+#include "QtSql/qsqlquery.h"
+#include "QtSql/qsqlrecord.h"
+#include "QtCore/qhash.h"
+#include "QtCore/qvarlengtharray.h"
+#include "QtCore/qvector.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSqlQueryModelPrivate: public QAbstractItemModelPrivate
+{
+ Q_DECLARE_PUBLIC(QSqlQueryModel)
+public:
+ QSqlQueryModelPrivate() : atEnd(false) {}
+ ~QSqlQueryModelPrivate();
+
+ void prefetch(int);
+ void initColOffsets(int size);
+
+ mutable QSqlQuery query;
+ mutable QSqlError error;
+ QModelIndex bottom;
+ QSqlRecord rec;
+ uint atEnd : 1;
+ QVector<QHash<int, QVariant> > headers;
+ QVarLengthArray<int, 56> colOffsets; // used to calculate indexInQuery of columns
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQLQUERYMODEL_P_H
diff --git a/src/sql/models/qsqlrelationaldelegate.cpp b/src/sql/models/qsqlrelationaldelegate.cpp
new file mode 100644
index 0000000000..4663ddbc65
--- /dev/null
+++ b/src/sql/models/qsqlrelationaldelegate.cpp
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qglobal.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QSqlRelationalDelegate
+ \brief The QSqlRelationalDelegate class provides a delegate that is used to
+ display and edit data from a QSqlRelationalTableModel.
+
+ Unlike the default delegate, QSqlRelationalDelegate provides a
+ combobox for fields that are foreign keys into other tables. To
+ use the class, simply call QAbstractItemView::setItemDelegate()
+ on the view with an instance of QSqlRelationalDelegate:
+
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 4
+
+ The \l{sql/relationaltablemodel}{Relational Table Model} example
+ (shown below) illustrates how to use QSqlRelationalDelegate in
+ conjunction with QSqlRelationalTableModel to provide tables with
+ foreign key support.
+
+ \image relationaltable.png
+
+ \sa QSqlRelationalTableModel, {Model/View Programming}
+*/
+
+
+/*!
+ \fn QSqlRelationalDelegate::QSqlRelationalDelegate(QObject *parent)
+
+ Constructs a QSqlRelationalDelegate object with the given \a
+ parent.
+*/
+
+/*!
+ \fn QSqlRelationalDelegate::~QSqlRelationalDelegate()
+
+ Destroys the QSqlRelationalDelegate object and frees any
+ allocated resources.
+*/
+
+/*!
+ \fn QWidget *QSqlRelationalDelegate::createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+ \reimp
+*/
+
+/*!
+ \fn void QSqlRelationalDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+ \reimp
+*/
+
+/*!
+ \fn void QSqlRelationalDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const
+ \reimp
+*/
+
+QT_END_NAMESPACE
diff --git a/src/sql/models/qsqlrelationaldelegate.h b/src/sql/models/qsqlrelationaldelegate.h
new file mode 100644
index 0000000000..f4f4bc5136
--- /dev/null
+++ b/src/sql/models/qsqlrelationaldelegate.h
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLRELATIONALDELEGATE_H
+#define QSQLRELATIONALDELEGATE_H
+
+#ifdef QT_GUI_LIB
+
+#include <QtGui/qitemdelegate.h>
+#include <QtGui/qlistview.h>
+#include <QtGui/qcombobox.h>
+#include <QtSql/qsqlrelationaltablemodel.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QSqlRelationalDelegate: public QItemDelegate
+{
+public:
+
+explicit QSqlRelationalDelegate(QObject *aParent = 0)
+ : QItemDelegate(aParent)
+{}
+
+~QSqlRelationalDelegate()
+{}
+
+QWidget *createEditor(QWidget *aParent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ const QSqlRelationalTableModel *sqlModel = qobject_cast<const QSqlRelationalTableModel *>(index.model());
+ QSqlTableModel *childModel = sqlModel ? sqlModel->relationModel(index.column()) : 0;
+ if (!childModel)
+ return QItemDelegate::createEditor(aParent, option, index);
+
+ QComboBox *combo = new QComboBox(aParent);
+ combo->setModel(childModel);
+ combo->setModelColumn(childModel->fieldIndex(sqlModel->relation(index.column()).displayColumn()));
+ combo->installEventFilter(const_cast<QSqlRelationalDelegate *>(this));
+
+ return combo;
+}
+
+void setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+ const QSqlRelationalTableModel *sqlModel = qobject_cast<const QSqlRelationalTableModel *>(index.model());
+ QComboBox *combo = qobject_cast<QComboBox *>(editor);
+ if (!sqlModel || !combo) {
+ QItemDelegate::setEditorData(editor, index);
+ return;
+ }
+ combo->setCurrentIndex(combo->findText(sqlModel->data(index).toString()));
+}
+
+void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return;
+
+ QSqlRelationalTableModel *sqlModel = qobject_cast<QSqlRelationalTableModel *>(model);
+ QSqlTableModel *childModel = sqlModel ? sqlModel->relationModel(index.column()) : 0;
+ QComboBox *combo = qobject_cast<QComboBox *>(editor);
+ if (!sqlModel || !childModel || !combo) {
+ QItemDelegate::setModelData(editor, model, index);
+ return;
+ }
+
+ int currentItem = combo->currentIndex();
+ int childColIndex = childModel->fieldIndex(sqlModel->relation(index.column()).displayColumn());
+ int childEditIndex = childModel->fieldIndex(sqlModel->relation(index.column()).indexColumn());
+ sqlModel->setData(index,
+ childModel->data(childModel->index(currentItem, childColIndex), Qt::DisplayRole),
+ Qt::DisplayRole);
+ sqlModel->setData(index,
+ childModel->data(childModel->index(currentItem, childEditIndex), Qt::EditRole),
+ Qt::EditRole);
+}
+
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QT_GUI_LIB
+
+#endif // QSQLRELATIONALDELEGATE_H
diff --git a/src/sql/models/qsqlrelationaltablemodel.cpp b/src/sql/models/qsqlrelationaltablemodel.cpp
new file mode 100644
index 0000000000..63633e6ed9
--- /dev/null
+++ b/src/sql/models/qsqlrelationaltablemodel.cpp
@@ -0,0 +1,753 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqlrelationaltablemodel.h"
+
+#include "qhash.h"
+#include "qstringlist.h"
+#include "qsqldatabase.h"
+#include "qsqldriver.h"
+#include "qsqlerror.h"
+#include "qsqlfield.h"
+#include "qsqlindex.h"
+#include "qsqlquery.h"
+#include "qsqlrecord.h"
+
+#include "qsqltablemodel_p.h"
+
+#include "qdebug.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QSqlRelation
+ \brief The QSqlRelation class stores information about an SQL foreign key.
+
+ QSqlRelation is a helper class for QSqlRelationalTableModel. See
+ QSqlRelationalTableModel::setRelation() and
+ QSqlRelationalTableModel::relation() for details.
+
+ \sa QSqlRelationalTableModel, QSqlRelationalDelegate,
+ {Relational Table Model Example}
+*/
+
+/*!
+ \fn QSqlRelation::QSqlRelation()
+
+ Constructs an invalid QSqlRelation object.
+
+ For such an object, the tableName(), indexColumn(), and
+ displayColumn() functions return an empty string.
+
+ \sa isValid()
+*/
+
+/*!
+ \fn QSqlRelation::QSqlRelation(const QString &tableName, const QString &indexColumn,
+ const QString &displayColumn)
+
+ Constructs a QSqlRelation object, where \a tableName is the SQL
+ table name to which a foreign key refers, \a indexColumn is the
+ foreign key, and \a displayColumn is the field that should be
+ presented to the user.
+
+ \sa tableName(), indexColumn(), displayColumn()
+*/
+
+/*!
+ \fn QString QSqlRelation::tableName() const
+
+ Returns the name of the table to which a foreign key refers.
+*/
+
+/*!
+ \fn QString QSqlRelation::indexColumn() const
+
+ Returns the index column from table tableName() to which a
+ foreign key refers.
+*/
+
+/*!
+ \fn QString QSqlRelation::displayColumn() const
+
+ Returns the column from table tableName() that should be
+ presented to the user instead of a foreign key.
+*/
+
+/*!
+ \fn bool QSqlRelation::isValid() const
+
+ Returns true if the QSqlRelation object is valid; otherwise
+ returns false.
+*/
+
+struct QRelation
+{
+ public:
+ QRelation(): model(0),m_parent(0),m_dictInitialized(false){}
+ void init(QSqlRelationalTableModel *parent, const QSqlRelation &relation);
+
+ void populateModel();
+
+ bool isDictionaryInitialized();
+ void populateDictionary();
+ void clearDictionary();
+
+ void clear();
+ bool isValid();
+
+ QSqlRelation rel;
+ QSqlTableModel *model;
+ QHash<QString, QVariant> dictionary;//maps keys to display values
+
+ private:
+ QSqlRelationalTableModel *m_parent;
+ bool m_dictInitialized;
+};
+
+/*
+ A QRelation must be initialized before it is considered valid.
+ Note: population of the model and dictionary are kept separate
+ from initialization, and are populated on an as needed basis.
+*/
+void QRelation::init(QSqlRelationalTableModel *parent, const QSqlRelation &relation)
+{
+ Q_ASSERT(parent != NULL);
+ m_parent = parent;
+ rel = relation;
+}
+
+void QRelation::populateModel()
+{
+ if (!isValid())
+ return;
+ Q_ASSERT(m_parent != NULL);
+
+ if (!model) {
+ model = new QSqlTableModel(m_parent, m_parent->database());
+ model->setTable(rel.tableName());
+ model->select();
+ }
+}
+
+bool QRelation::isDictionaryInitialized()
+{
+ return m_dictInitialized;
+}
+
+void QRelation::populateDictionary()
+{
+ if (!isValid())
+ return;
+
+ if (model == NULL)
+ populateModel();
+
+ QSqlRecord record;
+ QString indexColumn;
+ QString displayColumn;
+ for (int i=0; i < model->rowCount(); ++i) {
+ record = model->record(i);
+
+ indexColumn = rel.indexColumn();
+ if (m_parent->database().driver()->isIdentifierEscaped(indexColumn, QSqlDriver::FieldName))
+ indexColumn = m_parent->database().driver()->stripDelimiters(indexColumn, QSqlDriver::FieldName);
+
+ displayColumn = rel.displayColumn();
+ if (m_parent->database().driver()->isIdentifierEscaped(displayColumn, QSqlDriver::FieldName))
+ displayColumn = m_parent->database().driver()->stripDelimiters(displayColumn, QSqlDriver::FieldName);
+
+ dictionary[record.field(indexColumn).value().toString()] =
+ record.field(displayColumn).value();
+ }
+ m_dictInitialized = true;
+}
+
+void QRelation::clearDictionary()
+{
+ dictionary.clear();
+ m_dictInitialized = false;
+}
+
+void QRelation::clear()
+{
+ delete model;
+ model = 0;
+ clearDictionary();
+}
+
+bool QRelation::isValid()
+{
+ return (rel.isValid() && m_parent != NULL);
+}
+
+class QSqlRelationalTableModelPrivate: public QSqlTableModelPrivate
+{
+ Q_DECLARE_PUBLIC(QSqlRelationalTableModel)
+public:
+ QSqlRelationalTableModelPrivate()
+ : QSqlTableModelPrivate()
+ {}
+ QString relationField(const QString &tableName, const QString &fieldName) const;
+
+ int nameToIndex(const QString &name) const;
+ mutable QVector<QRelation> relations;
+ QSqlRecord baseRec; // the record without relations
+ void clearChanges();
+ void clearEditBuffer();
+ void clearCache();
+ void revertCachedRow(int row);
+
+ void translateFieldNames(int row, QSqlRecord &values) const;
+};
+
+static void qAppendWhereClause(QString &query, const QString &clause1, const QString &clause2)
+{
+ if (clause1.isEmpty() && clause2.isEmpty())
+ return;
+ if (clause1.isEmpty() || clause2.isEmpty())
+ query.append(QLatin1String(" WHERE (")).append(clause1).append(clause2);
+ else
+ query.append(QLatin1String(" WHERE (")).append(clause1).append(
+ QLatin1String(") AND (")).append(clause2);
+ query.append(QLatin1String(") "));
+}
+
+void QSqlRelationalTableModelPrivate::clearChanges()
+{
+ for (int i = 0; i < relations.count(); ++i) {
+ QRelation &rel = relations[i];
+ rel.clear();
+ }
+}
+
+void QSqlRelationalTableModelPrivate::revertCachedRow(int row)
+{
+ QSqlTableModelPrivate::revertCachedRow(row);
+}
+
+int QSqlRelationalTableModelPrivate::nameToIndex(const QString &name) const
+{
+ QString fieldname = name;
+ if (db.driver()->isIdentifierEscaped(fieldname, QSqlDriver::FieldName))
+ fieldname = db.driver()->stripDelimiters(fieldname, QSqlDriver::FieldName);
+ return baseRec.indexOf(fieldname);
+}
+
+void QSqlRelationalTableModelPrivate::clearEditBuffer()
+{
+ editBuffer = baseRec;
+ clearGenerated(editBuffer);
+}
+
+/*!
+ \reimp
+*/
+void QSqlRelationalTableModelPrivate::clearCache()
+{
+ for (int i = 0; i < relations.count(); ++i)
+ relations[i].clearDictionary();
+
+ QSqlTableModelPrivate::clearCache();
+}
+
+/*!
+ \class QSqlRelationalTableModel
+ \brief The QSqlRelationalTableModel class provides an editable
+ data model for a single database table, with foreign key support.
+
+ \ingroup database
+ \inmodule QtSql
+
+ QSqlRelationalTableModel acts like QSqlTableModel, but allows
+ columns to be set as foreign keys into other database tables.
+
+ \table
+ \row \o \inlineimage noforeignkeys.png
+ \o \inlineimage foreignkeys.png
+ \endtable
+
+ The screenshot on the left shows a plain QSqlTableModel in a
+ QTableView. Foreign keys (\c city and \c country) aren't resolved
+ to human-readable values. The screenshot on the right shows a
+ QSqlRelationalTableModel, with foreign keys resolved into
+ human-readable text strings.
+
+ The following code snippet shows how the QSqlRelationalTableModel
+ was set up:
+
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 0
+ \codeline
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 1
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 2
+
+ The setRelation() function calls establish a relationship between
+ two tables. The first call specifies that column 2 in table \c
+ employee is a foreign key that maps with field \c id of table \c
+ city, and that the view should present the \c{city}'s \c name
+ field to the user. The second call does something similar with
+ column 3.
+
+ If you use a read-write QSqlRelationalTableModel, you probably
+ want to use QSqlRelationalDelegate on the view. Unlike the default
+ delegate, QSqlRelationalDelegate provides a combobox for fields
+ that are foreign keys into other tables. To use the class, simply
+ call QAbstractItemView::setItemDelegate() on the view with an
+ instance of QSqlRelationalDelegate:
+
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 4
+
+ The \l{sql/relationaltablemodel} example illustrates how to use
+ QSqlRelationalTableModel in conjunction with
+ QSqlRelationalDelegate to provide tables with foreigh key
+ support.
+
+ \image relationaltable.png
+
+ Notes:
+
+ \list
+ \o The table must have a primary key declared.
+ \o The table's primary key may not contain a relation to
+ another table.
+ \o If a relational table contains keys that refer to non-existent
+ rows in the referenced table, the rows containing the invalid
+ keys will not be exposed through the model. The user or the
+ database is responsible for keeping referential integrity.
+ \o If a relation's display column name is also used as a column
+ name in the main table, or if it is used as display column
+ name in more than one relation it will be aliased. The alias is
+ is the relation's table name and display column name joined
+ by an underscore (e.g. tablename_columnname). All occurrences
+ of the duplicate display column name are aliased when
+ duplication is detected, but no aliasing is done to the column
+ names in the main table. The aliasing doesn't affect
+ QSqlRelation, so QSqlRelation::displayColumn() will return the
+ original display column name, but QSqlRecord::fieldName() will
+ return aliases.
+ \o When using setData() the role should always be Qt::EditRole,
+ and when using data() the role should always be Qt::DisplayRole.
+ \endlist
+
+ \sa QSqlRelation, QSqlRelationalDelegate,
+ {Relational Table Model Example}
+*/
+
+
+/*!
+ Creates an empty QSqlRelationalTableModel and sets the parent to \a parent
+ and the database connection to \a db. If \a db is not valid, the
+ default database connection will be used.
+*/
+QSqlRelationalTableModel::QSqlRelationalTableModel(QObject *parent, QSqlDatabase db)
+ : QSqlTableModel(*new QSqlRelationalTableModelPrivate, parent, db)
+{
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+QSqlRelationalTableModel::~QSqlRelationalTableModel()
+{
+}
+
+/*!
+ \reimp
+*/
+QVariant QSqlRelationalTableModel::data(const QModelIndex &index, int role) const
+{
+ Q_D(const QSqlRelationalTableModel);
+
+ if (role == Qt::DisplayRole && index.column() > 0 && index.column() < d->relations.count() &&
+ d->relations.value(index.column()).isValid()) {
+ QRelation &relation = d->relations[index.column()];
+ if (!relation.isDictionaryInitialized())
+ relation.populateDictionary();
+
+ //only perform a dictionary lookup for the display value
+ //when the value at index has been changed or added.
+ //At an unmodified index, the underlying model will
+ //already have the correct display value.
+ QVariant v;
+ switch (d->strategy) {
+ case OnFieldChange:
+ break;
+ case OnRowChange:
+ if ((index.row() == d->editIndex || index.row() == d->insertIndex)
+ && d->editBuffer.isGenerated(index.column()))
+ v = d->editBuffer.value(index.column());
+ break;
+ case OnManualSubmit:
+ const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row());
+ if (row.op != QSqlTableModelPrivate::None && row.rec.isGenerated(index.column()))
+ v = row.rec.value(index.column());
+ break;
+ }
+ if (v.isValid())
+ return relation.dictionary[v.toString()];
+ }
+ return QSqlTableModel::data(index, role);
+}
+
+/*!
+ Sets the data for the \a role in the item with the specified \a
+ index to the \a value given. Depending on the edit strategy, the
+ value might be applied to the database at once, or it may be
+ cached in the model.
+
+ Returns true if the value could be set, or false on error (for
+ example, if \a index is out of bounds).
+
+ For relational columns, \a value must be the index, not the
+ display value. The index must also exist in the referenced
+ table, otherwise the function returns false.
+
+ \sa editStrategy(), data(), submit(), revertRow()
+*/
+bool QSqlRelationalTableModel::setData(const QModelIndex &index, const QVariant &value,
+ int role)
+{
+ Q_D(QSqlRelationalTableModel);
+ if ( role == Qt::EditRole && index.column() > 0 && index.column() < d->relations.count()
+ && d->relations.value(index.column()).isValid()) {
+ QRelation &relation = d->relations[index.column()];
+ if (!relation.isDictionaryInitialized())
+ relation.populateDictionary();
+ if (!relation.dictionary.contains(value.toString()))
+ return false;
+ }
+ return QSqlTableModel::setData(index, value, role);
+}
+
+/*!
+ Lets the specified \a column be a foreign index specified by \a relation.
+
+ Example:
+
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 0
+ \codeline
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 1
+
+ The setRelation() call specifies that column 2 in table \c
+ employee is a foreign key that maps with field \c id of table \c
+ city, and that the view should present the \c{city}'s \c name
+ field to the user.
+
+ Note: The table's primary key may not contain a relation to another table.
+
+ \sa relation()
+*/
+void QSqlRelationalTableModel::setRelation(int column, const QSqlRelation &relation)
+{
+ Q_D(QSqlRelationalTableModel);
+ if (column < 0)
+ return;
+ if (d->relations.size() <= column)
+ d->relations.resize(column + 1);
+ d->relations[column].init(this, relation);
+}
+
+/*!
+ Returns the relation for the column \a column, or an invalid
+ relation if no relation is set.
+
+ \sa setRelation(), QSqlRelation::isValid()
+*/
+QSqlRelation QSqlRelationalTableModel::relation(int column) const
+{
+ Q_D(const QSqlRelationalTableModel);
+ return d->relations.value(column).rel;
+}
+
+QString QSqlRelationalTableModelPrivate::relationField(const QString &tableName,
+ const QString &fieldName) const
+{
+ QString ret;
+ ret.reserve(tableName.size() + fieldName.size() + 1);
+ ret.append(tableName).append(QLatin1Char('.')).append(fieldName);
+
+ return ret;
+}
+
+/*!
+ \reimp
+*/
+QString QSqlRelationalTableModel::selectStatement() const
+{
+ Q_D(const QSqlRelationalTableModel);
+ QString query;
+
+ if (tableName().isEmpty())
+ return query;
+ if (d->relations.isEmpty())
+ return QSqlTableModel::selectStatement();
+
+ QString tList;
+ QString fList;
+ QString where;
+
+ QSqlRecord rec = d->baseRec;
+ QStringList tables;
+ const QRelation nullRelation;
+
+ // Count how many times each field name occurs in the record
+ QHash<QString, int> fieldNames;
+ QStringList fieldList;
+ for (int i = 0; i < rec.count(); ++i) {
+ QSqlRelation relation = d->relations.value(i, nullRelation).rel;
+ QString name;
+ if (relation.isValid())
+ {
+ // Count the display column name, not the original foreign key
+ name = relation.displayColumn();
+ if (d->db.driver()->isIdentifierEscaped(name, QSqlDriver::FieldName))
+ name = d->db.driver()->stripDelimiters(name, QSqlDriver::FieldName);
+
+ QSqlRecord rec = database().record(relation.tableName());
+ for (int i = 0; i < rec.count(); ++i) {
+ if (name.compare(rec.fieldName(i), Qt::CaseInsensitive) == 0) {
+ name = rec.fieldName(i);
+ break;
+ }
+ }
+ }
+ else
+ name = rec.fieldName(i);
+ fieldNames.insert(name, fieldNames.value(name, 0) + 1);
+ fieldList.append(name);
+ }
+
+ for (int i = 0; i < rec.count(); ++i) {
+ QSqlRelation relation = d->relations.value(i, nullRelation).rel;
+ if (relation.isValid()) {
+ QString relTableAlias = QString::fromLatin1("relTblAl_%1").arg(i);
+ if (!fList.isEmpty())
+ fList.append(QLatin1String(", "));
+ fList.append(d->relationField(relTableAlias,relation.displayColumn()));
+
+ // If there are duplicate field names they must be aliased
+ if (fieldNames.value(fieldList[i]) > 1) {
+ QString relTableName = relation.tableName().section(QChar::fromLatin1('.'), -1, -1);
+ if (d->db.driver()->isIdentifierEscaped(relTableName, QSqlDriver::TableName))
+ relTableName = d->db.driver()->stripDelimiters(relTableName, QSqlDriver::TableName);
+ QString displayColumn = relation.displayColumn();
+ if (d->db.driver()->isIdentifierEscaped(displayColumn, QSqlDriver::FieldName))
+ displayColumn = d->db.driver()->stripDelimiters(displayColumn, QSqlDriver::FieldName);
+ fList.append(QString::fromLatin1(" AS %1_%2_%3").arg(relTableName).arg(displayColumn).arg(fieldNames.value(fieldList[i])));
+ fieldNames.insert(fieldList[i], fieldNames.value(fieldList[i])-1);
+ }
+
+ // this needs fixing!! the below if is borken.
+ tables.append(relation.tableName().append(QLatin1Char(' ')).append(relTableAlias));
+ if(!where.isEmpty())
+ where.append(QLatin1String(" AND "));
+ where.append(d->relationField(tableName(), d->db.driver()->escapeIdentifier(rec.fieldName(i), QSqlDriver::FieldName)));
+ where.append(QLatin1String(" = "));
+ where.append(d->relationField(relTableAlias, relation.indexColumn()));
+ } else {
+ if (!fList.isEmpty())
+ fList.append(QLatin1String(", "));
+ fList.append(d->relationField(tableName(), d->db.driver()->escapeIdentifier(rec.fieldName(i), QSqlDriver::FieldName)));
+ }
+ }
+ if (!tables.isEmpty())
+ tList.append(tables.join(QLatin1String(", ")));
+ if (fList.isEmpty())
+ return query;
+ if(!tList.isEmpty())
+ tList.prepend(QLatin1String(", "));
+ tList.prepend(tableName());
+ query.append(QLatin1String("SELECT "));
+ query.append(fList).append(QLatin1String(" FROM ")).append(tList);
+ qAppendWhereClause(query, where, filter());
+
+ QString orderBy = orderByClause();
+ if (!orderBy.isEmpty())
+ query.append(QLatin1Char(' ')).append(orderBy);
+
+ return query;
+}
+
+/*!
+ Returns a QSqlTableModel object for accessing the table for which
+ \a column is a foreign key, or 0 if there is no relation for the
+ given \a column.
+
+ The returned object is owned by the QSqlRelationalTableModel.
+
+ \sa setRelation(), relation()
+*/
+QSqlTableModel *QSqlRelationalTableModel::relationModel(int column) const
+{
+ Q_D(const QSqlRelationalTableModel);
+ if ( column < 0 || column >= d->relations.count())
+ return 0;
+
+ QRelation &relation = const_cast<QSqlRelationalTableModelPrivate *>(d)->relations[column];
+ if (!relation.isValid())
+ return 0;
+
+ if (!relation.model)
+ relation.populateModel();
+ return relation.model;
+}
+
+/*!
+ \reimp
+*/
+void QSqlRelationalTableModel::revertRow(int row)
+{
+ QSqlTableModel::revertRow(row);
+}
+
+/*!
+ \reimp
+*/
+void QSqlRelationalTableModel::clear()
+{
+ Q_D(QSqlRelationalTableModel);
+ d->clearChanges();
+ d->relations.clear();
+ QSqlTableModel::clear();
+}
+
+/*!
+ \reimp
+*/
+bool QSqlRelationalTableModel::select()
+{
+ return QSqlTableModel::select();
+}
+
+/*!
+ \reimp
+*/
+void QSqlRelationalTableModel::setTable(const QString &table)
+{
+ Q_D(QSqlRelationalTableModel);
+
+ // memorize the table before applying the relations
+ d->baseRec = d->db.record(table);
+
+ QSqlTableModel::setTable(table);
+}
+
+/*! \internal
+ */
+void QSqlRelationalTableModelPrivate::translateFieldNames(int row, QSqlRecord &values) const
+{
+ Q_Q(const QSqlRelationalTableModel);
+
+ for (int i = 0; i < values.count(); ++i) {
+ int realCol = q->indexInQuery(q->createIndex(row, i)).column();
+ if (realCol != -1 && relations.value(realCol).isValid()) {
+ QVariant v = values.value(i);
+ bool gen = values.isGenerated(i);
+ values.replace(i, baseRec.field(realCol));
+ values.setValue(i, v);
+ values.setGenerated(i, gen);
+ }
+ }
+}
+
+/*!
+ \reimp
+*/
+bool QSqlRelationalTableModel::updateRowInTable(int row, const QSqlRecord &values)
+{
+ Q_D(QSqlRelationalTableModel);
+
+ QSqlRecord rec = values;
+ d->translateFieldNames(row, rec);
+
+ return QSqlTableModel::updateRowInTable(row, rec);
+}
+
+/*!
+ \reimp
+*/
+bool QSqlRelationalTableModel::insertRowIntoTable(const QSqlRecord &values)
+{
+ Q_D(QSqlRelationalTableModel);
+
+ QSqlRecord rec = values;
+ d->translateFieldNames(0, rec);
+
+ return QSqlTableModel::insertRowIntoTable(rec);
+}
+
+/*!
+ \reimp
+*/
+QString QSqlRelationalTableModel::orderByClause() const
+{
+ Q_D(const QSqlRelationalTableModel);
+
+ const QSqlRelation rel = d->relations.value(d->sortColumn).rel;
+ if (!rel.isValid())
+ return QSqlTableModel::orderByClause();
+
+ QString s = QLatin1String("ORDER BY ");
+ s.append(d->relationField(QLatin1String("relTblAl_") + QString::number(d->sortColumn),
+ rel.displayColumn()));
+ s += d->sortOrder == Qt::AscendingOrder ? QLatin1String(" ASC") : QLatin1String(" DESC");
+ return s;
+}
+
+/*!
+ \reimp
+*/
+bool QSqlRelationalTableModel::removeColumns(int column, int count, const QModelIndex &parent)
+{
+ Q_D(QSqlRelationalTableModel);
+
+ if (parent.isValid() || column < 0 || column + count > d->rec.count())
+ return false;
+
+ for (int i = 0; i < count; ++i) {
+ d->baseRec.remove(column);
+ if (d->relations.count() > column)
+ d->relations.remove(column);
+ }
+ return QSqlTableModel::removeColumns(column, count, parent);
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/models/qsqlrelationaltablemodel.h b/src/sql/models/qsqlrelationaltablemodel.h
new file mode 100644
index 0000000000..58384821a1
--- /dev/null
+++ b/src/sql/models/qsqlrelationaltablemodel.h
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLRELATIONALTABLEMODEL_H
+#define QSQLRELATIONALTABLEMODEL_H
+
+#include <QtSql/qsqltablemodel.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class Q_SQL_EXPORT QSqlRelation
+{
+public:
+ QSqlRelation() {}
+ QSqlRelation(const QString &aTableName, const QString &indexCol,
+ const QString &displayCol)
+ : tName(aTableName), iColumn(indexCol), dColumn(displayCol) {}
+ inline QString tableName() const
+ { return tName; }
+ inline QString indexColumn() const
+ { return iColumn; }
+ inline QString displayColumn() const
+ { return dColumn; }
+ inline bool isValid() const
+ { return !(tName.isEmpty() || iColumn.isEmpty() || dColumn.isEmpty()); }
+private:
+ QString tName, iColumn, dColumn;
+};
+
+class QSqlRelationalTableModelPrivate;
+
+class Q_SQL_EXPORT QSqlRelationalTableModel: public QSqlTableModel
+{
+ Q_OBJECT
+
+public:
+ explicit QSqlRelationalTableModel(QObject *parent = 0,
+ QSqlDatabase db = QSqlDatabase());
+ virtual ~QSqlRelationalTableModel();
+
+ QVariant data(const QModelIndex &item, int role = Qt::DisplayRole) const;
+ bool setData(const QModelIndex &item, const QVariant &value, int role = Qt::EditRole);
+ bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex());
+
+ void clear();
+ bool select();
+
+ void setTable(const QString &tableName);
+ virtual void setRelation(int column, const QSqlRelation &relation);
+ QSqlRelation relation(int column) const;
+ virtual QSqlTableModel *relationModel(int column) const;
+
+public Q_SLOTS:
+ void revertRow(int row);
+
+protected:
+ QString selectStatement() const;
+ bool updateRowInTable(int row, const QSqlRecord &values);
+ bool insertRowIntoTable(const QSqlRecord &values);
+ QString orderByClause() const;
+
+private:
+ Q_DECLARE_PRIVATE(QSqlRelationalTableModel)
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLRELATIONALTABLEMODEL_H
diff --git a/src/sql/models/qsqltablemodel.cpp b/src/sql/models/qsqltablemodel.cpp
new file mode 100644
index 0000000000..99b516a05c
--- /dev/null
+++ b/src/sql/models/qsqltablemodel.cpp
@@ -0,0 +1,1366 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqltablemodel.h"
+
+#include "qsqldriver.h"
+#include "qsqlerror.h"
+#include "qsqlfield.h"
+#include "qsqlindex.h"
+#include "qsqlquery.h"
+#include "qsqlrecord.h"
+#include "qsqlresult.h"
+
+#include "qsqltablemodel_p.h"
+
+#include <qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+/*! \internal
+ Populates our record with values.
+*/
+QSqlRecord QSqlTableModelPrivate::record(const QVector<QVariant> &values) const
+{
+ QSqlRecord r = rec;
+ for (int i = 0; i < r.count() && i < values.count(); ++i)
+ r.setValue(i, values.at(i));
+ return r;
+}
+
+/*! \internal
+ Set a record for OnFieldChange and OnRowChange.
+*/
+bool QSqlTableModelPrivate::setRecord(int row, const QSqlRecord &record)
+{
+ Q_Q(QSqlTableModel);
+ bool isOk = true;
+
+ QSqlTableModel::EditStrategy oldStrategy = strategy;
+
+ // FieldChange strategy makes no sense when setting an entire row
+ if (strategy == QSqlTableModel::OnFieldChange)
+ strategy = QSqlTableModel::OnRowChange;
+ for (int i = 0; i < record.count(); ++i) {
+ int idx = nameToIndex(record.fieldName(i));
+ if (idx == -1)
+ continue;
+ QModelIndex cIndex = q->createIndex(row, idx);
+ QVariant value = record.value(i);
+ QVariant oldValue = q->data(cIndex);
+ if (oldValue.isNull() || oldValue != value)
+ isOk &= q->setData(cIndex, value, Qt::EditRole);
+ }
+ if (isOk && oldStrategy == QSqlTableModel::OnFieldChange)
+ q->submitAll();
+ strategy = oldStrategy;
+
+ return isOk;
+}
+
+int QSqlTableModelPrivate::nameToIndex(const QString &name) const
+{
+ QString fieldname = name;
+ if (db.driver()->isIdentifierEscaped(fieldname, QSqlDriver::FieldName))
+ fieldname = db.driver()->stripDelimiters(fieldname, QSqlDriver::FieldName);
+ return rec.indexOf(fieldname);
+}
+
+void QSqlTableModelPrivate::initRecordAndPrimaryIndex()
+{
+ rec = db.record(tableName);
+ primaryIndex = db.primaryIndex(tableName);
+}
+
+void QSqlTableModelPrivate::clear()
+{
+ editIndex = -1;
+ sortColumn = -1;
+ sortOrder = Qt::AscendingOrder;
+ tableName.clear();
+ editQuery.clear();
+ editBuffer.clear();
+ cache.clear();
+ primaryIndex.clear();
+ rec.clear();
+ filter.clear();
+}
+
+void QSqlTableModelPrivate::revertInsertedRow()
+{
+ Q_Q(QSqlTableModel);
+ if (insertIndex == -1)
+ return;
+
+ q->beginRemoveRows(QModelIndex(), insertIndex, insertIndex);
+ insertIndex = -1;
+ q->endRemoveRows();
+}
+
+void QSqlTableModelPrivate::clearEditBuffer()
+{
+ editBuffer = rec;
+ clearGenerated(editBuffer);
+}
+
+void QSqlTableModelPrivate::clearCache()
+{
+ cache.clear();
+}
+
+void QSqlTableModelPrivate::clearGenerated(QSqlRecord &rec)
+{
+ for (int i = rec.count() - 1; i >= 0; i--)
+ rec.setGenerated(i, false);
+}
+
+void QSqlTableModelPrivate::setGeneratedValue(QSqlRecord &rec, int c, QVariant v)
+{
+ rec.setValue(c, v);
+ rec.setGenerated(c, true);
+}
+
+void QSqlTableModelPrivate::revertCachedRow(int row)
+{
+ Q_Q(QSqlTableModel);
+ ModifiedRow r = cache.value(row);
+ switch (r.op) {
+ case QSqlTableModelPrivate::None:
+ Q_ASSERT_X(false, "QSqlTableModelPrivate::revertCachedRow()", "Invalid entry in cache map");
+ return;
+ case QSqlTableModelPrivate::Update:
+ case QSqlTableModelPrivate::Delete:
+ cache.remove(row);
+ emit q->dataChanged(q->createIndex(row, 0),
+ q->createIndex(row, q->columnCount() - 1));
+ break;
+ case QSqlTableModelPrivate::Insert: {
+ QMap<int, QSqlTableModelPrivate::ModifiedRow>::Iterator it = cache.find(row);
+ if (it == cache.end())
+ return;
+ q->beginRemoveRows(QModelIndex(), row, row);
+ it = cache.erase(it);
+ while (it != cache.end()) {
+ int oldKey = it.key();
+ const QSqlTableModelPrivate::ModifiedRow oldValue = it.value();
+ cache.erase(it);
+ it = cache.insert(oldKey - 1, oldValue);
+ ++it;
+ }
+ q->endRemoveRows();
+ break; }
+ }
+}
+
+bool QSqlTableModelPrivate::exec(const QString &stmt, bool prepStatement,
+ const QSqlRecord &rec, const QSqlRecord &whereValues)
+{
+ if (stmt.isEmpty())
+ return false;
+
+ // lazy initialization of editQuery
+ if (editQuery.driver() != db.driver())
+ editQuery = QSqlQuery(db);
+
+ // workaround for In-Process databases - remove all read locks
+ // from the table to make sure the editQuery succeeds
+ if (db.driver()->hasFeature(QSqlDriver::SimpleLocking))
+ const_cast<QSqlResult *>(query.result())->detachFromResultSet();
+
+ if (prepStatement) {
+ if (editQuery.lastQuery() != stmt) {
+ if (!editQuery.prepare(stmt)) {
+ error = editQuery.lastError();
+ return false;
+ }
+ }
+ int i;
+ for (i = 0; i < rec.count(); ++i) {
+ if (rec.isGenerated(i))
+ editQuery.addBindValue(rec.value(i));
+ }
+ for (i = 0; i < whereValues.count(); ++i) {
+ if (whereValues.isGenerated(i) && !whereValues.isNull(i))
+ editQuery.addBindValue(whereValues.value(i));
+ }
+
+ if (!editQuery.exec()) {
+ error = editQuery.lastError();
+ return false;
+ }
+ } else {
+ if (!editQuery.exec(stmt)) {
+ error = editQuery.lastError();
+ return false;
+ }
+ }
+ return true;
+}
+
+QSqlRecord QSqlTableModelPrivate::primaryValues(int row)
+{
+ QSqlRecord record;
+ if (!query.seek(row)) {
+ error = query.lastError();
+ return record;
+ }
+ if (primaryIndex.isEmpty()) {
+ record = rec;
+ for (int i = 0; i < record.count(); ++i)
+ record.setValue(i, query.value(i));
+ } else {
+ record = primaryIndex;
+ for (int i = 0; i < record.count(); ++i)
+ record.setValue(i, query.value(rec.indexOf(record.fieldName(i))));
+ }
+ return record;
+}
+
+/*!
+ \class QSqlTableModel
+ \brief The QSqlTableModel class provides an editable data model
+ for a single database table.
+
+ \ingroup database
+ \inmodule QtSql
+
+ QSqlTableModel is a high-level interface for reading and writing
+ database records from a single table. It is build on top of the
+ lower-level QSqlQuery and can be used to provide data to view
+ classes such as QTableView. For example:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 24
+
+ We set the SQL table's name and the edit strategy, then we set up
+ the labels displayed in the view header. The edit strategy
+ dictates when the changes done by the user in the view are
+ actually applied to the database. The possible values are \l
+ OnFieldChange, \l OnRowChange, and \l OnManualSubmit.
+
+ QSqlTableModel can also be used to access a database
+ programmatically, without binding it to a view:
+
+ \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 21
+
+ The code snippet above extracts the \c salary field from record 4 in
+ the result set of the query \c{SELECT * from employee}.
+
+ It is possible to set filters using setFilter(), or modify the
+ sort order using setSort(). At the end, you must call select() to
+ populate the model with data.
+
+ The \l{sql/tablemodel} example illustrates how to use
+ QSqlTableModel as the data source for a QTableView.
+
+ QSqlTableModel provides no direct support for foreign keys. Use
+ the QSqlRelationalTableModel and QSqlRelationalDelegate if you
+ want to resolve foreign keys.
+
+ \sa QSqlRelationalTableModel, QSqlQuery, {Model/View Programming},
+ {Table Model Example}, {Cached Table Example}
+*/
+
+/*!
+ \fn QSqlTableModel::beforeDelete(int row)
+
+ This signal is emitted by deleteRowFromTable() before the \a row
+ is deleted from the currently active database table.
+*/
+
+/*!
+ \fn void QSqlTableModel::primeInsert(int row, QSqlRecord &record)
+
+ This signal is emitted by insertRows(), when an insertion is
+ initiated in the given \a row of the currently active database
+ table. The \a record parameter can be written to (since it is a
+ reference), for example to populate some fields with default
+ values.
+*/
+
+/*!
+ \fn QSqlTableModel::beforeInsert(QSqlRecord &record)
+
+ This signal is emitted by insertRowIntoTable() before a new row is
+ inserted into the currently active database table. The values that
+ are about to be inserted are stored in \a record and can be
+ modified before they will be inserted.
+*/
+
+/*!
+ \fn QSqlTableModel::beforeUpdate(int row, QSqlRecord &record)
+
+ This signal is emitted by updateRowInTable() before the \a row is
+ updated in the currently active database table with the values
+ from \a record.
+
+ Note that only values that are marked as generated will be updated.
+ The generated flag can be set with \l QSqlRecord::setGenerated()
+ and checked with \l QSqlRecord::isGenerated().
+
+ \sa QSqlRecord::isGenerated()
+*/
+
+/*!
+ Creates an empty QSqlTableModel and sets the parent to \a parent
+ and the database connection to \a db. If \a db is not valid, the
+ default database connection will be used.
+
+ The default edit strategy is \l OnRowChange.
+*/
+QSqlTableModel::QSqlTableModel(QObject *parent, QSqlDatabase db)
+ : QSqlQueryModel(*new QSqlTableModelPrivate, parent)
+{
+ Q_D(QSqlTableModel);
+ d->db = db.isValid() ? db : QSqlDatabase::database();
+}
+
+/*! \internal
+*/
+QSqlTableModel::QSqlTableModel(QSqlTableModelPrivate &dd, QObject *parent, QSqlDatabase db)
+ : QSqlQueryModel(dd, parent)
+{
+ Q_D(QSqlTableModel);
+ d->db = db.isValid() ? db : QSqlDatabase::database();
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+QSqlTableModel::~QSqlTableModel()
+{
+}
+
+/*!
+ Sets the database table on which the model operates to \a
+ tableName. Does not select data from the table, but fetches its
+ field information.
+
+ To populate the model with the table's data, call select().
+
+ Error information can be retrieved with \l lastError().
+
+ \sa select(), setFilter(), lastError()
+*/
+void QSqlTableModel::setTable(const QString &tableName)
+{
+ Q_D(QSqlTableModel);
+ clear();
+ d->tableName = tableName;
+ d->initRecordAndPrimaryIndex();
+ d->initColOffsets(d->rec.count());
+
+ if (d->rec.count() == 0)
+ d->error = QSqlError(QLatin1String("Unable to find table ") + d->tableName, QString(),
+ QSqlError::StatementError);
+}
+
+/*!
+ Returns the name of the currently selected table.
+*/
+QString QSqlTableModel::tableName() const
+{
+ Q_D(const QSqlTableModel);
+ return d->tableName;
+}
+
+/*!
+ Populates the model with data from the table that was set via setTable(), using the
+ specified filter and sort condition, and returns true if successful; otherwise
+ returns false.
+
+ \note Calling select() will revert any unsubmitted changes and remove any inserted columns.
+
+ \sa setTable(), setFilter(), selectStatement()
+*/
+bool QSqlTableModel::select()
+{
+ Q_D(QSqlTableModel);
+ QString query = selectStatement();
+ if (query.isEmpty())
+ return false;
+
+ revertAll();
+ QSqlQuery qu(query, d->db);
+ setQuery(qu);
+
+ if (!qu.isActive() || lastError().isValid()) {
+ // something went wrong - revert to non-select state
+ d->initRecordAndPrimaryIndex();
+ return false;
+ }
+ return true;
+}
+
+/*!
+ \reimp
+*/
+QVariant QSqlTableModel::data(const QModelIndex &index, int role) const
+{
+ Q_D(const QSqlTableModel);
+ if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::EditRole))
+ return QVariant();
+
+ // Problem.. we need to use QSQM::indexInQuery to handle inserted columns
+ // but inserted rows we need to handle
+ // and indexInQuery is not virtual (grrr) so any values we pass to QSQM need
+ // to handle the insertedRows
+ QModelIndex item = indexInQuery(index);
+
+ switch (d->strategy) {
+ case OnFieldChange:
+ case OnRowChange:
+ if (index.row() == d->insertIndex) {
+ if (item.column() < 0 || item.column() >= d->rec.count())
+ return QVariant();
+ return d->editBuffer.value(index.column());
+ }
+ if (d->editIndex == item.row()) {
+ if (d->editBuffer.isGenerated(item.column()))
+ return d->editBuffer.value(item.column());
+ }
+ break;
+ case OnManualSubmit:
+ if (d->cache.contains(index.row())) {
+ const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row());
+ if (row.rec.isGenerated(item.column()) || row.op == QSqlTableModelPrivate::Insert)
+ return row.rec.value(item.column());
+ }
+ break;
+ }
+
+ // We need to handle row mapping here, but not column mapping
+ return QSqlQueryModel::data(index.sibling(item.row(), index.column()), role);
+}
+
+/*!
+ \reimp
+*/
+QVariant QSqlTableModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ Q_D(const QSqlTableModel);
+ if (orientation == Qt::Vertical && role == Qt::DisplayRole) {
+ switch (d->strategy) {
+ case OnFieldChange:
+ case OnRowChange:
+ if (d->insertIndex == section)
+ return QLatin1String("*");
+ break;
+ case OnManualSubmit:
+ QSqlTableModelPrivate::Op op = d->cache.value(section).op;
+ if (op == QSqlTableModelPrivate::Insert)
+ return QLatin1String("*");
+ else if (op == QSqlTableModelPrivate::Delete)
+ return QLatin1String("!");
+ break;
+ }
+ }
+ return QSqlQueryModel::headerData(section, orientation, role);
+}
+
+/*!
+ Returns true if the value at the index \a index is dirty, otherwise false.
+ Dirty values are values that were modified in the model
+ but not yet written into the database.
+
+ If \a index is invalid or points to a non-existing row, false is returned.
+*/
+bool QSqlTableModel::isDirty(const QModelIndex &index) const
+{
+ Q_D(const QSqlTableModel);
+ if (!index.isValid())
+ return false;
+
+ switch (d->strategy) {
+ case OnFieldChange:
+ return false;
+ case OnRowChange:
+ return index.row() == d->editIndex && d->editBuffer.isGenerated(index.column());
+ case OnManualSubmit: {
+ const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row());
+ return row.op == QSqlTableModelPrivate::Insert
+ || row.op == QSqlTableModelPrivate::Delete
+ || (row.op == QSqlTableModelPrivate::Update
+ && row.rec.isGenerated(index.column()));
+ }
+ }
+ return false;
+}
+
+/*!
+ Sets the data for the item \a index for the role \a role to \a
+ value. Depending on the edit strategy, the value might be applied
+ to the database at once or cached in the model.
+
+ Returns true if the value could be set or false on error, for
+ example if \a index is out of bounds.
+
+ \sa editStrategy(), data(), submit(), submitAll(), revertRow()
+*/
+bool QSqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ Q_D(QSqlTableModel);
+ if (role != Qt::EditRole)
+ return QSqlQueryModel::setData(index, value, role);
+
+ if (!index.isValid() || index.column() >= d->rec.count() || index.row() >= rowCount())
+ return false;
+
+ bool isOk = true;
+ switch (d->strategy) {
+ case OnFieldChange: {
+ if (index.row() == d->insertIndex) {
+ QSqlTableModelPrivate::setGeneratedValue(d->editBuffer, index.column(), value);
+ return true;
+ }
+ d->clearEditBuffer();
+ QSqlTableModelPrivate::setGeneratedValue(d->editBuffer, index.column(), value);
+ isOk = updateRowInTable(index.row(), d->editBuffer);
+ if (isOk)
+ select();
+ emit dataChanged(index, index);
+ break; }
+ case OnRowChange:
+ if (index.row() == d->insertIndex) {
+ QSqlTableModelPrivate::setGeneratedValue(d->editBuffer, index.column(), value);
+ return true;
+ }
+ if (d->editIndex != index.row()) {
+ if (d->editIndex != -1)
+ submit();
+ d->clearEditBuffer();
+ }
+ QSqlTableModelPrivate::setGeneratedValue(d->editBuffer, index.column(), value);
+ d->editIndex = index.row();
+ emit dataChanged(index, index);
+ break;
+ case OnManualSubmit: {
+ QSqlTableModelPrivate::ModifiedRow &row = d->cache[index.row()];
+ if (row.op == QSqlTableModelPrivate::None) {
+ row.op = QSqlTableModelPrivate::Update;
+ row.rec = d->rec;
+ QSqlTableModelPrivate::clearGenerated(row.rec);
+ row.primaryValues = d->primaryValues(indexInQuery(index).row());
+ }
+ QSqlTableModelPrivate::setGeneratedValue(row.rec, index.column(), value);
+ emit dataChanged(index, index);
+ break; }
+ }
+ return isOk;
+}
+
+/*!
+ This function simply calls QSqlQueryModel::setQuery(\a query).
+ You should normally not call it on a QSqlTableModel. Instead, use
+ setTable(), setSort(), setFilter(), etc., to set up the query.
+
+ \sa selectStatement()
+*/
+void QSqlTableModel::setQuery(const QSqlQuery &query)
+{
+ QSqlQueryModel::setQuery(query);
+}
+
+/*!
+ Updates the given \a row in the currently active database table
+ with the specified \a values. Returns true if successful; otherwise
+ returns false.
+
+ This is a low-level method that operates directly on the database
+ and should not be called directly. Use setData() to update values.
+ The model will decide depending on its edit strategy when to modify
+ the database.
+
+ Note that only values that have the generated-flag set are updated.
+ The generated-flag can be set with QSqlRecord::setGenerated() and
+ tested with QSqlRecord::isGenerated().
+
+ \sa QSqlRecord::isGenerated(), setData()
+*/
+bool QSqlTableModel::updateRowInTable(int row, const QSqlRecord &values)
+{
+ Q_D(QSqlTableModel);
+ QSqlRecord rec(values);
+ emit beforeUpdate(row, rec);
+
+ const QSqlRecord whereValues = d->strategy == OnManualSubmit ? d->cache[row].primaryValues : d->primaryValues(row);
+ bool prepStatement = d->db.driver()->hasFeature(QSqlDriver::PreparedQueries);
+ QString stmt = d->db.driver()->sqlStatement(QSqlDriver::UpdateStatement, d->tableName,
+ rec, prepStatement);
+ QString where = d->db.driver()->sqlStatement(QSqlDriver::WhereStatement, d->tableName,
+ whereValues, prepStatement);
+
+ if (stmt.isEmpty() || where.isEmpty() || row < 0 || row >= rowCount()) {
+ d->error = QSqlError(QLatin1String("No Fields to update"), QString(),
+ QSqlError::StatementError);
+ return false;
+ }
+ stmt.append(QLatin1Char(' ')).append(where);
+
+ return d->exec(stmt, prepStatement, rec, whereValues);
+}
+
+
+/*!
+ Inserts the values \a values into the currently active database table.
+
+ This is a low-level method that operates directly on the database
+ and should not be called directly. Use insertRow() and setData()
+ to insert values. The model will decide depending on its edit strategy
+ when to modify the database.
+
+ Returns true if the values could be inserted, otherwise false.
+ Error information can be retrieved with \l lastError().
+
+ \sa lastError(), insertRow(), insertRows()
+*/
+bool QSqlTableModel::insertRowIntoTable(const QSqlRecord &values)
+{
+ Q_D(QSqlTableModel);
+ QSqlRecord rec = values;
+ emit beforeInsert(rec);
+
+ bool prepStatement = d->db.driver()->hasFeature(QSqlDriver::PreparedQueries);
+ QString stmt = d->db.driver()->sqlStatement(QSqlDriver::InsertStatement, d->tableName,
+ rec, prepStatement);
+
+ if (stmt.isEmpty()) {
+ d->error = QSqlError(QLatin1String("No Fields to update"), QString(),
+ QSqlError::StatementError);
+ return false;
+ }
+
+ return d->exec(stmt, prepStatement, rec, QSqlRecord() /* no where values */);
+}
+
+/*!
+ Deletes the given \a row from the currently active database table.
+
+ This is a low-level method that operates directly on the database
+ and should not be called directly. Use removeRow() or removeRows()
+ to delete values. The model will decide depending on its edit strategy
+ when to modify the database.
+
+ Returns true if the row was deleted; otherwise returns false.
+
+ \sa removeRow(), removeRows()
+*/
+bool QSqlTableModel::deleteRowFromTable(int row)
+{
+ Q_D(QSqlTableModel);
+ emit beforeDelete(row);
+
+ const QSqlRecord whereValues = d->strategy == OnManualSubmit ? d->cache[row].primaryValues : d->primaryValues(row);
+ bool prepStatement = d->db.driver()->hasFeature(QSqlDriver::PreparedQueries);
+ QString stmt = d->db.driver()->sqlStatement(QSqlDriver::DeleteStatement,
+ d->tableName,
+ QSqlRecord(),
+ prepStatement);
+ QString where = d->db.driver()->sqlStatement(QSqlDriver::WhereStatement,
+ d->tableName,
+ whereValues,
+ prepStatement);
+
+ if (stmt.isEmpty() || where.isEmpty()) {
+ d->error = QSqlError(QLatin1String("Unable to delete row"), QString(),
+ QSqlError::StatementError);
+ return false;
+ }
+ stmt.append(QLatin1Char(' ')).append(where);
+
+ return d->exec(stmt, prepStatement, QSqlRecord() /* no new values */, whereValues);
+}
+
+/*!
+ Submits all pending changes and returns true on success.
+ Returns false on error, detailed error information can be
+ obtained with lastError().
+
+ On success the model will be repopulated. Any views
+ presenting it will lose their selections.
+
+ Note: In OnManualSubmit mode, already submitted changes won't
+ be cleared from the cache when submitAll() fails. This allows
+ transactions to be rolled back and resubmitted again without
+ losing data.
+
+ \sa revertAll(), lastError()
+*/
+bool QSqlTableModel::submitAll()
+{
+ Q_D(QSqlTableModel);
+
+ switch (d->strategy) {
+ case OnFieldChange:
+ if (d->insertIndex == -1)
+ return true;
+ // else fall through
+ case OnRowChange:
+ if (d->editBuffer.isEmpty())
+ return true;
+ if (d->insertIndex != -1) {
+ if (!insertRowIntoTable(d->editBuffer))
+ return false;
+ d->bottom = d->bottom.sibling(d->bottom.row() + 1, d->bottom.column());
+ } else {
+ if (!updateRowInTable(d->editIndex, d->editBuffer))
+ return false;
+ }
+ d->clearEditBuffer();
+ d->editIndex = -1;
+ d->insertIndex = -1;
+ return select();
+ case OnManualSubmit:
+ for (QSqlTableModelPrivate::CacheMap::ConstIterator it = d->cache.constBegin();
+ it != d->cache.constEnd(); ++it) {
+ switch (it.value().op) {
+ case QSqlTableModelPrivate::Insert:
+ if (!insertRowIntoTable(it.value().rec))
+ return false;
+ d->bottom = d->bottom.sibling(d->bottom.row() + 1, d->bottom.column());
+ break;
+ case QSqlTableModelPrivate::Update:
+ if (!updateRowInTable(it.key(), it.value().rec))
+ return false;
+ break;
+ case QSqlTableModelPrivate::Delete:
+ if (!deleteRowFromTable(it.key()))
+ return false;
+ break;
+ case QSqlTableModelPrivate::None:
+ Q_ASSERT_X(false, "QSqlTableModel::submitAll()", "Invalid cache operation");
+ break;
+ }
+ }
+ d->clearCache();
+ return select();
+ }
+ return false;
+}
+
+/*!
+ This reimplemented slot is called by the item delegates when the
+ user stopped editing the current row.
+
+ Submits the currently edited row if the model's strategy is set
+ to OnRowChange or OnFieldChange. Does nothing for the OnManualSubmit
+ strategy.
+
+ Use submitAll() to submit all pending changes for the
+ OnManualSubmit strategy.
+
+ Returns true on success; otherwise returns false. Use lastError()
+ to query detailed error information.
+
+ On success the model will be repopulated. Any views
+ presenting it will lose their selections.
+
+ \sa revert(), revertRow(), submitAll(), revertAll(), lastError()
+*/
+bool QSqlTableModel::submit()
+{
+ Q_D(QSqlTableModel);
+ if (d->strategy == OnRowChange || d->strategy == OnFieldChange)
+ return submitAll();
+ return true;
+}
+
+/*!
+ This reimplemented slot is called by the item delegates when the
+ user canceled editing the current row.
+
+ Reverts the changes if the model's strategy is set to
+ OnRowChange. Does nothing for the other edit strategies.
+
+ Use revertAll() to revert all pending changes for the
+ OnManualSubmit strategy or revertRow() to revert a specific row.
+
+ \sa submit(), submitAll(), revertRow(), revertAll()
+*/
+void QSqlTableModel::revert()
+{
+ Q_D(QSqlTableModel);
+ if (d->strategy == OnRowChange)
+ revertAll();
+}
+
+/*!
+ \enum QSqlTableModel::EditStrategy
+
+ This enum type describes which strategy to choose when editing values in the database.
+
+ \value OnFieldChange All changes to the model will be applied immediately to the database.
+ \value OnRowChange Changes to a row will be applied when the user selects a different row.
+ \value OnManualSubmit All changes will be cached in the model until either submitAll()
+ or revertAll() is called.
+
+ Note: To prevent inserting only partly initialized rows into the database,
+ \c OnFieldChange will behave like \c OnRowChange for newly inserted rows.
+
+ \sa setEditStrategy()
+*/
+
+
+/*!
+ Sets the strategy for editing values in the database to \a
+ strategy.
+
+ This will revert any pending changes.
+
+ \sa editStrategy(), revertAll()
+*/
+void QSqlTableModel::setEditStrategy(EditStrategy strategy)
+{
+ Q_D(QSqlTableModel);
+ revertAll();
+ d->strategy = strategy;
+}
+
+/*!
+ Returns the current edit strategy.
+
+ \sa setEditStrategy()
+*/
+QSqlTableModel::EditStrategy QSqlTableModel::editStrategy() const
+{
+ Q_D(const QSqlTableModel);
+ return d->strategy;
+}
+
+/*!
+ Reverts all pending changes.
+
+ \sa revert(), revertRow(), submitAll()
+*/
+void QSqlTableModel::revertAll()
+{
+ Q_D(QSqlTableModel);
+ switch (d->strategy) {
+ case OnFieldChange:
+ break;
+ case OnRowChange:
+ if (d->editIndex != -1)
+ revertRow(d->editIndex);
+ else if (d->insertIndex != -1)
+ revertRow(d->insertIndex);
+ break;
+ case OnManualSubmit:
+ while (!d->cache.isEmpty())
+ revertRow(d->cache.constBegin().key());
+ break;
+ }
+}
+
+/*!
+ Reverts all changes for the specified \a row.
+
+ \sa revert(), revertAll(), submit(), submitAll()
+*/
+void QSqlTableModel::revertRow(int row)
+{
+ if (row < 0)
+ return;
+
+ Q_D(QSqlTableModel);
+ switch (d->strategy) {
+ case OnFieldChange:
+ break;
+ case OnRowChange: {
+ if (d->editIndex == row) {
+ d->editBuffer.clear();
+ int oldIndex = d->editIndex;
+ d->editIndex = -1;
+ emit dataChanged(createIndex(oldIndex, 0), createIndex(oldIndex, columnCount()));
+ } else if (d->insertIndex == row) {
+ d->revertInsertedRow();
+ }
+ break; }
+ case OnManualSubmit:
+ d->revertCachedRow(row);
+ break;
+ }
+}
+
+/*!
+ Returns the primary key for the current table, or an empty
+ QSqlIndex if the table is not set or has no primary key.
+
+ \sa setTable(), setPrimaryKey(), QSqlDatabase::primaryIndex()
+*/
+QSqlIndex QSqlTableModel::primaryKey() const
+{
+ Q_D(const QSqlTableModel);
+ return d->primaryIndex;
+}
+
+/*!
+ Protected method that allows subclasses to set the primary key to
+ \a key.
+
+ Normally, the primary index is set automatically whenever you
+ call setTable().
+
+ \sa primaryKey(), QSqlDatabase::primaryIndex()
+*/
+void QSqlTableModel::setPrimaryKey(const QSqlIndex &key)
+{
+ Q_D(QSqlTableModel);
+ d->primaryIndex = key;
+}
+
+/*!
+ Returns a pointer to the used QSqlDatabase or 0 if no database was set.
+*/
+QSqlDatabase QSqlTableModel::database() const
+{
+ Q_D(const QSqlTableModel);
+ return d->db;
+}
+
+/*!
+ Sorts the data by \a column with the sort order \a order.
+ This will immediately select data, use setSort()
+ to set a sort order without populating the model with data.
+
+ \sa setSort(), select(), orderByClause()
+*/
+void QSqlTableModel::sort(int column, Qt::SortOrder order)
+{
+ setSort(column, order);
+ select();
+}
+
+/*!
+ Sets the sort order for \a column to \a order. This does not
+ affect the current data, to refresh the data using the new
+ sort order, call select().
+
+ \sa select(), orderByClause()
+*/
+void QSqlTableModel::setSort(int column, Qt::SortOrder order)
+{
+ Q_D(QSqlTableModel);
+ d->sortColumn = column;
+ d->sortOrder = order;
+}
+
+/*!
+ Returns an SQL \c{ORDER BY} clause based on the currently set
+ sort order.
+
+ \sa setSort(), selectStatement()
+*/
+QString QSqlTableModel::orderByClause() const
+{
+ Q_D(const QSqlTableModel);
+ QString s;
+ QSqlField f = d->rec.field(d->sortColumn);
+ if (!f.isValid())
+ return s;
+
+ QString table = d->tableName;
+ //we can safely escape the field because it would have been obtained from the database
+ //and have the correct case
+ QString field = d->db.driver()->escapeIdentifier(f.name(), QSqlDriver::FieldName);
+ s.append(QLatin1String("ORDER BY ")).append(table).append(QLatin1Char('.')).append(field);
+ s += d->sortOrder == Qt::AscendingOrder ? QLatin1String(" ASC") : QLatin1String(" DESC");
+
+ return s;
+}
+
+/*!
+ Returns the index of the field \a fieldName.
+*/
+int QSqlTableModel::fieldIndex(const QString &fieldName) const
+{
+ Q_D(const QSqlTableModel);
+ return d->rec.indexOf(fieldName);
+}
+
+/*!
+ Returns the SQL \c SELECT statement used internally to populate
+ the model. The statement includes the filter and the \c{ORDER BY}
+ clause.
+
+ \sa filter(), orderByClause()
+*/
+QString QSqlTableModel::selectStatement() const
+{
+ Q_D(const QSqlTableModel);
+ QString query;
+ if (d->tableName.isEmpty()) {
+ d->error = QSqlError(QLatin1String("No table name given"), QString(),
+ QSqlError::StatementError);
+ return query;
+ }
+ if (d->rec.isEmpty()) {
+ d->error = QSqlError(QLatin1String("Unable to find table ") + d->tableName, QString(),
+ QSqlError::StatementError);
+ return query;
+ }
+
+ query = d->db.driver()->sqlStatement(QSqlDriver::SelectStatement,
+ d->tableName,
+ d->rec,
+ false);
+ if (query.isEmpty()) {
+ d->error = QSqlError(QLatin1String("Unable to select fields from table ") + d->tableName,
+ QString(), QSqlError::StatementError);
+ return query;
+ }
+ if (!d->filter.isEmpty())
+ query.append(QLatin1String(" WHERE ")).append(d->filter);
+ QString orderBy(orderByClause());
+ if (!orderBy.isEmpty())
+ query.append(QLatin1Char(' ')).append(orderBy);
+
+ return query;
+}
+
+/*!
+ Removes \a count columns from the \a parent model, starting at
+ index \a column.
+
+ Returns if the columns were successfully removed; otherwise
+ returns false.
+
+ \sa removeRows()
+*/
+bool QSqlTableModel::removeColumns(int column, int count, const QModelIndex &parent)
+{
+ Q_D(QSqlTableModel);
+ if (parent.isValid() || column < 0 || column + count > d->rec.count())
+ return false;
+ for (int i = 0; i < count; ++i)
+ d->rec.remove(column);
+ if (d->query.isActive())
+ return select();
+ return true;
+}
+
+/*!
+ Removes \a count rows starting at \a row. Since this model
+ does not support hierarchical structures, \a parent must be
+ an invalid model index.
+
+ Emits the beforeDelete() signal before a row is deleted. When
+ the edit strategy is OnManualSubmit signal emission is delayed
+ until submitAll() is called.
+
+ Returns true if all rows could be removed; otherwise returns
+ false. Detailed error information can be retrieved using
+ lastError().
+
+ \sa removeColumns(), insertRows()
+*/
+bool QSqlTableModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+ Q_D(QSqlTableModel);
+ if (parent.isValid() || row < 0 || count <= 0)
+ return false;
+
+ int i;
+ switch (d->strategy) {
+ case OnFieldChange:
+ case OnRowChange:
+ for (i = 0; i < count; ++i) {
+ if (row + i == d->insertIndex)
+ d->revertInsertedRow();
+ else if (!deleteRowFromTable(row + i))
+ return false;
+ }
+ select();
+ break;
+ case OnManualSubmit:
+ for (i = 0; i < count; ++i) {
+ int idx = row + i;
+ if (idx >= rowCount())
+ return false;
+ if (d->cache.value(idx).op == QSqlTableModelPrivate::Insert) {
+ revertRow(idx);
+ // Reverting a row means all the other cache entries have been adjusted downwards
+ // so fake this by adjusting row
+ --row;
+ } else {
+ d->cache[idx].op = QSqlTableModelPrivate::Delete;
+ d->cache[idx].primaryValues = d->primaryValues(indexInQuery(createIndex(idx, 0)).row());
+ emit headerDataChanged(Qt::Vertical, idx, idx);
+ }
+ }
+ break;
+ }
+ return true;
+}
+
+/*!
+ Inserts \a count empty rows at position \a row. Note that \a
+ parent must be invalid, since this model does not support
+ parent-child relations.
+
+ Only one row at a time can be inserted when using the
+ OnFieldChange or OnRowChange update strategies.
+
+ The primeInsert() signal will be emitted for each new row.
+ Connect to it if you want to initialize the new row with default
+ values.
+
+ Returns false if the parameters are out of bounds; otherwise
+ returns true.
+
+ \sa primeInsert(), insertRecord()
+*/
+bool QSqlTableModel::insertRows(int row, int count, const QModelIndex &parent)
+{
+ Q_D(QSqlTableModel);
+ if (row < 0 || count <= 0 || row > rowCount() || parent.isValid())
+ return false;
+
+ switch (d->strategy) {
+ case OnFieldChange:
+ case OnRowChange:
+ if (count != 1)
+ return false;
+ beginInsertRows(parent, row, row);
+ d->insertIndex = row;
+ // ### apply dangling changes...
+ d->clearEditBuffer();
+ emit primeInsert(row, d->editBuffer);
+ break;
+ case OnManualSubmit:
+ beginInsertRows(parent, row, row + count - 1);
+ if (!d->cache.isEmpty()) {
+ QMap<int, QSqlTableModelPrivate::ModifiedRow>::Iterator it = d->cache.end();
+ while (it != d->cache.begin() && (--it).key() >= row) {
+ int oldKey = it.key();
+ const QSqlTableModelPrivate::ModifiedRow oldValue = it.value();
+ d->cache.erase(it);
+ it = d->cache.insert(oldKey + count, oldValue);
+ }
+ }
+
+ for (int i = 0; i < count; ++i) {
+ d->cache[row + i] = QSqlTableModelPrivate::ModifiedRow(QSqlTableModelPrivate::Insert,
+ d->rec);
+ emit primeInsert(row + i, d->cache[row + i].rec);
+ }
+ break;
+ }
+ endInsertRows();
+ return true;
+}
+
+/*!
+ Inserts the \a record after \a row. If \a row is negative, the
+ record will be appended to the end. Calls insertRows() and
+ setRecord() internally.
+
+ Returns true if the row could be inserted, otherwise false.
+
+ \sa insertRows(), removeRows()
+*/
+bool QSqlTableModel::insertRecord(int row, const QSqlRecord &record)
+{
+ Q_D(QSqlTableModel);
+ if (row < 0)
+ row = rowCount();
+ if (!insertRow(row, QModelIndex()))
+ return false;
+ if (!setRecord(row, record))
+ return false;
+ if (d->strategy == OnFieldChange || d->strategy == OnRowChange)
+ return submit();
+ return true;
+}
+
+/*! \reimp
+*/
+int QSqlTableModel::rowCount(const QModelIndex &parent) const
+{
+ Q_D(const QSqlTableModel);
+
+ if (parent.isValid())
+ return 0;
+
+ int rc = QSqlQueryModel::rowCount();
+ if (d->strategy == OnManualSubmit) {
+ for (QSqlTableModelPrivate::CacheMap::ConstIterator it = d->cache.constBegin();
+ it != d->cache.constEnd(); ++it) {
+ if (it.value().op == QSqlTableModelPrivate::Insert)
+ ++rc;
+ }
+ } else if (d->insertIndex >= 0) {
+ ++rc;
+ }
+ return rc;
+}
+
+/*!
+ Returns the index of the value in the database result set for the
+ given \a item in the model.
+
+ The return value is identical to \a item if no columns or rows
+ have been inserted, removed, or moved around.
+
+ Returns an invalid model index if \a item is out of bounds or if
+ \a item does not point to a value in the result set.
+
+ \sa QSqlQueryModel::indexInQuery()
+*/
+QModelIndex QSqlTableModel::indexInQuery(const QModelIndex &item) const
+{
+ Q_D(const QSqlTableModel);
+ const QModelIndex it = QSqlQueryModel::indexInQuery(item); // this adjusts columns only
+ if (d->strategy == OnManualSubmit) {
+ int rowOffset = 0;
+ QSqlTableModelPrivate::CacheMap::ConstIterator i = d->cache.constBegin();
+ while (i != d->cache.constEnd() && i.key() <= it.row()) {
+ if (i.value().op == QSqlTableModelPrivate::Insert)
+ ++rowOffset;
+ ++i;
+ }
+ return createIndex(it.row() - rowOffset, it.column(), it.internalPointer());
+ } else {
+ if (d->insertIndex >= 0 && it.row() >= d->insertIndex)
+ return createIndex(it.row() - 1, it.column(), it.internalPointer());
+ }
+ return it;
+}
+
+/*!
+ Returns the currently set filter.
+
+ \sa setFilter(), select()
+*/
+QString QSqlTableModel::filter() const
+{
+ Q_D(const QSqlTableModel);
+ return d->filter;
+}
+
+/*!
+ Sets the current filter to \a filter.
+
+ The filter is a SQL \c WHERE clause without the keyword \c WHERE
+ (for example, \c{name='Josephine')}.
+
+ If the model is already populated with data from a database,
+ the model re-selects it with the new filter. Otherwise, the filter
+ will be applied the next time select() is called.
+
+ \sa filter(), select(), selectStatement(), orderByClause()
+*/
+void QSqlTableModel::setFilter(const QString &filter)
+{
+ Q_D(QSqlTableModel);
+ d->filter = filter;
+ if (d->query.isActive())
+ select();
+}
+
+/*! \reimp
+*/
+void QSqlTableModel::clear()
+{
+ Q_D(QSqlTableModel);
+ d->clear();
+ QSqlQueryModel::clear();
+}
+
+/*! \reimp
+*/
+Qt::ItemFlags QSqlTableModel::flags(const QModelIndex &index) const
+{
+ Q_D(const QSqlTableModel);
+ if (index.internalPointer() || index.column() < 0 || index.column() >= d->rec.count()
+ || index.row() < 0)
+ return 0;
+ if (d->rec.field(index.column()).isReadOnly())
+ return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+ return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
+}
+
+/*!
+ Sets the values at the specified \a row to the values of \a
+ record. Returns true if all the values could be set; otherwise
+ returns false.
+
+ \sa record()
+*/
+bool QSqlTableModel::setRecord(int row, const QSqlRecord &record)
+{
+ Q_D(QSqlTableModel);
+ Q_ASSERT_X(row >= 0, "QSqlTableModel::setRecord()", "Cannot set a record to a row less than 0");
+ if (row >= rowCount())
+ return false;
+
+ bool isOk = true;
+ switch (d->strategy) {
+ case OnFieldChange:
+ case OnRowChange:
+ return d->setRecord(row, record);
+ case OnManualSubmit: {
+ QSqlTableModelPrivate::ModifiedRow &mrow = d->cache[row];
+ if (mrow.op == QSqlTableModelPrivate::None) {
+ mrow.op = QSqlTableModelPrivate::Update;
+ mrow.rec = d->rec;
+ QSqlTableModelPrivate::clearGenerated(mrow.rec);
+ mrow.primaryValues = d->primaryValues(indexInQuery(createIndex(row, 0)).row());
+ }
+ QString fieldName;
+ for (int i = 0; i < record.count(); ++i) {
+ fieldName = record.fieldName(i);
+ if (d->db.driver()->isIdentifierEscaped(fieldName, QSqlDriver::FieldName))
+ fieldName = d->db.driver()->stripDelimiters(fieldName, QSqlDriver::FieldName);
+ int idx = mrow.rec.indexOf(fieldName);
+ if (idx == -1) {
+ isOk = false;
+ } else {
+ QSqlTableModelPrivate::setGeneratedValue(mrow.rec, idx, record.value(i));
+ }
+ }
+
+ if (isOk)
+ emit dataChanged(createIndex(row, 0), createIndex(row, columnCount() - 1));
+ return isOk; }
+ }
+ return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/sql/models/qsqltablemodel.h b/src/sql/models/qsqltablemodel.h
new file mode 100644
index 0000000000..6c3e35a137
--- /dev/null
+++ b/src/sql/models/qsqltablemodel.h
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLTABLEMODEL_H
+#define QSQLTABLEMODEL_H
+
+#include <QtSql/qsqldatabase.h>
+#include <QtSql/qsqlquerymodel.h>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Sql)
+
+class QSqlTableModelPrivate;
+class QSqlRecord;
+class QSqlField;
+class QSqlIndex;
+
+class Q_SQL_EXPORT QSqlTableModel: public QSqlQueryModel
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QSqlTableModel)
+
+public:
+ enum EditStrategy {OnFieldChange, OnRowChange, OnManualSubmit};
+
+ explicit QSqlTableModel(QObject *parent = 0, QSqlDatabase db = QSqlDatabase());
+ virtual ~QSqlTableModel();
+
+ virtual bool select();
+
+ virtual void setTable(const QString &tableName);
+ QString tableName() const;
+
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+
+ QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ bool isDirty(const QModelIndex &index) const;
+ void clear();
+
+ virtual void setEditStrategy(EditStrategy strategy);
+ EditStrategy editStrategy() const;
+
+ QSqlIndex primaryKey() const;
+ QSqlDatabase database() const;
+ int fieldIndex(const QString &fieldName) const;
+
+ void sort(int column, Qt::SortOrder order);
+ virtual void setSort(int column, Qt::SortOrder order);
+
+ QString filter() const;
+ virtual void setFilter(const QString &filter);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+
+ bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex());
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
+
+ bool insertRecord(int row, const QSqlRecord &record);
+ bool setRecord(int row, const QSqlRecord &record);
+
+ virtual void revertRow(int row);
+
+public Q_SLOTS:
+ bool submit();
+ void revert();
+
+ bool submitAll();
+ void revertAll();
+
+Q_SIGNALS:
+ void primeInsert(int row, QSqlRecord &record);
+
+ void beforeInsert(QSqlRecord &record);
+ void beforeUpdate(int row, QSqlRecord &record);
+ void beforeDelete(int row);
+
+protected:
+ QSqlTableModel(QSqlTableModelPrivate &dd, QObject *parent = 0, QSqlDatabase db = QSqlDatabase());
+
+ virtual bool updateRowInTable(int row, const QSqlRecord &values);
+ virtual bool insertRowIntoTable(const QSqlRecord &values);
+ virtual bool deleteRowFromTable(int row);
+ virtual QString orderByClause() const;
+ virtual QString selectStatement() const;
+
+ void setPrimaryKey(const QSqlIndex &key);
+ void setQuery(const QSqlQuery &query);
+ QModelIndex indexInQuery(const QModelIndex &item) const;
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // QSQLTABLEMODEL_H
diff --git a/src/sql/models/qsqltablemodel_p.h b/src/sql/models/qsqltablemodel_p.h
new file mode 100644
index 0000000000..322c23b67d
--- /dev/null
+++ b/src/sql/models/qsqltablemodel_p.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QSQLTABLEMODEL_P_H
+#define QSQLTABLEMODEL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of qsql*model.h . This header file may change from version to version
+// without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "QtCore/qmap.h"
+#include "private/qsqlquerymodel_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QSqlTableModelPrivate: public QSqlQueryModelPrivate
+{
+ Q_DECLARE_PUBLIC(QSqlTableModel)
+
+public:
+ QSqlTableModelPrivate()
+ : editIndex(-1), insertIndex(-1), sortColumn(-1),
+ sortOrder(Qt::AscendingOrder),
+ strategy(QSqlTableModel::OnRowChange)
+ {}
+ void clear();
+ QSqlRecord primaryValues(int index);
+ virtual void clearEditBuffer();
+ virtual void clearCache();
+ static void clearGenerated(QSqlRecord &rec);
+ static void setGeneratedValue(QSqlRecord &rec, int c, QVariant v);
+ QSqlRecord record(const QVector<QVariant> &values) const;
+
+ bool exec(const QString &stmt, bool prepStatement,
+ const QSqlRecord &rec, const QSqlRecord &whereValues);
+ virtual void revertCachedRow(int row);
+ void revertInsertedRow();
+ bool setRecord(int row, const QSqlRecord &record);
+ virtual int nameToIndex(const QString &name) const;
+ void initRecordAndPrimaryIndex();
+
+ QSqlDatabase db;
+ int editIndex;
+ int insertIndex;
+
+ int sortColumn;
+ Qt::SortOrder sortOrder;
+
+ QSqlTableModel::EditStrategy strategy;
+
+ QSqlQuery editQuery;
+ QSqlIndex primaryIndex;
+ QString tableName;
+ QString filter;
+
+ enum Op { None, Insert, Update, Delete };
+
+ struct ModifiedRow
+ {
+ ModifiedRow(Op o = None, const QSqlRecord &r = QSqlRecord()): op(o), rec(r) { clearGenerated(rec);}
+ ModifiedRow(const ModifiedRow &other): op(other.op), rec(other.rec), primaryValues(other.primaryValues) {}
+ Op op;
+ QSqlRecord rec;
+ QSqlRecord primaryValues;
+ };
+
+ QSqlRecord editBuffer;
+
+ typedef QMap<int, ModifiedRow> CacheMap;
+ CacheMap cache;
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQLTABLEMODEL_P_H
diff --git a/src/sql/sql.pro b/src/sql/sql.pro
new file mode 100644
index 0000000000..81aa3c06c9
--- /dev/null
+++ b/src/sql/sql.pro
@@ -0,0 +1,29 @@
+TARGET = QtSql
+QPRO_PWD = $$PWD
+QT = core
+DEFINES += QT_BUILD_SQL_LIB
+DEFINES += QT_NO_USING_NAMESPACE
+win32-msvc*|win32-icc:QMAKE_LFLAGS += /BASE:0x62000000
+
+unix|win32-g++*:QMAKE_PKGCONFIG_REQUIRES = QtCore
+
+include(../qbase.pri)
+
+DEFINES += QT_NO_CAST_FROM_ASCII
+PRECOMPILED_HEADER = ../corelib/global/qt_pch.h
+SQL_P = sql
+
+include(kernel/kernel.pri)
+include(drivers/drivers.pri)
+include(models/models.pri)
+
+symbian: {
+ TARGET.UID3=0x2001E61D
+
+ # Problems using data exports from this DLL mean that we can't page it on releases that don't support
+ # data exports (currently that's any release before Symbian^3)
+ pagingBlock = "$${LITERAL_HASH}ifndef SYMBIAN_DLL_DATA_EXPORTS_SUPPORTED" \
+ "UNPAGED" \
+ "$${LITERAL_HASH}endif"
+ MMP_RULES += pagingBlock
+}