summaryrefslogtreecommitdiffstats
path: root/src/sql/models/qsqlrelationaltablemodel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/sql/models/qsqlrelationaltablemodel.cpp')
-rw-r--r--src/sql/models/qsqlrelationaltablemodel.cpp717
1 files changed, 717 insertions, 0 deletions
diff --git a/src/sql/models/qsqlrelationaltablemodel.cpp b/src/sql/models/qsqlrelationaltablemodel.cpp
new file mode 100644
index 0000000..935466b
--- /dev/null
+++ b/src/sql/models/qsqlrelationaltablemodel.cpp
@@ -0,0 +1,717 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtSql module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsqlrelationaltablemodel.h"
+
+#include "qhash.h"
+#include "qstringlist.h"
+#include "qsqldatabase.h"
+#include "qsqldriver.h"
+#include "qsqlerror.h"
+#include "qsqlfield.h"
+#include "qsqlindex.h"
+#include "qsqlquery.h"
+#include "qsqlrecord.h"
+
+#include "qsqltablemodel_p.h"
+
+#include "qdebug.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QSqlRelation
+ \brief The QSqlRelation class stores information about an SQL foreign key.
+
+ QSqlRelation is a helper class for QSqlRelationalTableModel. See
+ QSqlRelationalTableModel::setRelation() and
+ QSqlRelationalTableModel::relation() for details.
+
+ \sa QSqlRelationalTableModel, QSqlRelationalDelegate,
+ {Relational Table Model Example}
+*/
+
+/*!
+ \fn QSqlRelation::QSqlRelation()
+
+ Constructs an invalid QSqlRelation object.
+
+ For such an object, the tableName(), indexColumn(), and
+ displayColumn() functions return an empty string.
+
+ \sa isValid()
+*/
+
+/*!
+ \fn QSqlRelation::QSqlRelation(const QString &tableName, const QString &indexColumn,
+ const QString &displayColumn)
+
+ Constructs a QSqlRelation object, where \a tableName is the SQL
+ table name to which a foreign key refers, \a indexColumn is the
+ foreign key, and \a displayColumn is the field that should be
+ presented to the user.
+
+ \sa tableName(), indexColumn(), displayColumn()
+*/
+
+/*!
+ \fn QString QSqlRelation::tableName() const
+
+ Returns the name of the table to which a foreign key refers.
+*/
+
+/*!
+ \fn QString QSqlRelation::indexColumn() const
+
+ Returns the index column from table tableName() to which a
+ foreign key refers.
+*/
+
+/*!
+ \fn QString QSqlRelation::displayColumn() const
+
+ Returns the column from table tableName() that should be
+ presented to the user instead of a foreign key.
+*/
+
+/*!
+ \fn bool QSqlRelation::isValid() const
+
+ Returns true if the QSqlRelation object is valid; otherwise
+ returns false.
+*/
+
+struct QRelation
+{
+ public:
+ QRelation(): model(0),m_parent(0),m_dictInitialized(false){}
+ void init(QSqlRelationalTableModel *parent, const QSqlRelation &relation);
+
+ void populateModel();
+
+ bool isDictionaryInitialized();
+ void populateDictionary();
+ void clearDictionary();
+
+ void clear();
+ bool isValid();
+
+ QSqlRelation rel;
+ QSqlTableModel *model;
+ QHash<QString, QVariant> dictionary;//maps keys to display values
+
+ private:
+ QSqlRelationalTableModel *m_parent;
+ bool m_dictInitialized;
+};
+
+/*
+ A QRelation must be initialized before it is considered valid.
+ Note: population of the model and dictionary are kept separate
+ from initialization, and are populated on an as needed basis.
+*/
+void QRelation::init(QSqlRelationalTableModel *parent, const QSqlRelation &relation)
+{
+ Q_ASSERT(parent != NULL);
+ m_parent = parent;
+ rel = relation;
+}
+
+void QRelation::populateModel()
+{
+ if (!isValid())
+ return;
+ Q_ASSERT(m_parent != NULL);
+
+ if (!model) {
+ model = new QSqlTableModel(m_parent, m_parent->database());
+ model->setTable(rel.tableName());
+ model->select();
+ }
+}
+
+bool QRelation::isDictionaryInitialized()
+{
+ return m_dictInitialized;
+}
+
+void QRelation::populateDictionary()
+{
+ if (!isValid())
+ return;
+
+ if (model == NULL)
+ populateModel();
+
+ QSqlRecord record;
+ for (int i=0; i < model->rowCount(); ++i) {
+ record = model->record(i);
+ dictionary[record.field(rel.indexColumn()).value().toString()] =
+ record.field(rel.displayColumn()).value();
+ }
+ m_dictInitialized = true;
+}
+
+void QRelation::clearDictionary()
+{
+ dictionary.clear();
+ m_dictInitialized = false;
+}
+
+void QRelation::clear()
+{
+ delete model;
+ model = 0;
+ clearDictionary();
+}
+
+bool QRelation::isValid()
+{
+ return (rel.isValid() && m_parent != NULL);
+}
+
+class QSqlRelationalTableModelPrivate: public QSqlTableModelPrivate
+{
+ Q_DECLARE_PUBLIC(QSqlRelationalTableModel)
+public:
+ QSqlRelationalTableModelPrivate()
+ : QSqlTableModelPrivate()
+ {}
+ QString escapedRelationField(const QString &tableName, const QString &fieldName) const;
+
+ int nameToIndex(const QString &name) const;
+ mutable QVector<QRelation> relations;
+ QSqlRecord baseRec; // the record without relations
+ void clearChanges();
+ void clearEditBuffer();
+ void clearCache();
+ void revertCachedRow(int row);
+
+ void translateFieldNames(int row, QSqlRecord &values) const;
+};
+
+static void qAppendWhereClause(QString &query, const QString &clause1, const QString &clause2)
+{
+ if (clause1.isEmpty() && clause2.isEmpty())
+ return;
+ if (clause1.isEmpty() || clause2.isEmpty())
+ query.append(QLatin1String(" WHERE (")).append(clause1).append(clause2);
+ else
+ query.append(QLatin1String(" WHERE (")).append(clause1).append(
+ QLatin1String(") AND (")).append(clause2);
+ query.append(QLatin1String(") "));
+}
+
+void QSqlRelationalTableModelPrivate::clearChanges()
+{
+ for (int i = 0; i < relations.count(); ++i) {
+ QRelation &rel = relations[i];
+ rel.clear();
+ }
+}
+
+void QSqlRelationalTableModelPrivate::revertCachedRow(int row)
+{
+ QSqlTableModelPrivate::revertCachedRow(row);
+}
+
+int QSqlRelationalTableModelPrivate::nameToIndex(const QString &name) const
+{
+ return baseRec.indexOf(name);
+}
+
+void QSqlRelationalTableModelPrivate::clearEditBuffer()
+{
+ editBuffer = baseRec;
+}
+
+/*!
+ \reimp
+*/
+void QSqlRelationalTableModelPrivate::clearCache()
+{
+ for (int i = 0; i < relations.count(); ++i)
+ relations[i].clearDictionary();
+
+ QSqlTableModelPrivate::clearCache();
+}
+
+/*!
+ \class QSqlRelationalTableModel
+ \brief The QSqlRelationalTableModel class provides an editable
+ data model for a single database table, with foreign key support.
+
+ \ingroup database
+ \inmodule QtSql
+
+ QSqlRelationalTableModel acts like QSqlTableModel, but allows
+ columns to be set as foreign keys into other database tables.
+
+ \table
+ \row \o \inlineimage noforeignkeys.png
+ \o \inlineimage foreignkeys.png
+ \endtable
+
+ The screenshot on the left shows a plain QSqlTableModel in a
+ QTableView. Foreign keys (\c city and \c country) aren't resolved
+ to human-readable values. The screenshot on the right shows a
+ QSqlRelationalTableModel, with foreign keys resolved into
+ human-readable text strings.
+
+ The following code snippet shows how the QSqlRelationalTableModel
+ was set up:
+
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 0
+ \codeline
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 1
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 2
+
+ The setRelation() function calls establish a relationship between
+ two tables. The first call specifies that column 2 in table \c
+ employee is a foreign key that maps with field \c id of table \c
+ city, and that the view should present the \c{city}'s \c name
+ field to the user. The second call does something similar with
+ column 3.
+
+ If you use a read-write QSqlRelationalTableModel, you probably
+ want to use QSqlRelationalDelegate on the view. Unlike the default
+ delegate, QSqlRelationalDelegate provides a combobox for fields
+ that are foreign keys into other tables. To use the class, simply
+ call QAbstractItemView::setItemDelegate() on the view with an
+ instance of QSqlRelationalDelegate:
+
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 4
+
+ The \l{sql/relationaltablemodel} example illustrates how to use
+ QSqlRelationalTableModel in conjunction with
+ QSqlRelationalDelegate to provide tables with foreigh key
+ support.
+
+ \image relationaltable.png
+
+ Notes:
+
+ \list
+ \o The table must have a primary key declared.
+ \o The table's primary key may not contain a relation to
+ another table.
+ \o If a relational table contains keys that refer to non-existent
+ rows in the referenced table, the rows containing the invalid
+ keys will not be exposed through the model. The user or the
+ database is responsible for keeping referential integrity.
+ \o If a relation's display column name is also used as a column
+ name in the main table, or if it is used as display column
+ name in more than one relation it will be aliased. The alias is
+ is the relation's table name and display column name joined
+ by an underscore (e.g. tablename_columnname). All occurrences
+ of the duplicate display column name are aliased when
+ duplication is detected, but no aliasing is done to the column
+ names in the main table. The aliasing doesn't affect
+ QSqlRelation, so QSqlRelation::displayColumn() will return the
+ original display column name, but QSqlRecord::fieldName() will
+ return aliases.
+ \o When using setData() the role should always be Qt::EditRole,
+ and when using data() the role should always be Qt::DisplayRole.
+ \endlist
+
+ \sa QSqlRelation, QSqlRelationalDelegate,
+ {Relational Table Model Example}
+*/
+
+
+/*!
+ Creates an empty QSqlRelationalTableModel and sets the parent to \a parent
+ and the database connection to \a db. If \a db is not valid, the
+ default database connection will be used.
+*/
+QSqlRelationalTableModel::QSqlRelationalTableModel(QObject *parent, QSqlDatabase db)
+ : QSqlTableModel(*new QSqlRelationalTableModelPrivate, parent, db)
+{
+}
+
+/*!
+ Destroys the object and frees any allocated resources.
+*/
+QSqlRelationalTableModel::~QSqlRelationalTableModel()
+{
+}
+
+/*!
+ \reimp
+*/
+QVariant QSqlRelationalTableModel::data(const QModelIndex &index, int role) const
+{
+ Q_D(const QSqlRelationalTableModel);
+
+ if (role == Qt::DisplayRole && index.column() > 0 && index.column() < d->relations.count() &&
+ d->relations.value(index.column()).isValid()) {
+ QRelation &relation = d->relations[index.column()];
+ if (!relation.isDictionaryInitialized())
+ relation.populateDictionary();
+
+ //only perform a dictionary lookup for the display value
+ //when the value at index has been changed or added.
+ //At an unmodified index, the underlying model will
+ //already have the correct display value.
+ QVariant v;
+ switch (d->strategy) {
+ case OnFieldChange:
+ break;
+ case OnRowChange:
+ if (index.row() == d->editIndex || index.row() == d->insertIndex) {
+ v = d->editBuffer.value(index.column());
+ }
+ break;
+ case OnManualSubmit:
+ const QSqlTableModelPrivate::ModifiedRow row = d->cache.value(index.row());
+ v = row.rec.value(index.column());
+ break;
+ }
+ if (v.isValid())
+ return relation.dictionary[v.toString()];
+ }
+ return QSqlTableModel::data(index, role);
+}
+
+/*!
+ Sets the data for the \a role in the item with the specified \a
+ index to the \a value given. Depending on the edit strategy, the
+ value might be applied to the database at once, or it may be
+ cached in the model.
+
+ Returns true if the value could be set, or false on error (for
+ example, if \a index is out of bounds).
+
+ For relational columns, \a value must be the index, not the
+ display value. The index must also exist in the referenced
+ table, otherwise the function returns false.
+
+ \sa editStrategy(), data(), submit(), revertRow()
+*/
+bool QSqlRelationalTableModel::setData(const QModelIndex &index, const QVariant &value,
+ int role)
+{
+ Q_D(QSqlRelationalTableModel);
+ if ( role == Qt::EditRole && index.column() > 0 && index.column() < d->relations.count()
+ && d->relations.value(index.column()).isValid()) {
+ QRelation &relation = d->relations[index.column()];
+ if (!relation.isDictionaryInitialized())
+ relation.populateDictionary();
+ if (!relation.dictionary.contains(value.toString()))
+ return false;
+ }
+ return QSqlTableModel::setData(index, value, role);
+}
+
+/*!
+ Lets the specified \a column be a foreign index specified by \a relation.
+
+ Example:
+
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 0
+ \codeline
+ \snippet examples/sql/relationaltablemodel/relationaltablemodel.cpp 1
+
+ The setRelation() call specifies that column 2 in table \c
+ employee is a foreign key that maps with field \c id of table \c
+ city, and that the view should present the \c{city}'s \c name
+ field to the user.
+
+ Note: The table's primary key may not contain a relation to another table.
+
+ \sa relation()
+*/
+void QSqlRelationalTableModel::setRelation(int column, const QSqlRelation &relation)
+{
+ Q_D(QSqlRelationalTableModel);
+ if (column < 0)
+ return;
+ if (d->relations.size() <= column)
+ d->relations.resize(column + 1);
+ d->relations[column].init(this, relation);
+}
+
+/*!
+ Returns the relation for the column \a column, or an invalid
+ relation if no relation is set.
+
+ \sa setRelation(), QSqlRelation::isValid()
+*/
+QSqlRelation QSqlRelationalTableModel::relation(int column) const
+{
+ Q_D(const QSqlRelationalTableModel);
+ return d->relations.value(column).rel;
+}
+
+QString QSqlRelationalTableModelPrivate::escapedRelationField(const QString &tableName,
+ const QString &fieldName) const
+{
+ QString esc;
+ esc.reserve(tableName.size() + fieldName.size() + 1);
+ esc.append(tableName).append(QLatin1Char('.')).append(fieldName);
+
+ return db.driver()->escapeIdentifier(esc, QSqlDriver::FieldName);
+}
+
+/*!
+ \reimp
+*/
+QString QSqlRelationalTableModel::selectStatement() const
+{
+ Q_D(const QSqlRelationalTableModel);
+ QString query;
+
+ if (tableName().isEmpty())
+ return query;
+ if (d->relations.isEmpty())
+ return QSqlTableModel::selectStatement();
+
+ QString tList;
+ QString fList;
+ QString where;
+
+ QSqlRecord rec = d->baseRec;
+ QStringList tables;
+ const QRelation nullRelation;
+
+ // Count how many times each field name occurs in the record
+ QHash<QString, int> fieldNames;
+ for (int i = 0; i < rec.count(); ++i) {
+ QSqlRelation relation = d->relations.value(i, nullRelation).rel;
+ QString name;
+ if (relation.isValid())
+ // Count the display column name, not the original foreign key
+ name = relation.displayColumn();
+ else
+ name = rec.fieldName(i);
+ fieldNames.insert(name, fieldNames.value(name, 0) + 1);
+ }
+
+ for (int i = 0; i < rec.count(); ++i) {
+ QSqlRelation relation = d->relations.value(i, nullRelation).rel;
+ if (relation.isValid()) {
+ QString relTableAlias = QString::fromLatin1("relTblAl_%1").arg(i);
+ if (!fList.isEmpty())
+ fList.append(QLatin1String(", "));
+ fList.append(d->escapedRelationField(relTableAlias, relation.displayColumn()));
+
+ // If there are duplicate field names they must be aliased
+ if (fieldNames.value(relation.displayColumn()) > 1) {
+ fList.append(QString::fromLatin1(" AS %1_%2").arg(relation.tableName()).arg(relation.displayColumn()));
+ }
+
+ // this needs fixing!! the below if is borken.
+ if (!tables.contains(relation.tableName()))
+ tables.append(d->db.driver()->escapeIdentifier(relation.tableName(),QSqlDriver::TableName)
+ .append(QLatin1String(" "))
+ .append(d->db.driver()->escapeIdentifier(relTableAlias, QSqlDriver::TableName)));
+ if(!where.isEmpty())
+ where.append(QLatin1String(" AND "));
+ where.append(d->escapedRelationField(tableName(), rec.fieldName(i)));
+ where.append(QLatin1String(" = "));
+ where.append(d->escapedRelationField(relTableAlias, relation.indexColumn()));
+ } else {
+ if (!fList.isEmpty())
+ fList.append(QLatin1String(", "));
+ fList.append(d->escapedRelationField(tableName(), rec.fieldName(i)));
+ }
+ }
+ if (!tables.isEmpty())
+ tList.append(tables.join(QLatin1String(", ")));
+ if (fList.isEmpty())
+ return query;
+ if(!tList.isEmpty())
+ tList.prepend(QLatin1String(", "));
+ tList.prepend(d->db.driver()->escapeIdentifier(tableName(),QSqlDriver::TableName));
+ query.append(QLatin1String("SELECT "));
+ query.append(fList).append(QLatin1String(" FROM ")).append(tList);
+ qAppendWhereClause(query, where, filter());
+
+ QString orderBy = orderByClause();
+ if (!orderBy.isEmpty())
+ query.append(QLatin1Char(' ')).append(orderBy);
+
+ return query;
+}
+
+/*!
+ Returns a QSqlTableModel object for accessing the table for which
+ \a column is a foreign key, or 0 if there is no relation for the
+ given \a column.
+
+ The returned object is owned by the QSqlRelationalTableModel.
+
+ \sa setRelation(), relation()
+*/
+QSqlTableModel *QSqlRelationalTableModel::relationModel(int column) const
+{
+ Q_D(const QSqlRelationalTableModel);
+ if ( column < 0 || column >= d->relations.count())
+ return 0;
+
+ QRelation &relation = const_cast<QSqlRelationalTableModelPrivate *>(d)->relations[column];
+ if (!relation.isValid())
+ return 0;
+
+ if (!relation.model)
+ relation.populateModel();
+ return relation.model;
+}
+
+/*!
+ \reimp
+*/
+void QSqlRelationalTableModel::revertRow(int row)
+{
+ QSqlTableModel::revertRow(row);
+}
+
+/*!
+ \reimp
+*/
+void QSqlRelationalTableModel::clear()
+{
+ Q_D(QSqlRelationalTableModel);
+ d->clearChanges();
+ d->relations.clear();
+ QSqlTableModel::clear();
+}
+
+/*!
+ \reimp
+*/
+bool QSqlRelationalTableModel::select()
+{
+ return QSqlTableModel::select();
+}
+
+/*!
+ \reimp
+*/
+void QSqlRelationalTableModel::setTable(const QString &table)
+{
+ Q_D(QSqlRelationalTableModel);
+
+ // memorize the table before applying the relations
+ d->baseRec = d->db.record(table);
+
+ QSqlTableModel::setTable(table);
+}
+
+/*! \internal
+ */
+void QSqlRelationalTableModelPrivate::translateFieldNames(int row, QSqlRecord &values) const
+{
+ Q_Q(const QSqlRelationalTableModel);
+
+ for (int i = 0; i < values.count(); ++i) {
+ int realCol = q->indexInQuery(q->createIndex(row, i)).column();
+ if (realCol != -1 && relations.value(realCol).isValid()) {
+ QVariant v = values.value(i);
+ values.replace(i, baseRec.field(realCol));
+ values.setValue(i, v);
+ }
+ }
+}
+
+/*!
+ \reimp
+*/
+bool QSqlRelationalTableModel::updateRowInTable(int row, const QSqlRecord &values)
+{
+ Q_D(QSqlRelationalTableModel);
+
+ QSqlRecord rec = values;
+ d->translateFieldNames(row, rec);
+
+ return QSqlTableModel::updateRowInTable(row, rec);
+}
+
+/*!
+ \reimp
+*/
+bool QSqlRelationalTableModel::insertRowIntoTable(const QSqlRecord &values)
+{
+ Q_D(QSqlRelationalTableModel);
+
+ QSqlRecord rec = values;
+ d->translateFieldNames(0, rec);
+
+ return QSqlTableModel::insertRowIntoTable(rec);
+}
+
+/*!
+ \reimp
+*/
+QString QSqlRelationalTableModel::orderByClause() const
+{
+ Q_D(const QSqlRelationalTableModel);
+
+ const QSqlRelation rel = d->relations.value(d->sortColumn).rel;
+ if (!rel.isValid())
+ return QSqlTableModel::orderByClause();
+
+ QString s = QLatin1String("ORDER BY ");
+ s.append(d->escapedRelationField(QLatin1String("relTblAl_") + QString::number(d->sortColumn),
+ rel.displayColumn()));
+ s += d->sortOrder == Qt::AscendingOrder ? QLatin1String(" ASC") : QLatin1String(" DESC");
+ return s;
+}
+
+/*!
+ \reimp
+*/
+bool QSqlRelationalTableModel::removeColumns(int column, int count, const QModelIndex &parent)
+{
+ Q_D(QSqlRelationalTableModel);
+
+ if (parent.isValid() || column < 0 || column + count > d->rec.count())
+ return false;
+
+ for (int i = 0; i < count; ++i) {
+ d->baseRec.remove(column);
+ if (d->relations.count() > column)
+ d->relations.remove(column);
+ }
+ return QSqlTableModel::removeColumns(column, count, parent);
+}
+
+QT_END_NAMESPACE