Skip to content

Commit d96a274

Browse files
committedDec 18, 2015
Add Order By Clause
1 parent b9f0c06 commit d96a274

File tree

5 files changed

+468
-14
lines changed

5 files changed

+468
-14
lines changed
 

‎python/core/qgsfeaturerequest.sip

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,63 @@ class QgsFeatureRequest
2424
FilterFids //!< Filter using feature IDs
2525
};
2626

27+
/**
28+
* @brief The OrderByClause class represents an order by clause for a QgsFeatureRequest
29+
*/
30+
class OrderByClause
31+
{
32+
public:
33+
/**
34+
* Creates a new OrderByClause for a QgsFeatureRequest
35+
*
36+
* @param expression The expression to use for ordering
37+
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
38+
* If thr order is ascending, by default nulls are last
39+
* If thr order is descending, by default nulls are first
40+
*/
41+
OrderByClause( const QString &expression, bool ascending = true );
42+
/**
43+
* Creates a new OrderByClause for a QgsFeatureRequest
44+
*
45+
* @param expression The expression to use for ordering
46+
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
47+
* @param nullsfirst If true, NULLS are at the beginning, if false, NULLS are at the end
48+
*/
49+
OrderByClause( const QString &expression, bool ascending, bool nullsfirst );
50+
51+
/**
52+
* The expression
53+
* @return the expression
54+
*/
55+
QgsExpression expression() const;
56+
57+
/**
58+
* Order ascending
59+
* @return If ascending order is requested
60+
*/
61+
bool ascending() const;
62+
63+
/**
64+
* Set if ascending order is requested
65+
*/
66+
void setAscending( bool ascending );
67+
68+
/**
69+
* Set if NULLS should be returned first
70+
* @return if NULLS should be returned first
71+
*/
72+
bool nullsFirst() const;
73+
74+
/**
75+
* Set if NULLS should be returned first
76+
*/
77+
void setNullsFirst( bool nullsFirst );
78+
79+
};
80+
81+
/**
82+
* A special attribute that if set matches all attributes
83+
*/
2784
static const QString AllAttributes;
2885

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

