Skip to content

Commit 5a4a67a

Browse files
authoredSep 13, 2016
Merge pull request #3482 from mhugent/joins_over_several_tables2
Joins over several tables2
2 parents c151724 + bb4d83f commit 5a4a67a

File tree

3 files changed

+301
-103
lines changed

3 files changed

+301
-103
lines changed
 

‎src/core/qgsvectorlayerfeatureiterator.cpp

Lines changed: 187 additions & 100 deletions
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,147 +468,214 @@ 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 );
497+
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 );
502+
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;
506+
507+
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.subsetOfAttributes().contains( info.targetField ) )
508+
mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() << info.targetField );
509+
510+
mFetchJoinInfo.insert( joinInfo, info );
511+
}
512+
513+
// store field source index - we'll need it when fetching from provider
514+
mFetchJoinInfo[ joinInfo ].attributes.push_back( sourceLayerIndex );
515+
}
516+
517+
void QgsVectorLayerFeatureIterator::prepareExpression( int fieldIdx )
518+
{
519+
const QList<QgsExpressionFieldBuffer::ExpressionField>& exps = mSource->mExpressionFieldBuffer->expressions();
520+
521+
int oi = mSource->mFields.fieldOriginIndex( fieldIdx );
522+
QgsExpression* exp = new QgsExpression( exps[oi].cachedExpression );
491523

492-
int sourceLayerIndex;
493-
const QgsVectorJoinInfo* joinInfo = mSource->mJoinBuffer->joinForFieldIndex( *attIt, mSource->mFields, sourceLayerIndex );
494-
Q_ASSERT( joinInfo );
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() );
495531

496-
QgsVectorLayer* joinLayer = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinInfo->joinLayerId ) );
497-
Q_ASSERT( joinLayer );
532+
exp->prepare( mExpressionContext.data() );
533+
mExpressionFieldInfo.insert( fieldIdx, exp );
498534

499-
if ( !mFetchJoinInfo.contains( joinInfo ) )
535+
Q_FOREACH ( const QString& col, exp->referencedColumns() )
536+
{
537+
int dependantFieldIdx = mSource->mFields.fieldNameIndex( col );
538+
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
500539
{
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 );
540+
mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() << dependantFieldIdx );
521541
}
522-
523-
// store field source index - we'll need it when fetching from provider
524-
mFetchJoinInfo[ joinInfo ].attributes.push_back( sourceLayerIndex );
542+
// also need to fetch this dependant field
543+
if ( !mPreparedFields.contains( dependantFieldIdx ) && !mFieldsToPrepare.contains( dependantFieldIdx ) )
544+
mFieldsToPrepare << dependantFieldIdx;
525545
}
526546

527-
// add sourceJoinFields if we're using a subset
528-
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
529-
mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() + sourceJoinFields );
547+
if ( exp->needsGeometry() )
548+
{
549+
mRequest.setFlags( mRequest.flags() & ~QgsFeatureRequest::NoGeometry );
550+
}
530551
}
531552

532-
void QgsVectorLayerFeatureIterator::prepareExpressions()
553+
void QgsVectorLayerFeatureIterator::prepareFields()
533554
{
534-
const QList<QgsExpressionFieldBuffer::ExpressionField> exps = mSource->mExpressionFieldBuffer->expressions();
555+
mPreparedFields.clear();
556+
mFieldsToPrepare.clear();
557+
mFetchJoinInfo.clear();
558+
mOrderedJoinInfoList.clear();
535559

536560
mExpressionContext.reset( new QgsExpressionContext() );
537561
mExpressionContext->appendScope( QgsExpressionContextUtils::globalScope() );
538562
mExpressionContext->appendScope( QgsExpressionContextUtils::projectScope() );
539563
mExpressionContext->setFields( mSource->mFields );
540564

541-
QList< int > virtualFieldsToFetch;
542-
for ( int i = 0; i < mSource->mFields.count(); i++ )
565+
mFieldsToPrepare = ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes ) ? mRequest.subsetOfAttributes() : mSource->mFields.allAttributesList();
566+
567+
while ( !mFieldsToPrepare.isEmpty() )
543568
{
544-
if ( mSource->mFields.fieldOrigin( i ) == QgsFields::OriginExpression )
545-
{
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-
}
552-
}
569+
int fieldIdx = mFieldsToPrepare.takeFirst();
570+
if ( mPreparedFields.contains( fieldIdx ) )
571+
continue;
572+
573+
mPreparedFields << fieldIdx;
574+
prepareField( fieldIdx );
553575
}
554576

