Skip to content

Commit c27c9b5

Browse files
nyalldawsonmhugent
authored andcommitted
Correctly support joins using virtual fields
1 parent c151724 commit c27c9b5

File tree

3 files changed

+232
-102
lines changed

3 files changed

+232
-102
lines changed

src/core/qgsvectorlayerfeatureiterator.cpp

+124-100
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,7 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat
9595
, mFetchedFid( false )
9696
, mEditGeometrySimplifier( nullptr )
9797
{
98-
prepareExpressions();
99-
100-
// prepare joins: may add more attributes to fetch (in order to allow join)
101-
if ( mSource->mJoinBuffer->containsJoins() )
102-
prepareJoins();
98+
prepareFields();
10399

104100
mHasVirtualAttributes = !mFetchJoinInfo.isEmpty() || !mExpressionFieldInfo.isEmpty();
105101

@@ -472,128 +468,132 @@ void QgsVectorLayerFeatureIterator::rewindEditBuffer()
472468
mFetchChangedGeomIt = mSource->mChangedGeometries.constBegin();
473469
}
474470

471+
void QgsVectorLayerFeatureIterator::prepareJoin( int fieldIdx )
472+
{
473+
if ( !mSource->mFields.exists( fieldIdx ) )
474+
return;
475475

476+
if ( mSource->mFields.fieldOrigin( fieldIdx ) != QgsFields::OriginJoin )
477+
return;
476478

477-
void QgsVectorLayerFeatureIterator::prepareJoins()
478-
{
479-
QgsAttributeList fetchAttributes = ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes ) ? mRequest.subsetOfAttributes() : mSource->mFields.allAttributesList();
480-
QgsAttributeList sourceJoinFields; // attributes that also need to be fetched from this layer in order to have joins working
479+
int sourceLayerIndex;
480+
const QgsVectorJoinInfo* joinInfo = mSource->mJoinBuffer->joinForFieldIndex( fieldIdx, mSource->mFields, sourceLayerIndex );
481+
Q_ASSERT( joinInfo );
481482

482-
mFetchJoinInfo.clear();
483+
QgsVectorLayer* joinLayer = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinInfo->joinLayerId ) );
484+
Q_ASSERT( joinLayer );
483485

484-
for ( QgsAttributeList::const_iterator attIt = fetchAttributes.constBegin(); attIt != fetchAttributes.constEnd(); ++attIt )
486+
if ( !mFetchJoinInfo.contains( joinInfo ) )
485487
{
486-
if ( !mSource->mFields.exists( *attIt ) )
487-
continue;
488+
FetchJoinInfo info;
489+
info.joinInfo = joinInfo;
490+
info.joinLayer = joinLayer;
491+
info.indexOffset = mSource->mJoinBuffer->joinedFieldsOffset( joinInfo, mSource->mFields );
488492

489-
if ( mSource->mFields.fieldOrigin( *attIt ) != QgsFields::OriginJoin )
490-
continue;
493+
if ( joinInfo->targetFieldName.isEmpty() )
494+
info.targetField = joinInfo->targetFieldIndex; //for compatibility with 1.x
495+
else
496+
info.targetField = mSource->mFields.indexFromName( joinInfo->targetFieldName );
491497

492-
int sourceLayerIndex;
493-
const QgsVectorJoinInfo* joinInfo = mSource->mJoinBuffer->joinForFieldIndex( *attIt, mSource->mFields, sourceLayerIndex );
494-
Q_ASSERT( joinInfo );
498+
if ( joinInfo->joinFieldName.isEmpty() )
499+
info.joinField = joinInfo->joinFieldIndex; //for compatibility with 1.x
500+
else
501+
info.joinField = joinLayer->fields().indexFromName( joinInfo->joinFieldName );
495502

496-
QgsVectorLayer* joinLayer = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinInfo->joinLayerId ) );
497-
Q_ASSERT( joinLayer );
503+
// for joined fields, we always need to request the targetField from the provider too
504+
if ( !mPreparedFields.contains( info.targetField ) && !mFieldsToPrepare.contains( info.targetField ) )
505+
mFieldsToPrepare << info.targetField;
498506

