Skip to content
Permalink
Browse files
Fix some providers not requesting all required attributes needed
for client side order by clauses
  • Loading branch information
nyalldawson committed May 25, 2017
1 parent aa376c2 commit 47ffb588fea623b490fe37822076965a1e8e832a
@@ -142,6 +142,18 @@ QgsDelimitedTextFeatureIterator::QgsDelimitedTextFeatureIterator( QgsDelimitedTe
attributeIndexes += attrs.toSet();
mRequest.setSubsetOfAttributes( attributeIndexes.toList() );
}
// also need attributes required by order by
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
{
QgsAttributeList attrs = request.subsetOfAttributes();
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
{
int attrIndex = mSource->mFields.lookupField( attr );
if ( !attrs.contains( attrIndex ) )
attrs << attrIndex;
}
mRequest.setSubsetOfAttributes( attrs );
}

QgsDebugMsg( QString( "Iterator is scanning file: " ) + ( mMode == FileScan ? "Yes" : "No" ) );
QgsDebugMsg( QString( "Iterator is loading geometries: " ) + ( mLoadGeometry ? "Yes" : "No" ) );
@@ -87,6 +87,19 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource *source, bool
attrs = attributeIndexes.toList();
mRequest.setSubsetOfAttributes( attrs );
}
// also need attributes required by order by
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
{
QSet<int> attributeIndexes;
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
{
attributeIndexes << mSource->mFields.lookupField( attr );
}
attributeIndexes += attrs.toSet();
attrs = attributeIndexes.toList();
mRequest.setSubsetOfAttributes( attrs );
}

if ( request.filterType() == QgsFeatureRequest::FilterExpression && request.filterExpression()->needsGeometry() )
{
mFetchGeometry = true;
@@ -163,6 +163,19 @@ QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource
mOrderByCompiled = false;
}

// ensure that all attributes required for order by are fetched
if ( !mOrderByCompiled && mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
{
QgsAttributeList attrs = mRequest.subsetOfAttributes();
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
{
int attrIndex = mSource->mFields.lookupField( attr );
if ( !attrs.contains( attrIndex ) )
attrs << attrIndex;
}
mRequest.setSubsetOfAttributes( attrs );
}

if ( !mOrderByCompiled )
limitAtProvider = false;

@@ -129,7 +129,6 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature
}
}


whereClause = whereClauses.join( QStringLiteral( " AND " ) );

// Setup the order by
@@ -175,6 +174,18 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature
if ( !mOrderByCompiled )
limitAtProvider = false;

// also need attributes required by order by
if ( !mOrderByCompiled && mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
{
QSet<int> attributeIndexes;
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
{
attributeIndexes << mSource->mFields.lookupField( attr );
}
attributeIndexes += mRequest.subsetOfAttributes().toSet();
mRequest.setSubsetOfAttributes( attributeIndexes.toList() );
}

// preparing the SQL statement
bool success = prepareStatement( whereClause, limitAtProvider ? mRequest.limit() : -1, orderByParts.join( QStringLiteral( "," ) ) );
if ( !success && useFallbackWhereClause )
@@ -109,6 +109,17 @@ QgsVirtualLayerFeatureIterator::QgsVirtualLayerFeatureIterator( QgsVirtualLayerF
mAttributes << attrIdx;
}
}

// also need attributes required by order by
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
{
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
{
int attrIdx = mSource->mFields.lookupField( attr );
if ( !mAttributes.contains( attrIdx ) )
mAttributes << attrIdx;
}
}
}
else
{
@@ -854,6 +854,21 @@ QgsWFSFeatureIterator::QgsWFSFeatureIterator( QgsWFSFeatureSource *source,
}
}

// also need attributes required by order by
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
{
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
{
int idx = dataProviderFields.indexFromName( attr );
if ( idx >= 0 && !cacheSubSet.contains( idx ) )
cacheSubSet.append( idx );

idx = mShared->mFields.indexFromName( attr );
if ( idx >= 0 && !mSubSetAttributes.contains( idx ) )
mSubSetAttributes.append( idx );
}
}

if ( mFetchGeometry )
{
int hexwkbGeomIdx = dataProviderFields.indexFromName( QgsWFSConstants::FIELD_HEXWKB_GEOM );
@@ -270,19 +270,25 @@ def runGetFeatureTests(self, source):
self.assert_query(source, 'intersects($geometry,geom_from_gml( \'<gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-72.2,66.1 -65.2,66.1 -65.2,72.0 -72.2,72.0 -72.2,66.1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>\'))', [1, 2])

# combination of an uncompilable expression and limit
feature = next(self.source.getFeatures(QgsFeatureRequest().setFilterExpression('pk=4')))
context = QgsExpressionContext()
scope = QgsExpressionContextScope()
scope.setVariable('parent', feature)
context.appendScope(scope)

request = QgsFeatureRequest()
request.setExpressionContext(context)
request.setFilterExpression('"pk" = attribute(@parent, \'pk\')')
request.setLimit(1)
# TODO - move this test to FeatureSourceTestCase
# it's currently added in ProviderTestCase, but tests only using a QgsVectorLayer getting features,
# i.e. not directly requesting features from the provider. Turns out the WFS provider fails this
# and should be fixed - then we can enable this test at the FeatureSourceTestCase level

values = [f['pk'] for f in self.source.getFeatures(request)]
self.assertEqual(values, [4])
#feature = next(self.source.getFeatures(QgsFeatureRequest().setFilterExpression('pk=4')))
#context = QgsExpressionContext()
#scope = QgsExpressionContextScope()
#scope.setVariable('parent', feature)
#context.appendScope(scope)

#request = QgsFeatureRequest()
#request.setExpressionContext(context)
#request.setFilterExpression('"pk" = attribute(@parent, \'pk\')')
#request.setLimit(1)

#values = [f['pk'] for f in self.source.getFeatures(request)]
#self.assertEqual(values, [4])

def testGetFeaturesExp(self):
self.runGetFeatureTests(self.source)
@@ -342,6 +342,10 @@ def tearDownClass(cls):
shutil.rmtree(cls.basetestpath, True)
cls.vl = None # so as to properly close the provider and remove any temporary file

def testWkbType(self):
"""N/A for WFS provider"""
pass

def testInconsistentUri(self):
"""Test a URI with a typename that doesn't match a type of the capabilities"""

0 comments on commit 47ffb58

Please sign in to comment.