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 "qgstest.h"
18 #include <QObject>
19 
20 //qgis includes...
21 #include <qgsvectorlayer.h>
22 #include "qgsfeatureiterator.h"
23 #include "qgslayertreegroup.h"
24 #include "qgsreadwritecontext.h"
25 #include <qgsvectordataprovider.h>
26 #include <qgsapplication.h>
27 #include <qgsvectorlayerjoinbuffer.h>
28 #include <qgslayerdefinition.h>
29 #include <qgsproject.h>
30 #include "qgslayertree.h"
31 
32 /**
33  * @ingroup UnitTests
34  * This is a unit test for the vector layer join buffer
35  *
36  * \see QgsVectorLayerJoinBuffer
37  */
38 class TestVectorLayerJoinBuffer : public QObject
39 {
40     Q_OBJECT
41 
42   public:
TestVectorLayerJoinBuffer()43     TestVectorLayerJoinBuffer()
44       : mLayers( QMap<QPair<QString, QString>, QgsVectorLayer*>() )
45     {}
46 
47   private slots:
48     void initTestCase();      // will be called before the first testfunction is executed.
49     void cleanupTestCase();   // will be called after the last testfunction was executed.
50     void init();              // will be called before each testfunction is executed.
51     void cleanup();           // will be called after every testfunction.
52 
53     void testJoinBasic_data();
54     void testJoinBasic();
55     void testJoinTransitive_data();
56     void testJoinTransitive();
57     void testJoinDetectCycle_data();
58     void testJoinDetectCycle();
59     void testJoinSubset_data();
60     void testJoinSubset();
61     void testJoinTwoTimes_data();
62     void testJoinTwoTimes();
63     void testJoinLayerDefinitionFile();
64     void testCacheUpdate_data();
65     void testCacheUpdate();
66     void testRemoveJoinOnLayerDelete();
67     void testResolveReferences();
68     void testSignals();
69     void testChangeAttributeValues();
70     void testCollidingNameColumnCached();
71 
72   private:
73     QgsProject mProject;
74     QList<QString> mProviders;
75     // map of layers. First key is the name of the layer A, B or C and second key is the provider memory or PG.
76     QMap<QPair<QString, QString>, QgsVectorLayer *> mLayers;
77 };
78 
79 // runs before all tests
initTestCase()80 void TestVectorLayerJoinBuffer::initTestCase()
81 {
82   QgsApplication::init();
83   QgsApplication::initQgis();
84 
85   // Set up the QgsSettings environment
86   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
87   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
88   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
89 
90   mProviders = QList<QString>() << QStringLiteral( "memory" );
91 
92   // Create memory layers
93   // LAYER A //
94   QgsVectorLayer *vlA = new QgsVectorLayer( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "A" ), QStringLiteral( "memory" ) );
95   QVERIFY( vlA->isValid() );
96   QVERIFY( vlA->fields().count() == 1 );
97   // LAYER B //
98   QgsVectorLayer *vlB = new QgsVectorLayer( QStringLiteral( "Point?field=id_b:integer&field=value_b" ), QStringLiteral( "B" ), QStringLiteral( "memory" ) );
99   QVERIFY( vlB->isValid() );
100   QVERIFY( vlB->fields().count() == 2 );
101   // LAYER C //
102   QgsVectorLayer *vlC = new QgsVectorLayer( QStringLiteral( "Point?field=id_c:integer&field=value_c" ), QStringLiteral( "C" ), QStringLiteral( "memory" ) );
103   QVERIFY( vlC->isValid() );
104   QVERIFY( vlC->fields().count() == 2 );
105   // LAYER X //
106   QgsVectorLayer *vlX = new QgsVectorLayer( QStringLiteral( "Point?field=id_x:integer&field=value_x1:integer&field=value_x2" ), QStringLiteral( "X" ), QStringLiteral( "memory" ) );
107   QVERIFY( vlX->isValid() );
108   QVERIFY( vlX->fields().count() == 3 );
109 
110   mLayers = QMap<QPair<QString, QString>, QgsVectorLayer *>();
111   mLayers.insert( QPair<QString, QString>( QStringLiteral( "A" ), QStringLiteral( "memory" ) ), vlA );
112   mLayers.insert( QPair<QString, QString>( QStringLiteral( "B" ), QStringLiteral( "memory" ) ), vlB );
113   mLayers.insert( QPair<QString, QString>( QStringLiteral( "C" ), QStringLiteral( "memory" ) ), vlC );
114   mLayers.insert( QPair<QString, QString>( QStringLiteral( "X" ), QStringLiteral( "memory" ) ), vlX );
115 
116   // Add PG layers
117 #ifdef ENABLE_PGTEST
118   QString dbConn = getenv( "QGIS_PGTEST_DB" );
119   if ( dbConn.isEmpty() )
120   {
121     dbConn = "service=qgis_test";
122   }
123   QgsVectorLayer *vlA_PG = new QgsVectorLayer( QString( "%1 sslmode=disable key='id_a' table=\"qgis_test\".\"table_a\" sql=" ).arg( dbConn ), "A_PG", "postgres" );
124   QgsVectorLayer *vlB_PG = new QgsVectorLayer( QString( "%1 sslmode=disable key='id_b' table=\"qgis_test\".\"table_b\" sql=" ).arg( dbConn ), "B_PG", "postgres" );
125   QgsVectorLayer *vlC_PG = new QgsVectorLayer( QString( "%1 sslmode=disable key='id_c' table=\"qgis_test\".\"table_c\" sql=" ).arg( dbConn ), "C_PG", "postgres" );
126   QgsVectorLayer *vlX_PG = new QgsVectorLayer( QString( "%1 sslmode=disable key='id_x' table=\"qgis_test\".\"table_x\" sql=" ).arg( dbConn ), "X_PG", "postgres" );
127   QVERIFY( vlA_PG->isValid() );
128   QVERIFY( vlB_PG->isValid() );
129   QVERIFY( vlC_PG->isValid() );
130   QVERIFY( vlX_PG->isValid() );
131   QVERIFY( vlA_PG->fields().count() == 1 );
132   QVERIFY( vlB_PG->fields().count() == 2 );
133   QVERIFY( vlC_PG->fields().count() == 2 );
134   QVERIFY( vlX_PG->fields().count() == 3 );
135   mLayers.insert( QPair<QString, QString>( "A", "PG" ), vlA_PG );
136   mLayers.insert( QPair<QString, QString>( "B", "PG" ), vlB_PG );
137   mLayers.insert( QPair<QString, QString>( "C", "PG" ), vlC_PG );
138   mLayers.insert( QPair<QString, QString>( "X", "PG" ), vlX_PG );
139   mProviders << "PG";
140 #endif
141 
142   // Create features
143   QgsFeature fA1( vlA->dataProvider()->fields(), 1 );
144   fA1.setAttribute( QStringLiteral( "id_a" ), 1 );
145   QgsFeature fA2( vlA->dataProvider()->fields(), 2 );
146   fA2.setAttribute( QStringLiteral( "id_a" ), 2 );
147   QgsFeature fB1( vlB->dataProvider()->fields(), 1 );
148   fB1.setAttribute( QStringLiteral( "id_b" ), 1 );
149   fB1.setAttribute( QStringLiteral( "value_b" ), 11 );
150   QgsFeature fB2( vlB->dataProvider()->fields(), 2 );
151   fB2.setAttribute( QStringLiteral( "id_b" ), 2 );
152   fB2.setAttribute( QStringLiteral( "value_b" ), 12 );
153   QgsFeature fC1( vlC->dataProvider()->fields(), 1 );
154   fC1.setAttribute( QStringLiteral( "id_c" ), 1 );
155   fC1.setAttribute( QStringLiteral( "value_c" ), 101 );
156   QgsFeature fX1( vlX->dataProvider()->fields(), 1 );
157   fX1.setAttribute( QStringLiteral( "id_x" ), 1 );
158   fX1.setAttribute( QStringLiteral( "value_x1" ), 111 );
159   fX1.setAttribute( QStringLiteral( "value_x2" ), 222 );
160 
161   // Commit features and layers to qgis
162   Q_FOREACH ( const QString provider, mProviders )
163   {
164     QgsVectorLayer *vl = mLayers.value( QPair<QString, QString>( QStringLiteral( "A" ), provider ) );
165     vl->dataProvider()->addFeatures( QgsFeatureList() << fA1 << fA2 );
166     QVERIFY( vl->featureCount() == 2 );
167     mProject.addMapLayer( vl );
168   }
169 
170   Q_FOREACH ( const QString provider, mProviders )
171   {
172     QgsVectorLayer *vl = mLayers.value( QPair<QString, QString>( QStringLiteral( "B" ), provider ) );
173     vl->dataProvider()->addFeatures( QgsFeatureList() << fB1 << fB2 );
174     QVERIFY( vl->featureCount() == 2 );
175     mProject.addMapLayer( vl );
176   }
177 
178   Q_FOREACH ( const QString provider, mProviders )
179   {
180     QgsVectorLayer *vl = mLayers.value( QPair<QString, QString>( QStringLiteral( "C" ), provider ) );
181     vl->dataProvider()->addFeatures( QgsFeatureList() << fC1 );
182     QVERIFY( vl->featureCount() == 1 );
183     mProject.addMapLayer( vl );
184   }
185 
186   Q_FOREACH ( const QString provider, mProviders )
187   {
188     QgsVectorLayer *vl = mLayers.value( QPair<QString, QString>( QStringLiteral( "X" ), provider ) );
189     vl->dataProvider()->addFeatures( QgsFeatureList() << fX1 );
190     QVERIFY( vl->featureCount() == 1 );
191     mProject.addMapLayer( vl );
192   }
193 
194   QVERIFY( mProject.mapLayers().count() == 4 * mProviders.count() );
195 }
196 
init()197 void TestVectorLayerJoinBuffer::init()
198 {
199 }
200 
cleanup()201 void TestVectorLayerJoinBuffer::cleanup()
202 {
203 }
204 
cleanupTestCase()205 void TestVectorLayerJoinBuffer::cleanupTestCase()
206 {
207   QgsApplication::exitQgis();
208 }
209 
testJoinBasic_data()210 void TestVectorLayerJoinBuffer::testJoinBasic_data()
211 {
212   QTest::addColumn<QString>( "provider" );
213   QTest::addColumn<bool>( "memoryCache" );
214 
215   QTest::newRow( "memory with cache" ) << "memory" << true ;
216   QTest::newRow( "memory without cache" ) << "memory" << false;
217 
218 #ifdef ENABLE_PGTEST
219   QTest::newRow( "postgresql with cache" ) << "PG" << true ;
220   QTest::newRow( "postgresql without cache" ) << "PG" << false;
221 #endif
222 }
223 
testJoinBasic()224 void TestVectorLayerJoinBuffer::testJoinBasic()
225 {
226   QFETCH( bool, memoryCache );
227   QFETCH( QString, provider );
228 
229   QgsVectorLayer *vlA = mLayers.value( QPair<QString, QString>( QStringLiteral( "A" ), provider ) );
230   QgsVectorLayer *vlB = mLayers.value( QPair<QString, QString>( QStringLiteral( "B" ), provider ) );
231 
232   QVERIFY( vlA->fields().count() == 1 );
233 
234   QgsVectorLayerJoinInfo joinInfo;
235   joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
236   joinInfo.setJoinLayer( vlB );
237   joinInfo.setJoinFieldName( QStringLiteral( "id_b" ) );
238   joinInfo.setUsingMemoryCache( memoryCache );
239   joinInfo.setPrefix( QStringLiteral( "B_" ) );
240   vlA->addJoin( joinInfo );
241 
242   QVERIFY( vlA->fields().count() == 2 );
243 
244   QgsFeatureIterator fi = vlA->getFeatures();
245   QgsFeature fA1, fA2;
246   fi.nextFeature( fA1 );
247   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
248   QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 11 );
249   fi.nextFeature( fA2 );
250   QCOMPARE( fA2.attribute( "id_a" ).toInt(), 2 );
251   QCOMPARE( fA2.attribute( "B_value_b" ).toInt(), 12 );
252 
253   vlA->removeJoin( vlB->id() );
254 
255   QVERIFY( vlA->fields().count() == 1 );
256 }
257 
testJoinTransitive_data()258 void TestVectorLayerJoinBuffer::testJoinTransitive_data()
259 {
260   QTest::addColumn<QString>( "provider" );
261   QTest::newRow( "memory" ) << "memory";
262 #ifdef ENABLE_PGTEST
263   QTest::newRow( "postgresql" ) << "PG";
264 #endif
265 }
266 
testJoinTransitive()267 void TestVectorLayerJoinBuffer::testJoinTransitive()
268 {
269 
270   QFETCH( QString, provider );
271 
272   QgsVectorLayer *vlA = mLayers.value( QPair<QString, QString>( QStringLiteral( "A" ), provider ) );
273   QgsVectorLayer *vlB = mLayers.value( QPair<QString, QString>( QStringLiteral( "B" ), provider ) );
274   QgsVectorLayer *vlC = mLayers.value( QPair<QString, QString>( QStringLiteral( "C" ), provider ) );
275 
276   // test join A -> B -> C
277   // first we join A -> B and after that B -> C
278   // layer A should automatically update to include joined data from C
279 
280   QVERIFY( vlA->fields().count() == 1 ); // id_a
281 
282   // add join A -> B
283 
284   QgsVectorLayerJoinInfo joinInfo1;
285   joinInfo1.setTargetFieldName( QStringLiteral( "id_a" ) );
286   joinInfo1.setJoinLayer( vlB );
287   joinInfo1.setJoinFieldName( QStringLiteral( "id_b" ) );
288   joinInfo1.setUsingMemoryCache( true );
289   joinInfo1.setPrefix( QStringLiteral( "B_" ) );
290   vlA->addJoin( joinInfo1 );
291   QVERIFY( vlA->fields().count() == 2 ); // id_a, B_value_b
292 
293   // add join B -> C
294 
295   QgsVectorLayerJoinInfo joinInfo2;
296   joinInfo2.setTargetFieldName( QStringLiteral( "id_b" ) );
297   joinInfo2.setJoinLayer( vlC );
298   joinInfo2.setJoinFieldName( QStringLiteral( "id_c" ) );
299   joinInfo2.setUsingMemoryCache( true );
300   joinInfo2.setPrefix( QStringLiteral( "C_" ) );
301   vlB->addJoin( joinInfo2 );
302   QVERIFY( vlB->fields().count() == 3 ); // id_b, value_b, C_value_c
303 
304   // now layer A must include also data from layer C
305   QVERIFY( vlA->fields().count() == 3 ); // id_a, B_value_b, B_C_value_c
306 
307   QgsFeatureIterator fi = vlA->getFeatures();
308   QgsFeature fA1;
309   fi.nextFeature( fA1 );
310   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
311   QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 11 );
312   QCOMPARE( fA1.attribute( "B_C_value_c" ).toInt(), 101 );
313 
314   // test that layer A gets updated when layer C changes its fields
315   vlC->addExpressionField( QStringLiteral( "123" ), QgsField( QStringLiteral( "dummy" ), QVariant::Int ) );
316   QVERIFY( vlA->fields().count() == 4 ); // id_a, B_value_b, B_C_value_c, B_C_dummy
317   vlC->removeExpressionField( 0 );
318 
319   // cleanup
320   vlA->removeJoin( vlB->id() );
321   vlB->removeJoin( vlC->id() );
322 }
323 
testJoinDetectCycle_data()324 void TestVectorLayerJoinBuffer::testJoinDetectCycle_data()
325 {
326   QTest::addColumn<QString>( "provider" );
327   QTest::newRow( "memory" ) << "memory";
328 #ifdef ENABLE_PGTEST
329   QTest::newRow( "postgresql" ) << "PG";
330 #endif
331 }
332 
testJoinDetectCycle()333 void TestVectorLayerJoinBuffer::testJoinDetectCycle()
334 {
335   QFETCH( QString, provider );
336 
337   QgsVectorLayer *vlA = mLayers.value( QPair<QString, QString>( QStringLiteral( "A" ), provider ) );
338   QgsVectorLayer *vlB = mLayers.value( QPair<QString, QString>( QStringLiteral( "B" ), provider ) );
339 
340   // if A joins B and B joins A, we may get to an infinite loop if the case is not handled properly
341 
342   QgsVectorLayerJoinInfo joinInfo;
343   joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
344   joinInfo.setJoinLayer( vlB );
345   joinInfo.setJoinFieldName( QStringLiteral( "id_b" ) );
346   joinInfo.setUsingMemoryCache( true );
347   joinInfo.setPrefix( QStringLiteral( "B_" ) );
348   vlA->addJoin( joinInfo );
349 
350   QgsVectorLayerJoinInfo joinInfo2;
351   joinInfo2.setTargetFieldName( QStringLiteral( "id_b" ) );
352   joinInfo2.setJoinLayer( vlA );
353   joinInfo2.setJoinFieldName( QStringLiteral( "id_a" ) );
354   joinInfo2.setUsingMemoryCache( true );
355   joinInfo2.setPrefix( QStringLiteral( "A_" ) );
356   bool res = vlB->addJoin( joinInfo2 );
357 
358   QVERIFY( !res );
359 
360   // the join in layer B must be rejected
361   QVERIFY( vlB->vectorJoins().isEmpty() );
362 
363   vlA->removeJoin( vlB->id() );
364 }
365 
366 
testJoinSubset_data()367 void TestVectorLayerJoinBuffer::testJoinSubset_data()
368 {
369   QTest::addColumn<QString>( "provider" );
370   QTest::addColumn<bool>( "memoryCache" );
371 
372   QTest::newRow( "memory with cache" ) << "memory" << true ;
373   QTest::newRow( "memory without cache" ) << "memory" << false;
374 
375 #ifdef ENABLE_PGTEST
376   QTest::newRow( "postgresql with cache" ) << "PG" << true ;
377   QTest::newRow( "postgresql without cache" ) << "PG" << false;
378 #endif
379 }
380 
381 
testJoinSubset()382 void TestVectorLayerJoinBuffer::testJoinSubset()
383 {
384   QFETCH( bool, memoryCache );
385   QFETCH( QString, provider );
386 
387   QVERIFY( mProject.mapLayers().count() == 4 * mProviders.count() );
388 
389   QgsVectorLayer *vlA = mLayers.value( QPair<QString, QString>( QStringLiteral( "A" ), provider ) );
390   QgsVectorLayer *vlX = mLayers.value( QPair<QString, QString>( QStringLiteral( "X" ), provider ) );
391 
392   // case 1: join without subset
393 
394   QgsVectorLayerJoinInfo joinInfo;
395   joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
396   joinInfo.setJoinLayer( vlX );
397   joinInfo.setJoinFieldName( QStringLiteral( "id_x" ) );
398   joinInfo.setUsingMemoryCache( memoryCache );
399   joinInfo.setPrefix( QStringLiteral( "X_" ) );
400   bool res = vlA->addJoin( joinInfo );
401   QVERIFY( res );
402 
403   QCOMPARE( vlA->fields().count(), 3 ); // id_a, X_value_x1, X_value_x2
404   QgsFeatureIterator fi = vlA->getFeatures();
405   QgsFeature fAX;
406   fi.nextFeature( fAX );
407   QCOMPARE( fAX.attribute( "id_a" ).toInt(), 1 );
408   QCOMPARE( fAX.attribute( "X_value_x1" ).toInt(), 111 );
409   QCOMPARE( fAX.attribute( "X_value_x2" ).toInt(), 222 );
410 
411   vlA->removeJoin( vlX->id() );
412 
413   // case 2: join with subset
414 
415   QStringList *subset = new QStringList;
416   *subset << QStringLiteral( "value_x2" );
417   joinInfo.setJoinFieldNamesSubset( subset );
418   vlA->addJoin( joinInfo );
419 
420   QCOMPARE( vlA->fields().count(), 2 ); // id_a, X_value_x2
421 
422   fi = vlA->getFeatures();
423   fi.nextFeature( fAX );
424   QCOMPARE( fAX.attribute( "id_a" ).toInt(), 1 );
425   QCOMPARE( fAX.attribute( "X_value_x2" ).toInt(), 222 );
426 
427   vlA->removeJoin( vlX->id() );
428 }
429 
testJoinTwoTimes_data()430 void TestVectorLayerJoinBuffer::testJoinTwoTimes_data()
431 {
432   QTest::addColumn<QString>( "provider" );
433   QTest::newRow( "memory" ) << "memory";
434 #ifdef ENABLE_PGTEST
435   QTest::newRow( "postgresql" ) << "PG";
436 #endif
437 }
438 
testJoinTwoTimes()439 void TestVectorLayerJoinBuffer::testJoinTwoTimes()
440 {
441 
442   QFETCH( QString, provider );
443 
444   QgsVectorLayer *vlA = mLayers.value( QPair<QString, QString>( QStringLiteral( "A" ), provider ) );
445   QgsVectorLayer *vlB = mLayers.value( QPair<QString, QString>( QStringLiteral( "B" ), provider ) );
446 
447   QVERIFY( vlA->fields().count() == 1 );
448 
449   QgsVectorLayerJoinInfo joinInfo1;
450   joinInfo1.setTargetFieldName( QStringLiteral( "id_a" ) );
451   joinInfo1.setJoinLayer( vlB );
452   joinInfo1.setJoinFieldName( QStringLiteral( "id_b" ) );
453   joinInfo1.setUsingMemoryCache( true );
454   joinInfo1.setPrefix( QStringLiteral( "j1_" ) );
455   vlA->addJoin( joinInfo1 );
456 
457   QgsVectorLayerJoinInfo joinInfo2;
458   joinInfo2.setTargetFieldName( QStringLiteral( "id_a" ) );
459   joinInfo2.setJoinLayer( vlB );
460   joinInfo2.setJoinFieldName( QStringLiteral( "id_b" ) );
461   joinInfo2.setUsingMemoryCache( true );
462   joinInfo2.setPrefix( QStringLiteral( "j2_" ) );
463   vlA->addJoin( joinInfo2 );
464 
465   QCOMPARE( vlA->vectorJoins().count(), 2 );
466 
467   QVERIFY( vlA->fields().count() == 3 );
468 
469   QgsFeatureIterator fi = vlA->getFeatures();
470   QgsFeature fA1; //, fA2;
471   fi.nextFeature( fA1 );
472   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
473   QCOMPARE( fA1.attribute( "j1_value_b" ).toInt(), 11 );
474   QCOMPARE( fA1.attribute( "j2_value_b" ).toInt(), 11 );
475 
476   vlA->removeJoin( vlB->id() );
477   vlA->removeJoin( vlB->id() );
478 
479   QCOMPARE( vlA->vectorJoins().count(), 0 );
480 }
481 
testJoinLayerDefinitionFile()482 void TestVectorLayerJoinBuffer::testJoinLayerDefinitionFile()
483 {
484   bool r;
485 
486   mProject.removeAllMapLayers();
487 
488   // Create two layers
489   QgsVectorLayer *layerA = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=key:integer&field=value:double&index=yes" ), QStringLiteral( "layerA" ), QStringLiteral( "memory" ) );
490   QVERIFY( layerA );
491   mProject.addMapLayer( layerA );
492 
493   QgsVectorLayer *layerB = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=id:integer&index=yes" ), QStringLiteral( "layerB" ), QStringLiteral( "memory" ) );
494   QVERIFY( layerB );
495   mProject.addMapLayer( layerB );
496 
497   // Create vector join
498   QgsVectorLayerJoinInfo joinInfo;
499   joinInfo.setTargetFieldName( QStringLiteral( "id" ) );
500   joinInfo.setJoinLayer( layerA );
501   joinInfo.setJoinFieldName( QStringLiteral( "key" ) );
502   joinInfo.setUsingMemoryCache( true );
503   joinInfo.setPrefix( QStringLiteral( "joined_" ) );
504   r = layerB->addJoin( joinInfo );
505   QVERIFY( r );
506 
507   // Generate QLR
508   QDomDocument qlrDoc( QStringLiteral( "qgis-layer-definition" ) );
509   QString errorMessage;
510   r = QgsLayerDefinition::exportLayerDefinition( qlrDoc, mProject.layerTreeRoot()->children(), errorMessage, QgsReadWriteContext() );
511   QVERIFY2( r, errorMessage.toUtf8().constData() );
512 
513   // Clear
514   mProject.removeAllMapLayers();
515 
516   // Load QLR
517   QgsReadWriteContext context = QgsReadWriteContext();
518   r = QgsLayerDefinition::loadLayerDefinition( qlrDoc, &mProject, mProject.layerTreeRoot(), errorMessage, context );
519   QVERIFY2( r, errorMessage.toUtf8().constData() );
520 
521   // Get layer
522   QList<QgsMapLayer *> mapLayers = mProject.mapLayersByName( QStringLiteral( "layerB" ) );
523   QCOMPARE( mapLayers.count(), 1 );
524 
525   QgsVectorLayer *vLayer = dynamic_cast<QgsVectorLayer *>( mapLayers.value( 0 ) );
526   QVERIFY( vLayer );
527 
528   // Check for vector join
529   QCOMPARE( vLayer->vectorJoins().count(), 1 );
530 
531   // Check for joined field
532   QVERIFY( vLayer->fields().lookupField( joinInfo.prefix() + "value" ) >= 0 );
533 }
534 
testCacheUpdate_data()535 void TestVectorLayerJoinBuffer::testCacheUpdate_data()
536 {
537   QTest::addColumn<bool>( "useCache" );
538   QTest::newRow( "cache" ) << true;
539   QTest::newRow( "no cache" ) << false;
540 }
541 
testCacheUpdate()542 void TestVectorLayerJoinBuffer::testCacheUpdate()
543 {
544   QFETCH( bool, useCache );
545 
546   QgsVectorLayer *vlA = new QgsVectorLayer( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "cacheA" ), QStringLiteral( "memory" ) );
547   QVERIFY( vlA->isValid() );
548   QgsVectorLayer *vlB = new QgsVectorLayer( QStringLiteral( "Point?field=id_b:integer&field=value_b" ), QStringLiteral( "cacheB" ), QStringLiteral( "memory" ) );
549   QVERIFY( vlB->isValid() );
550   mProject.addMapLayer( vlA );
551   mProject.addMapLayer( vlB );
552 
553   QgsFeature fA1( vlA->dataProvider()->fields(), 1 );
554   fA1.setAttribute( QStringLiteral( "id_a" ), 1 );
555   QgsFeature fA2( vlA->dataProvider()->fields(), 2 );
556   fA2.setAttribute( QStringLiteral( "id_a" ), 2 );
557 
558   vlA->dataProvider()->addFeatures( QgsFeatureList() << fA1 << fA2 );
559 
560   QgsFeature fB1( vlB->dataProvider()->fields(), 1 );
561   fB1.setAttribute( QStringLiteral( "id_b" ), 1 );
562   fB1.setAttribute( QStringLiteral( "value_b" ), 11 );
563   QgsFeature fB2( vlB->dataProvider()->fields(), 2 );
564   fB2.setAttribute( QStringLiteral( "id_b" ), 2 );
565   fB2.setAttribute( QStringLiteral( "value_b" ), 12 );
566 
567   vlB->dataProvider()->addFeatures( QgsFeatureList() << fB1 << fB2 );
568 
569   QgsVectorLayerJoinInfo joinInfo;
570   joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
571   joinInfo.setJoinLayer( vlB );
572   joinInfo.setJoinFieldName( QStringLiteral( "id_b" ) );
573   joinInfo.setUsingMemoryCache( useCache );
574   joinInfo.setPrefix( QStringLiteral( "B_" ) );
575   vlA->addJoin( joinInfo );
576 
577   QgsFeatureIterator fi = vlA->getFeatures();
578   fi.nextFeature( fA1 );
579   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
580   QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 11 );
581   fi.nextFeature( fA2 );
582   QCOMPARE( fA2.attribute( "id_a" ).toInt(), 2 );
583   QCOMPARE( fA2.attribute( "B_value_b" ).toInt(), 12 );
584 
585   // change value in join target layer
586   vlB->startEditing();
587   vlB->changeAttributeValue( 1, 1, 111 );
588   vlB->changeAttributeValue( 2, 0, 3 );
589   vlB->commitChanges();
590 
591   fi = vlA->getFeatures();
592   fi.nextFeature( fA1 );
593   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
594   QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 111 );
595   fi.nextFeature( fA2 );
596   QCOMPARE( fA2.attribute( "id_a" ).toInt(), 2 );
597   QVERIFY( fA2.attribute( "B_value_b" ).isNull() );
598 
599   // change value in joined layer
600   vlA->startEditing();
601   vlA->changeAttributeValue( 2, 0, 3 );
602   vlA->commitChanges();
603 
604   fi = vlA->getFeatures();
605   fi.nextFeature( fA1 );
606   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
607   QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 111 );
608   fi.nextFeature( fA2 );
609   QCOMPARE( fA2.attribute( "id_a" ).toInt(), 3 );
610   QCOMPARE( fA2.attribute( "B_value_b" ).toInt(), 12 );
611 }
612 
testRemoveJoinOnLayerDelete()613 void TestVectorLayerJoinBuffer::testRemoveJoinOnLayerDelete()
614 {
615   QgsVectorLayer *vlA = new QgsVectorLayer( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "cacheA" ), QStringLiteral( "memory" ) );
616   QVERIFY( vlA->isValid() );
617   QgsVectorLayer *vlB = new QgsVectorLayer( QStringLiteral( "Point?field=id_b:integer&field=value_b" ), QStringLiteral( "cacheB" ), QStringLiteral( "memory" ) );
618   QVERIFY( vlB->isValid() );
619 
620   QgsVectorLayerJoinInfo joinInfo;
621   joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
622   joinInfo.setJoinLayer( vlB );
623   joinInfo.setJoinFieldName( QStringLiteral( "id_b" ) );
624   joinInfo.setUsingMemoryCache( true );
625   joinInfo.setPrefix( QStringLiteral( "B_" ) );
626   vlA->addJoin( joinInfo );
627 
628   QCOMPARE( vlA->vectorJoins().count(), 1 );
629   QCOMPARE( vlA->vectorJoins()[0].joinLayer(), vlB );
630   QCOMPARE( vlA->vectorJoins()[0].joinLayerId(), vlB->id() );
631   QCOMPARE( vlA->fields().count(), 2 );
632 
633   delete vlB;
634 
635   QCOMPARE( vlA->vectorJoins().count(), 0 );
636   QCOMPARE( vlA->fields().count(), 1 );
637 
638   delete vlA;
639 }
640 
testResolveReferences()641 void TestVectorLayerJoinBuffer::testResolveReferences()
642 {
643   QgsVectorLayer *vlA = new QgsVectorLayer( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "cacheA" ), QStringLiteral( "memory" ) );
644   QVERIFY( vlA->isValid() );
645   QgsVectorLayer *vlB = new QgsVectorLayer( QStringLiteral( "Point?field=id_b:integer&field=value_b" ), QStringLiteral( "cacheB" ), QStringLiteral( "memory" ) );
646   QVERIFY( vlB->isValid() );
647 
648   QgsVectorLayerJoinInfo joinInfo;
649   joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
650   joinInfo.setJoinLayerId( vlB->id() );
651   joinInfo.setJoinFieldName( QStringLiteral( "id_b" ) );
652   joinInfo.setUsingMemoryCache( true );
653   joinInfo.setPrefix( QStringLiteral( "B_" ) );
654   vlA->addJoin( joinInfo );
655 
656   QCOMPARE( vlA->fields().count(), 1 );
657   QCOMPARE( vlA->vectorJoins()[0].joinLayer(), ( QgsVectorLayer * ) nullptr );
658   QCOMPARE( vlA->vectorJoins()[0].joinLayerId(), vlB->id() );
659 
660   QgsProject project;
661   project.addMapLayer( vlB );
662 
663   vlA->resolveReferences( &project );
664 
665   QCOMPARE( vlA->fields().count(), 2 );
666   QCOMPARE( vlA->vectorJoins()[0].joinLayer(), vlB );
667   QCOMPARE( vlA->vectorJoins()[0].joinLayerId(), vlB->id() );
668 
669   delete vlA;
670 }
671 
testSignals()672 void TestVectorLayerJoinBuffer::testSignals()
673 {
674   mProject.clear();
675   QgsVectorLayer *vlA = new QgsVectorLayer( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "cacheA" ), QStringLiteral( "memory" ) );
676   QVERIFY( vlA->isValid() );
677   QgsVectorLayer *vlB = new QgsVectorLayer( QStringLiteral( "Point?field=id_b:integer&field=value_b" ), QStringLiteral( "cacheB" ), QStringLiteral( "memory" ) );
678   QVERIFY( vlB->isValid() );
679   mProject.addMapLayer( vlA );
680   mProject.addMapLayer( vlB );
681 
682   QgsFeature fA1( vlA->dataProvider()->fields(), 1 );
683   fA1.setAttribute( QStringLiteral( "id_a" ), 1 );
684   QgsFeature fA2( vlA->dataProvider()->fields(), 2 );
685   fA2.setAttribute( QStringLiteral( "id_a" ), 2 );
686 
687   vlA->dataProvider()->addFeatures( QgsFeatureList() << fA1 << fA2 );
688 
689   QgsVectorLayerJoinInfo joinInfo;
690   joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
691   joinInfo.setJoinLayer( vlB );
692   joinInfo.setJoinFieldName( QStringLiteral( "id_b" ) );
693   joinInfo.setPrefix( QStringLiteral( "B_" ) );
694   joinInfo.setEditable( true );
695   joinInfo.setUpsertOnEdit( true );
696   vlA->addJoin( joinInfo );
697 
698   QgsFeatureIterator fi = vlA->getFeatures();
699   fi.nextFeature( fA1 );
700   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
701   QVERIFY( !fA1.attribute( "B_value_b" ).isValid() );
702   fi.nextFeature( fA2 );
703   QCOMPARE( fA2.attribute( "id_a" ).toInt(), 2 );
704   QVERIFY( !fA2.attribute( "B_value_b" ).isValid() );
705 
706   // change value in join target layer, check for signals
707   QSignalSpy spy( vlA, &QgsVectorLayer::attributeValueChanged );
708   vlA->startEditing();
709   vlB->startEditing();
710   // adds new feature to second layer
711   QVERIFY( vlA->changeAttributeValue( 1, 1, 111 ) );
712   fi = vlA->getFeatures();
713   fi.nextFeature( fA1 );
714   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
715   QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 111 );
716   fi.nextFeature( fA2 );
717   QCOMPARE( fA2.attribute( "id_a" ).toInt(), 2 );
718   QVERIFY( !fA2.attribute( "B_value_b" ).isValid() );
719   QCOMPARE( spy.count(), 1 );
720   QVERIFY( vlA->changeAttributeValue( 2, 1, 222 ) );
721   fi = vlA->getFeatures();
722   fi.nextFeature( fA1 );
723   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
724   QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 111 );
725   fi.nextFeature( fA2 );
726   QCOMPARE( fA2.attribute( "id_a" ).toInt(), 2 );
727   QCOMPARE( fA2.attribute( "B_value_b" ).toInt(), 222 );
728   QCOMPARE( spy.count(), 2 );
729   // changes existing feature in second layer
730   QVERIFY( vlA->changeAttributeValue( 1, 1, 112 ) );
731   fi = vlA->getFeatures();
732   fi.nextFeature( fA1 );
733   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
734   QCOMPARE( fA1.attribute( "B_value_b" ).toInt(), 112 );
735   fi.nextFeature( fA2 );
736   QCOMPARE( fA2.attribute( "id_a" ).toInt(), 2 );
737   QCOMPARE( fA2.attribute( "B_value_b" ).toInt(), 222 );
738   QCOMPARE( spy.count(), 3 );
739 }
740 
testChangeAttributeValues()741 void TestVectorLayerJoinBuffer::testChangeAttributeValues()
742 {
743   // change attribute values in a vector layer which includes joins
744   mProject.clear();
745   QgsVectorLayer *vlA = new QgsVectorLayer( QStringLiteral( "Point?field=id_a:integer&field=value_a1:string&field=value_a2:string" ), QStringLiteral( "cacheA" ), QStringLiteral( "memory" ) );
746   QVERIFY( vlA->isValid() );
747   QgsVectorLayer *vlB = new QgsVectorLayer( QStringLiteral( "Point?field=id_b:integer&field=value_b1:string&field=value_b2:string" ), QStringLiteral( "cacheB" ), QStringLiteral( "memory" ) );
748   QVERIFY( vlB->isValid() );
749   mProject.addMapLayer( vlA );
750   mProject.addMapLayer( vlB );
751 
752   QgsFeature fA1( vlA->dataProvider()->fields(), 1 );
753   fA1.setAttribute( QStringLiteral( "id_a" ), 1 );
754   fA1.setAttribute( QStringLiteral( "value_a1" ), QStringLiteral( "a_1_1" ) );
755   fA1.setAttribute( QStringLiteral( "value_a2" ), QStringLiteral( "a_1_2" ) );
756   QgsFeature fA2( vlA->dataProvider()->fields(), 2 );
757   fA2.setAttribute( QStringLiteral( "id_a" ), 2 );
758   fA2.setAttribute( QStringLiteral( "value_a1" ), QStringLiteral( "a_2_1" ) );
759   fA2.setAttribute( QStringLiteral( "value_a2" ), QStringLiteral( "a_2_2" ) );
760 
761   QVERIFY( vlA->dataProvider()->addFeatures( QgsFeatureList() << fA1 << fA2 ) );
762 
763   QCOMPARE( vlA->getFeature( 1 ).attributes().size(), 3 );
764   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 0 ).toInt(), 1 );
765   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 1 ).toString(), QStringLiteral( "a_1_1" ) );
766   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 2 ).toString(), QStringLiteral( "a_1_2" ) );
767 
768   QgsVectorLayerJoinInfo joinInfo;
769   joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
770   joinInfo.setJoinLayer( vlB );
771   joinInfo.setJoinFieldName( QStringLiteral( "id_b" ) );
772   joinInfo.setPrefix( QStringLiteral( "B_" ) );
773   joinInfo.setEditable( true );
774   joinInfo.setUpsertOnEdit( true );
775   vlA->addJoin( joinInfo );
776 
777   QVERIFY( vlA->startEditing() );
778   QVERIFY( vlB->startEditing() );
779 
780   QCOMPARE( vlA->getFeature( 1 ).attributes().size(), 5 );
781   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 0 ).toInt(), 1 );
782   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 1 ).toString(), QStringLiteral( "a_1_1" ) );
783   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 2 ).toString(), QStringLiteral( "a_1_2" ) );
784   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 3 ).toString(), QString() );
785   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 4 ).toString(), QString() );
786 
787   // change a provider field
788   QVERIFY( vlA->changeAttributeValue( 1, 1, QStringLiteral( "new_a_1_1" ) ) );
789   // change a join field
790   QVERIFY( vlA->changeAttributeValue( 1, 3, QStringLiteral( "new_b_1_1" ) ) );
791 
792   QCOMPARE( vlA->getFeature( 1 ).attributes().size(), 5 );
793   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 0 ).toInt(), 1 );
794   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 1 ).toString(), QStringLiteral( "new_a_1_1" ) );
795   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 2 ).toString(), QStringLiteral( "a_1_2" ) );
796   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 3 ).toString(), QStringLiteral( "new_b_1_1" ) );
797   QCOMPARE( vlA->getFeature( 1 ).attributes().at( 4 ).toString(), QString() );
798 
799   QgsFeature joinFeature;
800   vlB->getFeatures().nextFeature( joinFeature );
801   QVERIFY( joinFeature.isValid() );
802   QCOMPARE( joinFeature.attributes().size(), 3 );
803   QCOMPARE( joinFeature.attributes().at( 0 ).toInt(), 1 );
804   QCOMPARE( joinFeature.attributes().at( 1 ).toString(), QStringLiteral( "new_b_1_1" ) );
805   QCOMPARE( joinFeature.attributes().at( 2 ).toString(), QString() );
806 
807   // change a combination of provider and joined fields at once
808   QVERIFY( vlA->changeAttributeValues( 2, QgsAttributeMap{ { 1, QStringLiteral( "new_a_2_1" ) },
809     { 2, QStringLiteral( "new_a_2_2" ) },
810     { 3, QStringLiteral( "new_b_2_1" ) },
811     { 4, QStringLiteral( "new_b_2_2" ) }} ) );
812 
813   QCOMPARE( vlA->getFeature( 2 ).attributes().size(), 5 );
814   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 0 ).toInt(), 2 );
815   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 1 ).toString(), QStringLiteral( "new_a_2_1" ) );
816   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 2 ).toString(), QStringLiteral( "new_a_2_2" ) );
817   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 3 ).toString(), QStringLiteral( "new_b_2_1" ) );
818   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 4 ).toString(), QStringLiteral( "new_b_2_2" ) );
819 
820   // change only provider fields
821   QVERIFY( vlA->changeAttributeValues( 2, QgsAttributeMap{ { 1, QStringLiteral( "new_a_2_1b" ) },
822     { 2, QStringLiteral( "new_a_2_2b" ) }} ) );
823 
824   QCOMPARE( vlA->getFeature( 2 ).attributes().size(), 5 );
825   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 0 ).toInt(), 2 );
826   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 1 ).toString(), QStringLiteral( "new_a_2_1b" ) );
827   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 2 ).toString(), QStringLiteral( "new_a_2_2b" ) );
828   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 3 ).toString(), QStringLiteral( "new_b_2_1" ) );
829   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 4 ).toString(), QStringLiteral( "new_b_2_2" ) );
830 
831   // change only joined fields
832   QVERIFY( vlA->changeAttributeValues( 2, QgsAttributeMap{ { 3, QStringLiteral( "new_b_2_1b" ) },
833     { 4, QStringLiteral( "new_b_2_2b" ) }} ) );
834 
835   QCOMPARE( vlA->getFeature( 2 ).attributes().size(), 5 );
836   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 0 ).toInt(), 2 );
837   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 1 ).toString(), QStringLiteral( "new_a_2_1b" ) );
838   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 2 ).toString(), QStringLiteral( "new_a_2_2b" ) );
839   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 3 ).toString(), QStringLiteral( "new_b_2_1b" ) );
840   QCOMPARE( vlA->getFeature( 2 ).attributes().at( 4 ).toString(), QStringLiteral( "new_b_2_2b" ) );
841 
842 }
843 
testCollidingNameColumnCached()844 void TestVectorLayerJoinBuffer::testCollidingNameColumnCached()
845 {
846   mProject.clear();
847   QgsVectorLayer *vlA = new QgsVectorLayer( QStringLiteral( "Point?field=id_a:integer&field=name" ), QStringLiteral( "cacheA" ), QStringLiteral( "memory" ) );
848   QVERIFY( vlA->isValid() );
849   QgsVectorLayer *vlB = new QgsVectorLayer( QStringLiteral( "Point?field=id_b:integer&field=name&field=value_b&field=value_c" ), QStringLiteral( "cacheB" ), QStringLiteral( "memory" ) );
850   QVERIFY( vlB->isValid() );
851   mProject.addMapLayer( vlA );
852   mProject.addMapLayer( vlB );
853 
854   QgsFeature fA1( vlA->dataProvider()->fields(), 1 );
855   fA1.setAttribute( QStringLiteral( "id_a" ), 1 );
856   fA1.setAttribute( QStringLiteral( "name" ), QStringLiteral( "name_a" ) );
857 
858   vlA->dataProvider()->addFeatures( QgsFeatureList() << fA1 );
859 
860   QgsFeature fB1( vlB->dataProvider()->fields(), 1 );
861   fB1.setAttribute( QStringLiteral( "id_b" ), 1 );
862   fB1.setAttribute( QStringLiteral( "name" ), QStringLiteral( "name_b" ) );
863   fB1.setAttribute( QStringLiteral( "value_b" ), QStringLiteral( "value_b" ) );
864   fB1.setAttribute( QStringLiteral( "value_c" ), QStringLiteral( "value_c" ) );
865 
866   vlB->dataProvider()->addFeatures( QgsFeatureList() << fB1 );
867 
868   QgsVectorLayerJoinInfo joinInfo;
869   joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
870   joinInfo.setJoinLayer( vlB );
871   joinInfo.setJoinFieldName( QStringLiteral( "id_b" ) );
872   joinInfo.setPrefix( QStringLiteral( "" ) );
873   joinInfo.setEditable( true );
874   joinInfo.setUpsertOnEdit( false );
875   joinInfo.setUsingMemoryCache( true );
876   vlA->addJoin( joinInfo );
877 
878   QgsFeatureIterator fi1 = vlA->getFeatures();
879   fi1.nextFeature( fA1 );
880   QCOMPARE( fA1.fields().names(), QStringList( {"id_a", "name", "value_b", "value_c"} ) );
881   QCOMPARE( fA1.attribute( "id_a" ).toInt(), 1 );
882   QCOMPARE( fA1.attribute( "name" ).toString(), QStringLiteral( "name_a" ) );
883   QCOMPARE( fA1.attribute( "value_b" ).toString(), QStringLiteral( "value_b" ) );
884   QCOMPARE( fA1.attribute( "value_c" ).toString(), QStringLiteral( "value_c" ) );
885 }
886 
887 QGSTEST_MAIN( TestVectorLayerJoinBuffer )
888 #include "testqgsvectorlayerjoinbuffer.moc"
889 
890 
891