Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsqlquerymodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include <qdebug.h>
9#include <qsqldriver.h>
10#include <qsqlfield.h>
11
13
14using namespace Qt::StringLiterals;
15
16#define QSQL_PREFETCH 255
17
18void QSqlQueryModelPrivate::prefetch(int limit)
19{
20 Q_Q(QSqlQueryModel);
21
22 if (atEnd || limit <= bottom.row() || bottom.column() == -1)
23 return;
24
25 QModelIndex newBottom;
26 const int oldBottomRow = qMax(bottom.row(), 0);
27
28 // try to seek directly
29 if (query.seek(limit)) {
30 newBottom = q->createIndex(limit, bottom.column());
31 } else {
32 // have to seek back to our old position for MS Access
33 int i = oldBottomRow;
34 if (query.seek(i)) {
35 while (query.next())
36 ++i;
37 newBottom = q->createIndex(i, bottom.column());
38 } else {
39 // empty or invalid query
40 newBottom = q->createIndex(-1, bottom.column());
41 }
42 atEnd = true; // this is the end.
43 }
44 if (newBottom.row() >= 0 && newBottom.row() > bottom.row()) {
45 q->beginInsertRows(QModelIndex(), bottom.row() + 1, newBottom.row());
46 bottom = newBottom;
47 q->endInsertRows();
48 } else {
49 bottom = newBottom;
50 }
51}
52
53QSqlQueryModelPrivate::~QSqlQueryModelPrivate()
54{
55}
56
57void QSqlQueryModelPrivate::initColOffsets(int size)
58{
59 colOffsets.resize(size);
60 memset(colOffsets.data(), 0, colOffsets.size() * sizeof(int));
61}
62
63int QSqlQueryModelPrivate::columnInQuery(int modelColumn) const
64{
65 if (modelColumn < 0 || modelColumn >= rec.count() || !rec.isGenerated(modelColumn) || modelColumn >= colOffsets.size())
66 return -1;
67 return modelColumn - colOffsets[modelColumn];
68}
69
70/*!
71 \class QSqlQueryModel
72 \brief The QSqlQueryModel class provides a read-only data model for SQL
73 result sets.
74
75 \ingroup database
76 \inmodule QtSql
77
78 QSqlQueryModel is a high-level interface for executing SQL
79 statements and traversing the result set. It is built on top of
80 the lower-level QSqlQuery and can be used to provide data to
81 view classes such as QTableView. For example:
82
83 \snippet sqldatabase/sqldatabase_snippet.cpp 16
84
85 We set the model's query, then we set up the labels displayed in
86 the view header.
87
88 QSqlQueryModel can also be used to access a database
89 programmatically, without binding it to a view:
90
91 \snippet sqldatabase/sqldatabase.cpp 21
92
93 The code snippet above extracts the \c salary field from record 4 in
94 the result set of the \c SELECT query. Since \c salary is the 2nd
95 column (or column index 1), we can rewrite the last line as follows:
96
97 \snippet sqldatabase/sqldatabase.cpp 22
98
99 The model is read-only by default. To make it read-write, you
100 must subclass it and reimplement setData() and flags(). Another
101 option is to use QSqlTableModel, which provides a read-write
102 model based on a single database table.
103
104 The \l{querymodel} example illustrates how to use
105 QSqlQueryModel to display the result of a query. It also shows
106 how to subclass QSqlQueryModel to customize the contents of the
107 data before showing it to the user, and how to create a
108 read-write model based on QSqlQueryModel.
109
110 If the database doesn't return the number of selected rows in
111 a query, the model will fetch rows incrementally.
112 See fetchMore() for more information.
113
114 \sa QSqlTableModel, QSqlRelationalTableModel, QSqlQuery,
115 {Model/View Programming}, {Query Model Example}
116*/
117
118/*!
119 Creates an empty QSqlQueryModel with the given \a parent.
120 */
121QSqlQueryModel::QSqlQueryModel(QObject *parent)
122 : QAbstractTableModel(*new QSqlQueryModelPrivate, parent)
123{
124}
125
126/*! \internal
127 */
128QSqlQueryModel::QSqlQueryModel(QSqlQueryModelPrivate &dd, QObject *parent)
129 : QAbstractTableModel(dd, parent)
130{
131}
132
133/*!
134 Destroys the object and frees any allocated resources.
135
136 \sa clear()
137*/
138QSqlQueryModel::~QSqlQueryModel()
139{
140}
141
142/*!
143 Fetches more rows from a database.
144 This only affects databases that don't report back the size of a query
145 (see QSqlDriver::hasFeature()).
146
147 To force fetching of the entire result set, you can use the following:
148
149 \snippet code/src_sql_models_qsqlquerymodel.cpp 0
150
151 \a parent should always be an invalid QModelIndex.
152
153 \sa canFetchMore()
154*/
155void QSqlQueryModel::fetchMore(const QModelIndex &parent)
156{
157 Q_D(QSqlQueryModel);
158 if (parent.isValid())
159 return;
160 d->prefetch(qMax(d->bottom.row(), 0) + QSQL_PREFETCH);
161}
162
163/*!
164 Returns \c true if it is possible to read more rows from the database.
165 This only affects databases that don't report back the size of a query
166 (see QSqlDriver::hasFeature()).
167
168 \a parent should always be an invalid QModelIndex.
169
170 \sa fetchMore()
171 */
172bool QSqlQueryModel::canFetchMore(const QModelIndex &parent) const
173{
174 Q_D(const QSqlQueryModel);
175 return (!parent.isValid() && !d->atEnd);
176}
177
178/*!
179 \since 5.10
180 \reimp
181
182 Returns the model's role names.
183
184 Qt defines only one role for the QSqlQueryModel:
185
186 \table
187 \header
188 \li Qt Role
189 \li QML Role Name
190 \row
191 \li Qt::DisplayRole
192 \li display
193 \endtable
194*/
195QHash<int, QByteArray> QSqlQueryModel::roleNames() const
196{
197 static const QHash<int, QByteArray> names = {
198 { Qt::DisplayRole, QByteArrayLiteral("display") }
199 };
200 return names;
201}
202
203/*! \internal
204 */
205void QSqlQueryModel::beginInsertRows(const QModelIndex &parent, int first, int last)
206{
207 Q_D(QSqlQueryModel);
208 if (!d->nestedResetLevel)
209 QAbstractTableModel::beginInsertRows(parent, first, last);
210}
211
212/*! \internal
213 */
214void QSqlQueryModel::endInsertRows()
215{
216 Q_D(QSqlQueryModel);
217 if (!d->nestedResetLevel)
218 QAbstractTableModel::endInsertRows();
219}
220
221/*! \internal
222 */
223void QSqlQueryModel::beginRemoveRows(const QModelIndex &parent, int first, int last)
224{
225 Q_D(QSqlQueryModel);
226 if (!d->nestedResetLevel)
227 QAbstractTableModel::beginRemoveRows(parent, first, last);
228}
229
230/*! \internal
231 */
232void QSqlQueryModel::endRemoveRows()
233{
234 Q_D(QSqlQueryModel);
235 if (!d->nestedResetLevel)
236 QAbstractTableModel::endRemoveRows();
237}
238
239/*! \internal
240 */
241void QSqlQueryModel::beginInsertColumns(const QModelIndex &parent, int first, int last)
242{
243 Q_D(QSqlQueryModel);
244 if (!d->nestedResetLevel)
245 QAbstractTableModel::beginInsertColumns(parent, first, last);
246}
247
248/*! \internal
249 */
250void QSqlQueryModel::endInsertColumns()
251{
252 Q_D(QSqlQueryModel);
253 if (!d->nestedResetLevel)
254 QAbstractTableModel::endInsertColumns();
255}
256
257/*! \internal
258 */
259void QSqlQueryModel::beginRemoveColumns(const QModelIndex &parent, int first, int last)
260{
261 Q_D(QSqlQueryModel);
262 if (!d->nestedResetLevel)
263 QAbstractTableModel::beginRemoveColumns(parent, first, last);
264}
265
266/*! \internal
267 */
268void QSqlQueryModel::endRemoveColumns()
269{
270 Q_D(QSqlQueryModel);
271 if (!d->nestedResetLevel)
272 QAbstractTableModel::endRemoveColumns();
273}
274
275/*! \internal
276 */
277void QSqlQueryModel::beginResetModel()
278{
279 Q_D(QSqlQueryModel);
280 if (!d->nestedResetLevel)
281 QAbstractTableModel::beginResetModel();
282 ++d->nestedResetLevel;
283}
284
285/*! \internal
286 */
287void QSqlQueryModel::endResetModel()
288{
289 Q_D(QSqlQueryModel);
290 --d->nestedResetLevel;
291 if (!d->nestedResetLevel)
292 QAbstractTableModel::endResetModel();
293}
294
295/*! \fn int QSqlQueryModel::rowCount(const QModelIndex &parent) const
296
297 If the database supports returning the size of a query
298 (see QSqlDriver::hasFeature()), the number of rows of the current
299 query is returned. Otherwise, returns the number of rows
300 currently cached on the client.
301
302 \a parent should always be an invalid QModelIndex.
303
304 \sa canFetchMore(), QSqlDriver::hasFeature()
305 */
306int QSqlQueryModel::rowCount(const QModelIndex &index) const
307{
308 Q_D(const QSqlQueryModel);
309 return index.isValid() ? 0 : d->bottom.row() + 1;
310}
311
312/*! \reimp
313 */
314int QSqlQueryModel::columnCount(const QModelIndex &index) const
315{
316 Q_D(const QSqlQueryModel);
317 return index.isValid() ? 0 : d->rec.count();
318}
319
320/*!
321 Returns the value for the specified \a item and \a role.
322
323 If \a item is out of bounds or if an error occurred, an invalid
324 QVariant is returned.
325
326 \sa lastError()
327*/
328QVariant QSqlQueryModel::data(const QModelIndex &item, int role) const
329{
330 Q_D(const QSqlQueryModel);
331 if (!item.isValid())
332 return QVariant();
333
334 if (role & ~(Qt::DisplayRole | Qt::EditRole))
335 return QVariant();
336
337 if (!d->rec.isGenerated(item.column()))
338 return QVariant();
339 QModelIndex dItem = indexInQuery(item);
340 if (dItem.row() > d->bottom.row())
341 const_cast<QSqlQueryModelPrivate *>(d)->prefetch(dItem.row());
342
343 if (!d->query.seek(dItem.row())) {
344 d->error = d->query.lastError();
345 return QVariant();
346 }
347
348 return d->query.value(dItem.column());
349}
350
351/*!
352 Returns the header data for the given \a role in the \a section
353 of the header with the specified \a orientation.
354*/
355QVariant QSqlQueryModel::headerData(int section, Qt::Orientation orientation, int role) const
356{
357 Q_D(const QSqlQueryModel);
358 if (orientation == Qt::Horizontal) {
359 QVariant val = d->headers.value(section).value(role);
360 if (role == Qt::DisplayRole && !val.isValid())
361 val = d->headers.value(section).value(Qt::EditRole);
362 if (val.isValid())
363 return val;
364 if (role == Qt::DisplayRole && d->rec.count() > section && d->columnInQuery(section) != -1)
365 return d->rec.fieldName(section);
366 }
367 return QAbstractItemModel::headerData(section, orientation, role);
368}
369
370/*!
371 This virtual function is called whenever the query changes. The
372 default implementation does nothing.
373
374 query() returns the new query.
375
376 \sa query(), setQuery()
377 */
378void QSqlQueryModel::queryChange()
379{
380 // do nothing
381}
382
383#if QT_REMOVAL_QT7_DEPRECATED_SINCE(6, 2)
384/*!
385 \deprecated [6.2] Use the \c{setQuery(QSqlQuery &&query)} overload instead.
386 This overload will be removed in Qt 7.
387
388 \overload
389*/
390void QSqlQueryModel::setQuery(const QSqlQuery &query)
391{
392 QT_IGNORE_DEPRECATIONS(QSqlQuery copy = query;)
393 setQuery(std::move(copy));
394}
395#endif // QT_REMOVAL_QT7_DEPRECATED_SINCE(6, 2)
396
397/*!
398 Resets the model and sets the data provider to be the given \a
399 query. Note that the query must be active and must not be
400 isForwardOnly().
401
402 lastError() can be used to retrieve verbose information if there
403 was an error setting the query.
404
405 \note Calling setQuery() will remove any inserted columns.
406
407 \since 6.2
408
409 \sa query(), QSqlQuery::isActive(), QSqlQuery::setForwardOnly(), lastError()
410*/
411void QSqlQueryModel::setQuery(QSqlQuery &&query)
412{
413 Q_D(QSqlQueryModel);
414 beginResetModel();
415
416 QSqlRecord newRec = query.record();
417 bool columnsChanged = (newRec != d->rec);
418
419 if (d->colOffsets.size() != newRec.count() || columnsChanged)
420 d->initColOffsets(newRec.count());
421
422 d->bottom = QModelIndex();
423 d->error = QSqlError();
424 d->query = std::move(query);
425 d->rec = newRec;
426 d->atEnd = true;
427
428 if (d->query.isForwardOnly()) {
429 d->error = QSqlError("Forward-only queries cannot be used in a data model"_L1,
430 QString(), QSqlError::ConnectionError);
431 endResetModel();
432 return;
433 }
434
435 if (!d->query.isActive()) {
436 d->error = d->query.lastError();
437 endResetModel();
438 return;
439 }
440
441 if (d->query.driver()->hasFeature(QSqlDriver::QuerySize) && d->query.size() > 0) {
442 d->bottom = createIndex(d->query.size() - 1, d->rec.count() - 1);
443 } else {
444 d->bottom = createIndex(-1, d->rec.count() - 1);
445 d->atEnd = false;
446 }
447
448
449 // fetchMore does the rowsInserted stuff for incremental models
450 fetchMore();
451
452 endResetModel();
453 queryChange();
454}
455
456/*! \overload
457
458 Executes the query \a query for the given database connection \a
459 db. If no database (or an invalid database) is specified, the
460 default connection is used.
461
462 lastError() can be used to retrieve verbose information if there
463 was an error setting the query.
464
465 Example:
466 \snippet code/src_sql_models_qsqlquerymodel.cpp 1
467
468 \sa query(), queryChange(), lastError()
469*/
470void QSqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db)
471{
472 setQuery(QSqlQuery(query, db));
473}
474
475/*!
476 \since 6.9
477 Re-executes the current query to fetch the data from the same database connection.
478
479 \note \c refresh() is not applicable when the query contains bound values.
480
481 \sa setQuery(QSqlQuery &&query), QSqlQuery::boundValue()
482*/
483void QSqlQueryModel::refresh()
484{
485 Q_D(QSqlQueryModel);
486 const auto connName = d->query.driver()
487 ? d->query.driver()->connectionName() : QString();
488 setQuery(d->query.executedQuery(), QSqlDatabase::database(connName));
489}
490
491/*!
492 Clears the model and releases any acquired resource.
493*/
494void QSqlQueryModel::clear()
495{
496 Q_D(QSqlQueryModel);
497 beginResetModel();
498 d->error = QSqlError();
499 d->atEnd = true;
500 d->query.clear();
501 d->rec.clear();
502 d->colOffsets.clear();
503 d->bottom = QModelIndex();
504 d->headers.clear();
505 endResetModel();
506}
507
508/*!
509 Sets the caption for a horizontal header for the specified \a role to
510 \a value. This is useful if the model is used to
511 display data in a view (e.g., QTableView).
512
513 Returns \c true if \a orientation is Qt::Horizontal and
514 the \a section refers to a valid section; otherwise returns
515 false.
516
517 Note that this function cannot be used to modify values in the
518 database since the model is read-only.
519
520 \sa data()
521 */
522bool QSqlQueryModel::setHeaderData(int section, Qt::Orientation orientation,
523 const QVariant &value, int role)
524{
525 Q_D(QSqlQueryModel);
526 if (orientation != Qt::Horizontal || section < 0 || columnCount() <= section)
527 return false;
528
529 if (d->headers.size() <= section)
530 d->headers.resize(qMax(section + 1, 16));
531 d->headers[section][role] = value;
532 emit headerDataChanged(orientation, section, section);
533 return true;
534}
535
536/*!
537 Returns a reference to the const QSqlQuery object associated with this model.
538
539 \sa setQuery()
540*/
541const QSqlQuery &QSqlQueryModel::query(QT6_IMPL_NEW_OVERLOAD) const
542{
543 Q_D(const QSqlQueryModel);
544 return d->query;
545}
546
547/*!
548 Returns information about the last error that occurred on the
549 database.
550
551 \sa query()
552*/
553QSqlError QSqlQueryModel::lastError() const
554{
555 Q_D(const QSqlQueryModel);
556 return d->error;
557}
558
559/*!
560 Protected function which allows derived classes to set the value of
561 the last error that occurred on the database to \a error.
562
563 \sa lastError()
564*/
565void QSqlQueryModel::setLastError(const QSqlError &error)
566{
567 Q_D(QSqlQueryModel);
568 d->error = error;
569}
570
571/*!
572 Returns the record containing information about the fields of the
573 current query. If \a row is the index of a valid row, the record
574 will be populated with values from that row.
575
576 If the model is not initialized, an empty record will be
577 returned.
578
579 \sa QSqlRecord::isEmpty()
580*/
581QSqlRecord QSqlQueryModel::record(int row) const
582{
583 Q_D(const QSqlQueryModel);
584 if (row < 0)
585 return d->rec;
586
587 QSqlRecord rec = d->rec;
588 for (int i = 0; i < rec.count(); ++i)
589 rec.setValue(i, data(createIndex(row, i), Qt::EditRole));
590 return rec;
591}
592
593/*! \overload
594
595 Returns an empty record containing information about the fields
596 of the current query.
597
598 If the model is not initialized, an empty record will be
599 returned.
600
601 \sa QSqlRecord::isEmpty()
602 */
603QSqlRecord QSqlQueryModel::record() const
604{
605 Q_D(const QSqlQueryModel);
606 return d->rec;
607}
608
609/*!
610 Inserts \a count columns into the model at position \a column. The
611 \a parent parameter must always be an invalid QModelIndex, since
612 the model does not support parent-child relationships.
613
614 Returns \c true if \a column is within bounds; otherwise returns \c false.
615
616 By default, inserted columns are empty. To fill them with data,
617 reimplement data() and handle any inserted column separately:
618
619 \snippet sqldatabase/sqldatabase.cpp 23
620
621 \sa removeColumns()
622*/
623bool QSqlQueryModel::insertColumns(int column, int count, const QModelIndex &parent)
624{
625 Q_D(QSqlQueryModel);
626 if (count <= 0 || parent.isValid() || column < 0 || column > d->rec.count())
627 return false;
628
629 beginInsertColumns(parent, column, column + count - 1);
630 for (int c = 0; c < count; ++c) {
631 QSqlField field;
632 field.setReadOnly(true);
633 field.setGenerated(false);
634 d->rec.insert(column, field);
635 if (d->colOffsets.size() < d->rec.count()) {
636 int nVal = d->colOffsets.isEmpty() ? 0 : d->colOffsets[d->colOffsets.size() - 1];
637 d->colOffsets.append(nVal);
638 Q_ASSERT(d->colOffsets.size() >= d->rec.count());
639 }
640 for (qsizetype i = column + 1; i < d->colOffsets.size(); ++i)
641 ++d->colOffsets[i];
642 }
643 endInsertColumns();
644 return true;
645}
646
647/*!
648 Removes \a count columns from the model starting from position \a
649 column. The \a parent parameter must always be an invalid
650 QModelIndex, since the model does not support parent-child
651 relationships.
652
653 Removing columns effectively hides them. It does not affect the
654 underlying QSqlQuery.
655
656 Returns \c true if the columns were removed; otherwise returns \c false.
657 */
658bool QSqlQueryModel::removeColumns(int column, int count, const QModelIndex &parent)
659{
660 Q_D(QSqlQueryModel);
661 if (count <= 0 || parent.isValid() || column < 0 || column >= d->rec.count())
662 return false;
663
664 beginRemoveColumns(parent, column, column + count - 1);
665
666 for (int i = 0; i < count; ++i)
667 d->rec.remove(column);
668 for (qsizetype i = column; i < d->colOffsets.size(); ++i)
669 d->colOffsets[i] -= count;
670
671 endRemoveColumns();
672 return true;
673}
674
675/*!
676 Returns the index of the value in the database result set for the
677 given \a item in the model.
678
679 The return value is identical to \a item if no columns or rows
680 have been inserted, removed, or moved around.
681
682 Returns an invalid model index if \a item is out of bounds or if
683 \a item does not point to a value in the result set.
684
685 \sa QSqlTableModel::indexInQuery(), insertColumns(), removeColumns()
686*/
687QModelIndex QSqlQueryModel::indexInQuery(const QModelIndex &item) const
688{
689 Q_D(const QSqlQueryModel);
690 int modelColumn = d->columnInQuery(item.column());
691 if (modelColumn < 0)
692 return QModelIndex();
693 return createIndex(item.row(), modelColumn, item.internalPointer());
694}
695
696QT_END_NAMESPACE
697
698#include "moc_qsqlquerymodel.cpp"
#define QSQL_PREFETCH