summaryrefslogtreecommitdiffstats
path: root/src/sql/models
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2009-03-23 10:18:55 +0100
committerSimon Hausmann <simon.hausmann@nokia.com>2009-03-23 10:18:55 +0100
commite5fcad302d86d316390c6b0f62759a067313e8a9 (patch)
treec2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/sql/models
Long live Qt 4.5!
Diffstat (limited to 'src/sql/models')
-rw-r--r--src/sql/models/models.pri12
-rw-r--r--src/sql/models/qsqlquerymodel.cpp592
-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.cpp717
-rw-r--r--src/sql/models/qsqlrelationaltablemodel.h112
-rw-r--r--src/sql/models/qsqltablemodel.cpp1332
-rw-r--r--src/sql/models/qsqltablemodel.h141
-rw-r--r--src/sql/models/qsqltablemodel_p.h118
11 files changed, 3446 insertions, 0 deletions
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..973d715d39
--- /dev/null
+++ b/src/sql/models/qsqlquerymodel.cpp
@@ -0,0 +1,592 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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;
+ if (role == Qt::DisplayRole && d->rec.count() > section)
+ 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.
+
+ \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);
+
+ 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)
+ 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 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)
+ 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..14e01c0f74
--- /dev/null
+++ b/src/sql/models/qsqlquerymodel.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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..ef331d7e26
--- /dev/null
+++ b/src/sql/models/qsqlquerymodel_p.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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..1043ef9a6e
--- /dev/null
+++ b/src/sql/models/qsqlrelationaldelegate.cpp
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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..dd9ad8fe40
--- /dev/null
+++ b/src/sql/models/qsqlrelationaldelegate.h
@@ -0,0 +1,129 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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 *parent = 0)
+ : QItemDelegate(parent)
+{}
+
+~QSqlRelationalDelegate()
+{}
+
+QWidget *createEditor(QWidget *parent,
+ 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(parent, option, index);
+
+ QComboBox *combo = new QComboBox(parent);
+ 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..935466bd4e
--- /dev/null
+++ b/src/sql/models/qsqlrelationaltablemodel.cpp
@@ -0,0 +1,717 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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;
+ for (int i=0; i < model->rowCount(); ++i) {
+ record = model->record(i);
+ dictionary[record.field(rel.indexColumn()).value().toString()] =
+ record.field(rel.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 escapedRelationField(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
+{
+ return baseRec.indexOf(name);
+}
+
+void QSqlRelationalTableModelPrivate::clearEditBuffer()
+{
+ editBuffer = baseRec;
+}
+
+/*!
+ \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) {
+ v = d->editBuffer.value(index.column());
+ }
+ break;
+ case OnManualSubmit:
+ const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row());
+ 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::escapedRelationField(const QString &tableName,
+ const QString &fieldName) const
+{
+ QString esc;
+ esc.reserve(tableName.size() + fieldName.size() + 1);
+ esc.append(tableName).append(QLatin1Char('.')).append(fieldName);
+
+ return db.driver()->escapeIdentifier(esc, QSqlDriver::FieldName);
+}
+
+/*!
+ \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;
+ 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();
+ else
+ name = rec.fieldName(i);
+ fieldNames.insert(name, fieldNames.value(name, 0) + 1);
+ }
+
+ 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->escapedRelationField(relTableAlias, relation.displayColumn()));
+
+ // If there are duplicate field names they must be aliased
+ if (fieldNames.value(relation.displayColumn()) > 1) {
+ fList.append(QString::fromLatin1(" AS %1_%2").arg(relation.tableName()).arg(relation.displayColumn()));
+ }
+
+ // this needs fixing!! the below if is borken.
+ if (!tables.contains(relation.tableName()))
+ tables.append(d->db.driver()->escapeIdentifier(relation.tableName(),QSqlDriver::TableName)
+ .append(QLatin1String(" "))
+ .append(d->db.driver()->escapeIdentifier(relTableAlias, QSqlDriver::TableName)));
+ if(!where.isEmpty())
+ where.append(QLatin1String(" AND "));
+ where.append(d->escapedRelationField(tableName(), rec.fieldName(i)));
+ where.append(QLatin1String(" = "));
+ where.append(d->escapedRelationField(relTableAlias, relation.indexColumn()));
+ } else {
+ if (!fList.isEmpty())
+ fList.append(QLatin1String(", "));
+ fList.append(d->escapedRelationField(tableName(), rec.fieldName(i)));
+ }
+ }
+ if (!tables.isEmpty())
+ tList.append(tables.join(QLatin1String(", ")));
+ if (fList.isEmpty())
+ return query;
+ if(!tList.isEmpty())
+ tList.prepend(QLatin1String(", "));
+ tList.prepend(d->db.driver()->escapeIdentifier(tableName(),QSqlDriver::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);
+ values.replace(i, baseRec.field(realCol));
+ values.setValue(i, v);
+ }
+ }
+}
+
+/*!
+ \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->escapedRelationField(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..fb11bae8e3
--- /dev/null
+++ b/src/sql/models/qsqlrelationaltablemodel.h
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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..2fb9b0fc42
--- /dev/null
+++ b/src/sql/models/qsqltablemodel.cpp
@@ -0,0 +1,1332 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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
+{
+ return rec.indexOf(name);
+}
+
+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;
+}
+
+void QSqlTableModelPrivate::clearCache()
+{
+ cache.clear();
+}
+
+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) && rec.value(i).type() != QVariant::Invalid)
+ editQuery.addBindValue(rec.value(i));
+ }
+ for (i = 0; i < whereValues.count(); ++i) {
+ if (whereValues.isGenerated(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 25
+
+ 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();
+ if(d->db.tables().contains(tableName.toUpper()))
+ d->tableName = tableName.toUpper();
+ else
+ 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.
+
+ \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()) {
+ // 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();
+
+ QModelIndex item = indexInQuery(index);
+
+ switch (d->strategy) {
+ case OnFieldChange:
+ case OnRowChange:
+ if (index.row() == d->insertIndex) {
+ QVariant val;
+ if (item.column() < 0 || item.column() >= d->rec.count())
+ return val;
+ val = d->editBuffer.value(index.column());
+ if (val.type() == QVariant::Invalid)
+ val = QVariant(d->rec.field(item.column()).type());
+ return val;
+ }
+ if (d->editIndex == item.row()) {
+ QVariant var = d->editBuffer.value(item.column());
+ if (var.isValid())
+ return var;
+ }
+ break;
+ case OnManualSubmit: {
+ const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row());
+ const QVariant var = row.rec.value(item.column());
+ if (var.isValid() || row.op == QSqlTableModelPrivate::Insert)
+ return var;
+ break; }
+ }
+ return QSqlQueryModel::data(item, 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.value(index.column()).isValid();
+ 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.value(index.column()).isValid());
+ }
+ }
+ 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) {
+ d->editBuffer.setValue(index.column(), value);
+ return true;
+ }
+ d->clearEditBuffer();
+ d->editBuffer.setValue(index.column(), value);
+ isOk = updateRowInTable(index.row(), d->editBuffer);
+ if (isOk)
+ select();
+ break; }
+ case OnRowChange:
+ if (index.row() == d->insertIndex) {
+ d->editBuffer.setValue(index.column(), value);
+ return true;
+ }
+ if (d->editIndex != index.row()) {
+ if (d->editIndex != -1)
+ submit();
+ d->clearEditBuffer();
+ }
+ d->editBuffer.setValue(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;
+ row.primaryValues = d->primaryValues(indexInQuery(index).row());
+ }
+ row.rec.setValue(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);
+}
+
+/*!
+ 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);
+
+ QSqlRecord rec = 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,
+ rec,
+ 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, rec);
+}
+
+/*!
+ 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->db.driver()->escapeIdentifier(d->tableName, QSqlDriver::TableName);
+ 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);
+ else {
+ d->cache[idx].op = QSqlTableModelPrivate::Delete;
+ 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);
+ 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;
+ mrow.primaryValues = d->primaryValues(indexInQuery(createIndex(row, 0)).row());
+ }
+ for (int i = 0; i < record.count(); ++i) {
+ int idx = mrow.rec.indexOf(record.fieldName(i));
+ if (idx == -1)
+ isOk = false;
+ else
+ mrow.rec.setValue(idx, record.value(i));
+ }
+ 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..14c4c4fa3f
--- /dev/null
+++ b/src/sql/models/qsqltablemodel.h
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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..fa3b44b066
--- /dev/null
+++ b/src/sql/models/qsqltablemodel_p.h
@@ -0,0 +1,118 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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();
+ QSqlRecord record(const QVector<QVariant> &values) const;
+
+ bool exec(const QString &stmt, bool prepStatement,
+ const QSqlRecord &rec, const QSqlRecord &whereValues = QSqlRecord());
+ 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) {}
+ 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