499-
if ( !mFetchJoinInfo.contains( joinInfo ) )
500-
{
501-
FetchJoinInfo info;
502-
info.joinInfo = joinInfo;
503-
info.joinLayer = joinLayer;
504-
info.indexOffset = mSource->mJoinBuffer->joinedFieldsOffset( joinInfo, mSource->mFields );
505-
506-
if ( joinInfo->targetFieldName.isEmpty() )
507-
info.targetField = joinInfo->targetFieldIndex; //for compatibility with 1.x
508-
else
509-
info.targetField = mSource->mFields.indexFromName( joinInfo->targetFieldName );
510-
511-
if ( joinInfo->joinFieldName.isEmpty() )
512-
info.joinField = joinInfo->joinFieldIndex; //for compatibility with 1.x
513-
else
514-
info.joinField = joinLayer->fields().indexFromName( joinInfo->joinFieldName );
515-
516-
// for joined fields, we always need to request the targetField from the provider too
517-
if ( !fetchAttributes.contains( info.targetField ) )
518-
sourceJoinFields << info.targetField;
519-
520-
mFetchJoinInfo.insert( joinInfo, info );
521-
}
507+
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.subsetOfAttributes().contains( info.targetField ) )
508+
mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() << info.targetField );
522509

523-
// store field source index - we'll need it when fetching from provider
524-
mFetchJoinInfo[ joinInfo ].attributes.push_back( sourceLayerIndex );
510+
mFetchJoinInfo.insert( joinInfo, info );
525511
}
526512

527-
// add sourceJoinFields if we're using a subset
528-
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
529-
mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() + sourceJoinFields );
513+
// store field source index - we'll need it when fetching from provider
514+
mFetchJoinInfo[ joinInfo ].attributes.push_back( sourceLayerIndex );
530515
}
531516

532-
void QgsVectorLayerFeatureIterator::prepareExpressions()
517+
void QgsVectorLayerFeatureIterator::prepareExpression( int fieldIdx )
533518
{
534-
const QList<QgsExpressionFieldBuffer::ExpressionField> exps = mSource->mExpressionFieldBuffer->expressions();
519+
const QList<QgsExpressionFieldBuffer::ExpressionField>& exps = mSource->mExpressionFieldBuffer->expressions();
535520

536-
mExpressionContext.reset( new QgsExpressionContext() );
537-
mExpressionContext->appendScope( QgsExpressionContextUtils::globalScope() );
538-
mExpressionContext->appendScope( QgsExpressionContextUtils::projectScope() );
539-
mExpressionContext->setFields( mSource->mFields );
521+
int oi = mSource->mFields.fieldOriginIndex( fieldIdx );
522+
QgsExpression* exp = new QgsExpression( exps[oi].cachedExpression );
540523

541-
QList< int > virtualFieldsToFetch;
542-
for ( int i = 0; i < mSource->mFields.count(); i++ )
524+
QgsDistanceArea da;
525+
da.setSourceCrs( mSource->mCrsId );
526+
da.setEllipsoidalMode( true );
527+
da.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
528+
exp->setGeomCalculator( da );
529+
exp->setDistanceUnits( QgsProject::instance()->distanceUnits() );
530+
exp->setAreaUnits( QgsProject::instance()->areaUnits() );
531+
532+
exp->prepare( mExpressionContext.data() );
533+
mExpressionFieldInfo.insert( fieldIdx, exp );
534+
535+
Q_FOREACH ( const QString& col, exp->referencedColumns() )
543536
{
544-
if ( mSource->mFields.fieldOrigin( i ) == QgsFields::OriginExpression )
537+
int dependantFieldIdx = mSource->mFields.fieldNameIndex( col );
538+
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
545539
{
546-
// Only prepare if there is no subset defined or the subset contains this field
547-
if ( !( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
548-
|| mRequest.subsetOfAttributes().contains( i ) )
549-
{
550-
virtualFieldsToFetch << i;
551-
}
540+
mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() << dependantFieldIdx );
552541
}
542+
// also need to fetch this dependant field
543+
if ( !mPreparedFields.contains( dependantFieldIdx ) && !mFieldsToPrepare.contains( dependantFieldIdx ) )
544+
mFieldsToPrepare << dependantFieldIdx;
553545
}
554546

555-
QList< int > virtualFieldsProcessed;
556-
while ( !virtualFieldsToFetch.isEmpty() )
547+
if ( exp->needsGeometry() )
557548
{
558-
int fieldIdx = virtualFieldsToFetch.takeFirst();
559-
if ( virtualFieldsProcessed.contains( fieldIdx ) )
560-
continue;
549+
mRequest.setFlags( mRequest.flags() & ~QgsFeatureRequest::NoGeometry );
550+
}
551+
}
561552