555-
QList< int > virtualFieldsProcessed;
556-
while ( !virtualFieldsToFetch.isEmpty() )
577+
//sort joins by dependency
578+
if ( mFetchJoinInfo.size() > 0 )
557579
{
558-
int fieldIdx = virtualFieldsToFetch.takeFirst();
559-
if ( virtualFieldsProcessed.contains( fieldIdx ) )
560-
continue;
580+
createOrderedJoinList();
581+
}
582+
}
561583

562-
virtualFieldsProcessed << fieldIdx;
584+
void QgsVectorLayerFeatureIterator::createOrderedJoinList()
585+
{
586+
mOrderedJoinInfoList = mFetchJoinInfo.values();
587+
if ( mOrderedJoinInfoList.size() < 2 )
588+
{
589+
return;
590+
}
563591

592+
QSet<int> resolvedFields; //todo: get provider / virtual fields without joins
564593

565-
int oi = mSource->mFields.fieldOriginIndex( fieldIdx );
566-
QgsExpression* exp = new QgsExpression( exps[oi].cachedExpression );
594+
//add all provider fields without joins as resolved fields
595+
QList< int >::const_iterator prepFieldIt = mPreparedFields.constBegin();
596+
for ( ; prepFieldIt != mPreparedFields.constEnd(); ++prepFieldIt )
597+
{
598+
if ( mSource->mFields.fieldOrigin( *prepFieldIt ) != QgsFields::OriginJoin )
599+
{
600+
resolvedFields.insert( *prepFieldIt );
601+
}
602+
}
567603

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() );
604+
//iterate through the joins. If target field is not yet covered, move the entry to the end of the list
575605

576-
exp->prepare( mExpressionContext.data() );
577-
mExpressionFieldInfo.insert( fieldIdx, exp );
606+
//some join combinations might not have a resolution at all
607+
int maxIterations = ( mOrderedJoinInfoList.size() + 1 ) * mOrderedJoinInfoList.size() / 2.0;
608+
int currentIteration = 0;
578609

579-
Q_FOREACH ( const QString& col, exp->referencedColumns() )
610+
for ( int i = 0; i < mOrderedJoinInfoList.size() - 1; ++i )
611+
{
612+
if ( !resolvedFields.contains( mOrderedJoinInfoList.at( i ).targetField ) )
613+
{
614+
mOrderedJoinInfoList.append( mOrderedJoinInfoList.at( i ) );
615+
mOrderedJoinInfoList.removeAt( i );
616+
--i;
617+
}
618+
else
580619
{
581-
int dependantFieldIdx = mSource->mFields.fieldNameIndex( col );
582-
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
620+
int offset = mOrderedJoinInfoList.at( i ).indexOffset;
621+
int joinField = mOrderedJoinInfoList.at( i ).joinField;
622+
623+
QgsAttributeList attributes = mOrderedJoinInfoList.at( i ).attributes;
624+
QgsAttributeList::const_iterator attIt = attributes.constBegin();
625+
for ( ; attIt != attributes.constEnd(); ++attIt )
583626
{
584-
mRequest.setSubsetOfAttributes( mRequest.subsetOfAttributes() << dependantFieldIdx );
627+
if ( *attIt != joinField )
628+
{
629+
resolvedFields.insert( joinField < *attIt ? *attIt + offset - 1 : *attIt + offset );
630+
}
585631
}
586-
// also need to fetch this dependant field
587-
if ( mSource->mFields.fieldOrigin( dependantFieldIdx ) == QgsFields::OriginExpression )
588-
virtualFieldsToFetch << dependantFieldIdx;
589632
}
590633

591-
if ( exp->needsGeometry() )
634+
++currentIteration;
635+
if ( currentIteration >= maxIterations )
592636
{
593-
mRequest.setFlags( mRequest.flags() & ~QgsFeatureRequest::NoGeometry );
637+
break;
594638
}
595639
}
640+
}
641+
642+
void QgsVectorLayerFeatureIterator::prepareField( int fieldIdx )
643+
{
644+
switch ( mSource->mFields.fieldOrigin( fieldIdx ) )
645+
{
646+
case QgsFields::OriginExpression:
647+
prepareExpression( fieldIdx );
648+
break;
649+
650+
case QgsFields::OriginJoin:
651+
if ( mSource->mJoinBuffer->containsJoins() )
652+
{
653+
prepareJoin( fieldIdx );
654+
}
655+
break;
596656

657+
case QgsFields::OriginUnknown:
658+
case QgsFields::OriginProvider:
659+
case QgsFields::OriginEdit:
660+
break;
661+
}
597662
}
598663

