Skip to content

Commit edb16d0

Browse files
committed
[FEATURE] Feature limit support for feature requests
Limits the maximum number of features returned by the iterator. Some providers (postgres, spatialite, MS SQL) pass the limit on to the provider to result in faster queries.
1 parent 60ad688 commit edb16d0

12 files changed

+158
-26
lines changed

python/core/qgsfeaturerequest.sip

+13
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ class QgsFeatureRequest
9292
*/
9393
QgsFeatureRequest& disableFilter();
9494

95+
/** Set the maximum number of features to request.
96+
* @param limit maximum number of features, or -1 to request all features.
97+
* @see limit()
98+
* @note added in QGIS 2.14
99+
*/
100+
QgsFeatureRequest& setLimit( long limit );
101+
102+
/** Returns the maximum number of features to request, or -1 if no limit set.
103+
* @see setLimit
104+
* @note added in QGIS 2.14
105+
*/
106+
long limit() const;
107+
95108
//! Set flags that affect how features will be fetched
96109
QgsFeatureRequest& setFlags( const Flags& flags );
97110
const Flags& flags() const;

src/core/qgsexpression.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -2184,6 +2184,7 @@ static QVariant fcnGetFeature( const QVariantList& values, const QgsExpressionCo
21842184
QgsFeatureRequest req;
21852185
req.setFilterExpression( QString( "%1=%2" ).arg( QgsExpression::quotedColumnRef( attribute ),
21862186
QgsExpression::quotedString( attVal.toString() ) ) );
2187+
req.setLimit( 1 );
21872188
if ( !parent->needsGeometry() )
21882189
{
21892190
req.setFlags( QgsFeatureRequest::NoGeometry );

src/core/qgsfeatureiterator.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ QgsAbstractFeatureIterator::QgsAbstractFeatureIterator( const QgsFeatureRequest&
2222
: mRequest( request )
2323
, mClosed( false )
2424
, refs( 0 )
25+
, mFetchedCount( 0 )
2526
, mGeometrySimplifier( NULL )
2627
, mLocalSimplification( false )
2728
{
@@ -36,6 +37,10 @@ QgsAbstractFeatureIterator::~QgsAbstractFeatureIterator()
3637
bool QgsAbstractFeatureIterator::nextFeature( QgsFeature& f )
3738
{
3839
bool dataOk = false;
40+
if ( mRequest.limit() >= 0 && mFetchedCount >= mRequest.limit() )
41+
{
42+
return false;
43+
}
3944

4045
switch ( mRequest.filterType() )
4146
{
@@ -59,6 +64,9 @@ bool QgsAbstractFeatureIterator::nextFeature( QgsFeature& f )
5964
if ( geometry )
6065
simplify( f );
6166
}
67+
if ( dataOk )
68+
mFetchedCount++;
69+
6270
return dataOk;
6371
}
6472

src/core/qgsfeatureiterator.h

+9
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ class CORE_EXPORT QgsAbstractFeatureIterator
8787
void deref(); //!< remove reference, delete if refs == 0
8888
friend class QgsFeatureIterator;
8989

90+
//! Number of features already fetched by iterator
91+
long mFetchedCount;
92+
9093
//! Setup the simplification of geometries to fetch using the specified simplify method
9194
virtual bool prepareSimplification( const QgsSimplifyMethod& simplifyMethod );
9295

@@ -198,11 +201,17 @@ inline bool QgsFeatureIterator::nextFeature( QgsFeature& f )
198201

199202
inline bool QgsFeatureIterator::rewind()
200203
{
204+
if ( mIter )
205+
mIter->mFetchedCount = 0;
206+
201207
return mIter ? mIter->rewind() : false;
202208
}
203209

204210
inline bool QgsFeatureIterator::close()
205211
{
212+
if ( mIter )
213+
mIter->mFetchedCount = 0;
214+
206215
return mIter ? mIter->close() : false;
207216
}
208217

src/core/qgsfeaturerequest.cpp

+13-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ QgsFeatureRequest::QgsFeatureRequest()
2727
, mFilterFid( -1 )
2828
, mFilterExpression( 0 )
2929
, mFlags( 0 )
30+
, mLimit( -1 )
3031
{
3132
}
3233

@@ -35,6 +36,7 @@ QgsFeatureRequest::QgsFeatureRequest( QgsFeatureId fid )
3536
, mFilterFid( fid )
3637
, mFilterExpression( 0 )
3738
, mFlags( 0 )
39+
, mLimit( -1 )
3840
{
3941
}
4042

@@ -44,6 +46,7 @@ QgsFeatureRequest::QgsFeatureRequest( const QgsRectangle& rect )
4446
, mFilterFid( -1 )
4547
, mFilterExpression( 0 )
4648
, mFlags( 0 )
49+
, mLimit( -1 )
4750
{
4851
}
4952

@@ -53,6 +56,7 @@ QgsFeatureRequest::QgsFeatureRequest( const QgsExpression& expr, const QgsExpres
5356
, mFilterExpression( new QgsExpression( expr.expression() ) )
5457
, mExpressionContext( context )
5558
, mFlags( 0 )
59+
, mLimit( -1 )
5660
{
5761
}
5862

@@ -79,6 +83,7 @@ QgsFeatureRequest& QgsFeatureRequest::operator=( const QgsFeatureRequest & rh )
7983
mExpressionContext = rh.mExpressionContext;
8084
mAttrs = rh.mAttrs;
8185
mSimplifyMethod = rh.mSimplifyMethod;
86+
mLimit = rh.mLimit;
8287
return *this;
8388
}
8489

@@ -102,7 +107,7 @@ QgsFeatureRequest& QgsFeatureRequest::setFilterFid( QgsFeatureId fid )
102107
return *this;
103108
}
104109

105-
QgsFeatureRequest&QgsFeatureRequest::setFilterFids( const QgsFeatureIds& fids )
110+
QgsFeatureRequest& QgsFeatureRequest::setFilterFids( const QgsFeatureIds& fids )
106111
{
107112
mFilter = FilterFids;
108113
mFilterFids = fids;
@@ -117,7 +122,7 @@ QgsFeatureRequest& QgsFeatureRequest::setFilterExpression( const QString& expres
117122
return *this;
118123
}
119124

120-
QgsFeatureRequest&QgsFeatureRequest::combineFilterExpression( const QString& expression )
125+
QgsFeatureRequest& QgsFeatureRequest::combineFilterExpression( const QString& expression )
121126
{
122127
if ( mFilterExpression )
123128
{
@@ -136,6 +141,12 @@ QgsFeatureRequest &QgsFeatureRequest::setExpressionContext( const QgsExpressionC
136141
return *this;
137142
}
138143

144+
QgsFeatureRequest& QgsFeatureRequest::setLimit( long limit )
145+
{
146+
mLimit = limit;
147+
return *this;
148+
}
149+
139150
QgsFeatureRequest& QgsFeatureRequest::setFlags( const QgsFeatureRequest::Flags& flags )
140151
{
141152
mFlags = flags;

src/core/qgsfeaturerequest.h

+14
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,19 @@ class CORE_EXPORT QgsFeatureRequest
175175
*/
176176
QgsFeatureRequest& disableFilter() { mFilter = FilterNone; return *this; }
177177

178+
/** Set the maximum number of features to request.
179+
* @param limit maximum number of features, or -1 to request all features.
180+
* @see limit()
181+
* @note added in QGIS 2.14
182+
*/
183+
QgsFeatureRequest& setLimit( long limit );
184+
185+
/** Returns the maximum number of features to request, or -1 if no limit set.
186+
* @see setLimit
187+
* @note added in QGIS 2.14
188+
*/
189+
long limit() const { return mLimit; }
190+
178191
//! Set flags that affect how features will be fetched
179192
QgsFeatureRequest& setFlags( const QgsFeatureRequest::Flags& flags );
180193
const Flags& flags() const { return mFlags; }
@@ -223,6 +236,7 @@ class CORE_EXPORT QgsFeatureRequest
223236
Flags mFlags;
224237
QgsAttributeList mAttrs;
225238
QgsSimplifyMethod mSimplifyMethod;
239+
long mLimit;
226240
};
227241

228242
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsFeatureRequest::Flags )

src/providers/mssql/qgsmssqlfeatureiterator.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ void QgsMssqlFeatureIterator::BuildStatement( const QgsFeatureRequest& request )
6363
// build sql statement
6464
mStatement = QString( "SELECT " );
6565

66+
if ( request.limit() >= 0 && request.filterType() != QgsFeatureRequest::FilterExpression )
67+
mStatement += QString( "TOP %1 " ).arg( mRequest.limit() );
68+
6669
mStatement += QString( "[%1]" ).arg( mSource->mFidColName );
6770
mFidCol = mSource->mFields.indexFromName( mSource->mFidColName );
6871
mAttributesToFetch.append( mFidCol );

src/providers/postgres/qgspostgresfeatureiterator.cpp

+23-8
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource
5959
mCursorName = mConn->uniqueCursorName();
6060
QString whereClause;
6161

62+
bool limitAtProvider = ( mRequest.limit() >= 0 );
63+
6264
if ( !request.filterRect().isNull() && !mSource->mGeometryColumn.isNull() )
6365
{
6466
whereClause = whereClauseRect();
@@ -76,15 +78,25 @@ QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource
7678

7779
whereClause = QgsPostgresUtils::andWhereClauses( whereClause, fidsWhereClause );
7880
}
79-
else if ( request.filterType() == QgsFeatureRequest::FilterExpression
80-
&& QSettings().value( "/qgis/compileExpressions", true ).toBool() )
81+
else if ( request.filterType() == QgsFeatureRequest::FilterExpression )
8182
{
82-
QgsPostgresExpressionCompiler compiler = QgsPostgresExpressionCompiler( source );
83+
if ( QSettings().value( "/qgis/compileExpressions", true ).toBool() )
84+
{
85+
QgsPostgresExpressionCompiler compiler = QgsPostgresExpressionCompiler( source );
8386

84-
if ( compiler.compile( request.filterExpression() ) == QgsSqlExpressionCompiler::Complete )
87+
if ( compiler.compile( request.filterExpression() ) == QgsSqlExpressionCompiler::Complete )
88+
{
89+
whereClause = QgsPostgresUtils::andWhereClauses( whereClause, compiler.result() );
90+
mExpressionCompiled = true;
91+
}
92+
else
93+
{
94+
limitAtProvider = false;
95+
}
96+
}
97+
else
8598
{
86-
whereClause = QgsPostgresUtils::andWhereClauses( whereClause, compiler.result() );
87-
mExpressionCompiled = true;
99+
limitAtProvider = false;
88100
}
89101
}
90102

@@ -96,7 +108,7 @@ QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource
96108
whereClause += '(' + mSource->mSqlWhereClause + ')';
97109
}
98110

99-
if ( !declareCursor( whereClause ) )
111+
if ( !declareCursor( whereClause, limitAtProvider ? mRequest.limit() : -1 ) )
100112
{
101113
mClosed = true;
102114
iteratorClosed();
@@ -324,7 +336,7 @@ QString QgsPostgresFeatureIterator::whereClauseRect()
324336

325337

326338

327-
bool QgsPostgresFeatureIterator::declareCursor( const QString& whereClause )
339+
bool QgsPostgresFeatureIterator::declareCursor( const QString& whereClause, long limit )
328340
{
329341
mFetchGeometry = !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) && !mSource->mGeometryColumn.isNull();
330342
#if 0
@@ -483,6 +495,9 @@ bool QgsPostgresFeatureIterator::declareCursor( const QString& whereClause )
483495
if ( !whereClause.isEmpty() )
484496
query += QString( " WHERE %1" ).arg( whereClause );
485497

498+
if ( limit >= 0 )
499+
query += QString( " LIMIT %1" ).arg( limit );
500+
486501
if ( !mConn->openCursor( mCursorName, query ) )
487502
{
488503

src/providers/postgres/qgspostgresfeatureiterator.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class QgsPostgresFeatureIterator : public QgsAbstractFeatureIteratorFromSource<Q
9797
QString whereClauseRect();
9898
bool getFeature( QgsPostgresResult &queryResult, int row, QgsFeature &feature );
9999
void getFeatureAttribute( int idx, QgsPostgresResult& queryResult, int row, int& col, QgsFeature& feature );
100-
bool declareCursor( const QString& whereClause );
100+
bool declareCursor( const QString& whereClause, long limit = -1 );
101101

102102
QString mCursorName;
103103

src/providers/spatialite/qgsspatialitefeatureiterator.cpp

+32-13
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature
3737

3838
QStringList whereClauses;
3939
QString whereClause;
40+
41+
//beware - limitAtProvider needs to be set to false if the request cannot be completely handled
42+
//by the provider (eg utilising QGIS expression filters)
43+
bool limitAtProvider = ( mRequest.limit() >= 0 );
44+
4045
if ( !request.filterRect().isNull() && !mSource->mGeometryColumn.isNull() )
4146
{
4247
// some kind of MBR spatial filtering is required
@@ -63,23 +68,34 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature
6368
whereClauses.append( whereClause );
6469
}
6570
}
66-
else if ( request.filterType() == QgsFeatureRequest::FilterExpression
67-
&& QSettings().value( "/qgis/compileExpressions", true ).toBool() )
71+
else if ( request.filterType() == QgsFeatureRequest::FilterExpression )
6872
{
69-
QgsSpatiaLiteExpressionCompiler compiler = QgsSpatiaLiteExpressionCompiler( source );
73+
if ( QSettings().value( "/qgis/compileExpressions", true ).toBool() )
74+
{
75+
QgsSpatiaLiteExpressionCompiler compiler = QgsSpatiaLiteExpressionCompiler( source );
7076

71-
QgsSqlExpressionCompiler::Result result = compiler.compile( request.filterExpression() );
77+
QgsSqlExpressionCompiler::Result result = compiler.compile( request.filterExpression() );
7278

73-
if ( result == QgsSqlExpressionCompiler::Complete || result == QgsSqlExpressionCompiler::Partial )
74-
{
75-
whereClause = compiler.result();
76-
if ( !whereClause.isEmpty() )
79+
if ( result == QgsSqlExpressionCompiler::Complete || result == QgsSqlExpressionCompiler::Partial )
80+
{
81+
whereClause = compiler.result();
82+
if ( !whereClause.isEmpty() )
83+
{
84+
whereClauses.append( whereClause );
85+
//if only partial success when compiling expression, we need to double-check results using QGIS' expressions
86+
mExpressionCompiled = ( result == QgsSqlExpressionCompiler::Complete );
87+
}
88+
}
89+
if ( result != QgsSqlExpressionCompiler::Complete )
7790
{
78-
whereClauses.append( whereClause );
79-
//if only partial success when compiling expression, we need to double-check results using QGIS' expressions
80-
mExpressionCompiled = ( result == QgsSqlExpressionCompiler::Complete );
91+
//can't apply limit at provider side as we need to check all results using QGIS expressions
92+
limitAtProvider = false;
8193
}
8294
}
95+
else
96+
{
97+
limitAtProvider = false;
98+
}
8399
}
84100

85101
if ( !mSource->mSubsetString.isEmpty() )
@@ -94,7 +110,7 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature
94110
whereClause = whereClauses.join( " AND " );
95111

96112
// preparing the SQL statement
97-
if ( !prepareStatement( whereClause ) )
113+
if ( !prepareStatement( whereClause, limitAtProvider ? mRequest.limit() : -1 ) )
98114
{
99115
// some error occurred
100116
sqliteStatement = NULL;
@@ -183,7 +199,7 @@ bool QgsSpatiaLiteFeatureIterator::close()
183199
////
184200

185201

186-
bool QgsSpatiaLiteFeatureIterator::prepareStatement( const QString& whereClause )
202+
bool QgsSpatiaLiteFeatureIterator::prepareStatement( const QString& whereClause, long limit )
187203
{
188204
if ( !mHandle )
189205
return false;
@@ -222,6 +238,9 @@ bool QgsSpatiaLiteFeatureIterator::prepareStatement( const QString& whereClause
222238
if ( !whereClause.isEmpty() )
223239
sql += QString( " WHERE %1" ).arg( whereClause );
224240

241+
if ( limit >= 0 )
242+
sql += QString( " LIMIT %1" ).arg( limit );
243+
225244
if ( sqlite3_prepare_v2( mHandle->handle(), sql.toUtf8().constData(), -1, &sqliteStatement, NULL ) != SQLITE_OK )
226245
{
227246
// some error occurred

src/providers/spatialite/qgsspatialitefeatureiterator.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class QgsSpatiaLiteFeatureIterator : public QgsAbstractFeatureIteratorFromSource
7777
QString whereClauseFid();
7878
QString whereClauseFids();
7979
QString mbr( const QgsRectangle& rect );
80-
bool prepareStatement( const QString& whereClause );
80+
bool prepareStatement( const QString& whereClause, long limit = -1 );
8181
QString quotedPrimaryKey();
8282
bool getFeature( sqlite3_stmt *stmt, QgsFeature &feature );
8383
QString fieldName( const QgsField& fld );

0 commit comments

Comments
 (0)