Skip to content

Commit

Permalink
Add Order By Clause
Browse files Browse the repository at this point in the history
  • Loading branch information
m-kuhn committed Dec 18, 2015
1 parent b9f0c06 commit d96a274
Show file tree
Hide file tree
Showing 5 changed files with 468 additions and 14 deletions.
90 changes: 90 additions & 0 deletions python/core/qgsfeaturerequest.sip
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,63 @@ class QgsFeatureRequest
FilterFids //!< Filter using feature IDs
};

/**
* @brief The OrderByClause class represents an order by clause for a QgsFeatureRequest
*/
class OrderByClause
{
public:
/**
* Creates a new OrderByClause for a QgsFeatureRequest
*
* @param expression The expression to use for ordering
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
* If thr order is ascending, by default nulls are last
* If thr order is descending, by default nulls are first
*/
OrderByClause( const QString &expression, bool ascending = true );
/**
* Creates a new OrderByClause for a QgsFeatureRequest
*
* @param expression The expression to use for ordering
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
* @param nullsfirst If true, NULLS are at the beginning, if false, NULLS are at the end
*/
OrderByClause( const QString &expression, bool ascending, bool nullsfirst );

/**
* The expression
* @return the expression
*/
QgsExpression expression() const;

/**
* Order ascending
* @return If ascending order is requested
*/
bool ascending() const;

/**
* Set if ascending order is requested
*/
void setAscending( bool ascending );

/**
* Set if NULLS should be returned first
* @return if NULLS should be returned first
*/
bool nullsFirst() const;

/**
* Set if NULLS should be returned first
*/
void setNullsFirst( bool nullsFirst );

};

/**
* A special attribute that if set matches all attributes
*/
static const QString AllAttributes;

//! construct a default request: for all features get attributes and geometries
Expand Down Expand Up @@ -92,6 +149,39 @@ class QgsFeatureRequest
*/
QgsFeatureRequest& disableFilter();

/**
* Adds a new OrderByClause
*
* @param expression The expression to use for ordering
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
* If the order is ascending, by default nulls are last
* If the order is descending, by default nulls are first
*
* @added in QGIS 2.14
*/

QgsFeatureRequest& addOrderBy( const QString &expression, bool ascending = true );
/**
* Adds a new OrderByClause, appending it as the least important one.
*
* @param expression The expression to use for ordering
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
* @param nullsfirst If true, NULLS are at the beginning, if false, NULLS are at the end
*
* @added in QGIS 2.14
*/
QgsFeatureRequest& addOrderBy( const QString &expression, bool ascending, bool nullsfirst );

/**
* Return a list of order by clauses specified for this feature request.
*/
QList<QgsFeatureRequest::OrderByClause> orderBys() const;

/**
* Set a list of order by clauses.
*/
void setOrderBys(const QList<QgsFeatureRequest::OrderByClause>& orderBys );