599664
void QgsVectorLayerFeatureIterator::addJoinedAttributes( QgsFeature &f )
600665
{
601-
QMap<const QgsVectorJoinInfo*, FetchJoinInfo>::const_iterator joinIt = mFetchJoinInfo.constBegin();
602-
for ( ; joinIt != mFetchJoinInfo.constEnd(); ++joinIt )
666+
QList< FetchJoinInfo >::const_iterator joinIt = mOrderedJoinInfoList.constBegin();
667+
for ( ; joinIt != mOrderedJoinInfoList.constEnd(); ++joinIt )
603668
{
604-
const FetchJoinInfo& info = joinIt.value();
605-
Q_ASSERT( joinIt.key() );
669+
QVariant targetFieldValue = f.attribute( joinIt->targetField );
606670

607-
QVariant targetFieldValue = f.attribute( info.targetField );
608671
if ( !targetFieldValue.isValid() )
609672
continue;
610673

611-
const QHash< QString, QgsAttributes>& memoryCache = info.joinInfo->cachedAttributes;
674+
const QHash< QString, QgsAttributes>& memoryCache = joinIt->joinInfo->cachedAttributes;
612675
if ( memoryCache.isEmpty() )
613-
info.addJoinedAttributesDirect( f, targetFieldValue );
676+
joinIt->addJoinedAttributesDirect( f, targetFieldValue );
614677
else
615-
info.addJoinedAttributesCached( f, targetFieldValue );
678+
joinIt->addJoinedAttributesCached( f, targetFieldValue );
616679
}
617680
}
618681

@@ -623,24 +686,48 @@ void QgsVectorLayerFeatureIterator::addVirtualAttributes( QgsFeature& f )
623686
attr.resize( mSource->mFields.count() ); // Provider attrs count + joined attrs count + expression attrs count
624687
f.setAttributes( attr );
625688

689+
// possible TODO - handle combinations of expression -> join -> expression -> join?
690+
// but for now, write that off as too complex and an unlikely rare, unsupported use case
691+
692+
QList< int > fetchedVirtualAttributes;
693+
//first, check through joins for any virtual fields we need
694+
QMap<const QgsVectorJoinInfo*, FetchJoinInfo>::const_iterator joinIt = mFetchJoinInfo.constBegin();
695+
for ( ; joinIt != mFetchJoinInfo.constEnd(); ++joinIt )
696+
{
697+
if ( mExpressionFieldInfo.contains( joinIt->targetField ) )
698+
{
699+
// have to calculate expression field before we can handle this join
700+
addExpressionAttribute( f, joinIt->targetField );
701+
fetchedVirtualAttributes << joinIt->targetField;
702+
}
703+
}
704+
626705
if ( !mFetchJoinInfo.isEmpty() )
627706
addJoinedAttributes( f );
628707

708+
// add remaining expression fields
629709
if ( !mExpressionFieldInfo.isEmpty() )
630710
{
631711
QMap<int, QgsExpression*>::ConstIterator it = mExpressionFieldInfo.constBegin();
632-
633712
for ( ; it != mExpressionFieldInfo.constEnd(); ++it )
634713
{
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 );
714+
if ( fetchedVirtualAttributes.contains( it.key() ) )
715+
continue;
716+
717+
addExpressionAttribute( f, it.key() );
640718
}
641719
}
642720
}
643721