152+
/**
153+
* Adds a new OrderByClause
154+
*
155+
* @param expression The expression to use for ordering
156+
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
157+
* If the order is ascending, by default nulls are last
158+
* If the order is descending, by default nulls are first
159+
*
160+
* @added in QGIS 2.14
161+
*/
162+
163+
QgsFeatureRequest& addOrderBy( const QString &expression, bool ascending = true );
164+
/**
165+
* Adds a new OrderByClause, appending it as the least important one.
166+
*
167+
* @param expression The expression to use for ordering
168+
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
169+
* @param nullsfirst If true, NULLS are at the beginning, if false, NULLS are at the end
170+
*
171+
* @added in QGIS 2.14
172+
*/
173+
QgsFeatureRequest& addOrderBy( const QString &expression, bool ascending, bool nullsfirst );
174+
175+
/**
176+
* Return a list of order by clauses specified for this feature request.
177+
*/
178+
QList<QgsFeatureRequest::OrderByClause> orderBys() const;
179+
180+
/**
181+
* Set a list of order by clauses.
182+
*/
183+
void setOrderBys(const QList<QgsFeatureRequest::OrderByClause>& orderBys );
184+
95185
/** Set the maximum number of features to request.
96186
* @param limit maximum number of features, or -1 to request all features.
97187
* @see limit()

‎src/core/qgsfeatureiterator.cpp

Lines changed: 173 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,116 @@
1818
#include "qgsgeometrysimplifier.h"
1919
#include "qgssimplifymethod.h"
2020

21+
#include <algorithm>
22+
23+
class QgsExpressionSorter
24+
{
25+
public:
26+
QgsExpressionSorter( const QList<QgsFeatureRequest::OrderByClause>& preparedOrderBys )
27+
: mPreparedOrderBys( preparedOrderBys )
28+
{}
29+
30+
bool operator()( const QgsIndexedFeature& f1, const QgsIndexedFeature& f2 ) const
31+
{
32+
int i = 0;
33+
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& orderBy, mPreparedOrderBys )
34+
{
35+
const QVariant& v1 = f1.mIndexes.at( i );
36+
const QVariant& v2 = f2.mIndexes.at( i );
37+
++i;
38+
39+
// Both NULL: don't care
40+
if ( v1.isNull() && v2.isNull() )
41+
continue;
42+
43+
// Check for NULLs first
44+
if ( v1.isNull() != v2.isNull() )
45+
{
46+
if ( orderBy.nullsFirst() )
47+
return v1.isNull();
48+
else
49+
return !v1.isNull();
50+
}
51+
52+
// Both values are not NULL
53+
switch ( v1.type() )
54+
{
55+
case QVariant::Int:
56+
case QVariant::UInt:
57+
case QVariant::LongLong:
58+
case QVariant::ULongLong:
59+
if ( v1.toLongLong() == v2.toLongLong() )
60+
continue;
61+
if ( orderBy.ascending() )
62+
return v1.toLongLong() < v2.toLongLong();
63+
else
64+
return v1.toLongLong() > v2.toLongLong();
65+
66+
case QVariant::Double:
67+
if ( v1.toDouble() == v2.toDouble() )
68+
continue;
69+
if ( orderBy.ascending() )
70+
return v1.toDouble() < v2.toDouble();
71+
else
72+
return v1.toDouble() > v2.toDouble();
73+
74+
case QVariant::Date:
75+
if ( v1.toDate() == v2.toDate() )
76+
continue;
77+
if ( orderBy.ascending() )
78+
return v1.toDate() < v2.toDate();
79+
else
80+
return v1.toDate() > v2.toDate();
81+
82+
case QVariant::DateTime:
83+
if ( v1.toDateTime() == v2.toDateTime() )
84+
continue;
85+
if ( orderBy.ascending() )
86+
return v1.toDateTime() < v2.toDateTime();
87+
else
88+
return v1.toDateTime() > v2.toDateTime();
89+
90+
case QVariant::Bool:
91+
if ( v1.toBool() == v2.toBool() )
92+
continue;
93+
if ( orderBy.ascending() )
94+
return !v1.toBool();
95+
else
96+
return v1.toBool();
97+
98+
default:
99+
if ( 0 == v1.toString().localeAwareCompare( v2.toString() ) )
100+
continue;
101+
if ( orderBy.ascending() )
102+
return v1.toString().localeAwareCompare( v2.toString() ) < 0;
103+
else
104+
return v1.toString().localeAwareCompare( v2.toString() ) > 0;
105+
}
106+
}
107+
108+
// Equal
109+
return true;
110+
}
111+
112+
private:
113+
QList<QgsFeatureRequest::OrderByClause> mPreparedOrderBys;
114+
};
115+
116+
21117
QgsAbstractFeatureIterator::QgsAbstractFeatureIterator( const QgsFeatureRequest& request )
22118
: mRequest( request )
23119
, mClosed( false )
24120
, refs( 0 )
25121
, mFetchedCount( 0 )
26122
, mGeometrySimplifier( nullptr )
27123
, mLocalSimplification( false )
124+
, mUseCachedFeatures( false )
28125
{
29126
}
30127

31128
QgsAbstractFeatureIterator::~QgsAbstractFeatureIterator()
32129
{
33130
delete mGeometrySimplifier;
34-
mGeometrySimplifier = nullptr;
35131
}
36132

37133
bool QgsAbstractFeatureIterator::nextFeature( QgsFeature& f )
@@ -42,26 +138,37 @@ bool QgsAbstractFeatureIterator::nextFeature( QgsFeature& f )
42138
return false;
43139
}
44140

45-
switch ( mRequest.filterType() )
141+
if ( mUseCachedFeatures )
142+
{
143+
if ( mFeatureIterator != mCachedFeatures.constEnd() )
144+
{
145+
f = mFeatureIterator->mFeature;
146+
++mFeatureIterator;
147+
dataOk = true;
148+
}
149+
}
150+
else
46151
{
47-
case QgsFeatureRequest::FilterExpression:
48-
dataOk = nextFeatureFilterExpression( f );
49-
break;
152+
switch ( mRequest.filterType() )
153+
{
154+
case QgsFeatureRequest::FilterExpression:
155+
dataOk = nextFeatureFilterExpression( f );
156+
break;
50157

51-
case QgsFeatureRequest::FilterFids:
52-
dataOk = nextFeatureFilterFids( f );
53-
break;
158+
case QgsFeatureRequest::FilterFids:
159+
dataOk = nextFeatureFilterFids( f );
160+
break;
54161

55-
default:
56-
dataOk = fetchFeature( f );
57-
break;
162+
default:
163+
dataOk = fetchFeature( f );
164+
break;
165+
}
58166
}
59167

60168
// simplify the geometry using the simplifier configured
61169
if ( dataOk && mLocalSimplification )
62170
{
63-
const QgsGeometry* geometry = f.constGeometry();
64-
if ( geometry )
171+
if ( f.constGeometry() )
65172
simplify( f );
66173
}
67174
if ( dataOk )
@@ -101,6 +208,9 @@ void QgsAbstractFeatureIterator::ref()
101208
if ( refs == 0 )
102209
{
103210
prepareSimplification( mRequest.simplifyMethod() );
211+
212+
// Should be called as last preparation step since it possibly will already fetch all features
213+
setupOrderBy( mRequest.orderBys() );
104214
}
105215
refs++;
106216
}
@@ -129,6 +239,50 @@ bool QgsAbstractFeatureIterator::prepareSimplification( const QgsSimplifyMethod&
129239
return false;
130240
}
131241

242+
void QgsAbstractFeatureIterator::setupOrderBy( const QList<QgsFeatureRequest::OrderByClause>& orderBys )
243+
{
244+
// Let the provider try using an efficient order by strategy first
245+
if ( !orderBys.isEmpty() && !prepareOrderBy( orderBys ) )
246+
{
247+
// No success from the provider
248+
249+
// Prepare the expressions
250+
QList<QgsFeatureRequest::OrderByClause> preparedOrderBys( orderBys );
251+
QList<QgsFeatureRequest::OrderByClause>::iterator orderByIt( preparedOrderBys.begin() );
252+
253+
QgsExpressionContext* expressionContext( mRequest.expressionContext() );
254+
do
255+
{
256+
orderByIt->expression().prepare( expressionContext );
257+
}
258+
while ( ++orderByIt != preparedOrderBys.end() );
259+
260+
// Fetch all features
261+
QgsIndexedFeature indexedFeature;
262+
indexedFeature.mIndexes.resize( preparedOrderBys.size() );
263+
264+
while ( nextFeature( indexedFeature.mFeature ) )
265+
{
266+
expressionContext->setFeature( indexedFeature.mFeature );
267+
int i = 0;
268+
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& orderBy, preparedOrderBys )
269+
{
270+
indexedFeature.mIndexes.replace( i++, orderBy.expression().evaluate( expressionContext ) );
271+
}
272+
273+
// We need all features, to ignore the limit for this pre-fetch
274+
// keep the fetched count at 0.
275+
mFetchedCount = 0;
276+
mCachedFeatures.append( indexedFeature );
277+
}
278+
279+
std::sort( mCachedFeatures.begin(), mCachedFeatures.end(), QgsExpressionSorter( preparedOrderBys ) );
280+
281+
mFeatureIterator = mCachedFeatures.constBegin();
282+
mUseCachedFeatures = true;
283+
}
284+
}
285+
132286
bool QgsAbstractFeatureIterator::providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const
133287
{
134288
Q_UNUSED( methodType )
@@ -149,6 +303,12 @@ bool QgsAbstractFeatureIterator::simplify( QgsFeature& feature )
149303
return false;
150304
}
151305

306+
bool QgsAbstractFeatureIterator::prepareOrderBy( const QList<QgsFeatureRequest::OrderByClause>& orderBys )
307+
{
308+
Q_UNUSED( orderBys )
309+
return false;
310+
}
311+
152312
///////
153313

154314
QgsFeatureIterator& QgsFeatureIterator::operator=( const QgsFeatureIterator & other )

‎src/core/qgsfeatureiterator.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@
2020

2121
class QgsAbstractGeometrySimplifier;
2222

23+
// Temporarily used structure to cache order by information
24+
class QgsIndexedFeature
25+
{
26+
public:
27+
QVector<QVariant> mIndexes;
28+
QgsFeature mFeature;
29+
};
30+
2331
/** \ingroup core
2432
* Internal feature iterator to be implemented within data providers
2533
*/
@@ -99,11 +107,33 @@ class CORE_EXPORT QgsAbstractFeatureIterator
99107
//! this iterator runs local simplification
100108
bool mLocalSimplification;
101109

