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