source: trunk/src/sql/models/qsqlquerymodel.cpp@ 846

Last change on this file since 846 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 17.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtSql module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qsqlquerymodel.h"
43
44#include <qdebug.h>
45#include <qsqldriver.h>
46#include <qsqlfield.h>
47
48#include "qsqlquerymodel_p.h"
49
50QT_BEGIN_NAMESPACE
51
52#define QSQL_PREFETCH 255
53
54void QSqlQueryModelPrivate::prefetch(int limit)
55{
56 Q_Q(QSqlQueryModel);
57
58 if (atEnd || limit <= bottom.row() || bottom.column() == -1)
59 return;
60
61 QModelIndex newBottom;
62 const int oldBottomRow = qMax(bottom.row(), 0);
63
64 // try to seek directly
65 if (query.seek(limit)) {
66 newBottom = q->createIndex(limit, bottom.column());
67 } else {
68 // have to seek back to our old position for MS Access
69 int i = oldBottomRow;
70 if (query.seek(i)) {
71 while (query.next())
72 ++i;
73 newBottom = q->createIndex(i, bottom.column());
74 } else {
75 // empty or invalid query
76 newBottom = q->createIndex(-1, bottom.column());
77 }
78 atEnd = true; // this is the end.
79 }
80 if (newBottom.row() >= 0 && newBottom.row() > bottom.row()) {
81 q->beginInsertRows(QModelIndex(), bottom.row() + 1, newBottom.row());
82 bottom = newBottom;
83 q->endInsertRows();
84 } else {
85 bottom = newBottom;
86 }
87}
88
89QSqlQueryModelPrivate::~QSqlQueryModelPrivate()
90{
91}
92
93void QSqlQueryModelPrivate::initColOffsets(int size)
94{
95 colOffsets.resize(size);
96 memset(colOffsets.data(), 0, colOffsets.size() * sizeof(int));
97}
98
99/*!
100 \class QSqlQueryModel
101 \brief The QSqlQueryModel class provides a read-only data model for SQL
102 result sets.
103
104 \ingroup database
105 \inmodule QtSql
106
107 QSqlQueryModel is a high-level interface for executing SQL
108 statements and traversing the result set. It is built on top of
109 the lower-level QSqlQuery and can be used to provide data to
110 view classes such as QTableView. For example:
111
112 \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 16
113
114 We set the model's query, then we set up the labels displayed in
115 the view header.
116
117 QSqlQueryModel can also be used to access a database
118 programmatically, without binding it to a view:
119
120 \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 21
121
122 The code snippet above extracts the \c salary field from record 4 in
123 the result set of the query \c{SELECT * from employee}. Assuming
124 that \c salary is column 2, we can rewrite the last line as follows:
125
126 \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 22
127
128 The model is read-only by default. To make it read-write, you
129 must subclass it and reimplement setData() and flags(). Another
130 option is to use QSqlTableModel, which provides a read-write
131 model based on a single database table.
132
133 The \l{sql/querymodel} example illustrates how to use
134 QSqlQueryModel to display the result of a query. It also shows
135 how to subclass QSqlQueryModel to customize the contents of the
136 data before showing it to the user, and how to create a
137 read-write model based on QSqlQueryModel.
138
139 If the database doesn't return the amount of selected rows in
140 a query, the model will fetch rows incrementally.
141 See fetchMore() for more information.
142
143 \sa QSqlTableModel, QSqlRelationalTableModel, QSqlQuery,
144 {Model/View Programming}, {Query Model Example}
145*/
146
147/*!
148 Creates an empty QSqlQueryModel with the given \a parent.
149 */
150QSqlQueryModel::QSqlQueryModel(QObject *parent)
151 : QAbstractTableModel(*new QSqlQueryModelPrivate, parent)
152{
153}
154
155/*! \internal
156 */
157QSqlQueryModel::QSqlQueryModel(QSqlQueryModelPrivate &dd, QObject *parent)
158 : QAbstractTableModel(dd, parent)
159{
160}
161
162/*!
163 Destroys the object and frees any allocated resources.
164
165 \sa clear()
166*/
167QSqlQueryModel::~QSqlQueryModel()
168{
169}
170
171/*!
172 \since 4.1
173
174 Fetches more rows from a database.
175 This only affects databases that don't report back the size of a query
176 (see QSqlDriver::hasFeature()).
177
178 To force fetching of the entire database, you can use the following:
179
180 \snippet doc/src/snippets/code/src_sql_models_qsqlquerymodel.cpp 0
181
182 \a parent should always be an invalid QModelIndex.
183
184 \sa canFetchMore()
185*/
186void QSqlQueryModel::fetchMore(const QModelIndex &parent)
187{
188 Q_D(QSqlQueryModel);
189 if (parent.isValid())
190 return;
191 d->prefetch(qMax(d->bottom.row(), 0) + QSQL_PREFETCH);
192}
193
194/*!
195 \since 4.1
196
197 Returns true if it is possible to read more rows from the database.
198 This only affects databases that don't report back the size of a query
199 (see QSqlDriver::hasFeature()).
200
201 \a parent should always be an invalid QModelIndex.
202
203 \sa fetchMore()
204 */
205bool QSqlQueryModel::canFetchMore(const QModelIndex &parent) const
206{
207 Q_D(const QSqlQueryModel);
208 return (!parent.isValid() && !d->atEnd);
209}
210
211/*! \fn int QSqlQueryModel::rowCount(const QModelIndex &parent) const
212 \since 4.1
213
214 If the database supports returning the size of a query
215 (see QSqlDriver::hasFeature()), the amount of rows of the current
216 query is returned. Otherwise, returns the amount of rows
217 currently cached on the client.
218
219 \a parent should always be an invalid QModelIndex.
220
221 \sa canFetchMore(), QSqlDriver::hasFeature()
222 */
223int QSqlQueryModel::rowCount(const QModelIndex &index) const
224{
225 Q_D(const QSqlQueryModel);
226 return index.isValid() ? 0 : d->bottom.row() + 1;
227}
228
229/*! \reimp
230 */
231int QSqlQueryModel::columnCount(const QModelIndex &index) const
232{
233 Q_D(const QSqlQueryModel);
234 return index.isValid() ? 0 : d->rec.count();
235}
236
237/*!
238 Returns the value for the specified \a item and \a role.
239
240 If \a item is out of bounds or if an error occurred, an invalid
241 QVariant is returned.
242
243 \sa lastError()
244*/
245QVariant QSqlQueryModel::data(const QModelIndex &item, int role) const
246{
247 Q_D(const QSqlQueryModel);
248 if (!item.isValid())
249 return QVariant();
250
251 QVariant v;
252 if (role & ~(Qt::DisplayRole | Qt::EditRole))
253 return v;
254
255 if (!d->rec.isGenerated(item.column()))
256 return v;
257 QModelIndex dItem = indexInQuery(item);
258 if (dItem.row() > d->bottom.row())
259 const_cast<QSqlQueryModelPrivate *>(d)->prefetch(dItem.row());
260
261 if (!d->query.seek(dItem.row())) {
262 d->error = d->query.lastError();
263 return v;
264 }
265
266 return d->query.value(dItem.column());
267}
268
269/*!
270 Returns the header data for the given \a role in the \a section
271 of the header with the specified \a orientation.
272*/
273QVariant QSqlQueryModel::headerData(int section, Qt::Orientation orientation, int role) const
274{
275 Q_D(const QSqlQueryModel);
276 if (orientation == Qt::Horizontal) {
277 QVariant val = d->headers.value(section).value(role);
278 if (role == Qt::DisplayRole && !val.isValid())
279 val = d->headers.value(section).value(Qt::EditRole);
280 if (val.isValid())
281 return val;
282
283 // See if it's an inserted column (iiq.column() != -1)
284 QModelIndex dItem = indexInQuery(createIndex(0, section));
285
286 if (role == Qt::DisplayRole && d->rec.count() > section && dItem.column() != -1)
287 return d->rec.fieldName(section);
288 }
289 return QAbstractItemModel::headerData(section, orientation, role);
290}
291
292/*!
293 This virtual function is called whenever the query changes. The
294 default implementation does nothing.
295
296 query() returns the new query.
297
298 \sa query(), setQuery()
299 */
300void QSqlQueryModel::queryChange()
301{
302 // do nothing
303}
304
305/*!
306 Resets the model and sets the data provider to be the given \a
307 query. Note that the query must be active and must not be
308 isForwardOnly().
309
310 lastError() can be used to retrieve verbose information if there
311 was an error setting the query.
312
313 \note Calling setQuery() will remove any inserted columns.
314
315 \sa query(), QSqlQuery::isActive(), QSqlQuery::setForwardOnly(), lastError()
316*/
317void QSqlQueryModel::setQuery(const QSqlQuery &query)
318{
319 Q_D(QSqlQueryModel);
320 QSqlRecord newRec = query.record();
321 bool columnsChanged = (newRec != d->rec);
322 bool hasQuerySize = query.driver()->hasFeature(QSqlDriver::QuerySize);
323 bool hasNewData = (newRec != QSqlRecord()) || !query.lastError().isValid();
324
325 if (d->colOffsets.size() != newRec.count() || columnsChanged)
326 d->initColOffsets(newRec.count());
327
328 bool mustClearModel = d->bottom.isValid();
329 if (mustClearModel) {
330 d->atEnd = true;
331 beginRemoveRows(QModelIndex(), 0, qMax(d->bottom.row(), 0));
332 d->bottom = QModelIndex();
333 }
334
335 d->error = QSqlError();
336 d->query = query;
337 d->rec = newRec;
338
339 if (mustClearModel)
340 endRemoveRows();
341
342 d->atEnd = false;
343
344 if (columnsChanged && hasNewData)
345 reset();
346
347 if (!query.isActive() || query.isForwardOnly()) {
348 d->atEnd = true;
349 d->bottom = QModelIndex();
350 if (query.isForwardOnly())
351 d->error = QSqlError(QLatin1String("Forward-only queries "
352 "cannot be used in a data model"),
353 QString(), QSqlError::ConnectionError);
354 else
355 d->error = query.lastError();
356 return;
357 }
358 QModelIndex newBottom;
359 if (hasQuerySize && d->query.size() > 0) {
360 newBottom = createIndex(d->query.size() - 1, d->rec.count() - 1);
361 beginInsertRows(QModelIndex(), 0, qMax(0, newBottom.row()));
362 d->bottom = createIndex(d->query.size() - 1, columnsChanged ? 0 : d->rec.count() - 1);
363 d->atEnd = true;
364 endInsertRows();
365 } else {
366 newBottom = createIndex(-1, d->rec.count() - 1);
367 }
368 d->bottom = newBottom;
369
370 queryChange();
371
372 // fetchMore does the rowsInserted stuff for incremental models
373 fetchMore();
374}
375
376/*! \overload
377
378 Executes the query \a query for the given database connection \a
379 db. If no database (or an invalid database) is specified, the
380 default connection is used.
381
382 lastError() can be used to retrieve verbose information if there
383 was an error setting the query.
384
385 Example:
386 \snippet doc/src/snippets/code/src_sql_models_qsqlquerymodel.cpp 1
387
388 \sa query(), queryChange(), lastError()
389*/
390void QSqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db)
391{
392 setQuery(QSqlQuery(query, db));
393}
394
395/*!
396 Clears the model and releases any acquired resource.
397*/
398void QSqlQueryModel::clear()
399{
400 Q_D(QSqlQueryModel);
401 d->error = QSqlError();
402 d->atEnd = true;
403 d->query.clear();
404 d->rec.clear();
405 d->colOffsets.clear();
406 d->bottom = QModelIndex();
407 d->headers.clear();
408}
409
410/*!
411 Sets the caption for a horizontal header for the specified \a role to
412 \a value. This is useful if the model is used to
413 display data in a view (e.g., QTableView).
414
415 Returns true if \a orientation is Qt::Horizontal and
416 the \a section refers to a valid section; otherwise returns
417 false.
418
419 Note that this function cannot be used to modify values in the
420 database since the model is read-only.
421
422 \sa data()
423 */
424bool QSqlQueryModel::setHeaderData(int section, Qt::Orientation orientation,
425 const QVariant &value, int role)
426{
427 Q_D(QSqlQueryModel);
428 if (orientation != Qt::Horizontal || section < 0 || columnCount() <= section)
429 return false;
430
431 if (d->headers.size() <= section)
432 d->headers.resize(qMax(section + 1, 16));
433 d->headers[section][role] = value;
434 emit headerDataChanged(orientation, section, section);
435 return true;
436}
437
438/*!
439 Returns the QSqlQuery associated with this model.
440
441 \sa setQuery()
442*/
443QSqlQuery QSqlQueryModel::query() const
444{
445 Q_D(const QSqlQueryModel);
446 return d->query;
447}
448
449/*!
450 Returns information about the last error that occurred on the
451 database.
452
453 \sa query()
454*/
455QSqlError QSqlQueryModel::lastError() const
456{
457 Q_D(const QSqlQueryModel);
458 return d->error;
459}
460
461/*!
462 Protected function which allows derived classes to set the value of
463 the last error that occurred on the database to \a error.
464
465 \sa lastError()
466*/
467void QSqlQueryModel::setLastError(const QSqlError &error)
468{
469 Q_D(QSqlQueryModel);
470 d->error = error;
471}
472
473/*!
474 Returns the record containing information about the fields of the
475 current query. If \a row is the index of a valid row, the record
476 will be populated with values from that row.
477
478 If the model is not initialized, an empty record will be
479 returned.
480
481 \sa QSqlRecord::isEmpty()
482*/
483QSqlRecord QSqlQueryModel::record(int row) const
484{
485 Q_D(const QSqlQueryModel);
486 if (row < 0)
487 return d->rec;
488
489 QSqlRecord rec = d->rec;
490 for (int i = 0; i < rec.count(); ++i)
491 rec.setValue(i, data(createIndex(row, i), Qt::EditRole));
492 return rec;
493}
494
495/*! \overload
496
497 Returns an empty record containing information about the fields
498 of the current query.
499
500 If the model is not initialized, an empty record will be
501 returned.
502
503 \sa QSqlRecord::isEmpty()
504 */
505QSqlRecord QSqlQueryModel::record() const
506{
507 Q_D(const QSqlQueryModel);
508 return d->rec;
509}
510
511/*!
512 Inserts \a count columns into the model at position \a column. The
513 \a parent parameter must always be an invalid QModelIndex, since
514 the model does not support parent-child relationships.
515
516 Returns true if \a column is within bounds; otherwise returns false.
517
518 By default, inserted columns are empty. To fill them with data,
519 reimplement data() and handle any inserted column separately:
520
521 \snippet doc/src/snippets/sqldatabase/sqldatabase.cpp 23
522
523 \sa removeColumns()
524*/
525bool QSqlQueryModel::insertColumns(int column, int count, const QModelIndex &parent)
526{
527 Q_D(QSqlQueryModel);
528 if (count <= 0 || parent.isValid() || column < 0 || column > d->rec.count())
529 return false;
530
531 beginInsertColumns(parent, column, column + count - 1);
532 for (int c = 0; c < count; ++c) {
533 QSqlField field;
534 field.setReadOnly(true);
535 field.setGenerated(false);
536 d->rec.insert(column, field);
537 if (d->colOffsets.size() < d->rec.count()) {
538 int nVal = d->colOffsets.isEmpty() ? 0 : d->colOffsets[d->colOffsets.size() - 1];
539 d->colOffsets.append(nVal);
540 Q_ASSERT(d->colOffsets.size() >= d->rec.count());
541 }
542 for (int i = column + 1; i < d->colOffsets.count(); ++i)
543 ++d->colOffsets[i];
544 }
545 endInsertColumns();
546 return true;
547}
548
549/*!
550 Removes \a count columns from the model starting from position \a
551 column. The \a parent parameter must always be an invalid
552 QModelIndex, since the model does not support parent-child
553 relationships.
554
555 Removing columns effectively hides them. It does not affect the
556 underlying QSqlQuery.
557
558 Returns true if the columns were removed; otherwise returns false.
559 */
560bool QSqlQueryModel::removeColumns(int column, int count, const QModelIndex &parent)
561{
562 Q_D(QSqlQueryModel);
563 if (count <= 0 || parent.isValid() || column < 0 || column >= d->rec.count())
564 return false;
565
566 beginRemoveColumns(parent, column, column + count - 1);
567
568 int i;
569 for (i = 0; i < count; ++i)
570 d->rec.remove(column);
571 for (i = column; i < d->colOffsets.count(); ++i)
572 d->colOffsets[i] -= count;
573
574 endRemoveColumns();
575 return true;
576}
577
578/*!
579 Returns the index of the value in the database result set for the
580 given \a item in the model.
581
582 The return value is identical to \a item if no columns or rows
583 have been inserted, removed, or moved around.
584
585 Returns an invalid model index if \a item is out of bounds or if
586 \a item does not point to a value in the result set.
587
588 \sa QSqlTableModel::indexInQuery(), insertColumns(), removeColumns()
589*/
590QModelIndex QSqlQueryModel::indexInQuery(const QModelIndex &item) const
591{
592 Q_D(const QSqlQueryModel);
593 if (item.column() < 0 || item.column() >= d->rec.count()
594 || !d->rec.isGenerated(item.column()))
595 return QModelIndex();
596 return createIndex(item.row(), item.column() - d->colOffsets[item.column()],
597 item.internalPointer());
598}
599
600QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.