/** Set the maximum number of features to request.
* @param limit maximum number of features, or -1 to request all features.
* @see limit()
Expand Down
186 changes: 173 additions & 13 deletions src/core/qgsfeatureiterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,116 @@
#include "qgsgeometrysimplifier.h"
#include "qgssimplifymethod.h"

#include <algorithm>

class QgsExpressionSorter
{
public:
QgsExpressionSorter( const QList<QgsFeatureRequest::OrderByClause>& preparedOrderBys )
: mPreparedOrderBys( preparedOrderBys )
{}

bool operator()( const QgsIndexedFeature& f1, const QgsIndexedFeature& f2 ) const
{
int i = 0;
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& orderBy, mPreparedOrderBys )
{
const QVariant& v1 = f1.mIndexes.at( i );
const QVariant& v2 = f2.mIndexes.at( i );
++i;

// Both NULL: don't care
if ( v1.isNull() && v2.isNull() )
continue;

// Check for NULLs first
if ( v1.isNull() != v2.isNull() )
{
if ( orderBy.nullsFirst() )
return v1.isNull();
else
return !v1.isNull();
}

// Both values are not NULL
switch ( v1.type() )
{
case QVariant::Int:
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong:
if ( v1.toLongLong() == v2.toLongLong() )
continue;
if ( orderBy.ascending() )
return v1.toLongLong() < v2.toLongLong();
else
return v1.toLongLong() > v2.toLongLong();

case QVariant::Double:
if ( v1.toDouble() == v2.toDouble() )
continue;
if ( orderBy.ascending() )
return v1.toDouble() < v2.toDouble();
else
return v1.toDouble() > v2.toDouble();

case QVariant::Date:
if ( v1.toDate() == v2.toDate() )
continue;
if ( orderBy.ascending() )
return v1.toDate() < v2.toDate();
else
return v1.toDate() > v2.toDate();

case QVariant::DateTime:
if ( v1.toDateTime() == v2.toDateTime() )
continue;
if ( orderBy.ascending() )
return v1.toDateTime() < v2.toDateTime();
else
return v1.toDateTime() > v2.toDateTime();

case QVariant::Bool:
if ( v1.toBool() == v2.toBool() )
continue;
if ( orderBy.ascending() )
return !v1.toBool();
else
return v1.toBool();

default:
if ( 0 == v1.toString().localeAwareCompare( v2.toString() ) )
continue;
if ( orderBy.ascending() )
return v1.toString().localeAwareCompare( v2.toString() ) < 0;
else
return v1.toString().localeAwareCompare( v2.toString() ) > 0;
}
}

// Equal
return true;
}

private:
QList<QgsFeatureRequest::OrderByClause> mPreparedOrderBys;
};


QgsAbstractFeatureIterator::QgsAbstractFeatureIterator( const QgsFeatureRequest& request )
: mRequest( request )
, mClosed( false )
, refs( 0 )
, mFetchedCount( 0 )
, mGeometrySimplifier( nullptr )
, mLocalSimplification( false )
, mUseCachedFeatures( false )
{
}

QgsAbstractFeatureIterator::~QgsAbstractFeatureIterator()
{
delete mGeometrySimplifier;
mGeometrySimplifier = nullptr;
}

bool QgsAbstractFeatureIterator::nextFeature( QgsFeature& f )
Expand All @@ -42,26 +138,37 @@ bool QgsAbstractFeatureIterator::nextFeature( QgsFeature& f )
return false;
}

switch ( mRequest.filterType() )
if ( mUseCachedFeatures )
{
if ( mFeatureIterator != mCachedFeatures.constEnd() )
{
f = mFeatureIterator->mFeature;
++mFeatureIterator;
dataOk = true;
}
}
else
{
case QgsFeatureRequest::FilterExpression:
dataOk = nextFeatureFilterExpression( f );
break;
switch ( mRequest.filterType() )
{
case QgsFeatureRequest::FilterExpression:
dataOk = nextFeatureFilterExpression( f );
break;

case QgsFeatureRequest::FilterFids:
dataOk = nextFeatureFilterFids( f );
break;
case QgsFeatureRequest::FilterFids:
dataOk = nextFeatureFilterFids( f );
break;

default:
dataOk = fetchFeature( f );
break;
default:
dataOk = fetchFeature( f );
break;
}
}

// simplify the geometry using the simplifier configured
if ( dataOk && mLocalSimplification )
{
const QgsGeometry* geometry = f.constGeometry();
if ( geometry )
if ( f.constGeometry() )
simplify( f );
}
if ( dataOk )
Expand Down Expand Up @@ -101,6 +208,9 @@ void QgsAbstractFeatureIterator::ref()
if ( refs == 0 )
{
prepareSimplification( mRequest.simplifyMethod() );

// Should be called as last preparation step since it possibly will already fetch all features
setupOrderBy( mRequest.orderBys() );
}
refs++;
}
Expand Down Expand Up @@ -129,6 +239,50 @@ bool QgsAbstractFeatureIterator::prepareSimplification( const QgsSimplifyMethod&
return false;
}

void QgsAbstractFeatureIterator::setupOrderBy( const QList<QgsFeatureRequest::OrderByClause>& orderBys )
{
// Let the provider try using an efficient order by strategy first
if ( !orderBys.isEmpty() && !prepareOrderBy( orderBys ) )
{
// No success from the provider

// Prepare the expressions
QList<QgsFeatureRequest::OrderByClause> preparedOrderBys( orderBys );
QList<QgsFeatureRequest::OrderByClause>::iterator orderByIt( preparedOrderBys.begin() );

QgsExpressionContext* expressionContext( mRequest.expressionContext() );
do
{
orderByIt->expression().prepare( expressionContext );
}
while ( ++orderByIt != preparedOrderBys.end() );

// Fetch all features
QgsIndexedFeature indexedFeature;
indexedFeature.mIndexes.resize( preparedOrderBys.size() );

while ( nextFeature( indexedFeature.mFeature ) )
{
expressionContext->setFeature( indexedFeature.mFeature );
int i = 0;
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& orderBy, preparedOrderBys )
{
indexedFeature.mIndexes.replace( i++, orderBy.expression().evaluate( expressionContext ) );
}

// We need all features, to ignore the limit for this pre-fetch
// keep the fetched count at 0.
mFetchedCount = 0;
mCachedFeatures.append( indexedFeature );
}

std::sort( mCachedFeatures.begin(), mCachedFeatures.end(), QgsExpressionSorter( preparedOrderBys ) );

mFeatureIterator = mCachedFeatures.constBegin();
mUseCachedFeatures = true;
}
}

bool QgsAbstractFeatureIterator::providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const
{
Q_UNUSED( methodType )
Expand All @@ -149,6 +303,12 @@ bool QgsAbstractFeatureIterator::simplify( QgsFeature& feature )
return false;
}

bool QgsAbstractFeatureIterator::prepareOrderBy( const QList<QgsFeatureRequest::OrderByClause>& orderBys )
{
Q_UNUSED( orderBys )
return false;
}

///////

QgsFeatureIterator& QgsFeatureIterator::operator=( const QgsFeatureIterator & other )
Expand Down
30 changes: 30 additions & 0 deletions src/core/qgsfeatureiterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@

class QgsAbstractGeometrySimplifier;

// Temporarily used structure to cache order by information
class QgsIndexedFeature
{
public:
QVector<QVariant> mIndexes;
QgsFeature mFeature;
};

/** \ingroup core
* Internal feature iterator to be implemented within data providers
*/
Expand Down Expand Up @@ -99,11 +107,33 @@ class CORE_EXPORT QgsAbstractFeatureIterator
//! this iterator runs local simplification
bool mLocalSimplification;

bool mUseCachedFeatures;
QList<QgsIndexedFeature> mCachedFeatures;
QList<QgsIndexedFeature>::ConstIterator mFeatureIterator;

//! returns whether the iterator supports simplify geometries on provider side
virtual bool providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const;

//! simplify the specified geometry if it was configured
virtual bool simplify( QgsFeature& feature );

/**
* Should be overwritten by providers which implement an own order by strategy
* If the own order by strategy is successful, return true, if not, return false
* and a local order by will be triggered instead.
* By default returns false
*
* @note added in QGIS 2.14
*/
virtual bool prepareOrderBy( const QList<QgsFeatureRequest::OrderByClause>& orderBys );

/**
* Setup the orderby. Internally calls prepareOrderBy and if false is returned will
* cache all features and order them with local expression evaluation.
*
* @note added in QGIS 2.14
*/
void setupOrderBy( const QList<QgsFeatureRequest::OrderByClause>& orderBys );
};


Expand Down
Loading

0 comments on commit d96a274

Please sign in to comment.