Skip to content

Commit 071a5ec

Browse files
committedSep 9, 2014
Fix #10912 (joined attributes are not correctly propagated in nested joins)
This commit makes QgsVectorLayerJoinBuffer listen to changes in fields of joined vector layers in order to update the cache and inform parent layer
1 parent 1df01c0 commit 071a5ec

11 files changed

+298
-6
lines changed
 

‎python/core/qgsfield.sip

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ class QgsFields
193193
//! Utility function to return a list of QgsField instances
194194
QList<QgsField> toList() const;
195195

196+
//! @note added in 2.6
197+
bool operator==( const QgsFields& other ) const;
198+
//! @note added in 2.6
199+
bool operator!=( const QgsFields& other ) const;
200+
196201
/* SIP_PYOBJECT __getitem__(int key);
197202
%MethodCode
198203
if (a0 = sipConvertFromSequenceIndex(a0, sipCpp->count()) < 0)

‎python/core/qgsvectorlayerjoinbuffer.sip

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
class QgsVectorLayerJoinBuffer
1+
class QgsVectorLayerJoinBuffer : QObject
22
{
33
%TypeHeaderCode
44
#include <qgsvectorlayerjoinbuffer.h>
@@ -39,4 +39,12 @@ class QgsVectorLayerJoinBuffer
3939
@param sourceFieldIndex Output: field's index in source layer */
4040
const QgsVectorJoinInfo* joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex /Out/ ) const;
4141

42+
//! Create a copy of the join buffer
43+
//! @note added in 2.6
44+
QgsVectorLayerJoinBuffer* clone() const /Factory/;
45+
46+
signals:
47+
//! Emitted whenever the list of joined fields changes (e.g. added join or joined layer's fields change)
48+
//! @note added in 2.6
49+
void joinedFieldsChanged();
4250
};

‎src/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ SET(QGIS_CORE_MOC_HDRS
369369
qgsnetworkaccessmanager.h
370370
qgsvectordataprovider.h
371371
qgsvectorlayercache.h
372+
qgsvectorlayerjoinbuffer.h
372373
qgsgeometryvalidator.h
373374

374375
composer/qgsaddremoveitemcommand.h

‎src/core/qgsfield.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ class CORE_EXPORT QgsFields
178178
Field(): origin( OriginUnknown ), originIndex( -1 ) {}
179179
Field( const QgsField& f, FieldOrigin o, int oi ): field( f ), origin( o ), originIndex( oi ) {}
180180

181+
//! @note added in 2.6
182+
bool operator==( const Field& other ) const { return field == other.field && origin == other.origin && originIndex == other.originIndex; }
183+
//! @note added in 2.6
184+
bool operator!=( const Field& other ) const { return !( *this == other ); }
185+
181186
QgsField field; //!< field
182187
FieldOrigin origin; //!< origin of the field
183188
int originIndex; //!< index specific to the origin
@@ -238,6 +243,11 @@ class CORE_EXPORT QgsFields
238243
//! Utility function to return a list of QgsField instances
239244
QList<QgsField> toList() const;
240245

246+
//! @note added in 2.6
247+
bool operator==( const QgsFields& other ) const { return mFields == other.mFields; }
248+
//! @note added in 2.6
249+
bool operator!=( const QgsFields& other ) const { return ! ( *this == other ); }
250+
241251
protected:
242252
//! internal storage of the container
243253
QVector<Field> mFields;

‎src/core/qgsvectorlayer.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,7 @@ bool QgsVectorLayer::readXml( const QDomNode& layer_node )
13041304
if ( !mJoinBuffer )
13051305
{
13061306
mJoinBuffer = new QgsVectorLayerJoinBuffer();
1307+
connect( mJoinBuffer, SIGNAL( joinedFieldsChanged() ), this, SLOT( onJoinedFieldsChanged() ) );
13071308
}
13081309
mJoinBuffer->readXml( layer_node );
13091310

@@ -1366,6 +1367,7 @@ bool QgsVectorLayer::setDataProvider( QString const & provider )
13661367
mWkbType = mDataProvider->geometryType();
13671368

13681369
mJoinBuffer = new QgsVectorLayerJoinBuffer();
1370+
connect( mJoinBuffer, SIGNAL( joinedFieldsChanged() ), this, SLOT( onJoinedFieldsChanged() ) );
13691371
mExpressionFieldBuffer = new QgsExpressionFieldBuffer();
13701372
updateFields();
13711373

@@ -2782,6 +2784,8 @@ void QgsVectorLayer::updateFields()
27822784
if ( !mDataProvider )
27832785
return;
27842786

2787+
QgsFields oldFields = mUpdatedFields;
2788+
27852789
mUpdatedFields = mDataProvider->fields();
27862790

27872791
// added / removed fields
@@ -2795,7 +2799,8 @@ void QgsVectorLayer::updateFields()
27952799
if ( mExpressionFieldBuffer )
27962800
mExpressionFieldBuffer->updateFields( mUpdatedFields );
27972801

2798-
emit updatedFields();
2802+
if ( oldFields != mUpdatedFields )
2803+
emit updatedFields();
27992804
}
28002805

