diff options
Diffstat (limited to 'src/sql/models')
-rw-r--r-- | src/sql/models/models.pri | 12 | ||||
-rw-r--r-- | src/sql/models/qsqlquerymodel.cpp | 592 | ||||
-rw-r--r-- | src/sql/models/qsqlquerymodel.h | 105 | ||||
-rw-r--r-- | src/sql/models/qsqlquerymodel_p.h | 87 | ||||
-rw-r--r-- | src/sql/models/qsqlrelationaldelegate.cpp | 101 | ||||
-rw-r--r-- | src/sql/models/qsqlrelationaldelegate.h | 129 | ||||
-rw-r--r-- | src/sql/models/qsqlrelationaltablemodel.cpp | 717 | ||||
-rw-r--r-- | src/sql/models/qsqlrelationaltablemodel.h | 112 | ||||
-rw-r--r-- | src/sql/models/qsqltablemodel.cpp | 1332 | ||||
-rw-r--r-- | src/sql/models/qsqltablemodel.h | 141 | ||||
-rw-r--r-- | src/sql/models/qsqltablemodel_p.h | 118 |
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 0000000..972cf2f --- /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 0000000..973d715 --- /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 0000000..14e01c0 --- /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 0000000..ef331d7 --- /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 0000000..1043ef9 --- /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 0000000..dd9ad8f --- /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 0000000..935466b --- /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 0000000..fb11bae --- /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 0000000..2fb9b0f --- /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 0000000..14c4c4f --- /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 0000000..fa3b44b --- /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 |