1 /***************************************************************************
2                           qgsvectorlayerjoinbuffer.cpp
3                           ----------------------------
4     begin                : Feb 09, 2011
5     copyright            : (C) 2011 by Marco Hugentobler
6     email                : marco dot hugentobler at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgsvectorlayerjoinbuffer.h"
19 
20 #include "qgsfeatureiterator.h"
21 #include "qgslogger.h"
22 #include "qgsproject.h"
23 #include "qgsvectordataprovider.h"
24 #include "qgsauxiliarystorage.h"
25 
26 #include <QDomElement>
27 
QgsVectorLayerJoinBuffer(QgsVectorLayer * layer)28 QgsVectorLayerJoinBuffer::QgsVectorLayerJoinBuffer( QgsVectorLayer *layer )
29   : mLayer( layer )
30 {
31 }
32 
_outEdges(QgsVectorLayer * vl)33 static QList<QgsVectorLayer *> _outEdges( QgsVectorLayer *vl )
34 {
35   QList<QgsVectorLayer *> lst;
36   const auto constVectorJoins = vl->vectorJoins();
37   for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
38   {
39     if ( QgsVectorLayer *joinVl = info.joinLayer() )
40       lst << joinVl;
41   }
42   return lst;
43 }
44 
_hasCycleDFS(QgsVectorLayer * n,QHash<QgsVectorLayer *,int> & mark)45 static bool _hasCycleDFS( QgsVectorLayer *n, QHash<QgsVectorLayer *, int> &mark )
46 {
47   if ( mark.value( n ) == 1 ) // temporary
48     return true;
49   if ( mark.value( n ) == 0 ) // not visited
50   {
51     mark[n] = 1; // temporary
52     const auto outEdges { _outEdges( n ) };
53     for ( QgsVectorLayer *m : outEdges )
54     {
55       if ( _hasCycleDFS( m, mark ) )
56         return true;
57     }
58     mark[n] = 2; // permanent
59   }
60   return false;
61 }
62 
63 
addJoin(const QgsVectorLayerJoinInfo & joinInfo)64 bool QgsVectorLayerJoinBuffer::addJoin( const QgsVectorLayerJoinInfo &joinInfo )
65 {
66   QMutexLocker locker( &mMutex );
67   mVectorJoins.push_back( joinInfo );
68 
69   // run depth-first search to detect cycles in the graph of joins between layers.
70   // any cycle would cause infinite recursion when updating fields
71   QHash<QgsVectorLayer *, int> markDFS;
72   if ( mLayer && _hasCycleDFS( mLayer, markDFS ) )
73   {
74     // we have to reject this one
75     mVectorJoins.pop_back();
76     return false;
77   }
78 
79   // Wait for notifications about changed fields in joined layer to propagate them.
80   // During project load the joined layers possibly do not exist yet so the connection will not be created,
81   // but then QgsProject makes sure to call createJoinCaches() which will do the connection.
82   // Unique connection makes sure we do not respond to one layer's update more times (in case of multiple join)
83   if ( QgsVectorLayer *vl = joinInfo.joinLayer() )
84   {
85     connectJoinedLayer( vl );
86   }
87 
88   mLayer->updateFields();
89 
90   //cache joined layer to virtual memory if specified by user
91   if ( joinInfo.isUsingMemoryCache() )
92   {
93     cacheJoinLayer( mVectorJoins.last() );
94   }
95 
96   locker.unlock();
97 
98   return true;
99 }
100 
101 
removeJoin(const QString & joinLayerId)102 bool QgsVectorLayerJoinBuffer::removeJoin( const QString &joinLayerId )
103 {
104   bool res = false;
105   {
106     QMutexLocker locker( &mMutex );
107     for ( int i = 0; i < mVectorJoins.size(); ++i )
108     {
109       if ( mVectorJoins.at( i ).joinLayerId() == joinLayerId )
110       {
111         if ( QgsVectorLayer *vl = mVectorJoins.at( i ).joinLayer() )
112         {
113           disconnect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields );
114         }
115 
116         mVectorJoins.removeAt( i );
117         res = true;
118       }
119     }
120   }
121 
122   emit joinedFieldsChanged();
123   return res;
124 }
125 
cacheJoinLayer(QgsVectorLayerJoinInfo & joinInfo)126 void QgsVectorLayerJoinBuffer::cacheJoinLayer( QgsVectorLayerJoinInfo &joinInfo )
127 {
128   //memory cache not required or already done
129   if ( !joinInfo.isUsingMemoryCache() || !joinInfo.cacheDirty )
130   {
131     return;
132   }
133 
134   QgsVectorLayer *cacheLayer = joinInfo.joinLayer();
135   if ( cacheLayer )
136   {
137     int joinFieldIndex = cacheLayer->fields().indexFromName( joinInfo.joinFieldName() );
138 
139     if ( joinFieldIndex < 0 || joinFieldIndex >= cacheLayer->fields().count() )
140       return;
141 
142     joinInfo.cachedAttributes.clear();
143 
144     QgsFeatureRequest request;
145     request.setFlags( QgsFeatureRequest::NoGeometry );
146     // maybe user requested just a subset of layer's attributes
147     // so we do not have to cache everything
148     QVector<int> subsetIndices;
149     if ( joinInfo.hasSubset() )
150     {
151       const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( joinInfo );
152       subsetIndices = joinSubsetIndices( cacheLayer, subsetNames );
153 
154       // we need just subset of attributes - but make sure to include join field name
155       QgsAttributeList cacheLayerAttrs = subsetIndices.toList();
156       if ( !cacheLayerAttrs.contains( joinFieldIndex ) )
157         cacheLayerAttrs.append( joinFieldIndex );
158       request.setSubsetOfAttributes( cacheLayerAttrs );
159     }
160 
161     QgsFeatureIterator fit = cacheLayer->getFeatures( request );
162     QgsFeature f;
163     while ( fit.nextFeature( f ) )
164     {
165       QgsAttributes attrs = f.attributes();
166       QString key = attrs.at( joinFieldIndex ).toString();
167       if ( joinInfo.hasSubset() )
168       {
169         QgsAttributes subsetAttrs( subsetIndices.count() );
170         for ( int i = 0; i < subsetIndices.count(); ++i )
171           subsetAttrs[i] = attrs.at( subsetIndices.at( i ) );
172         joinInfo.cachedAttributes.insert( key, subsetAttrs );
173       }
174       else
175       {
176         QgsAttributes attributesCache;
177         for ( int i = 0; i < attrs.size(); i++ )
178         {
179           if ( i == joinFieldIndex )
180             continue;
181 
182           QString joinInfoPrefix = joinInfo.prefix();
183           if ( joinInfoPrefix.isNull() ) // Default prefix 'layerName_' used
184             joinInfoPrefix = QString( "%1_" ).arg( cacheLayer->name() );
185 
186           // Joined field name
187           const QString joinFieldName = joinInfoPrefix + cacheLayer->fields().names().at( i );
188 
189           // Check for name collisions
190           int fieldIndex = mLayer->fields().indexFromName( joinFieldName );
191           if ( fieldIndex >= 0
192                && mLayer->fields().fieldOrigin( fieldIndex ) != QgsFields::OriginJoin )
193             continue;
194 
195           attributesCache.append( attrs.at( i ) );
196         }
197         joinInfo.cachedAttributes.insert( key, attributesCache );
198       }
199     }
200     joinInfo.cacheDirty = false;
201   }
202 }
203 
204 
joinSubsetIndices(QgsVectorLayer * joinLayer,const QStringList & joinFieldsSubset)205 QVector<int> QgsVectorLayerJoinBuffer::joinSubsetIndices( QgsVectorLayer *joinLayer, const QStringList &joinFieldsSubset )
206 {
207   return joinSubsetIndices( joinLayer->fields(), joinFieldsSubset );
208 }
209 
joinSubsetIndices(const QgsFields & joinLayerFields,const QStringList & joinFieldsSubset)210 QVector<int> QgsVectorLayerJoinBuffer::joinSubsetIndices( const QgsFields &joinLayerFields, const QStringList &joinFieldsSubset )
211 {
212   QVector<int> subsetIndices;
213   for ( int i = 0; i < joinFieldsSubset.count(); ++i )
214   {
215     QString joinedFieldName = joinFieldsSubset.at( i );
216     int index = joinLayerFields.lookupField( joinedFieldName );
217     if ( index != -1 )
218     {
219       subsetIndices.append( index );
220     }
221     else
222     {
223       QgsDebugMsg( "Join layer subset field not found: " + joinedFieldName );
224     }
225   }
226 
227   return subsetIndices;
228 }
229 
updateFields(QgsFields & fields)230 void QgsVectorLayerJoinBuffer::updateFields( QgsFields &fields )
231 {
232   QString prefix;
233 
234   QList< QgsVectorLayerJoinInfo>::const_iterator joinIt = mVectorJoins.constBegin();
235   for ( int joinIdx = 0; joinIt != mVectorJoins.constEnd(); ++joinIt, ++joinIdx )
236   {
237     QgsVectorLayer *joinLayer = joinIt->joinLayer();
238     if ( !joinLayer )
239     {
240       continue;
241     }
242 
243     const QgsFields &joinFields = joinLayer->fields();
244     QString joinFieldName = joinIt->joinFieldName();
245 
246     QSet<QString> subset;
247     if ( joinIt->hasSubset() )
248     {
249       const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *joinIt );
250       subset = qgis::listToSet( subsetNames );
251     }
252 
253     if ( joinIt->prefix().isNull() )
254     {
255       prefix = joinLayer->name() + '_';
256     }
257     else
258     {
259       prefix = joinIt->prefix();
260     }
261 
262     for ( int idx = 0; idx < joinFields.count(); ++idx )
263     {
264       // if using just a subset of fields, filter some of them out
265       if ( joinIt->hasSubset() && !subset.contains( joinFields.at( idx ).name() ) )
266         continue;
267 
268       //skip the join field to avoid double field names (fields often have the same name)
269       // when using subset of field, use all the selected fields
270       if ( joinIt->hasSubset() || joinFields.at( idx ).name() != joinFieldName )
271       {
272         QgsField f = joinFields.at( idx );
273         f.setName( prefix + f.name() );
274         fields.append( f, QgsFields::OriginJoin, idx + ( joinIdx * 1000 ) );
275       }
276     }
277   }
278 }
279 
createJoinCaches()280 void QgsVectorLayerJoinBuffer::createJoinCaches()
281 {
282   QMutexLocker locker( &mMutex );
283   QList< QgsVectorLayerJoinInfo >::iterator joinIt = mVectorJoins.begin();
284   for ( ; joinIt != mVectorJoins.end(); ++joinIt )
285   {
286     if ( joinIt->isUsingMemoryCache() && joinIt->cacheDirty )
287       cacheJoinLayer( *joinIt );
288   }
289 }
290 
291 
writeXml(QDomNode & layer_node,QDomDocument & document) const292 void QgsVectorLayerJoinBuffer::writeXml( QDomNode &layer_node, QDomDocument &document ) const
293 {
294   QDomElement vectorJoinsElem = document.createElement( QStringLiteral( "vectorjoins" ) );
295   layer_node.appendChild( vectorJoinsElem );
296   QList< QgsVectorLayerJoinInfo >::const_iterator joinIt = mVectorJoins.constBegin();
297   for ( ; joinIt != mVectorJoins.constEnd(); ++joinIt )
298   {
299     if ( isAuxiliaryJoin( *joinIt ) )
300       continue;
301 
302     QDomElement joinElem = document.createElement( QStringLiteral( "join" ) );
303 
304     joinElem.setAttribute( QStringLiteral( "targetFieldName" ), joinIt->targetFieldName() );
305 
306     joinElem.setAttribute( QStringLiteral( "joinLayerId" ), joinIt->joinLayerId() );
307     joinElem.setAttribute( QStringLiteral( "joinFieldName" ), joinIt->joinFieldName() );
308 
309     joinElem.setAttribute( QStringLiteral( "memoryCache" ), joinIt->isUsingMemoryCache() );
310     joinElem.setAttribute( QStringLiteral( "dynamicForm" ), joinIt->isDynamicFormEnabled() );
311     joinElem.setAttribute( QStringLiteral( "editable" ), joinIt->isEditable() );
312     joinElem.setAttribute( QStringLiteral( "upsertOnEdit" ), joinIt->hasUpsertOnEdit() );
313     joinElem.setAttribute( QStringLiteral( "cascadedDelete" ), joinIt->hasCascadedDelete() );
314 
315     if ( joinIt->hasSubset() )
316     {
317       QDomElement subsetElem = document.createElement( QStringLiteral( "joinFieldsSubset" ) );
318       const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *joinIt );
319 
320       const auto constSubsetNames = subsetNames;
321       for ( const QString &fieldName : constSubsetNames )
322       {
323         QDomElement fieldElem = document.createElement( QStringLiteral( "field" ) );
324         fieldElem.setAttribute( QStringLiteral( "name" ), fieldName );
325         subsetElem.appendChild( fieldElem );
326       }
327 
328       joinElem.appendChild( subsetElem );
329     }
330 
331     if ( !joinIt->prefix().isNull() )
332     {
333       joinElem.setAttribute( QStringLiteral( "customPrefix" ), joinIt->prefix() );
334       joinElem.setAttribute( QStringLiteral( "hasCustomPrefix" ), 1 );
335     }
336 
337     vectorJoinsElem.appendChild( joinElem );
338   }
339 }
340 
readXml(const QDomNode & layer_node)341 void QgsVectorLayerJoinBuffer::readXml( const QDomNode &layer_node )
342 {
343   mVectorJoins.clear();
344   QDomElement vectorJoinsElem = layer_node.firstChildElement( QStringLiteral( "vectorjoins" ) );
345   if ( !vectorJoinsElem.isNull() )
346   {
347     QDomNodeList joinList = vectorJoinsElem.elementsByTagName( QStringLiteral( "join" ) );
348     for ( int i = 0; i < joinList.size(); ++i )
349     {
350       QDomElement infoElem = joinList.at( i ).toElement();
351       QgsVectorLayerJoinInfo info;
352       info.setJoinFieldName( infoElem.attribute( QStringLiteral( "joinFieldName" ) ) );
353       // read layer ID - to turn it into layer object, caller will need to call resolveReferences() later
354       info.setJoinLayerId( infoElem.attribute( QStringLiteral( "joinLayerId" ) ) );
355       info.setTargetFieldName( infoElem.attribute( QStringLiteral( "targetFieldName" ) ) );
356       info.setUsingMemoryCache( infoElem.attribute( QStringLiteral( "memoryCache" ) ).toInt() );
357       info.setDynamicFormEnabled( infoElem.attribute( QStringLiteral( "dynamicForm" ) ).toInt() );
358       info.setEditable( infoElem.attribute( QStringLiteral( "editable" ) ).toInt() );
359       info.setUpsertOnEdit( infoElem.attribute( QStringLiteral( "upsertOnEdit" ) ).toInt() );
360       info.setCascadedDelete( infoElem.attribute( QStringLiteral( "cascadedDelete" ) ).toInt() );
361 
362       QDomElement subsetElem = infoElem.firstChildElement( QStringLiteral( "joinFieldsSubset" ) );
363       if ( !subsetElem.isNull() )
364       {
365         QStringList *fieldNames = new QStringList;
366         QDomNodeList fieldNodes = infoElem.elementsByTagName( QStringLiteral( "field" ) );
367         fieldNames->reserve( fieldNodes.count() );
368         for ( int i = 0; i < fieldNodes.count(); ++i )
369           *fieldNames << fieldNodes.at( i ).toElement().attribute( QStringLiteral( "name" ) );
370         info.setJoinFieldNamesSubset( fieldNames );
371       }
372 
373       if ( infoElem.attribute( QStringLiteral( "hasCustomPrefix" ) ).toInt() )
374         info.setPrefix( infoElem.attribute( QStringLiteral( "customPrefix" ) ) );
375       else
376         info.setPrefix( QString() );
377 
378       addJoin( info );
379     }
380   }
381 }
382 
resolveReferences(QgsProject * project)383 void QgsVectorLayerJoinBuffer::resolveReferences( QgsProject *project )
384 {
385   bool resolved = false;
386   for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
387   {
388     if ( it->joinLayer() )
389       continue;  // already resolved
390 
391     if ( QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( project->mapLayer( it->joinLayerId() ) ) )
392     {
393       it->setJoinLayer( joinedLayer );
394       connectJoinedLayer( joinedLayer );
395       resolved = true;
396     }
397   }
398 
399   if ( resolved )
400     emit joinedFieldsChanged();
401 }
402 
joinedFieldsOffset(const QgsVectorLayerJoinInfo * info,const QgsFields & fields)403 int QgsVectorLayerJoinBuffer::joinedFieldsOffset( const QgsVectorLayerJoinInfo *info, const QgsFields &fields )
404 {
405   if ( !info )
406     return -1;
407 
408   int joinIndex = mVectorJoins.indexOf( *info );
409   if ( joinIndex == -1 )
410     return -1;
411 
412   for ( int i = 0; i < fields.count(); ++i )
413   {
414     if ( fields.fieldOrigin( i ) != QgsFields::OriginJoin )
415       continue;
416 
417     if ( fields.fieldOriginIndex( i ) / 1000 == joinIndex )
418       return i;
419   }
420   return -1;
421 }
422 
joinForFieldIndex(int index,const QgsFields & fields,int & sourceFieldIndex) const423 const QgsVectorLayerJoinInfo *QgsVectorLayerJoinBuffer::joinForFieldIndex( int index, const QgsFields &fields, int &sourceFieldIndex ) const
424 {
425   if ( fields.fieldOrigin( index ) != QgsFields::OriginJoin )
426     return nullptr;
427 
428   int originIndex = fields.fieldOriginIndex( index );
429   int sourceJoinIndex = originIndex / 1000;
430   sourceFieldIndex = originIndex % 1000;
431 
432   if ( sourceJoinIndex < 0 || sourceJoinIndex >= mVectorJoins.count() )
433     return nullptr;
434 
435   return &( mVectorJoins[sourceJoinIndex] );
436 }
437 
joinsWhereFieldIsId(const QgsField & field) const438 QList<const QgsVectorLayerJoinInfo *> QgsVectorLayerJoinBuffer::joinsWhereFieldIsId( const QgsField &field ) const
439 {
440   QList<const QgsVectorLayerJoinInfo *> infos;
441 
442   const auto constMVectorJoins = mVectorJoins;
443   for ( const QgsVectorLayerJoinInfo &info : constMVectorJoins )
444   {
445     if ( infos.contains( &info ) )
446       continue;
447 
448     if ( info.targetFieldName() == field.name() )
449       infos.append( &info );
450   }
451 
452   return infos;
453 }
454 
joinedFeatureOf(const QgsVectorLayerJoinInfo * info,const QgsFeature & feature) const455 QgsFeature QgsVectorLayerJoinBuffer::joinedFeatureOf( const QgsVectorLayerJoinInfo *info, const QgsFeature &feature ) const
456 {
457   QgsFeature joinedFeature;
458 
459   if ( info->joinLayer() )
460   {
461     joinedFeature.initAttributes( info->joinLayer()->fields().count() );
462     joinedFeature.setFields( info->joinLayer()->fields() );
463 
464     QString joinFieldName = info->joinFieldName();
465     const QVariant targetValue = feature.attribute( info->targetFieldName() );
466     QString filter = QgsExpression::createFieldEqualityExpression( joinFieldName, targetValue );
467 
468     QgsFeatureRequest request;
469     request.setFilterExpression( filter );
470     request.setLimit( 1 );
471 
472     QgsFeatureIterator it = info->joinLayer()->getFeatures( request );
473     it.nextFeature( joinedFeature );
474   }
475 
476   return joinedFeature;
477 }
478 
targetedFeatureOf(const QgsVectorLayerJoinInfo * info,const QgsFeature & feature) const479 QgsFeature QgsVectorLayerJoinBuffer::targetedFeatureOf( const QgsVectorLayerJoinInfo *info, const QgsFeature &feature ) const
480 {
481   QgsFeature targetedFeature;
482 
483   if ( info->joinLayer() )
484   {
485     const QVariant targetValue = feature.attribute( info->joinFieldName() );
486     const QString filter = QgsExpression::createFieldEqualityExpression( info->targetFieldName(), targetValue );
487 
488     QgsFeatureRequest request;
489     request.setFilterExpression( filter );
490     request.setLimit( 1 );
491 
492     QgsFeatureIterator it = mLayer->getFeatures( request );
493     it.nextFeature( targetedFeature );
494   }
495 
496   return targetedFeature;
497 }
498 
clone() const499 QgsVectorLayerJoinBuffer *QgsVectorLayerJoinBuffer::clone() const
500 {
501   QgsVectorLayerJoinBuffer *cloned = new QgsVectorLayerJoinBuffer( mLayer );
502   cloned->mVectorJoins = mVectorJoins;
503   return cloned;
504 }
505 
joinedLayerUpdatedFields()506 void QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields()
507 {
508   // TODO - check - this whole method is probably not needed anymore,
509   // since the cache handling is covered by joinedLayerModified()
510 
511   QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
512   Q_ASSERT( joinedLayer );
513 
514   // recache the joined layer
515   for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
516   {
517     if ( joinedLayer == it->joinLayer() )
518     {
519       it->cachedAttributes.clear();
520       cacheJoinLayer( *it );
521     }
522   }
523 
524   emit joinedFieldsChanged();
525 }
526 
joinedLayerModified()527 void QgsVectorLayerJoinBuffer::joinedLayerModified()
528 {
529   QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
530   Q_ASSERT( joinedLayer );
531 
532   // recache the joined layer
533   for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
534   {
535     if ( joinedLayer == it->joinLayer() )
536     {
537       it->cacheDirty = true;
538     }
539   }
540 }
541 
joinedLayerWillBeDeleted()542 void QgsVectorLayerJoinBuffer::joinedLayerWillBeDeleted()
543 {
544   QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
545   Q_ASSERT( joinedLayer );
546 
547   removeJoin( joinedLayer->id() );
548 }
549 
connectJoinedLayer(QgsVectorLayer * vl)550 void QgsVectorLayerJoinBuffer::connectJoinedLayer( QgsVectorLayer *vl )
551 {
552   connect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields, Qt::UniqueConnection );
553   connect( vl, &QgsVectorLayer::layerModified, this, &QgsVectorLayerJoinBuffer::joinedLayerModified, Qt::UniqueConnection );
554   connect( vl, &QgsVectorLayer::willBeDeleted, this, &QgsVectorLayerJoinBuffer::joinedLayerWillBeDeleted, Qt::UniqueConnection );
555 }
556 
addFeatures(QgsFeatureList & features,QgsFeatureSink::Flags)557 bool QgsVectorLayerJoinBuffer::addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags )
558 {
559   if ( !containsJoins() )
560     return false;
561 
562   // try to add/update a feature in each joined layer
563   const QgsVectorJoinList joins = vectorJoins();
564   for ( const QgsVectorLayerJoinInfo &info : joins )
565   {
566     QgsVectorLayer *joinLayer = info.joinLayer();
567 
568     if ( joinLayer && joinLayer->isEditable() && info.isEditable() && info.hasUpsertOnEdit() )
569     {
570       QgsFeatureList joinFeatures;
571 
572       for ( const QgsFeature &feature : std::as_const( features ) )
573       {
574         const QgsFeature joinFeature = info.extractJoinedFeature( feature );
575 
576         // we don't want to add a new feature in joined layer when the id
577         // column value yet exist, we just want to update the existing one
578         const QVariant idFieldValue = feature.attribute( info.targetFieldName() );
579         const QString filter = QgsExpression::createFieldEqualityExpression( info.joinFieldName(), idFieldValue.toString() );
580 
581         QgsFeatureRequest request;
582         request.setFlags( QgsFeatureRequest::NoGeometry );
583         request.setNoAttributes();
584         request.setFilterExpression( filter );
585         request.setLimit( 1 );
586 
587         QgsFeatureIterator it = info.joinLayer()->getFeatures( request );
588         QgsFeature existingFeature;
589         it.nextFeature( existingFeature );
590 
591         if ( existingFeature.isValid() )
592         {
593           if ( info.hasSubset() )
594           {
595             const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( info );
596             const auto constSubsetNames = subsetNames;
597             for ( const QString &field : constSubsetNames )
598             {
599               QVariant newValue = joinFeature.attribute( field );
600               int fieldIndex = joinLayer->fields().indexOf( field );
601               joinLayer->changeAttributeValue( existingFeature.id(), fieldIndex, newValue );
602             }
603           }
604           else
605           {
606             const QgsFields joinFields = joinFeature.fields();
607             for ( const auto &field : joinFields )
608             {
609               QVariant newValue = joinFeature.attribute( field.name() );
610               int fieldIndex = joinLayer->fields().indexOf( field.name() );
611               joinLayer->changeAttributeValue( existingFeature.id(), fieldIndex, newValue );
612             }
613           }
614         }
615         else
616         {
617           // joined feature is added only if one of its field is not null
618           bool notNullFields = false;
619           const QgsFields joinFields = joinFeature.fields();
620           for ( const auto &field : joinFields )
621           {
622             if ( field.name() == info.joinFieldName() )
623               continue;
624 
625             if ( !joinFeature.attribute( field.name() ).isNull() )
626             {
627               notNullFields = true;
628               break;
629             }
630           }
631 
632           if ( notNullFields )
633             joinFeatures << joinFeature;
634         }
635       }
636 
637       joinLayer->addFeatures( joinFeatures );
638     }
639   }
640 
641   return true;
642 }
643 
changeAttributeValue(QgsFeatureId fid,int field,const QVariant & newValue,const QVariant & oldValue)644 bool QgsVectorLayerJoinBuffer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue )
645 {
646   if ( mLayer->fields().fieldOrigin( field ) != QgsFields::OriginJoin )
647     return false;
648 
649   int srcFieldIndex;
650   const QgsVectorLayerJoinInfo *info = joinForFieldIndex( field, mLayer->fields(), srcFieldIndex );
651   if ( info && info->joinLayer() && info->isEditable() )
652   {
653     QgsFeature feature = mLayer->getFeature( fid );
654 
655     if ( !feature.isValid() )
656       return false;
657 
658     const QgsFeature joinFeature = joinedFeatureOf( info, feature );
659 
660     if ( joinFeature.isValid() )
661       return info->joinLayer()->changeAttributeValue( joinFeature.id(), srcFieldIndex, newValue, oldValue );
662     else
663     {
664       feature.setAttribute( field, newValue );
665       return addFeatures( QgsFeatureList() << feature );
666     }
667   }
668   else
669     return false;
670 }
671 
changeAttributeValues(QgsFeatureId fid,const QgsAttributeMap & newValues,const QgsAttributeMap & oldValues)672 bool QgsVectorLayerJoinBuffer::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues )
673 {
674   bool success = true;
675 
676   for ( auto it = newValues.constBegin(); it != newValues.constEnd(); ++it )
677   {
678     const int field = it.key();
679     const QVariant newValue = it.value();
680     QVariant oldValue;
681 
682     if ( oldValues.contains( field ) )
683       oldValue = oldValues[field];
684 
685     success &= changeAttributeValue( fid, field, newValue, oldValue );
686   }
687 
688   return success;
689 }
690 
deleteFeature(QgsFeatureId fid,QgsVectorLayer::DeleteContext * context) const691 bool QgsVectorLayerJoinBuffer::deleteFeature( QgsFeatureId fid, QgsVectorLayer::DeleteContext *context ) const
692 {
693   return deleteFeatures( QgsFeatureIds() << fid, context );
694 }
695 
deleteFeatures(const QgsFeatureIds & fids,QgsVectorLayer::DeleteContext * context) const696 bool QgsVectorLayerJoinBuffer::deleteFeatures( const QgsFeatureIds &fids, QgsVectorLayer::DeleteContext *context ) const
697 {
698   if ( !containsJoins() )
699     return false;
700 
701   const auto constFids = fids;
702   for ( const QgsFeatureId &fid : constFids )
703   {
704     const auto constVectorJoins = vectorJoins();
705     for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
706     {
707       if ( info.isEditable() && info.hasCascadedDelete() )
708       {
709         const QgsFeature joinFeature = joinedFeatureOf( &info, mLayer->getFeature( fid ) );
710         if ( joinFeature.isValid() )
711           info.joinLayer()->deleteFeature( joinFeature.id(), context );
712       }
713     }
714   }
715 
716   return true;
717 }
718 
isAuxiliaryJoin(const QgsVectorLayerJoinInfo & info) const719 bool QgsVectorLayerJoinBuffer::isAuxiliaryJoin( const QgsVectorLayerJoinInfo &info ) const
720 {
721   const QgsAuxiliaryLayer *al = mLayer->auxiliaryLayer();
722 
723   return al && al->id() == info.joinLayerId();
724 }
725