28012806

@@ -3563,6 +3568,12 @@ void QgsVectorLayer::onRelationsLoaded()
35633568
}
35643569
}
35653570

3571+
void QgsVectorLayer::onJoinedFieldsChanged()
3572+
{
3573+
// some of the fields of joined layers have changed -> we need to update this layer's fields too
3574+
updateFields();
3575+
}
3576+
35663577
QgsVectorLayer::ValueRelationData QgsVectorLayer::valueRelation( int idx )
35673578
{
35683579
if ( editorWidgetV2( idx ) == "ValueRelation" )

‎src/core/qgsvectorlayer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
16411641

16421642
private slots:
16431643
void onRelationsLoaded();
1644+
void onJoinedFieldsChanged();
16441645

16451646
protected:
16461647
/** Set the extent */

‎src/core/qgsvectorlayerfeatureiterator.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ QgsVectorLayerFeatureSource::QgsVectorLayerFeatureSource( QgsVectorLayer *layer
2727
{
2828
mProviderFeatureSource = layer->dataProvider()->featureSource();
2929
mFields = layer->pendingFields();
30-
mJoinBuffer = new QgsVectorLayerJoinBuffer( *layer->mJoinBuffer );
30+
mJoinBuffer = layer->mJoinBuffer->clone();
3131
mExpressionFieldBuffer = new QgsExpressionFieldBuffer( *layer->mExpressionFieldBuffer );
3232

3333
mCanBeSimplified = layer->hasGeometryType() && layer->geometryType() != QGis::Point;

‎src/core/qgsvectorlayerjoinbuffer.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,18 @@ void QgsVectorLayerJoinBuffer::addJoin( const QgsVectorJoinInfo& joinInfo )
3939
{
4040
cacheJoinLayer( mVectorJoins.last() );
4141
}
42+
43+
// Wait for notifications about changed fields in joined layer to propagate them.
44+
// During project load the joined layers possibly do not exist yet so the connection will not be created,
45+
// but then QgsProject makes sure to call createJoinCaches() which will do the connection.
46+
// Unique connection makes sure we do not respond to one layer's update more times (in case of multiple join)
47+
if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinInfo.joinLayerId ) ) )
48+
connect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ), Qt::UniqueConnection );
49+
50+
emit joinedFieldsChanged();
4251
}
4352

53+
4454
void QgsVectorLayerJoinBuffer::removeJoin( const QString& joinLayerId )
4555
{
4656
for ( int i = 0; i < mVectorJoins.size(); ++i )
@@ -50,6 +60,11 @@ void QgsVectorLayerJoinBuffer::removeJoin( const QString& joinLayerId )
5060
mVectorJoins.removeAt( i );
5161
}
5262
}
63+
64+
if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinLayerId ) ) )
65+
disconnect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ) );
66+
67+
emit joinedFieldsChanged();
5368
}
5469

5570
void QgsVectorLayerJoinBuffer::cacheJoinLayer( QgsVectorJoinInfo& joinInfo )
@@ -121,6 +136,10 @@ void QgsVectorLayerJoinBuffer::createJoinCaches()
121136
for ( ; joinIt != mVectorJoins.end(); ++joinIt )
122137
{
123138
cacheJoinLayer( *joinIt );
139+
140+
// make sure we are connected to the joined layer
141+
if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinIt->joinLayerId ) ) )
142+
connect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ), Qt::UniqueConnection );
124143
}
125144
}
126145

