summaryrefslogtreecommitdiffstats
path: root/src/sql
diff options
context:
space:
mode:
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
+}