722+
void QgsVectorLayerFeatureIterator::addExpressionAttribute( QgsFeature& f, int attrIndex )
723+
{
724+
QgsExpression* exp = mExpressionFieldInfo.value( attrIndex );
725+
mExpressionContext->setFeature( f );
726+
QVariant val = exp->evaluate( mExpressionContext.data() );
727+
mSource->mFields.at( attrIndex ).convertCompatible( val );
728+
f.setAttribute( attrIndex, val );
729+
}
730+
644731
bool QgsVectorLayerFeatureIterator::prepareSimplification( const QgsSimplifyMethod& simplifyMethod )
645732
{
646733
delete mEditGeometrySimplifier;

‎src/core/qgsvectorlayerfeatureiterator.h

Lines changed: 27 additions & 2 deletions
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,12 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera
179196

180197
QScopedPointer<QgsExpressionContext> mExpressionContext;
181198

199+
QList< int > mPreparedFields;
200+
QList< int > mFieldsToPrepare;
201+
202+
/** Join list sorted by dependency*/
203+
QList< FetchJoinInfo > mOrderedJoinInfoList;
204+
182205
/**
183206
* Will always return true. We assume that ordering has been done on provider level already.
184207
*
@@ -187,6 +210,8 @@ class CORE_EXPORT QgsVectorLayerFeatureIterator : public QgsAbstractFeatureItera
187210

188211
//! returns whether the iterator supports simplify geometries on provider side
189212
virtual bool providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const override;
213+
214+
void createOrderedJoinList();
190215
};
191216

192217
#endif // QGSVECTORLAYERFEATUREITERATOR_H

‎tests/src/python/test_qgsfeatureiterator.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@
1515
import qgis
1616
import os
1717

18-
from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsFeature, QgsField, NULL
18+
from qgis.core import QgsVectorLayer, QgsFeatureRequest, QgsFeature, QgsField, QgsMapLayerRegistry, QgsVectorJoinInfo, NULL
1919
from qgis.testing import (start_app,
2020
unittest
2121
)
2222
from PyQt4.QtCore import QVariant
2323
from utilities import (unitTestDataPath,
2424
compareWkt
2525
)
26+
2627
start_app()
2728
TEST_DATA_DIR = unitTestDataPath()
2829

@@ -155,6 +156,91 @@ def test_ExpressionFieldNestedCircular(self):
155156
fet = next(layer.getFeatures(QgsFeatureRequest().setSubsetOfAttributes(['exp2'], layer.fields())))
156157
self.assertEqual(fet['Class'], NULL)
157158

159+
def test_JoinUsingExpression(self):
160+
""" test joining a layer using a virtual field """
161+
joinLayer = QgsVectorLayer(
162+
"Point?field=x:string&field=y:integer&field=z:integer",
163+
"joinlayer", "memory")
164+
pr = joinLayer.dataProvider()
165+
f1 = QgsFeature()
166+
f1.setAttributes(["foo", 246, 321])
167+
f2 = QgsFeature()
168+
f2.setAttributes(["bar", 456, 654])
169+
self.assertTrue(pr.addFeatures([f1, f2]))
170+
171+
layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
172+
"addfeat", "memory")
173+
pr = layer.dataProvider()
174+
f = QgsFeature()
175+
f.setAttributes(["test", 123])
176+
self.assertTrue(pr.addFeatures([f]))
177+
layer.addExpressionField('"fldint"*2', QgsField('exp1', QVariant.LongLong))
178+
179+
QgsMapLayerRegistry.instance().addMapLayers([layer, joinLayer])
180+
181+
join = QgsVectorJoinInfo()
182+
join.targetFieldName = "exp1"
183+
join.joinLayerId = joinLayer.id()
184+
join.joinFieldName = "y"
185+
join.memoryCache = True
186+
layer.addJoin(join)
187+
188+
f = QgsFeature()
189+
fi = layer.getFeatures()
190+
self.assertTrue(fi.nextFeature(f))
191+
attrs = f.attributes()
192+
self.assertEqual(attrs[0], "test")
193+
self.assertEqual(attrs[1], 123)
194+
self.assertEqual(attrs[2], "foo")
195+
self.assertEqual(attrs[3], 321)
196+
self.assertEqual(attrs[4], 246)
197+
self.assertFalse(fi.nextFeature(f))
198+
199+
QgsMapLayerRegistry.instance().removeMapLayers([layer, joinLayer])
200+
201+
def test_JoinUsingExpression2(self):
202+
""" test joining a layer using a virtual field (the other way!) """
203+
joinLayer = QgsVectorLayer(
204+
"Point?field=x:string&field=y:integer&field=z:integer",
205+
"joinlayer", "memory")
206+
pr = joinLayer.dataProvider()
207+
f1 = QgsFeature()
208+
f1.setAttributes(["foo", 246, 321])
209+
f2 = QgsFeature()
210+
f2.setAttributes(["bar", 456, 654])
211+
self.assertTrue(pr.addFeatures([f1, f2]))
212+
joinLayer.addExpressionField('"y"/2', QgsField('exp1', QVariant.LongLong))
213+
214+
layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
215+
"addfeat", "memory")
216+
pr = layer.dataProvider()
217+
f = QgsFeature()
218+
f.setAttributes(["test", 123])
219+
self.assertTrue(pr.addFeatures([f]))
220+
221+
QgsMapLayerRegistry.instance().addMapLayers([layer, joinLayer])
222+
223+
join = QgsVectorJoinInfo()
224+
join.targetFieldName = "fldint"
225+
join.joinLayerId = joinLayer.id()
226+
join.joinFieldName = "exp1"
227+
join.memoryCache = True
228+
layer.addJoin(join)
229+
230+
f = QgsFeature()
231+
fi = layer.getFeatures()
232+
self.assertTrue(fi.nextFeature(f))
233+
attrs = f.attributes()
234+
self.assertEqual(attrs[0], "test")
235+
self.assertEqual(attrs[1], 123)
236+
self.assertEqual(attrs[2], "foo")
237+
self.assertEqual(attrs[3], 246)
238+
self.assertEqual(attrs[4], 321)
239+
self.assertFalse(fi.nextFeature(f))
240+
241+
QgsMapLayerRegistry.instance().removeMapLayers([layer, joinLayer])
242+
# try the other way too
243+
158244

159245
if __name__ == '__main__':
160246
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.