@@ -188,3 +207,28 @@ const QgsVectorJoinInfo* QgsVectorLayerJoinBuffer::joinForFieldIndex( int index,
188207

189208
return &( mVectorJoins[sourceJoinIndex] );
190209
}
210+
211+
QgsVectorLayerJoinBuffer* QgsVectorLayerJoinBuffer::clone() const
212+
{
213+
QgsVectorLayerJoinBuffer* cloned = new QgsVectorLayerJoinBuffer;
214+
cloned->mVectorJoins = mVectorJoins;
215+
return cloned;
216+
}
217+
218+
void QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields()
219+
{
220+
QgsVectorLayer* joinedLayer = qobject_cast<QgsVectorLayer*>( sender() );
221+
Q_ASSERT( joinedLayer );
222+
223+
// recache the joined layer
224+
for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
225+
{
226+
if ( joinedLayer->id() == it->joinLayerId )
227+
{
228+
it->cachedAttributes.clear();
229+
cacheJoinLayer( *it );
230+
}
231+
}
232+
233+
emit joinedFieldsChanged();
234+
}

‎src/core/qgsvectorlayerjoinbuffer.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@
2525
#include <QString>
2626

2727

28+
typedef QList< QgsVectorJoinInfo > QgsVectorJoinList;
2829

2930

3031
/**Manages joined fields for a vector layer*/
31-
class CORE_EXPORT QgsVectorLayerJoinBuffer
32+
class CORE_EXPORT QgsVectorLayerJoinBuffer : public QObject
3233
{
34+
Q_OBJECT
3335
public:
3436
QgsVectorLayerJoinBuffer();
3537
~QgsVectorLayerJoinBuffer();
@@ -58,18 +60,30 @@ class CORE_EXPORT QgsVectorLayerJoinBuffer
5860
/**Quick way to test if there is any join at all*/
5961
bool containsJoins() const { return !mVectorJoins.isEmpty(); }
6062

61-
const QList< QgsVectorJoinInfo >& vectorJoins() const { return mVectorJoins; }
63+
const QgsVectorJoinList& vectorJoins() const { return mVectorJoins; }
6264

6365
/**Finds the vector join for a layer field index.
6466
@param index this layers attribute index
6567
@param fields fields of the vector layer (including joined fields)
6668
@param sourceFieldIndex Output: field's index in source layer */
6769
const QgsVectorJoinInfo* joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex ) const;
6870

71+
//! Create a copy of the join buffer
72+
//! @note added in 2.6
73+
QgsVectorLayerJoinBuffer* clone() const;
74+
75+
signals:
76+
//! Emitted whenever the list of joined fields changes (e.g. added join or joined layer's fields change)
77+
//! @note added in 2.6
78+
void joinedFieldsChanged();
79+
80+
private slots:
81+
void joinedLayerUpdatedFields();
82+
6983
private:
7084

7185
/**Joined vector layers*/
72-
QList< QgsVectorJoinInfo > mVectorJoins;
86+
QgsVectorJoinList mVectorJoins;
7387

7488
/**Caches attributes of join layer in memory if QgsVectorJoinInfo.memoryCache is true (and the cache is not already there)*/
7589
void cacheJoinLayer( QgsVectorJoinInfo& joinInfo );