562-
virtualFieldsProcessed << fieldIdx;
553+
void QgsVectorLayerFeatureIterator::prepareFields()
554+
{
555+
mPreparedFields.clear();
556+
mFieldsToPrepare.clear();
557+
mFetchJoinInfo.clear();
563558

559+
mExpressionContext.reset( new QgsExpressionContext() );
560+
mExpressionContext->appendScope( QgsExpressionContextUtils::globalScope() );
561+
mExpressionContext->appendScope( QgsExpressionContextUtils::projectScope() );
562+
mExpressionContext->setFields( mSource->mFields );
564563

565-
int oi = mSource->mFields.fieldOriginIndex( fieldIdx );
566-
QgsExpression* exp = new QgsExpression( exps[oi].cachedExpression );
564+
mFieldsToPrepare = ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes ) ? mRequest.subsetOfAttributes() : mSource->mFields.allAttributesList();
567565

568-
QgsDistanceArea da;
569-
da.setSourceCrs( mSource->mCrsId );
570-
da.setEllipsoidalMode( true );
571-
da.setEllipsoid( QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE ) );
572-
exp->setGeomCalculator( da );
573-
exp->setDistanceUnits( QgsProject::instance()->distanceUnits() );
574-
exp->setAreaUnits( QgsProject::instance()->areaUnits() );
566+
while ( !mFieldsToPrepare.isEmpty() )
567+
{
568+
int fieldIdx = mFieldsToPrepare.takeFirst();
569+
if ( mPreparedFields.contains( fieldIdx ) )
570+
continue;
575571

576-
exp->prepare( mExpressionContext.data() );
577-
mExpressionFieldInfo.insert( fieldIdx, exp );
572+
mPreparedFields << fieldIdx;
573+
prepareField( fieldIdx );
574+
}
575+
}
578576

579-
Q_FOREACH ( const QString& col, exp->referencedColumns() )
580-
{
581-
int dependantFieldIdx = mSource->mFields.fieldNameIndex( col );
582-
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
577+
void QgsVectorLayerFeatureIterator::prepareField( int fieldIdx )
578+
{
579+
switch ( mSource->mFields.fieldOrigin( fieldIdx ) )
580+
{
581+
case QgsFields::OriginExpression:
582+
prepareExpression( fieldIdx );
583+
break;
584+
585+
case QgsFields::OriginJoin:
586+
if ( mSource->mJoinBuffer->containsJoins() )
583587
{
584-
mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() << dependantFieldIdx );
588+
prepareJoin( fieldIdx );
585589
}
586-
// also need to fetch this dependant field
587-
if ( mSource->mFields.fieldOrigin( dependantFieldIdx ) == QgsFields::OriginExpression )
588-
virtualFieldsToFetch << dependantFieldIdx;
589-
}
590+
break;
590591

591-
if ( exp->needsGeometry() )
592-
{
593-
mRequest.setFlags( mRequest.flags() & ~QgsFeatureRequest::NoGeometry );
594-
}
592+
case QgsFields::OriginUnknown:
593+
case QgsFields::OriginProvider:
594+
case QgsFields::OriginEdit:
595+
break;
595596
}
596-
597597
}
598598

599599
void QgsVectorLayerFeatureIterator::addJoinedAttributes( QgsFeature &f )
@@ -623,24 +623,48 @@ void QgsVectorLayerFeatureIterator::addVirtualAttributes( QgsFeature& f )
623623
attr.resize( mSource->mFields.count() ); // Provider attrs count + joined attrs count + expression attrs count
624624
f.setAttributes( attr );
625625

