| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtSql module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "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 |
| |
| typedef QSqlTableModelSql Sql; |
| |
| QSqlTableModelPrivate::~QSqlTableModelPrivate() |
| { |
| |
| } |
| |
| /*! \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; |
| } |
| |
| int QSqlTableModelPrivate::nameToIndex(const QString &name) const |
| { |
| return rec.indexOf(strippedFieldName(name)); |
| } |
| |
| QString QSqlTableModelPrivate::strippedFieldName(const QString &name) const |
| { |
| QString fieldname = name; |
| if (db.driver()->isIdentifierEscaped(fieldname, QSqlDriver::FieldName)) |
| fieldname = db.driver()->stripDelimiters(fieldname, QSqlDriver::FieldName); |
| return fieldname; |
| } |
| |
| int QSqlTableModelPrivate::insertCount(int maxRow) const |
| { |
| int cnt = 0; |
| CacheMap::ConstIterator i = cache.constBegin(); |
| const CacheMap::ConstIterator e = cache.constEnd(); |
| for ( ; i != e && (maxRow < 0 || i.key() <= maxRow); ++i) |
| if (i.value().insert()) |
| ++cnt; |
| |
| return cnt; |
| } |
| |
| void QSqlTableModelPrivate::initRecordAndPrimaryIndex() |
| { |
| rec = db.record(tableName); |
| primaryIndex = db.primaryIndex(tableName); |
| initColOffsets(rec.count()); |
| } |
| |
| void QSqlTableModelPrivate::clear() |
| { |
| sortColumn = -1; |
| sortOrder = Qt::AscendingOrder; |
| tableName.clear(); |
| editQuery.clear(); |
| cache.clear(); |
| primaryIndex.clear(); |
| rec.clear(); |
| filter.clear(); |
| } |
| |
| 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: |
| if (!r.submitted()) { |
| cache[row].revert(); |
| emit q->dataChanged(q->createIndex(row, 0), |
| q->createIndex(row, q->columnCount() - 1)); |
| } |
| break; |
| case QSqlTableModelPrivate::Insert: { |
| QMap<int, QSqlTableModelPrivate::ModifiedRow>::Iterator it = cache.find(row); |
| if (it == cache.end()) |
| return; |
| q->beginRemoveRows(QModelIndex(), row, row); |
| it = cache.erase(it); |
| while (it != cache.end()) { |
| int oldKey = it.key(); |
| const QSqlTableModelPrivate::ModifiedRow oldValue = it.value(); |
| cache.erase(it); |
| it = cache.insert(oldKey - 1, oldValue); |
| ++it; |
| } |
| q->endRemoveRows(); |
| break; } |
| } |
| } |
| |
| bool QSqlTableModelPrivate::exec(const QString &stmt, bool prepStatement, |
| const QSqlRecord &rec, const QSqlRecord &whereValues) |
| { |
| if (stmt.isEmpty()) |
| return false; |
| |
| // lazy initialization of editQuery |
| if (editQuery.driver() != db.driver()) |
| editQuery = QSqlQuery(db); |
| |
| // workaround for In-Process databases - remove all read locks |
| // from the table to make sure the editQuery succeeds |
| if (db.driver()->hasFeature(QSqlDriver::SimpleLocking)) |
| const_cast<QSqlResult *>(query.result())->detachFromResultSet(); |
| |
| if (prepStatement) { |
| if (editQuery.lastQuery() != stmt) { |
| if (!editQuery.prepare(stmt)) { |
| error = editQuery.lastError(); |
| return false; |
| } |
| } |
| int i; |
| for (i = 0; i < rec.count(); ++i) |
| if (rec.isGenerated(i)) |
| editQuery.addBindValue(rec.value(i)); |
| for (i = 0; i < whereValues.count(); ++i) |
| if (whereValues.isGenerated(i) && !whereValues.isNull(i)) |
| editQuery.addBindValue(whereValues.value(i)); |
| |
| if (!editQuery.exec()) { |
| error = editQuery.lastError(); |
| return false; |
| } |
| } else { |
| if (!editQuery.exec(stmt)) { |
| error = editQuery.lastError(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /*! |
| \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 built on top of the |
| lower-level QSqlQuery and can be used to provide data to view |
| classes such as QTableView. For example: |
| |
| \snippet sqldatabase/sqldatabase_snippet.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 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{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 and set the generated flags of the fields. Do not try to |
| edit the record via other means such as setData() or setRecord() |
| while handling this signal. |
| */ |
| |
| /*! |
| \fn QSqlTableModel::beforeInsert(QSqlRecord &record) |
| |
| This signal is emitted by insertRowIntoTable() before a new row is |
| inserted into the currently active database table. The values that |
| are about to be inserted are stored in \a record and can be |
| modified before they will be inserted. |
| */ |
| |
| /*! |
| \fn QSqlTableModel::beforeUpdate(int row, QSqlRecord &record) |
| |
| This signal is emitted by updateRowInTable() before the \a row is |
| updated in the currently active database table with the values |
| from \a record. |
| |
| Note that only values that are marked as generated will be updated. |
| The generated flag can be set with \l QSqlRecord::setGenerated() |
| and checked with \l QSqlRecord::isGenerated(). |
| |
| \sa QSqlRecord::isGenerated() |
| */ |
| |
| /*! |
| Creates an empty QSqlTableModel and sets the parent to \a parent |
| and the database connection to \a db. If \a db is not valid, the |
| default database connection will be used. |
| |
| The default edit strategy is \l OnRowChange. |
| */ |
| QSqlTableModel::QSqlTableModel(QObject *parent, QSqlDatabase db) |
| : QSqlQueryModel(*new QSqlTableModelPrivate, parent) |
| { |
| Q_D(QSqlTableModel); |
| d->db = db.isValid() ? db : QSqlDatabase::database(); |
| } |
| |
| /*! \internal |
| */ |
| QSqlTableModel::QSqlTableModel(QSqlTableModelPrivate &dd, QObject *parent, QSqlDatabase db) |
| : QSqlQueryModel(dd, parent) |
| { |
| Q_D(QSqlTableModel); |
| d->db = db.isValid() ? db : QSqlDatabase::database(); |
| } |
| |
| /*! |
| Destroys the object and frees any allocated resources. |
| */ |
| QSqlTableModel::~QSqlTableModel() |
| { |
| } |
| |
| /*! |
| Sets the database table on which the model operates to \a |
| tableName. Does not select data from the table, but fetches its |
| field information. |
| |
| To populate the model with the table's data, call select(). |
| |
| Error information can be retrieved with \l lastError(). |
| |
| \sa select(), setFilter(), lastError() |
| */ |
| void QSqlTableModel::setTable(const QString &tableName) |
| { |
| Q_D(QSqlTableModel); |
| clear(); |
| d->tableName = tableName; |
| d->initRecordAndPrimaryIndex(); |
| |
| if (d->rec.count() == 0) |
| d->error = QSqlError(QLatin1String("Unable to find table ") + d->tableName, QString(), |
| QSqlError::StatementError); |
| |
| // Remember the auto index column if there is one now. |
| // The record that will be obtained from the query after select lacks this feature. |
| d->autoColumn.clear(); |
| for (int c = 0; c < d->rec.count(); ++c) { |
| if (d->rec.field(c).isAutoValue()) { |
| d->autoColumn = d->rec.fieldName(c); |
| break; |
| } |
| } |
| } |
| |
| /*! |
| 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 \c true if successful; otherwise |
| returns \c false. |
| |
| \note Calling select() will revert any unsubmitted changes and remove any inserted columns. |
| |
| \sa setTable(), setFilter(), selectStatement() |
| */ |
| bool QSqlTableModel::select() |
| { |
| Q_D(QSqlTableModel); |
| const QString query = selectStatement(); |
| if (query.isEmpty()) |
| return false; |
| |
| beginResetModel(); |
| |
| d->clearCache(); |
| |
| QSqlQuery qu(query, d->db); |
| setQuery(qu); |
| |
| if (!qu.isActive() || lastError().isValid()) { |
| // something went wrong - revert to non-select state |
| d->initRecordAndPrimaryIndex(); |
| endResetModel(); |
| return false; |
| } |
| endResetModel(); |
| return true; |
| } |
| |
| /*! |
| \since 5.0 |
| |
| Refreshes \a row in the model with values from the database table row matching |
| on primary key values. Without a primary key, all column values must match. If |
| no matching row is found, the model will show an empty row. |
| |
| Returns \c true if successful; otherwise returns \c false. |
| |
| \sa select() |
| */ |
| bool QSqlTableModel::selectRow(int row) |
| { |
| Q_D(QSqlTableModel); |
| |
| if (row < 0 || row >= rowCount()) |
| return false; |
| |
| const int table_sort_col = d->sortColumn; |
| d->sortColumn = -1; |
| const QString table_filter = d->filter; |
| d->filter = d->db.driver()->sqlStatement(QSqlDriver::WhereStatement, |
| d->tableName, |
| primaryValues(row), |
| false); |
| static const QString wh = Sql::where() + Sql::sp(); |
| if (d->filter.startsWith(wh, Qt::CaseInsensitive)) |
| d->filter.remove(0, wh.length()); |
| |
| QString stmt; |
| |
| if (!d->filter.isEmpty()) |
| stmt = selectStatement(); |
| |
| d->sortColumn = table_sort_col; |
| d->filter = table_filter; |
| |
| if (stmt.isEmpty()) |
| return false; |
| |
| bool exists; |
| QSqlRecord newValues; |
| |
| { |
| QSqlQuery q(d->db); |
| q.setForwardOnly(true); |
| if (!q.exec(stmt)) |
| return false; |
| |
| exists = q.next(); |
| newValues = q.record(); |
| } |
| |
| bool needsAddingToCache = !exists || d->cache.contains(row); |
| |
| if (!needsAddingToCache) { |
| const QSqlRecord curValues = record(row); |
| needsAddingToCache = curValues.count() != newValues.count(); |
| if (!needsAddingToCache) { |
| // Look for changed values. Primary key fields are customarily first |
| // and probably change less often than other fields, so start at the end. |
| for (int f = curValues.count() - 1; f >= 0; --f) { |
| if (curValues.value(f) != newValues.value(f)) { |
| needsAddingToCache = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (needsAddingToCache) { |
| d->cache[row].refresh(exists, newValues); |
| emit headerDataChanged(Qt::Vertical, row, row); |
| emit dataChanged(createIndex(row, 0), createIndex(row, columnCount() - 1)); |
| } |
| |
| 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(); |
| |
| const auto it = d->cache.constFind(index.row()); |
| if (it != d->cache.constEnd() && it->op() != QSqlTableModelPrivate::None) |
| return it->rec().value(index.column()); |
| |
| return QSqlQueryModel::data(index, role); |
| } |
| |
| /*! |
| \reimp |
| */ |
| QVariant QSqlTableModel::headerData(int section, Qt::Orientation orientation, int role) const |
| { |
| Q_D(const QSqlTableModel); |
| if (orientation == Qt::Vertical && role == Qt::DisplayRole) { |
| const QSqlTableModelPrivate::Op op = d->cache.value(section).op(); |
| if (op == QSqlTableModelPrivate::Insert) |
| return QLatin1String("*"); |
| else if (op == QSqlTableModelPrivate::Delete) |
| return QLatin1String("!"); |
| } |
| return QSqlQueryModel::headerData(section, orientation, role); |
| } |
| |
| /*! |
| \overload |
| \since 5.0 |
| |
| Returns \c true if the model contains modified values that have not been |
| committed to the database, otherwise false. |
| */ |
| bool QSqlTableModel::isDirty() const |
| { |
| Q_D(const QSqlTableModel); |
| QSqlTableModelPrivate::CacheMap::ConstIterator i = d->cache.constBegin(); |
| const QSqlTableModelPrivate::CacheMap::ConstIterator e = d->cache.constEnd(); |
| for (; i != e; ++i) { |
| if (!i.value().submitted()) |
| return true; |
| } |
| return false; |
| } |
| |
| /*! |
| Returns \c 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; |
| |
| const auto it = d->cache.constFind(index.row()); |
| if (it == d->cache.constEnd()) |
| return false; |
| const QSqlTableModelPrivate::ModifiedRow &row = *it; |
| if (row.submitted()) |
| return false; |
| |
| return row.op() == QSqlTableModelPrivate::Insert |
| || row.op() == QSqlTableModelPrivate::Delete |
| || (row.op() == QSqlTableModelPrivate::Update |
| && row.rec().isGenerated(index.column())); |
| } |
| |
| /*! |
| Sets the data for the item \a index for the role \a role to \a |
| value. |
| |
| For edit strategy OnFieldChange, an index may receive a change |
| only if no other index has a cached change. Changes are |
| submitted immediately. However, rows that have not yet been |
| inserted in the database may be freely changed and are not |
| submitted automatically. Submitted changes are not reverted upon |
| failure. |
| |
| For OnRowChange, an index may receive a change only if no other |
| row has a cached change. Changes are not submitted automatically. |
| |
| Returns \c true if \a value is equal to the current value. However, |
| the value will not be submitted to the database. |
| |
| Returns \c true if the value could be set or false on error, for |
| example if \a index is out of bounds. |
| |
| Returns \c false if the role is not Qt::EditRole. To set data |
| for roles other than EditRole, either use a custom proxy model |
| or subclass QSqlTableModel. |
| |
| \sa editStrategy(), data(), submit(), submitAll(), revertRow() |
| */ |
| bool QSqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role) |
| { |
| Q_D(QSqlTableModel); |
| if (d->busyInsertingRows) |
| return false; |
| |
| if (role != Qt::EditRole) |
| return QSqlQueryModel::setData(index, value, role); |
| |
| if (!index.isValid() || index.column() >= d->rec.count() || index.row() >= rowCount()) |
| return false; |
| |
| if (!(flags(index) & Qt::ItemIsEditable)) |
| return false; |
| |
| const QVariant oldValue = QSqlTableModel::data(index, role); |
| if (value == oldValue |
| && value.isNull() == oldValue.isNull() |
| && d->cache.value(index.row()).op() != QSqlTableModelPrivate::Insert) |
| return true; |
| |
| QSqlTableModelPrivate::ModifiedRow &row = d->cache[index.row()]; |
| |
| if (row.op() == QSqlTableModelPrivate::None) |
| row = QSqlTableModelPrivate::ModifiedRow(QSqlTableModelPrivate::Update, |
| QSqlQueryModel::record(index.row())); |
| |
| row.setValue(index.column(), value); |
| emit dataChanged(index, index); |
| |
| if (d->strategy == OnFieldChange && row.op() != QSqlTableModelPrivate::Insert) |
| return submit(); |
| |
| return true; |
| } |
| |
| #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
| /*! |
| \reimp |
| */ |
| bool QSqlTableModel::clearItemData(const QModelIndex &index) |
| { |
| return setData(index, QVariant(), Qt::EditRole); |
| } |
| #endif |
| |
| /*! |
| 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 \c true if successful; otherwise |
| returns \c 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 = primaryValues(row); |
| const bool prepStatement = d->db.driver()->hasFeature(QSqlDriver::PreparedQueries); |
| const QString stmt = d->db.driver()->sqlStatement(QSqlDriver::UpdateStatement, d->tableName, |
| rec, prepStatement); |
| const 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; |
| } |
| |
| return d->exec(Sql::concat(stmt, where), 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 \c 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); |
| |
| const bool prepStatement = d->db.driver()->hasFeature(QSqlDriver::PreparedQueries); |
| const QString stmt = d->db.driver()->sqlStatement(QSqlDriver::InsertStatement, d->tableName, |
| rec, prepStatement); |
| |
| if (stmt.isEmpty()) { |
| d->error = QSqlError(QLatin1String("No Fields to update"), QString(), |
| QSqlError::StatementError); |
| return false; |
| } |
| |
| return d->exec(stmt, prepStatement, rec, QSqlRecord() /* no where values */); |
| } |
| |
| /*! |
| Deletes the given \a row from the currently active database table. |
| |
| This is a low-level method that operates directly on the database |
| and should not be called directly. Use removeRow() or removeRows() |
| to delete values. The model will decide depending on its edit strategy |
| when to modify the database. |
| |
| Returns \c true if the row was deleted; otherwise returns \c false. |
| |
| \sa removeRow(), removeRows() |
| */ |
| bool QSqlTableModel::deleteRowFromTable(int row) |
| { |
| Q_D(QSqlTableModel); |
| emit beforeDelete(row); |
| |
| const QSqlRecord whereValues = primaryValues(row); |
| const bool prepStatement = d->db.driver()->hasFeature(QSqlDriver::PreparedQueries); |
| const QString stmt = d->db.driver()->sqlStatement(QSqlDriver::DeleteStatement, |
| d->tableName, |
| QSqlRecord(), |
| prepStatement); |
| const QString where = d->db.driver()->sqlStatement(QSqlDriver::WhereStatement, |
| d->tableName, |
| whereValues, |
| prepStatement); |
| |
| if (stmt.isEmpty() || where.isEmpty()) { |
| d->error = QSqlError(QLatin1String("Unable to delete row"), QString(), |
| QSqlError::StatementError); |
| return false; |
| } |
| |
| return d->exec(Sql::concat(stmt, where), prepStatement, QSqlRecord() /* no new values */, whereValues); |
| } |
| |
| /*! |
| Submits all pending changes and returns \c true on success. |
| Returns \c false on error, detailed error information can be |
| obtained with lastError(). |
| |
| In OnManualSubmit, 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 without |
| losing data. |
| |
| \sa revertAll(), lastError() |
| */ |
| bool QSqlTableModel::submitAll() |
| { |
| Q_D(QSqlTableModel); |
| |
| bool success = true; |
| |
| const auto cachedKeys = d->cache.keys(); |
| for (int row : cachedKeys) { |
| // be sure cache *still* contains the row since overridden selectRow() could have called select() |
| QSqlTableModelPrivate::CacheMap::iterator it = d->cache.find(row); |
| if (it == d->cache.end()) |
| continue; |
| |
| QSqlTableModelPrivate::ModifiedRow &mrow = it.value(); |
| if (mrow.submitted()) |
| continue; |
| |
| switch (mrow.op()) { |
| case QSqlTableModelPrivate::Insert: |
| success = insertRowIntoTable(mrow.rec()); |
| break; |
| case QSqlTableModelPrivate::Update: |
| success = updateRowInTable(row, mrow.rec()); |
| break; |
| case QSqlTableModelPrivate::Delete: |
| success = deleteRowFromTable(row); |
| break; |
| case QSqlTableModelPrivate::None: |
| Q_ASSERT_X(false, "QSqlTableModel::submitAll()", "Invalid cache operation"); |
| break; |
| } |
| |
| if (success) { |
| if (d->strategy != OnManualSubmit && mrow.op() == QSqlTableModelPrivate::Insert) { |
| int c = mrow.rec().indexOf(d->autoColumn); |
| if (c != -1 && !mrow.rec().isGenerated(c)) |
| mrow.setValue(c, d->editQuery.lastInsertId()); |
| } |
| mrow.setSubmitted(); |
| if (d->strategy != OnManualSubmit) |
| success = selectRow(row); |
| } |
| |
| if (!success) |
| break; |
| } |
| |
| if (success) { |
| if (d->strategy == OnManualSubmit) |
| success = select(); |
| } |
| |
| return success; |
| } |
| |
| /*! |
| 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 \c true on success; otherwise returns \c false. Use lastError() |
| to query detailed error information. |
| |
| Does not automatically repopulate the model. Submitted rows are |
| refreshed from the database on success. |
| |
| \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 or OnFieldChange. Does nothing for the OnManualSubmit |
| strategy. |
| |
| 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 || d->strategy == OnFieldChange) |
| 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); |
| |
| const QList<int> rows(d->cache.keys()); |
| for (int i = rows.size() - 1; i >= 0; --i) |
| revertRow(rows.value(i)); |
| } |
| |
| /*! |
| 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); |
| d->revertCachedRow(row); |
| } |
| |
| /*! |
| 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 the model's database connection. |
| */ |
| 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); |
| QSqlField f = d->rec.field(d->sortColumn); |
| if (!f.isValid()) |
| return QString(); |
| |
| //we can safely escape the field because it would have been obtained from the database |
| //and have the correct case |
| QString field = d->tableName + QLatin1Char('.') |
| + d->db.driver()->escapeIdentifier(f.name(), QSqlDriver::FieldName); |
| field = d->sortOrder == Qt::AscendingOrder ? Sql::asc(field) : Sql::desc(field); |
| return Sql::orderBy(field); |
| } |
| |
| /*! |
| Returns the index of the field \a fieldName, or -1 if no corresponding field |
| exists in the model. |
| */ |
| 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); |
| if (d->tableName.isEmpty()) { |
| d->error = QSqlError(QLatin1String("No table name given"), QString(), |
| QSqlError::StatementError); |
| return QString(); |
| } |
| if (d->rec.isEmpty()) { |
| d->error = QSqlError(QLatin1String("Unable to find table ") + d->tableName, QString(), |
| QSqlError::StatementError); |
| return QString(); |
| } |
| |
| const QString stmt = d->db.driver()->sqlStatement(QSqlDriver::SelectStatement, |
| d->tableName, |
| d->rec, |
| false); |
| if (stmt.isEmpty()) { |
| d->error = QSqlError(QLatin1String("Unable to select fields from table ") + d->tableName, |
| QString(), QSqlError::StatementError); |
| return stmt; |
| } |
| return Sql::concat(Sql::concat(stmt, Sql::where(d->filter)), orderByClause()); |
| } |
| |
| /*! |
| Removes \a count columns from the \a parent model, starting at |
| index \a column. |
| |
| Returns if the columns were successfully removed; otherwise |
| returns \c 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. |
| |
| When the edit strategy is OnManualSubmit, deletion of rows from |
| the database is delayed until submitAll() is called. |
| |
| For OnFieldChange and OnRowChange, only one row may be deleted |
| at a time and only if no other row has a cached change. Deletions |
| are submitted immediately to the database. The model retains a |
| blank row for successfully deleted row until refreshed with select(). |
| |
| After failed deletion, the operation is not reverted in the model. |
| The application may resubmit or revert. |
| |
| Inserted but not yet successfully submitted rows in the range to be |
| removed are immediately removed from the model. |
| |
| Before a row is deleted from the database, the beforeDelete() |
| signal is emitted. |
| |
| If row < 0 or row + count > rowCount(), no action is taken and |
| false is returned. Returns \c true if all rows could be removed; |
| otherwise returns \c false. Detailed database 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; |
| else if (row + count > rowCount()) |
| return false; |
| else if (!count) |
| return true; |
| |
| if (d->strategy != OnManualSubmit) |
| if (count > 1 || (d->cache.value(row).submitted() && isDirty())) |
| return false; |
| |
| // Iterate backwards so we don't have to worry about removed rows causing |
| // higher cache entries to shift downwards. |
| for (int idx = row + count - 1; idx >= row; --idx) { |
| QSqlTableModelPrivate::ModifiedRow& mrow = d->cache[idx]; |
| if (mrow.op() == QSqlTableModelPrivate::Insert) { |
| revertRow(idx); |
| } else { |
| if (mrow.op() == QSqlTableModelPrivate::None) |
| mrow = QSqlTableModelPrivate::ModifiedRow(QSqlTableModelPrivate::Delete, |
| QSqlQueryModel::record(idx)); |
| else |
| mrow.setOp(QSqlTableModelPrivate::Delete); |
| if (d->strategy == OnManualSubmit) |
| emit headerDataChanged(Qt::Vertical, idx, idx); |
| } |
| } |
| |
| if (d->strategy != OnManualSubmit) |
| return submit(); |
| |
| 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. |
| |
| For edit strategies OnFieldChange and OnRowChange, only one row |
| may be inserted at a time and the model may not contain other |
| cached changes. |
| |
| The primeInsert() signal will be emitted for each new row. |
| Connect to it if you want to initialize the new row with default |
| values. |
| |
| Does not submit rows, regardless of edit strategy. |
| |
| Returns \c false if the parameters are out of bounds or the row cannot be |
| inserted; otherwise returns \c 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; |
| |
| if (d->strategy != OnManualSubmit) |
| if (count != 1 || isDirty()) |
| return false; |
| |
| d->busyInsertingRows = true; |
| beginInsertRows(parent, row, row + count - 1); |
| |
| if (d->strategy != OnManualSubmit) |
| d->cache.empty(); |
| |
| 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].recRef()); |
| } |
| |
| endInsertRows(); |
| d->busyInsertingRows = false; |
| return true; |
| } |
| |
| /*! |
| Inserts the \a record at position \a row. If \a row is negative, |
| the record will be appended to the end. Calls insertRows() and |
| setRecord() internally. |
| |
| Returns \c true if the record could be inserted, otherwise false. |
| |
| Changes are submitted immediately for OnFieldChange and |
| OnRowChange. Failure does not leave a new row in the model. |
| |
| \sa insertRows(), removeRows(), setRecord() |
| */ |
| bool QSqlTableModel::insertRecord(int row, const QSqlRecord &record) |
| { |
| if (row < 0) |
| row = rowCount(); |
| if (!insertRow(row, QModelIndex())) |
| return false; |
| if (!setRecord(row, record)) { |
| revertRow(row); |
| return false; |
| } |
| return true; |
| } |
| |
| /*! \reimp |
| */ |
| int QSqlTableModel::rowCount(const QModelIndex &parent) const |
| { |
| Q_D(const QSqlTableModel); |
| |
| if (parent.isValid()) |
| return 0; |
| |
| return QSqlQueryModel::rowCount() + d->insertCount(); |
| } |
| |
| /*! |
| 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 auto it = d->cache.constFind(item.row()); |
| if (it != d->cache.constEnd() && it->insert()) |
| return QModelIndex(); |
| |
| const int rowOffset = d->insertCount(item.row()); |
| return QSqlQueryModel::indexInQuery(createIndex(item.row() - rowOffset, item.column(), item.internalPointer())); |
| } |
| |
| /*! |
| 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); |
| beginResetModel(); |
| d->clear(); |
| QSqlQueryModel::clear(); |
| endResetModel(); |
| } |
| |
| /*! \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 { }; |
| |
| bool editable = true; |
| |
| if (d->rec.field(index.column()).isReadOnly()) { |
| editable = false; |
| } |
| else { |
| const QSqlTableModelPrivate::ModifiedRow mrow = d->cache.value(index.row()); |
| if (mrow.op() == QSqlTableModelPrivate::Delete) { |
| editable = false; |
| } |
| else if (d->strategy == OnFieldChange) { |
| if (mrow.op() != QSqlTableModelPrivate::Insert) |
| if (!isDirty(index) && isDirty()) |
| editable = false; |
| } |
| else if (d->strategy == OnRowChange) { |
| if (mrow.submitted() && isDirty()) |
| editable = false; |
| } |
| } |
| |
| if (!editable) |
| return QSqlQueryModel::flags(index); |
| else |
| return QSqlQueryModel::flags(index) | Qt::ItemIsEditable; |
| } |
| |
| /*! |
| This is an overloaded function. |
| |
| It returns an empty record, having only the field names. This function can be used to |
| retrieve the field names of a record. |
| |
| \sa QSqlRecord::isEmpty() |
| */ |
| QSqlRecord QSqlTableModel::record() const |
| { |
| return QSqlQueryModel::record(); |
| } |
| |
| /*! |
| \since 5.0 |
| Returns the record at \a row in the model. |
| |
| 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 QSqlTableModel::record(int row) const |
| { |
| Q_D(const QSqlTableModel); |
| |
| // the query gets the values from virtual data() |
| QSqlRecord rec = QSqlQueryModel::record(row); |
| |
| // get generated flags from the cache |
| const QSqlTableModelPrivate::ModifiedRow mrow = d->cache.value(row); |
| if (mrow.op() != QSqlTableModelPrivate::None) { |
| const QSqlRecord &crec = mrow.rec(); |
| for (int i = 0, cnt = rec.count(); i < cnt; ++i) |
| rec.setGenerated(i, crec.isGenerated(i)); |
| } |
| |
| return rec; |
| } |
| |
| /*! |
| Applies \a values to the \a row in the model. The source and |
| target fields are mapped by field name, not by position in |
| the record. |
| |
| Note that the generated flags in \a values are preserved to |
| determine whether the corresponding fields are used when changes |
| are submitted to the database. By default, it is set to \c true |
| for all fields in a QSqlRecord. You must set the flag to \c false |
| using \l{QSqlRecord::}{setGenerated}(false) for any value in |
| \a values, to save changes back to the database. |
| |
| For edit strategies OnFieldChange and OnRowChange, a row may |
| receive a change only if no other row has a cached change. |
| Changes are submitted immediately. Submitted changes are not |
| reverted upon failure. |
| |
| Returns \c true if all the values could be set; otherwise returns |
| false. |
| |
| \sa record(), editStrategy() |
| */ |
| bool QSqlTableModel::setRecord(int row, const QSqlRecord &values) |
| { |
| Q_D(QSqlTableModel); |
| Q_ASSERT_X(row >= 0, "QSqlTableModel::setRecord()", "Cannot set a record to a row less than 0"); |
| if (d->busyInsertingRows) |
| return false; |
| |
| if (row >= rowCount()) |
| return false; |
| |
| if (d->cache.value(row).op() == QSqlTableModelPrivate::Delete) |
| return false; |
| |
| if (d->strategy != OnManualSubmit && d->cache.value(row).submitted() && isDirty()) |
| return false; |
| |
| // Check field names and remember mapping |
| typedef QMap<int, int> Map; |
| Map map; |
| for (int i = 0; i < values.count(); ++i) { |
| int idx = d->nameToIndex(values.fieldName(i)); |
| if (idx == -1) |
| return false; |
| map[i] = idx; |
| } |
| |
| QSqlTableModelPrivate::ModifiedRow &mrow = d->cache[row]; |
| if (mrow.op() == QSqlTableModelPrivate::None) |
| mrow = QSqlTableModelPrivate::ModifiedRow(QSqlTableModelPrivate::Update, |
| QSqlQueryModel::record(row)); |
| |
| Map::const_iterator i = map.constBegin(); |
| const Map::const_iterator e = map.constEnd(); |
| for ( ; i != e; ++i) { |
| // have to use virtual setData() here rather than mrow.setValue() |
| EditStrategy strategy = d->strategy; |
| d->strategy = OnManualSubmit; |
| QModelIndex cIndex = createIndex(row, i.value()); |
| setData(cIndex, values.value(i.key())); |
| d->strategy = strategy; |
| // setData() sets generated to TRUE, but source record should prevail. |
| if (!values.isGenerated(i.key())) |
| mrow.recRef().setGenerated(i.value(), false); |
| } |
| |
| if (d->strategy != OnManualSubmit) |
| return submit(); |
| |
| return true; |
| } |
| |
| /*! |
| \since 5.1 |
| Returns a record containing the fields represented in the primary key set to the values |
| at \a row. If no primary key is defined, the returned record will contain all fields. |
| |
| \sa primaryKey() |
| */ |
| QSqlRecord QSqlTableModel::primaryValues(int row) const |
| { |
| Q_D(const QSqlTableModel); |
| |
| const QSqlRecord &pIndex = d->primaryIndex.isEmpty() ? d->rec : d->primaryIndex; |
| |
| QSqlTableModelPrivate::ModifiedRow mr = d->cache.value(row); |
| if (mr.op() != QSqlTableModelPrivate::None) |
| return mr.primaryValues(pIndex); |
| else |
| return QSqlQueryModel::record(row).keyValues(pIndex); |
| } |
| |
| QT_END_NAMESPACE |