source: trunk/src/sql/models/qsqlrelationaltablemodel.cpp@ 448

Last change on this file since 448 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 21.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtSql module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** 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 are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qsqlrelationaltablemodel.h"
43
44#include "qhash.h"
45#include "qstringlist.h"
46#include "qsqldatabase.h"
47#include "qsqldriver.h"
48#include "qsqlerror.h"
49#include "qsqlfield.h"
50#include "qsqlindex.h"
51#include "qsqlquery.h"
52#include "qsqlrecord.h"
53
54#include "qsqltablemodel_p.h"
55
56#include "qdebug.h"
57
58QT_BEGIN_NAMESPACE
59
60/*!
61 \class QSqlRelation
62 \brief The QSqlRelation class stores information about an SQL foreign key.
63
64 QSqlRelation is a helper class for QSqlRelationalTableModel. See
65 QSqlRelationalTableModel::setRelation() and
66 QSqlRelationalTableModel::relation() for details.
67
68 \sa QSqlRelationalTableModel, QSqlRelationalDelegate,
69 {Relational Table Model Example}
70*/
71
72/*!
73 \fn QSqlRelation::QSqlRelation()
74
75 Constructs an invalid QSqlRelation object.
76
77 For such an object, the tableName(), indexColumn(), and
78 displayColumn() functions return an empty string.
79
80 \sa isValid()
81*/
82
83/*!
84 \fn QSqlRelation::QSqlRelation(const QString &tableName, const QString &indexColumn,
85 const QString &displayColumn)
86
87 Constructs a QSqlRelation object, where \a tableName is the SQL
88 table name to which a foreign key refers, \a indexColumn is the
89 foreign key, and \a displayColumn is the field that should be
90 presented to the user.
91
92 \sa tableName(), indexColumn(), displayColumn()
93*/
94
95/*!
96 \fn QString QSqlRelation::tableName() const
97
98 Returns the name of the table to which a foreign key refers.
99*/
100
101/*!
102 \fn QString QSqlRelation::indexColumn() const
103
104 Returns the index column from table tableName() to which a
105 foreign key refers.
106*/
107
108/*!
109 \fn QString QSqlRelation::displayColumn() const
110
111 Returns the column from table tableName() that should be
112 presented to the user instead of a foreign key.
113*/
114
115/*!
116 \fn bool QSqlRelation::isValid() const
117
118 Returns true if the QSqlRelation object is valid; otherwise
119 returns false.
120*/
121
122struct QRelation
123{
124 public:
125 QRelation(): model(0),m_parent(0),m_dictInitialized(false){}
126 void init(QSqlRelationalTableModel *parent, const QSqlRelation &relation);
127
128 void populateModel();
129
130 bool isDictionaryInitialized();
131 void populateDictionary();
132 void clearDictionary();
133
134 void clear();
135 bool isValid();
136
137 QSqlRelation rel;
138 QSqlTableModel *model;
139 QHash<QString, QVariant> dictionary;//maps keys to display values
140
141 private:
142 QSqlRelationalTableModel *m_parent;
143 bool m_dictInitialized;
144};
145
146/*
147 A QRelation must be initialized before it is considered valid.
148 Note: population of the model and dictionary are kept separate
149 from initialization, and are populated on an as needed basis.
150*/
151void QRelation::init(QSqlRelationalTableModel *parent, const QSqlRelation &relation)
152{
153 Q_ASSERT(parent != NULL);
154 m_parent = parent;
155 rel = relation;
156}
157
158void QRelation::populateModel()
159{
160 if (!isValid())
161 return;
162 Q_ASSERT(m_parent != NULL);
163
164 if (!model) {
165 model = new QSqlTableModel(m_parent, m_parent->database());
166 model->setTable(rel.tableName());
167 model->select();
168 }
169}
170
171bool QRelation::isDictionaryInitialized()
172{
173 return m_dictInitialized;
174}
175
176void QRelation::populateDictionary()
177{
178 if (!isValid())
179 return;
180
181 if (model == NULL)
182 populateModel();
183
184 QSqlRecord record;
185 for (int i=0; i < model->rowCount(); ++i) {
186 record = model->record(i);
187 dictionary[record.field(rel.indexColumn()).value().toString()] =
188 record.field(rel.displayColumn()).value();
189 }
190 m_dictInitialized = true;
191}
192
193void QRelation::clearDictionary()
194{
195 dictionary.clear();
196 m_dictInitialized = false;
197}
198
199void QRelation::clear()
200{
201 delete model;
202 model = 0;
203 clearDictionary();
204}
205
206bool QRelation::isValid()
207{
208 return (rel.isValid() && m_parent != NULL);
209}
210
211class QSqlRelationalTableModelPrivate: public QSqlTableModelPrivate
212{
213 Q_DECLARE_PUBLIC(QSqlRelationalTableModel)
214public:
215 QSqlRelationalTableModelPrivate()
216 : QSqlTableModelPrivate()
217 {}
218 QString escapedRelationField(const QString &tableName, const QString &fieldName) const;
219
220 int nameToIndex(const QString &name) const;
221 mutable QVector<QRelation> relations;
222 QSqlRecord baseRec; // the record without relations
223 void clearChanges();
224 void clearEditBuffer();
225 void clearCache();
226 void revertCachedRow(int row);
227
228 void translateFieldNames(int row, QSqlRecord &values) const;
229};
230
231static void qAppendWhereClause(QString &query, const QString &clause1, const QString &clause2)
232{
233 if (clause1.isEmpty() && clause2.isEmpty())
234 return;
235 if (clause1.isEmpty() || clause2.isEmpty())
236 query.append(QLatin1String(" WHERE (")).append(clause1).append(clause2);
237 else
238 query.append(QLatin1String(" WHERE (")).append(clause1).append(
239 QLatin1String(") AND (")).append(clause2);
240 query.append(QLatin1String(") "));
241}
242
243void QSqlRelationalTableModelPrivate::clearChanges()
244{
245 for (int i = 0; i < relations.count(); ++i) {
246 QRelation &rel = relations[i];
247 rel.clear();
248 }
249}
250
251void QSqlRelationalTableModelPrivate::revertCachedRow(int row)
252{
253 QSqlTableModelPrivate::revertCachedRow(row);
254}
255
256int QSqlRelationalTableModelPrivate::nameToIndex(const QString &name) const
257{
258 return baseRec.indexOf(name);
259}
260
261void QSqlRelationalTableModelPrivate::clearEditBuffer()
262{
263 editBuffer = baseRec;
264}
265
266/*!
267 \reimp
268*/
269void QSqlRelationalTableModelPrivate::clearCache()
270{
271 for (int i = 0; i < relations.count(); ++i)
272 relations[i].clearDictionary();
273
274 QSqlTableModelPrivate::clearCache();
275}
276
277/*!
278 \class QSqlRelationalTableModel
279 \brief The QSqlRelationalTableModel class provides an editable
280 data model for a single database table, with foreign key support.
281
282 \ingroup database
283 \inmodule QtSql
284
285 QSqlRelationalTableModel acts like QSqlTableModel, but allows
286 columns to be set as foreign keys into other database tables.
287
288 \table
289 \row \o \inlineimage noforeignkeys.png
290 \o \inlineimage foreignkeys.png
291 \endtable
292
293 The screenshot on the left shows a plain QSqlTableModel in a
294 QTableView. Foreign keys (\c city and \c country) aren't resolved
295 to human-readable values. The screenshot on the right shows a
296 QSqlRelationalTableModel, with foreign keys resolved into
297 human-readable text strings.
298
299 The following code snippet shows how the QSqlRelationalTableModel
300 was set up:
301
302 \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 0
303 \codeline
304 \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 1
305 \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 2
306
307 The setRelation() function calls establish a relationship between
308 two tables. The first call specifies that column 2 in table \c
309 employee is a foreign key that maps with field \c id of table \c
310 city, and that the view should present the \c{city}'s \c name
311 field to the user. The second call does something similar with
312 column 3.
313
314 If you use a read-write QSqlRelationalTableModel, you probably
315 want to use QSqlRelationalDelegate on the view. Unlike the default
316 delegate, QSqlRelationalDelegate provides a combobox for fields
317 that are foreign keys into other tables. To use the class, simply
318 call QAbstractItemView::setItemDelegate() on the view with an
319 instance of QSqlRelationalDelegate:
320
321 \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 4
322
323 The \l{sql/relationaltablemodel} example illustrates how to use
324 QSqlRelationalTableModel in conjunction with
325 QSqlRelationalDelegate to provide tables with foreigh key
326 support.
327
328 \image relationaltable.png
329
330 Notes:
331
332 \list
333 \o The table must have a primary key declared.
334 \o The table's primary key may not contain a relation to
335 another table.
336 \o If a relational table contains keys that refer to non-existent
337 rows in the referenced table, the rows containing the invalid
338 keys will not be exposed through the model. The user or the
339 database is responsible for keeping referential integrity.
340 \o If a relation's display column name is also used as a column
341 name in the main table, or if it is used as display column
342 name in more than one relation it will be aliased. The alias is
343 is the relation's table name and display column name joined
344 by an underscore (e.g. tablename_columnname). All occurrences
345 of the duplicate display column name are aliased when
346 duplication is detected, but no aliasing is done to the column
347 names in the main table. The aliasing doesn't affect
348 QSqlRelation, so QSqlRelation::displayColumn() will return the
349 original display column name, but QSqlRecord::fieldName() will
350 return aliases.
351 \o When using setData() the role should always be Qt::EditRole,
352 and when using data() the role should always be Qt::DisplayRole.
353 \endlist
354
355 \sa QSqlRelation, QSqlRelationalDelegate,
356 {Relational Table Model Example}
357*/
358
359
360/*!
361 Creates an empty QSqlRelationalTableModel and sets the parent to \a parent
362 and the database connection to \a db. If \a db is not valid, the
363 default database connection will be used.
364*/
365QSqlRelationalTableModel::QSqlRelationalTableModel(QObject *parent, QSqlDatabase db)
366 : QSqlTableModel(*new QSqlRelationalTableModelPrivate, parent, db)
367{
368}
369
370/*!
371 Destroys the object and frees any allocated resources.
372*/
373QSqlRelationalTableModel::~QSqlRelationalTableModel()
374{
375}
376
377/*!
378 \reimp
379*/
380QVariant QSqlRelationalTableModel::data(const QModelIndex &index, int role) const
381{
382 Q_D(const QSqlRelationalTableModel);
383
384 if (role == Qt::DisplayRole && index.column() > 0 && index.column() < d->relations.count() &&
385 d->relations.value(index.column()).isValid()) {
386 QRelation &relation = d->relations[index.column()];
387 if (!relation.isDictionaryInitialized())
388 relation.populateDictionary();
389
390 //only perform a dictionary lookup for the display value
391 //when the value at index has been changed or added.
392 //At an unmodified index, the underlying model will
393 //already have the correct display value.
394 QVariant v;
395 switch (d->strategy) {
396 case OnFieldChange:
397 break;
398 case OnRowChange:
399 if (index.row() == d->editIndex || index.row() == d->insertIndex) {
400 v = d->editBuffer.value(index.column());
401 }
402 break;
403 case OnManualSubmit:
404 const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row());
405 v = row.rec.value(index.column());
406 break;
407 }
408 if (v.isValid())
409 return relation.dictionary[v.toString()];
410 }
411 return QSqlTableModel::data(index, role);
412}
413
414/*!
415 Sets the data for the \a role in the item with the specified \a
416 index to the \a value given. Depending on the edit strategy, the
417 value might be applied to the database at once, or it may be
418 cached in the model.
419
420 Returns true if the value could be set, or false on error (for
421 example, if \a index is out of bounds).
422
423 For relational columns, \a value must be the index, not the
424 display value. The index must also exist in the referenced
425 table, otherwise the function returns false.
426
427 \sa editStrategy(), data(), submit(), revertRow()
428*/
429bool QSqlRelationalTableModel::setData(const QModelIndex &index, const QVariant &value,
430 int role)
431{
432 Q_D(QSqlRelationalTableModel);
433 if ( role == Qt::EditRole && index.column() > 0 && index.column() < d->relations.count()
434 && d->relations.value(index.column()).isValid()) {
435 QRelation &relation = d->relations[index.column()];
436 if (!relation.isDictionaryInitialized())
437 relation.populateDictionary();
438 if (!relation.dictionary.contains(value.toString()))
439 return false;
440 }
441 return QSqlTableModel::setData(index, value, role);
442}
443
444/*!
445 Lets the specified \a column be a foreign index specified by \a relation.
446
447 Example:
448
449 \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 0
450 \codeline
451 \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 1
452
453 The setRelation() call specifies that column 2 in table \c
454 employee is a foreign key that maps with field \c id of table \c
455 city, and that the view should present the \c{city}'s \c name
456 field to the user.
457
458 Note: The table's primary key may not contain a relation to another table.
459
460 \sa relation()
461*/
462void QSqlRelationalTableModel::setRelation(int column, const QSqlRelation &relation)
463{
464 Q_D(QSqlRelationalTableModel);
465 if (column < 0)
466 return;
467 if (d->relations.size() <= column)
468 d->relations.resize(column + 1);
469 d->relations[column].init(this, relation);
470}
471
472/*!
473 Returns the relation for the column \a column, or an invalid
474 relation if no relation is set.
475
476 \sa setRelation(), QSqlRelation::isValid()
477*/
478QSqlRelation QSqlRelationalTableModel::relation(int column) const
479{
480 Q_D(const QSqlRelationalTableModel);
481 return d->relations.value(column).rel;
482}
483
484QString QSqlRelationalTableModelPrivate::escapedRelationField(const QString &tableName,
485 const QString &fieldName) const
486{
487 QString esc;
488 esc.reserve(tableName.size() + fieldName.size() + 1);
489 esc.append(tableName).append(QLatin1Char('.')).append(fieldName);
490
491 return db.driver()->escapeIdentifier(esc, QSqlDriver::FieldName);
492}
493
494/*!
495 \reimp
496*/
497QString QSqlRelationalTableModel::selectStatement() const
498{
499 Q_D(const QSqlRelationalTableModel);
500 QString query;
501
502 if (tableName().isEmpty())
503 return query;
504 if (d->relations.isEmpty())
505 return QSqlTableModel::selectStatement();
506
507 QString tList;
508 QString fList;
509 QString where;
510
511 QSqlRecord rec = d->baseRec;
512 QStringList tables;
513 const QRelation nullRelation;
514
515 // Count how many times each field name occurs in the record
516 QHash<QString, int> fieldNames;
517 for (int i = 0; i < rec.count(); ++i) {
518 QSqlRelation relation = d->relations.value(i, nullRelation).rel;
519 QString name;
520 if (relation.isValid())
521 // Count the display column name, not the original foreign key
522 name = relation.displayColumn();
523 else
524 name = rec.fieldName(i);
525 fieldNames.insert(name, fieldNames.value(name, 0) + 1);
526 }
527
528 for (int i = 0; i < rec.count(); ++i) {
529 QSqlRelation relation = d->relations.value(i, nullRelation).rel;
530 if (relation.isValid()) {
531 QString relTableAlias = QString::fromLatin1("relTblAl_%1").arg(i);
532 if (!fList.isEmpty())
533 fList.append(QLatin1String(", "));
534 fList.append(d->escapedRelationField(relTableAlias, relation.displayColumn()));
535
536 // If there are duplicate field names they must be aliased
537 if (fieldNames.value(relation.displayColumn()) > 1) {
538 fList.append(QString::fromLatin1(" AS %1_%2").arg(relation.tableName()).arg(relation.displayColumn()));
539 }
540
541 // this needs fixing!! the below if is borken.
542 if (!tables.contains(relation.tableName()))
543 tables.append(d->db.driver()->escapeIdentifier(relation.tableName(),QSqlDriver::TableName)
544 .append(QLatin1String(" "))
545 .append(d->db.driver()->escapeIdentifier(relTableAlias, QSqlDriver::TableName)));
546 if(!where.isEmpty())
547 where.append(QLatin1String(" AND "));
548 where.append(d->escapedRelationField(tableName(), rec.fieldName(i)));
549 where.append(QLatin1String(" = "));
550 where.append(d->escapedRelationField(relTableAlias, relation.indexColumn()));
551 } else {
552 if (!fList.isEmpty())
553 fList.append(QLatin1String(", "));
554 fList.append(d->escapedRelationField(tableName(), rec.fieldName(i)));
555 }
556 }
557 if (!tables.isEmpty())
558 tList.append(tables.join(QLatin1String(", ")));
559 if (fList.isEmpty())
560 return query;
561 if(!tList.isEmpty())
562 tList.prepend(QLatin1String(", "));
563 tList.prepend(d->db.driver()->escapeIdentifier(tableName(),QSqlDriver::TableName));
564 query.append(QLatin1String("SELECT "));
565 query.append(fList).append(QLatin1String(" FROM ")).append(tList);
566 qAppendWhereClause(query, where, filter());
567
568 QString orderBy = orderByClause();
569 if (!orderBy.isEmpty())
570 query.append(QLatin1Char(' ')).append(orderBy);
571
572 return query;
573}
574
575/*!
576 Returns a QSqlTableModel object for accessing the table for which
577 \a column is a foreign key, or 0 if there is no relation for the
578 given \a column.
579
580 The returned object is owned by the QSqlRelationalTableModel.
581
582 \sa setRelation(), relation()
583*/
584QSqlTableModel *QSqlRelationalTableModel::relationModel(int column) const
585{
586 Q_D(const QSqlRelationalTableModel);
587 if ( column < 0 || column >= d->relations.count())
588 return 0;
589
590 QRelation &relation = const_cast<QSqlRelationalTableModelPrivate *>(d)->relations[column];
591 if (!relation.isValid())
592 return 0;
593
594 if (!relation.model)
595 relation.populateModel();
596 return relation.model;
597}
598
599/*!
600 \reimp
601*/
602void QSqlRelationalTableModel::revertRow(int row)
603{
604 QSqlTableModel::revertRow(row);
605}
606
607/*!
608 \reimp
609*/
610void QSqlRelationalTableModel::clear()
611{
612 Q_D(QSqlRelationalTableModel);
613 d->clearChanges();
614 d->relations.clear();
615 QSqlTableModel::clear();
616}
617
618/*!
619 \reimp
620*/
621bool QSqlRelationalTableModel::select()
622{
623 return QSqlTableModel::select();
624}
625
626/*!
627 \reimp
628*/
629void QSqlRelationalTableModel::setTable(const QString &table)
630{
631 Q_D(QSqlRelationalTableModel);
632
633 // memorize the table before applying the relations
634 d->baseRec = d->db.record(table);
635
636 QSqlTableModel::setTable(table);
637}
638
639/*! \internal
640 */
641void QSqlRelationalTableModelPrivate::translateFieldNames(int row, QSqlRecord &values) const
642{
643 Q_Q(const QSqlRelationalTableModel);
644
645 for (int i = 0; i < values.count(); ++i) {
646 int realCol = q->indexInQuery(q->createIndex(row, i)).column();
647 if (realCol != -1 && relations.value(realCol).isValid()) {
648 QVariant v = values.value(i);
649 values.replace(i, baseRec.field(realCol));
650 values.setValue(i, v);
651 }
652 }
653}
654
655/*!
656 \reimp
657*/
658bool QSqlRelationalTableModel::updateRowInTable(int row, const QSqlRecord &values)
659{
660 Q_D(QSqlRelationalTableModel);
661
662 QSqlRecord rec = values;
663 d->translateFieldNames(row, rec);
664
665 return QSqlTableModel::updateRowInTable(row, rec);
666}
667
668/*!
669 \reimp
670*/
671bool QSqlRelationalTableModel::insertRowIntoTable(const QSqlRecord &values)
672{
673 Q_D(QSqlRelationalTableModel);
674
675 QSqlRecord rec = values;
676 d->translateFieldNames(0, rec);
677
678 return QSqlTableModel::insertRowIntoTable(rec);
679}
680
681/*!
682 \reimp
683*/
684QString QSqlRelationalTableModel::orderByClause() const
685{
686 Q_D(const QSqlRelationalTableModel);
687
688 const QSqlRelation rel = d->relations.value(d->sortColumn).rel;
689 if (!rel.isValid())
690 return QSqlTableModel::orderByClause();
691
692 QString s = QLatin1String("ORDER BY ");
693 s.append(d->escapedRelationField(QLatin1String("relTblAl_") + QString::number(d->sortColumn),
694 rel.displayColumn()));
695 s += d->sortOrder == Qt::AscendingOrder ? QLatin1String(" ASC") : QLatin1String(" DESC");
696 return s;
697}
698
699/*!
700 \reimp
701*/
702bool QSqlRelationalTableModel::removeColumns(int column, int count, const QModelIndex &parent)
703{
704 Q_D(QSqlRelationalTableModel);
705
706 if (parent.isValid() || column < 0 || column + count > d->rec.count())
707 return false;
708
709 for (int i = 0; i < count; ++i) {
710 d->baseRec.remove(column);
711 if (d->relations.count() > column)
712 d->relations.remove(column);
713 }
714 return QSqlTableModel::removeColumns(column, count, parent);
715}
716
717QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.