110+
bool mUseCachedFeatures;
111+
QList<QgsIndexedFeature> mCachedFeatures;
112+
QList<QgsIndexedFeature>::ConstIterator mFeatureIterator;
113+
102114
//! returns whether the iterator supports simplify geometries on provider side
103115
virtual bool providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const;
104116

105117
//! simplify the specified geometry if it was configured
106118
virtual bool simplify( QgsFeature& feature );
119+
120+
/**
121+
* Should be overwritten by providers which implement an own order by strategy
122+
* If the own order by strategy is successful, return true, if not, return false
123+
* and a local order by will be triggered instead.
124+
* By default returns false
125+
*
126+
* @note added in QGIS 2.14
127+
*/
128+
virtual bool prepareOrderBy( const QList<QgsFeatureRequest::OrderByClause>& orderBys );
129+
130+
/**
131+
* Setup the orderby. Internally calls prepareOrderBy and if false is returned will
132+
* cache all features and order them with local expression evaluation.
133+
*
134+
* @note added in QGIS 2.14
135+
*/
136+
void setupOrderBy( const QList<QgsFeatureRequest::OrderByClause>& orderBys );
107137
};
108138

109139

‎src/core/qgsfeaturerequest.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ QgsFeatureRequest& QgsFeatureRequest::operator=( const QgsFeatureRequest & rh )
8484
mAttrs = rh.mAttrs;
8585
mSimplifyMethod = rh.mSimplifyMethod;
8686
mLimit = rh.mLimit;
87+
mOrderBys = rh.mOrderBys;
8788
return *this;
8889
}
8990