626+
// possible TODO - handle combinations of expression -> join -> expression -> join?
627+
// but for now, write that off as too complex and an unlikely rare, unsupported use case
628+
629+
QList< int > fetchedVirtualAttributes;
630+
//first, check through joins for any virtual fields we need
631+
QMap<const QgsVectorJoinInfo*, FetchJoinInfo>::const_iterator joinIt = mFetchJoinInfo.constBegin();
632+
for ( ; joinIt != mFetchJoinInfo.constEnd(); ++joinIt )
633+
{
634+
if ( mExpressionFieldInfo.contains( joinIt->targetField ) )
635+
{
636+
// have to calculate expression field before we can handle this join
637+
addExpressionAttribute( f, joinIt->targetField );
638+
fetchedVirtualAttributes << joinIt->targetField;
639+
}
640+
}
641+
626642
if ( !mFetchJoinInfo.isEmpty() )
627643
addJoinedAttributes( f );
628644

645+
// add remaining expression fields
629646
if ( !mExpressionFieldInfo.isEmpty() )
630647
{
631648
QMap<int, QgsExpression*>::ConstIterator it = mExpressionFieldInfo.constBegin();
632-
633649
for ( ; it != mExpressionFieldInfo.constEnd(); ++it )
634650
{
635-
QgsExpression* exp = it.value();
636-
mExpressionContext->setFeature( f );
637-
QVariant val = exp->evaluate( mExpressionContext.data() );
638-
mSource->mFields.at( it.key() ).convertCompatible( val );
639-
f.setAttribute( it.key(), val );
651+
if ( fetchedVirtualAttributes.contains( it.key() ) )
652+
continue;
653+
654+
addExpressionAttribute( f, it.key() );
640655
}
641656
}
642657
}
643658

659+
void QgsVectorLayerFeatureIterator::addExpressionAttribute( QgsFeature& f, int attrIndex )
660+
{
661+
QgsExpression* exp = mExpressionFieldInfo.value( attrIndex );
662+
mExpressionContext->setFeature( f );
663+
QVariant val = exp->evaluate( mExpressionContext.data() );
664+
mSource->mFields.at( attrIndex ).convertCompatible( val );
665+
f.setAttribute( attrIndex, val );
666+
}
667+
644668
bool QgsVectorLayerFeatureIterator::prepareSimplification( const QgsSimplifyMethod& simplifyMethod )
645669
{
646670
delete mEditGeometrySimplifier;

src/core/qgsvectorlayerfeatureiterator.h

+22-2
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,19 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera
9797

9898
//! @note not available in Python bindings
9999
void rewindEditBuffer();
100+
101+
//! @note not available in Python bindings
102+
void prepareJoin( int fieldIdx );
103+
100104
//! @note not available in Python bindings
101-
void prepareJoins();
105+
void prepareExpression( int fieldIdx );
106+
102107
//! @note not available in Python bindings
103-
void prepareExpressions();
108+
void prepareFields();
109+
110+
//! @note not available in Python bindings
111+
void prepareField( int fieldIdx );
112+
104113
//! @note not available in Python bindings
105114
bool fetchNextAddedFeature( QgsFeature& f );
106115
//! @note not available in Python bindings
@@ -127,6 +136,14 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera
127136
*/
128137
void addVirtualAttributes( QgsFeature &f );
129138

139+
/** Adds an expression based attribute to a feature
140+
* @param f feature
141+
* @param attrIndex attribute index
142+
* @note added in QGIS 2.14
143+
* @note not available in Python bindings
144+
*/
145+
void addExpressionAttribute( QgsFeature& f, int attrIndex );
146+
130147
/** Update feature with uncommited attribute updates.
131148
* @note not available in Python bindings
132149
*/
@@ -179,6 +196,9 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera
179196

180197
QScopedPointer<QgsExpressionContext> mExpressionContext;
181198

199+
QList< int > mPreparedFields;
200+
QList< int > mFieldsToPrepare;
201+
182202
/**
183203
* Will always return true. We assume that ordering has been done on provider level already.
184204
*

0 commit comments

Comments
 (0)