Permalink
Browse files

Upgraded selectRelated to work like in the current Django version. (#52)

* Upgraded selectRelated to work like in the current Django version. One can list the foreign keys to be loaded thus limiting the DB traverse

* Fixed issues for tests

* Removed unnecessary comments and added documentation. Changed pointer to reference argument for selectRelated function

* Added include

* Removed errnoeous Insert merge

* Added example usage
  • Loading branch information...
pavleb authored and jlaine committed Mar 7, 2018
1 parent d7bae10 commit bda4755ece9d173a67b880e498027fcdc51598a8
Showing with 165 additions and 13 deletions.
  1. +117 −0 doc/queries.doc
  2. +20 −5 src/db/QDjangoMetaModel.cpp
  3. +2 −1 src/db/QDjangoMetaModel.h
  4. +14 −4 src/db/QDjangoQuerySet.cpp
  5. +10 −2 src/db/QDjangoQuerySet.h
  6. +2 −1 src/db/QDjangoQuerySet_p.h
View
@@ -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 <QObject>
#include <QDjangoModel.h>
#include <QByteArray>
#include <QDateTime>
#include <enterprise.h>
#include <row_status_type.h>
#include <site_type.h>
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.
*/
@@ -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<QObject*>();
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<QObject*>();
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<QObject*>();
if (object)
{
const QDjangoMetaModel foreignMeta = QDjango::metaModel(d->foreignFields[fkName]);
foreignMeta.load(object, properties, pos);
}
}
}
}
@@ -21,6 +21,7 @@
#include <QMap>
#include <QSharedDataPointer>
#include <QVariant>
#include <QStringList>
#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;
View
@@ -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();
View
@@ -296,7 +296,7 @@ template <class T>
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<T> QDjangoQuerySet<T>::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<T>::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 <class T>
QDjangoQuerySet<T> QDjangoQuerySet<T>::selectRelated() const
QDjangoQuerySet<T> QDjangoQuerySet<T>::selectRelated(const QStringList &relatedFields) const
{
QDjangoQuerySet<T> other = all();
other.d->selectRelated = true;
other.d->relatedFields = relatedFields;
return other;
}
@@ -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<QVariantList> properties;
bool selectRelated;
QStringList relatedFields;
private:
Q_DISABLE_COPY(QDjangoQuerySetPrivate)

0 comments on commit bda4755

Please sign in to comment.