@@ -141,6 +142,28 @@ QgsFeatureRequest &QgsFeatureRequest::setExpressionContext( const QgsExpressionC
141142
return *this;
142143
}
143144

145+
QgsFeatureRequest& QgsFeatureRequest::addOrderBy( const QString& expression, bool ascending )
146+
{
147+
mOrderBys.append( OrderByClause( expression, ascending ) );
148+
return *this;
149+
}
150+
151+
QgsFeatureRequest& QgsFeatureRequest::addOrderBy( const QString& expression, bool ascending, bool nullsfirst )
152+
{
153+
mOrderBys.append( OrderByClause( expression, ascending, nullsfirst ) );
154+
return *this;
155+
}
156+
157+
QList<QgsFeatureRequest::OrderByClause> QgsFeatureRequest::orderBys() const
158+
{
159+
return mOrderBys;
160+
}
161+
162+
void QgsFeatureRequest::setOrderBys( const QList<QgsFeatureRequest::OrderByClause>& orderBys )
163+
{
164+
mOrderBys = orderBys;
165+
}
166+
144167
QgsFeatureRequest& QgsFeatureRequest::setLimit( long limit )
145168
{
146169
mLimit = limit;
@@ -228,6 +251,7 @@ bool QgsFeatureRequest::acceptFeature( const QgsFeature& feature )
228251
return true;
229252
}
230253

254+
231255
#include "qgsfeatureiterator.h"
232256
#include "qgslogger.h"
233257

@@ -252,3 +276,43 @@ void QgsAbstractFeatureSource::iteratorClosed( QgsAbstractFeatureIterator* it )
252276
}
253277

254278

279+
280+
QgsFeatureRequest::OrderByClause::OrderByClause( const QString& expression, bool ascending )
281+
: mExpression( expression )
282+
, mAscending( ascending )
283+
{
284+
// postgres behavior: default for ASC: NULLS LAST, default for DESC: NULLS FIRST
285+
mNullsFirst = !ascending;
286+
}
287+
288+
QgsFeatureRequest::OrderByClause::OrderByClause( const QString& expression, bool ascending, bool nullsfirst )
289+
: mExpression( expression )
290+
, mAscending( ascending )
291+
, mNullsFirst( nullsfirst )
292+
{
293+
}
294+
295+
bool QgsFeatureRequest::OrderByClause::ascending() const
296+
{
297+
return mAscending;
298+
}
299+
300+
void QgsFeatureRequest::OrderByClause::setAscending( bool ascending )
301+
{
302+
mAscending = ascending;
303+
}
304+
305+
bool QgsFeatureRequest::OrderByClause::nullsFirst() const
306+
{
307+
return mNullsFirst;
308+
}
309+
310+
void QgsFeatureRequest::OrderByClause::setNullsFirst( bool nullsFirst )
311+
{
312+
mNullsFirst = nullsFirst;
313+
}
314+
315+
QgsExpression QgsFeatureRequest::OrderByClause::expression() const
316+
{
317+
return mExpression;
318+
}

‎src/core/qgsfeaturerequest.h

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,83 @@ class CORE_EXPORT QgsFeatureRequest
8484
FilterFids //!< Filter using feature IDs
8585
};
8686

