diff --git a/doc/queries.doc b/doc/queries.doc index 740a163..3a25b27 100644 --- a/doc/queries.doc +++ b/doc/queries.doc @@ -106,4 +106,121 @@ int numberOfUsers = someUsers.count(); someUsers.remove(); \endcode +\section select-related Selecting related objects +It is efficient to fetch the main object together with the selected one. +This is particularly true if multiple objects are needed so that all of them are fetched with a single query. +QDjagno offers two ways how to select related objects. +The default option is to traverse all foreign keys recursuvely. +In case of small dependence trees this option is suitable, however when the database structure is complex, fetching all related object in a recursive manner can be overwhelming. +Therefore, the QDjangoQuerySet::selectRelated() method can be invoked with a list of foreign keys that have to be traversed, thus limiting the set of fetched related objects. + + +Let us have the following model structure: +\code +#include +#include +#include +#include +#include +#include +#include +class Author : public QDjangoModel +{ Q_OBJECT + Q_PROPERTY(long author_id READ author_id WRITE set_author_id) + Q_CLASSINFO("author_id","primary_key=true auto_increment=true") + Q_PROPERTY(QString name READ name WRITE set_name) + Q_CLASSINFO("name","max_length=254 null=true blank=true") + Q_PROPERTY(QDateTime birthday READ birthday WRITE set_birthday) + Q_CLASSINFO("birthday","null=true blank=true") +public: + Author(QObject *parent=0); + long author_id() const; + void set_author_id(long value); + QString name() const; + QDateTime birthday() const; + void set_birthday(QDateTime value); +private: + long m_author_id; + QString m_name; + QDateTime m_birthday; +}; + +class Location : public QDjangoModel +{ Q_OBJECT + Q_PROPERTY(long location_id READ publisher_id WRITE set_location_id) + Q_CLASSINFO("location_id","primary_key=true auto_increment=true") + Q_PROPERTY(QString name READ name WRITE set_name) + Q_CLASSINFO("name","max_length=254 null=true blank=true") +public: + Location(QObject *parent=0); + long location_id() const; + void set_location_id(long value); + QString name() const; +private: + long m_location_id; + QString m_name; +}; + +class Publisher : public QDjangoModel +{ Q_OBJECT + Q_PROPERTY(long publisher_id READ publisher_id WRITE set_publisher_id) + Q_CLASSINFO("author_id","primary_key=true auto_increment=true") + Q_PROPERTY(QString name READ name WRITE set_name) + Q_CLASSINFO("name","max_length=254 null=true blank=true") + Q_PROPERTY(Location* location READ location WRITE set_location) +public: + Publisher(QObject *parent=0); + long publisher_id() const; + void set_publisher_id(long value); + QString name() const; + void sel_location(Location *value); + Location* location(); +private: + long m_publisher_id; + QString m_name; +}; + +class Book : public QDjangoModel +{ Q_OBJECT + Q_PROPERTY(long book_id READ book_id WRITE set_book_id) + Q_CLASSINFO("book_id","primary_key=true auto_increment=true") + Q_PROPERTY(QString name READ name WRITE set_name) + Q_CLASSINFO("name","max_length=254 null=true blank=true") + Q_PROPERTY(Author* author READ author WRITE set_author) + Q_PROPERTY(Publisher* publisher READ publisher WRITE set_publisher) +public: + Book(QObject *parent=0); + long book_id() const; + void set_book_id(long value); + QString name() const; + void set_author(Author* value); + Author* author(); + void set_publisher(Publisher* value); + Publisher* publisher(); +private: + long m_book_id; + QString m_name; +}; +\endcode + +If one invokes QDjangoQuerySet::selectRelated() without arguments on a QuerySet of Book, QDjango will return the selected Book object with filled Author, Publisher as well as Location of the Publisher. +In order to limit the pre-fetched objects, just for Author the QDjangoQuerySet::selectRelated() method should be invoked using a QStringList in the following format +\code +QStringList relatedNames; +relatedNames << "author" << "publisher"; +\endcode + +Limiting the pre-fetching to publisher and its location the QStringList becomes: +\code +QStringList relatedNames; +relatedNames << "publisher__location"; +\endcode + +Fetching all the complete structure as if the method was invoked without any argument is equivalent to the following QStringList: +\code +QStringList relatedNames; +relatedNames << "publisher__location" << "author"; +\endcode + +The first level of the related names represent the foreign keys of the class Book. The subsequent foreign keys of the related objects are separated by double underscore '__' as in the QDjangoWhere class. */ diff --git a/src/db/QDjangoMetaModel.cpp b/src/db/QDjangoMetaModel.cpp index 93f0a7d..741fdb6 100644 --- a/src/db/QDjangoMetaModel.cpp +++ b/src/db/QDjangoMetaModel.cpp @@ -725,7 +725,7 @@ void QDjangoMetaModel::setForeignKey(QObject *model, const char *name, QObject * /*! Loads the given properties into a \a model instance. */ -void QDjangoMetaModel::load(QObject *model, const QVariantList &properties, int &pos) const +void QDjangoMetaModel::load(QObject *model, const QVariantList &properties, int &pos, const QStringList &relatedFields) const { // process local fields foreach (const QDjangoMetaField &field, d->localFields) @@ -736,11 +736,26 @@ void QDjangoMetaModel::load(QObject *model, const QVariantList &properties, int return; foreach (const QByteArray &fkName, d->foreignFields.keys()) { - QObject *object = model->property(fkName + "_ptr").value(); - if (object) + QString fkS(fkName); + if ( relatedFields.contains(fkS) ) { - const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[fkName]); - foreignMeta.load(object, properties, pos); + QStringList nsl = relatedFields.filter(QRegExp("^" + fkS + "__")).replaceInStrings(QRegExp("^" + fkS + "__"),""); + QObject *object = model->property(fkName + "_ptr").value(); + if (object) + { + const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[fkName]); + foreignMeta.load(object, properties, pos, nsl); + } + } + + if (relatedFields.isEmpty()) + { + QObject *object = model->property(fkName + "_ptr").value(); + if (object) + { + const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[fkName]); + foreignMeta.load(object, properties, pos); + } } } } diff --git a/src/db/QDjangoMetaModel.h b/src/db/QDjangoMetaModel.h index a2d6125..b75f31d 100644 --- a/src/db/QDjangoMetaModel.h +++ b/src/db/QDjangoMetaModel.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "QDjango_p.h" @@ -75,7 +76,7 @@ class QDJANGO_DB_EXPORT QDjangoMetaModel QStringList createTableSql() const; bool dropTable() const; - void load(QObject *model, const QVariantList &props, int &pos) const; + void load(QObject *model, const QVariantList &props, int &pos, const QStringList &relatedFields = QStringList()) const; bool remove(QObject *model) const; bool save(QObject *model) const; diff --git a/src/db/QDjangoQuerySet.cpp b/src/db/QDjangoQuerySet.cpp index 0971233..99b222d 100644 --- a/src/db/QDjangoQuerySet.cpp +++ b/src/db/QDjangoQuerySet.cpp @@ -99,7 +99,7 @@ QString QDjangoCompiler::databaseColumn(const QString &name) return modelRef + QLatin1Char('.') + driver->escapeIdentifier(field.column(), QSqlDriver::FieldName); } -QStringList QDjangoCompiler::fieldNames(bool recurse, QDjangoMetaModel *metaModel, const QString &modelPath, bool nullable) +QStringList QDjangoCompiler::fieldNames(bool recurse, const QStringList *fields, QDjangoMetaModel *metaModel, const QString &modelPath, bool nullable) { QStringList columns; if (!metaModel) @@ -117,7 +117,17 @@ QStringList QDjangoCompiler::fieldNames(bool recurse, QDjangoMetaModel *metaMode foreach (const QByteArray &fkName, metaModel->foreignFields().keys()) { QDjangoMetaModel metaForeign = QDjango::metaModel(metaModel->foreignFields()[fkName]); bool nullableForeign = metaModel->localField(fkName + QByteArray("_id")).isNullable(); - columns += fieldNames(recurse, &metaForeign, pathPrefix + QString::fromLatin1(fkName), nullableForeign); + QString fkS(fkName); + if ( (fields != 0) && (fields->contains(fkS) ) ) + { + QStringList nsl = fields->filter(QRegExp("^" + fkS + "__")).replaceInStrings(QRegExp("^" + fkS + "__"),""); + columns += fieldNames(recurse, &nsl, &metaForeign, pathPrefix + QString::fromLatin1(fkName), nullableForeign); + } + + if (fields == 0) + { + columns += fieldNames(recurse, 0, &metaForeign, pathPrefix + QString::fromLatin1(fkName), nullableForeign); + } } return columns; } @@ -335,7 +345,7 @@ bool QDjangoQuerySetPrivate::sqlLoad(QObject *model, int index) const QDjangoMetaModel metaModel = QDjango::metaModel(m_modelName); int pos = 0; - metaModel.load(model, properties.at(index), pos); + metaModel.load(model, properties.at(index), pos, this->relatedFields); return true; } @@ -438,7 +448,7 @@ QDjangoQuery QDjangoQuerySetPrivate::selectQuery() const QDjangoWhere resolvedWhere(whereClause); compiler.resolve(resolvedWhere); - const QStringList columns = compiler.fieldNames(selectRelated); + const QStringList columns = compiler.fieldNames(selectRelated, &this->relatedFields); const QString where = resolvedWhere.sql(db); const QString limit = compiler.orderLimitSql(orderBy, lowMark, highMark); QString sql = QLatin1String("SELECT ") + columns.join(QLatin1String(", ")) + QLatin1String(" FROM ") + compiler.fromSql(); diff --git a/src/db/QDjangoQuerySet.h b/src/db/QDjangoQuerySet.h index c5cd1cb..4e1e53f 100644 --- a/src/db/QDjangoQuerySet.h +++ b/src/db/QDjangoQuerySet.h @@ -296,7 +296,7 @@ template QDjangoQuerySet limit(int pos, int length = -1) const; QDjangoQuerySet none() const; QDjangoQuerySet orderBy(const QStringList &keys) const; - QDjangoQuerySet selectRelated() const; + QDjangoQuerySet selectRelated(const QStringList &relatedFields = QStringList()) const; int count() const; QVariant aggregate(const QDjangoWhere::AggregateType func, const QString& field) const; @@ -426,6 +426,7 @@ QDjangoQuerySet QDjangoQuerySet::all() const other.d->highMark = d->highMark; other.d->orderBy = d->orderBy; other.d->selectRelated = d->selectRelated; + other.d->relatedFields = d->relatedFields; other.d->whereClause = d->whereClause; return other; } @@ -589,12 +590,19 @@ bool QDjangoQuerySet::remove() /** Returns a QDjangoQuerySet that will automatically "follow" foreign-key * relationships, selecting that additional related-object data when it * executes its query. + * + * \param relatedFields If provided it will follow only the listed foreign + * keys. This is handy for very complex DB structures and allows the user + * to limit the amount of retrieved data. If omitted, the basic functionality + * is preserved and the function will traverse all foreign key relationships. + * Each list element is a chain of foreing keys separated by double underscore "__". */ template -QDjangoQuerySet QDjangoQuerySet::selectRelated() const +QDjangoQuerySet QDjangoQuerySet::selectRelated(const QStringList &relatedFields) const { QDjangoQuerySet other = all(); other.d->selectRelated = true; + other.d->relatedFields = relatedFields; return other; } diff --git a/src/db/QDjangoQuerySet_p.h b/src/db/QDjangoQuerySet_p.h index 3816284..9e363e8 100644 --- a/src/db/QDjangoQuerySet_p.h +++ b/src/db/QDjangoQuerySet_p.h @@ -61,7 +61,7 @@ class QDJANGO_DB_EXPORT QDjangoCompiler public: QDjangoCompiler(const char *modelName, const QSqlDatabase &db); QString fromSql(); - QStringList fieldNames(bool recurse, QDjangoMetaModel *metaModel = 0, const QString &modelPath = QString(), bool nullable = false); + QStringList fieldNames(bool recurse, const QStringList *fields = 0, QDjangoMetaModel *metaModel = 0, const QString &modelPath = QString(), bool nullable = false); QString orderLimitSql(const QStringList &orderBy, int lowMark, int highMark); void resolve(QDjangoWhere &where); @@ -110,6 +110,7 @@ class QDJANGO_DB_EXPORT QDjangoQuerySetPrivate QStringList orderBy; QList properties; bool selectRelated; + QStringList relatedFields; private: Q_DISABLE_COPY(QDjangoQuerySetPrivate)