‎tests/src/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,4 @@ ADD_QGIS_TEST(colorschemeregistry testqgscolorschemeregistry.cpp)
133133
ADD_QGIS_TEST(colorscheme testqgscolorscheme.cpp)
134134
ADD_QGIS_TEST(networkcontentfetcher testqgsnetworkcontentfetcher.cpp )
135135
ADD_QGIS_TEST(legendrenderertest testqgslegendrenderer.cpp )
136+
ADD_QGIS_TEST(vectorlayerjoinbuffer testqgsvectorlayerjoinbuffer.cpp )
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/***************************************************************************
2+
testqgsvectorlayerjoinbuffer.cpp
3+
--------------------------------------
4+
Date : September 2014
5+
Copyright : (C) 2014 Martin Dobias
6+
Email : wonder.sk at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
17+
#include <QtTest>
18+
#include <QObject>
19+
20+
//qgis includes...
21+
#include <qgsvectorlayer.h>
22+
#include <qgsvectordataprovider.h>
23+
#include <qgsapplication.h>
24+
#include <qgsvectorlayerjoinbuffer.h>
25+
#include <qgsmaplayerregistry.h>
26+
27+
/** @ingroup UnitTests
28+
* This is a unit test for the vector layer join buffer
29+
*
30+
* @see QgsVectorLayerJoinBuffer
31+
*/
32+
class TestVectorLayerJoinBuffer: public QObject
33+
{
34+
Q_OBJECT
35+
36+
private slots:
37+
void initTestCase(); // will be called before the first testfunction is executed.
38+
void cleanupTestCase(); // will be called after the last testfunction was executed.
39+
void init(); // will be called before each testfunction is executed.
40+
void cleanup(); // will be called after every testfunction.
41+
42+
void testCacheBasic();
43+
void testCacheTransitive();
44+
45+
private:
46+
QgsVectorLayer* mLayerA;
47+
QgsVectorLayer* mLayerB;
48+
QgsVectorLayer* mLayerC;
49+
};
50+
51+
// runs before all tests
52+
void TestVectorLayerJoinBuffer::initTestCase()
53+
{
54+
QgsApplication::init();
55+
QgsApplication::initQgis();
56+
57+
// LAYER A //
58+
59+
mLayerA = new QgsVectorLayer( "Point?field=id_a:integer", "A", "memory" );
60+
QVERIFY( mLayerA->isValid() );
61+
QVERIFY( mLayerA->pendingFields().count() == 1 );
62+
63+
QgsFeature fA1( mLayerA->dataProvider()->fields(), 1 );
64+
fA1.setAttribute( "id_a", 1 );
65+
QgsFeature fA2( mLayerA->dataProvider()->fields(), 2 );
66+
fA2.setAttribute( "id_a", 2 );
67+
mLayerA->dataProvider()->addFeatures( QgsFeatureList() << fA1 << fA2 );
68+
QVERIFY( mLayerA->pendingFeatureCount() == 2 );
69+
70+
// LAYER B //
71+
72+
mLayerB = new QgsVectorLayer( "Point?field=id_b:integer&field=value_b", "B", "memory" );
73+
QVERIFY( mLayerB->isValid() );
74+
QVERIFY( mLayerB->pendingFields().count() == 2 );
75+
76+
QgsFeature fB1( mLayerB->dataProvider()->fields(), 1 );
77+
fB1.setAttribute( "id_b", 1 );
78+
fB1.setAttribute( "value_b", 11 );
79+
QgsFeature fB2( mLayerB->dataProvider()->fields(), 2 );
80+
fB2.setAttribute( "id_b", 2 );
81+
fB2.setAttribute( "value_b", 12 );
82+
mLayerB->dataProvider()->addFeatures( QgsFeatureList() << fB1 << fB2 );
83+
QVERIFY( mLayerB->pendingFeatureCount() == 2 );
84+
85+
// LAYER C //
86+
87+
mLayerC = new QgsVectorLayer( "Point?field=id_c:integer&field=value_c", "C", "memory" );
88+
QVERIFY( mLayerC->isValid() );
89+
QVERIFY( mLayerC->pendingFields().count() == 2 );
90+
91+
QgsFeature fC1( mLayerC->dataProvider()->fields(), 1 );
92+
fC1.setAttribute( "id_c", 1 );
93+
fC1.setAttribute( "value_c", 101 );
94+
mLayerC->dataProvider()->addFeatures( QgsFeatureList() << fC1 );
95+
QVERIFY( mLayerC->pendingFeatureCount() == 1 );
96+
97+
QgsMapLayerRegistry::instance()->addMapLayer( mLayerA );
98+
QgsMapLayerRegistry::instance()->addMapLayer( mLayerB );
99+
QgsMapLayerRegistry::instance()->addMapLayer( mLayerC );
100+
}
101+
102+
void TestVectorLayerJoinBuffer::init()
103+
{
104+
}
105+
106+
void TestVectorLayerJoinBuffer::cleanup()
107+
{
108+
}
109+
110+
void TestVectorLayerJoinBuffer::cleanupTestCase()
111+
{
112+
QgsMapLayerRegistry::instance()->removeAllMapLayers();
113+
}
114+
115+
void TestVectorLayerJoinBuffer::testCacheBasic()
116+
{
117+
QVERIFY( mLayerA->pendingFields().count() == 1 );
118+
119+
QgsVectorJoinInfo joinInfo;
120+
joinInfo.targetFieldName = "id_a";
121+
joinInfo.joinLayerId = mLayerB->id();
122+
joinInfo.joinFieldName = "id_b";
123+
// memory provider does not implement setSubsetString() so direct join does not work!
124+
joinInfo.memoryCache = true;
125+
mLayerA->addJoin( joinInfo );
126+
127+
QVERIFY( mLayerA->pendingFields().count() == 2 );
128+
129+
QgsFeatureIterator fi = mLayerA->getFeatures();
130+
QgsFeature fA1, fA2;
131+
fi.nextFeature( fA1 );
132+
QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
133+
QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 11 );
134+
fi.nextFeature( fA2 );
135+
QCOMPARE( fA2.attribute( "id_a" ).toInt(), 2 );
136+
QCOMPARE( fA2.attribute( "B_value_b" ).toInt(), 12 );
137+
138+
mLayerA->removeJoin( mLayerB->id() );
139+
140+
QVERIFY( mLayerA->pendingFields().count() == 1 );
141+
}
142+
143+
void TestVectorLayerJoinBuffer::testCacheTransitive()
144+
{
145+
// test join A -> B -> C
146+
// first we join A -> B and after that B -> C
147+
// layer A should automatically update to include joined data from C
148+
149+
QVERIFY( mLayerA->pendingFields().count() == 1 ); // id_a
150+
151+
// add join A -> B
152+
153+
QgsVectorJoinInfo joinInfo1;
154+
joinInfo1.targetFieldName = "id_a";
155+
joinInfo1.joinLayerId = mLayerB->id();
156+
joinInfo1.joinFieldName = "id_b";
157+
// memory provider does not implement setSubsetString() so direct join does not work!
158+
joinInfo1.memoryCache = true;
159+
mLayerA->addJoin( joinInfo1 );
160+
QVERIFY( mLayerA->pendingFields().count() == 2 ); // id_a, B_value_b
161+
162+
// add join B -> C
163+
164+
QgsVectorJoinInfo joinInfo2;
165+
joinInfo2.targetFieldName = "id_b";
166+
joinInfo2.joinLayerId = mLayerC->id();
167+
joinInfo2.joinFieldName = "id_c";
168+
// memory provider does not implement setSubsetString() so direct join does not work!
169+
joinInfo2.memoryCache = true;
170+
mLayerB->addJoin( joinInfo2 );
171+
QVERIFY( mLayerB->pendingFields().count() == 3 ); // id_b, value_b, C_value_c
172+
173+
// now layer A must include also data from layer C
174+
QVERIFY( mLayerA->pendingFields().count() == 3 ); // id_a, B_value_b, B_C_value_c
175+
176+
QgsFeatureIterator fi = mLayerA->getFeatures();
177+
QgsFeature fA1;
178+
fi.nextFeature( fA1 );
179+
QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
180+
QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 11 );
181+
QCOMPARE( fA1.attribute( "B_C_value_c" ).toInt(), 101 );
182+
183+
// test that layer A gets updated when layer C changes its fields
184+
mLayerC->addExpressionField( "123", QgsField( "dummy", QVariant::Int ) );
185+
QVERIFY( mLayerA->pendingFields().count() == 4 ); // id_a, B_value_b, B_C_value_c, B_C_dummy
186+
mLayerC->removeExpressionField( 0 );
187+
188+
// cleanup
189+
mLayerA->removeJoin( mLayerB->id() );
190+
mLayerB->removeJoin( mLayerC->id() );
191+
}
192+
193+
194+
QTEST_MAIN( TestVectorLayerJoinBuffer )
195+
#include "moc_testqgsvectorlayerjoinbuffer.cxx"
196+
197+

0 commit comments

Comments
 (0)
Please sign in to comment.