87+
/**
88+
* The OrderByClause class represents an order by clause for a QgsFeatureRequest.
89+
*
90+
* It can be a simple field or an expression. Multiple order by clauses can be added to
91+
* a QgsFeatureRequest to fine tune the behavior if a single field or expression is not
92+
* enough to completely specify the required behavior.
93+
*
94+
* If expression compilation is activated in the settings and the expression can be
95+
* translated for the provider in question, it will be evaluated on provider side.
96+
* If one of these two premises does not apply, the ordering will take place locally
97+
* which results in increased memory and CPU usage.
98+
*
99+
* If the ordering is done on strings, the order depends on the system's locale if the
100+
* local fallback implementation is used. The order depends on the server system's locale
101+
* and implementation if ordering is done on the server.
102+
*
103+
* In case the fallback code needs to be used, a limit set on the request will be respected
104+
* for the features returned by the iterator but internally all features will be requested
105+
* from the provider.
106+
*
107+
* @note added in QGIS 2.14
108+
*/
109+
class OrderByClause
110+
{
111+
public:
112+
/**
113+
* Creates a new OrderByClause for a QgsFeatureRequest
114+
*
115+
* @param expression The expression to use for ordering
116+
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
117+
* If the order is ascending, by default nulls are last
118+
* If the order is descending, by default nulls are first
119+
*/
120+
OrderByClause( const QString &expression, bool ascending = true );
121+
/**
122+
* Creates a new OrderByClause for a QgsFeatureRequest
123+
*
124+
* @param expression The expression to use for ordering
125+
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
126+
* @param nullsfirst If true, NULLS are at the beginning, if false, NULLS are at the end
127+
*/
128+
OrderByClause( const QString &expression, bool ascending, bool nullsfirst );
129+
130+
/**
131+
* The expression
132+
* @return the expression
133+
*/
134+
QgsExpression expression() const;
135+
136+
/**
137+
* Order ascending
138+
* @return If ascending order is requested
139+
*/
140+
bool ascending() const;
141+
142+
/**
143+
* Set if ascending order is requested
144+
*/
145+
void setAscending( bool ascending );
146+
147+
/**
148+
* Set if NULLS should be returned first
149+
* @return if NULLS should be returned first
150+
*/
151+
bool nullsFirst() const;
152+
153+
/**
154+
* Set if NULLS should be returned first
155+
*/
156+
void setNullsFirst( bool nullsFirst );
157+
158+
private:
159+
QgsExpression mExpression;
160+
bool mAscending;
161+
bool mNullsFirst;
162+
};
163+
87164
/**
88165
* A special attribute that if set matches all attributes
89166
*/
@@ -175,6 +252,39 @@ class CORE_EXPORT QgsFeatureRequest
175252
*/
176253
QgsFeatureRequest& disableFilter() { mFilter = FilterNone; return *this; }
177254

255+
/**
256+
* Adds a new OrderByClause, appending it as the least important one.
257+
*
258+
* @param expression The expression to use for ordering
259+
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
260+
* If the order is ascending, by default nulls are last
261+
* If the order is descending, by default nulls are first
262+
*
263+
* @note added in QGIS 2.14
264+
*/
265+
266+
QgsFeatureRequest& addOrderBy( const QString &expression, bool ascending = true );
267+
/**
268+
* Adds a new OrderByClause, appending it as the least important one.
269+
*
270+
* @param expression The expression to use for ordering
271+
* @param ascending If the order should be ascending (1,2,3) or descending (3,2,1)
272+
* @param nullsfirst If true, NULLS are at the beginning, if false, NULLS are at the end
273+
*
274+
* @note added in QGIS 2.14
275+
*/
276+
QgsFeatureRequest& addOrderBy( const QString &expression, bool ascending, bool nullsfirst );
277+
278+
/**
279+
* Return a list of order by clauses specified for this feature request.
280+
*/
281+
QList<OrderByClause> orderBys() const;
282+
283+
/**
284+
* Set a list of order by clauses.
285+
*/
286+
void setOrderBys( const QList<OrderByClause>& orderBys );
287+
178288
/** Set the maximum number of features to request.
179289
* @param limit maximum number of features, or -1 to request all features.
180290
* @see limit()
@@ -224,7 +334,6 @@ class CORE_EXPORT QgsFeatureRequest
224334

225335
// TODO: in future
226336
// void setFilterNativeExpression(con QString& expr); // using provider's SQL (if supported)
227-
// void setLimit(int limit);
228337

229338
protected:
230339
FilterType mFilter;
@@ -237,6 +346,7 @@ class CORE_EXPORT QgsFeatureRequest
237346
QgsAttributeList mAttrs;
238347
QgsSimplifyMethod mSimplifyMethod;
239348
long mLimit;
349+
QList<OrderByClause> mOrderBys;
240350
};
241351

242352
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsFeatureRequest::Flags )

0 commit comments

Comments
 (0)
Please sign in to comment.