QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsvectorlayerjoinbuffer.cpp
Go to the documentation of this file.
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
19
20#include "qgsfeatureiterator.h"
21#include "qgslogger.h"
22#include "qgsproject.h"
24#include "qgsauxiliarystorage.h"
25
26#include <QDomElement>
27
29 : mLayer( layer )
30{
31}
32
33static 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
45static 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
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 locker.unlock();
89 mLayer->updateFields();
90 locker.relock();
91
92 //cache joined layer to virtual memory if specified by user
93 if ( joinInfo.isUsingMemoryCache() )
94 {
95 cacheJoinLayer( mVectorJoins.last() );
96 }
97
98 locker.unlock();
99
100 return true;
101}
102
103
104bool QgsVectorLayerJoinBuffer::removeJoin( const QString &joinLayerId )
105{
106 bool res = false;
107 {
108 QMutexLocker locker( &mMutex );
109 for ( int i = 0; i < mVectorJoins.size(); ++i )
110 {
111 if ( mVectorJoins.at( i ).joinLayerId() == joinLayerId )
112 {
113 if ( QgsVectorLayer *vl = mVectorJoins.at( i ).joinLayer() )
114 {
115 disconnect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields );
116 }
117
118 mVectorJoins.removeAt( i );
119 res = true;
120 }
121 }
122 }
123
124 emit joinedFieldsChanged();
125 return res;
126}
127
128void QgsVectorLayerJoinBuffer::cacheJoinLayer( QgsVectorLayerJoinInfo &joinInfo )
129{
130 //memory cache not required or already done
131 if ( !joinInfo.isUsingMemoryCache() || !joinInfo.cacheDirty )
132 {
133 return;
134 }
135
136 QgsVectorLayer *cacheLayer = joinInfo.joinLayer();
137 if ( cacheLayer )
138 {
139 int joinFieldIndex = cacheLayer->fields().indexFromName( joinInfo.joinFieldName() );
140
141 if ( joinFieldIndex < 0 || joinFieldIndex >= cacheLayer->fields().count() )
142 return;
143
144 joinInfo.cachedAttributes.clear();
145
146 QgsFeatureRequest request;
148 // maybe user requested just a subset of layer's attributes
149 // so we do not have to cache everything
150 QVector<int> subsetIndices;
151 if ( joinInfo.hasSubset() )
152 {
153 const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( joinInfo );
154 subsetIndices = joinSubsetIndices( cacheLayer, subsetNames );
155
156 // we need just subset of attributes - but make sure to include join field name
157 QgsAttributeList cacheLayerAttrs = subsetIndices.toList();
158 if ( !cacheLayerAttrs.contains( joinFieldIndex ) )
159 cacheLayerAttrs.append( joinFieldIndex );
160 request.setSubsetOfAttributes( cacheLayerAttrs );
161 }
162
163 QgsFeatureIterator fit = cacheLayer->getFeatures( request );
164 QgsFeature f;
165 while ( fit.nextFeature( f ) )
166 {
167 QgsAttributes attrs = f.attributes();
168 QString key = attrs.at( joinFieldIndex ).toString();
169 if ( joinInfo.hasSubset() )
170 {
171 QgsAttributes subsetAttrs( subsetIndices.count() );
172 for ( int i = 0; i < subsetIndices.count(); ++i )
173 subsetAttrs[i] = attrs.at( subsetIndices.at( i ) );
174 joinInfo.cachedAttributes.insert( key, subsetAttrs );
175 }
176 else
177 {
178 QgsAttributes attributesCache;
179 for ( int i = 0; i < attrs.size(); i++ )
180 {
181 if ( i == joinFieldIndex )
182 continue;
183
184 QString joinInfoPrefix = joinInfo.prefix();
185 if ( joinInfoPrefix.isNull() ) // Default prefix 'layerName_' used
186 joinInfoPrefix = QString( "%1_" ).arg( cacheLayer->name() );
187
188 // Joined field name
189 const QString joinFieldName = joinInfoPrefix + cacheLayer->fields().names().at( i );
190
191 // Check for name collisions
192 int fieldIndex = mLayer->fields().indexFromName( joinFieldName );
193 if ( fieldIndex >= 0
194 && mLayer->fields().fieldOrigin( fieldIndex ) != QgsFields::OriginJoin )
195 continue;
196
197 attributesCache.append( attrs.at( i ) );
198 }
199 joinInfo.cachedAttributes.insert( key, attributesCache );
200 }
201 }
202 joinInfo.cacheDirty = false;
203 }
204}
205
206
207QVector<int> QgsVectorLayerJoinBuffer::joinSubsetIndices( QgsVectorLayer *joinLayer, const QStringList &joinFieldsSubset )
208{
209 return joinSubsetIndices( joinLayer->fields(), joinFieldsSubset );
210}
211
212QVector<int> QgsVectorLayerJoinBuffer::joinSubsetIndices( const QgsFields &joinLayerFields, const QStringList &joinFieldsSubset )
213{
214 QVector<int> subsetIndices;
215 for ( int i = 0; i < joinFieldsSubset.count(); ++i )
216 {
217 QString joinedFieldName = joinFieldsSubset.at( i );
218 int index = joinLayerFields.lookupField( joinedFieldName );
219 if ( index != -1 )
220 {
221 subsetIndices.append( index );
222 }
223 else
224 {
225 QgsDebugMsg( "Join layer subset field not found: " + joinedFieldName );
226 }
227 }
228
229 return subsetIndices;
230}
231
233{
234 QString prefix;
235
236 QList< QgsVectorLayerJoinInfo>::const_iterator joinIt = mVectorJoins.constBegin();
237 for ( int joinIdx = 0; joinIt != mVectorJoins.constEnd(); ++joinIt, ++joinIdx )
238 {
239 QgsVectorLayer *joinLayer = joinIt->joinLayer();
240 if ( !joinLayer )
241 {
242 continue;
243 }
244
245 const QgsFields &joinFields = joinLayer->fields();
246 QString joinFieldName = joinIt->joinFieldName();
247
248 QSet<QString> subset;
249 if ( joinIt->hasSubset() )
250 {
251 const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *joinIt );
252 subset = qgis::listToSet( subsetNames );
253 }
254
255 if ( joinIt->prefix().isNull() )
256 {
257 prefix = joinLayer->name() + '_';
258 }
259 else
260 {
261 prefix = joinIt->prefix();
262 }
263
264 for ( int idx = 0; idx < joinFields.count(); ++idx )
265 {
266 // if using just a subset of fields, filter some of them out
267 if ( joinIt->hasSubset() && !subset.contains( joinFields.at( idx ).name() ) )
268 continue;
269
270 //skip the join field to avoid double field names (fields often have the same name)
271 // when using subset of field, use all the selected fields
272 if ( joinIt->hasSubset() || joinFields.at( idx ).name() != joinFieldName )
273 {
274 QgsField f = joinFields.at( idx );
275 f.setName( prefix + f.name() );
276 fields.append( f, QgsFields::OriginJoin, idx + ( joinIdx * 1000 ) );
277 }
278 }
279 }
280}
281
283{
284 QMutexLocker locker( &mMutex );
285 QList< QgsVectorLayerJoinInfo >::iterator joinIt = mVectorJoins.begin();
286 for ( ; joinIt != mVectorJoins.end(); ++joinIt )
287 {
288 if ( joinIt->isUsingMemoryCache() && joinIt->cacheDirty )
289 cacheJoinLayer( *joinIt );
290 }
291}
292
293
294void QgsVectorLayerJoinBuffer::writeXml( QDomNode &layer_node, QDomDocument &document ) const
295{
296 QDomElement vectorJoinsElem = document.createElement( QStringLiteral( "vectorjoins" ) );
297 layer_node.appendChild( vectorJoinsElem );
298 QList< QgsVectorLayerJoinInfo >::const_iterator joinIt = mVectorJoins.constBegin();
299 for ( ; joinIt != mVectorJoins.constEnd(); ++joinIt )
300 {
301 if ( isAuxiliaryJoin( *joinIt ) )
302 continue;
303
304 QDomElement joinElem = document.createElement( QStringLiteral( "join" ) );
305
306 joinElem.setAttribute( QStringLiteral( "targetFieldName" ), joinIt->targetFieldName() );
307
308 joinElem.setAttribute( QStringLiteral( "joinLayerId" ), joinIt->joinLayerId() );
309 joinElem.setAttribute( QStringLiteral( "joinFieldName" ), joinIt->joinFieldName() );
310
311 joinElem.setAttribute( QStringLiteral( "memoryCache" ), joinIt->isUsingMemoryCache() );
312 joinElem.setAttribute( QStringLiteral( "dynamicForm" ), joinIt->isDynamicFormEnabled() );
313 joinElem.setAttribute( QStringLiteral( "editable" ), joinIt->isEditable() );
314 joinElem.setAttribute( QStringLiteral( "upsertOnEdit" ), joinIt->hasUpsertOnEdit() );
315 joinElem.setAttribute( QStringLiteral( "cascadedDelete" ), joinIt->hasCascadedDelete() );
316
317 if ( joinIt->hasSubset() )
318 {
319 QDomElement subsetElem = document.createElement( QStringLiteral( "joinFieldsSubset" ) );
320 const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( *joinIt );
321
322 const auto constSubsetNames = subsetNames;
323 for ( const QString &fieldName : constSubsetNames )
324 {
325 QDomElement fieldElem = document.createElement( QStringLiteral( "field" ) );
326 fieldElem.setAttribute( QStringLiteral( "name" ), fieldName );
327 subsetElem.appendChild( fieldElem );
328 }
329
330 joinElem.appendChild( subsetElem );
331 }
332
333 if ( !joinIt->prefix().isNull() )
334 {
335 joinElem.setAttribute( QStringLiteral( "customPrefix" ), joinIt->prefix() );
336 joinElem.setAttribute( QStringLiteral( "hasCustomPrefix" ), 1 );
337 }
338
339 vectorJoinsElem.appendChild( joinElem );
340 }
341}
342
343void QgsVectorLayerJoinBuffer::readXml( const QDomNode &layer_node )
344{
345 mVectorJoins.clear();
346 QDomElement vectorJoinsElem = layer_node.firstChildElement( QStringLiteral( "vectorjoins" ) );
347 if ( !vectorJoinsElem.isNull() )
348 {
349 QDomNodeList joinList = vectorJoinsElem.elementsByTagName( QStringLiteral( "join" ) );
350 for ( int i = 0; i < joinList.size(); ++i )
351 {
352 QDomElement infoElem = joinList.at( i ).toElement();
354 info.setJoinFieldName( infoElem.attribute( QStringLiteral( "joinFieldName" ) ) );
355 // read layer ID - to turn it into layer object, caller will need to call resolveReferences() later
356 info.setJoinLayerId( infoElem.attribute( QStringLiteral( "joinLayerId" ) ) );
357 info.setTargetFieldName( infoElem.attribute( QStringLiteral( "targetFieldName" ) ) );
358 info.setUsingMemoryCache( infoElem.attribute( QStringLiteral( "memoryCache" ) ).toInt() );
359 info.setDynamicFormEnabled( infoElem.attribute( QStringLiteral( "dynamicForm" ) ).toInt() );
360 info.setEditable( infoElem.attribute( QStringLiteral( "editable" ) ).toInt() );
361 info.setUpsertOnEdit( infoElem.attribute( QStringLiteral( "upsertOnEdit" ) ).toInt() );
362 info.setCascadedDelete( infoElem.attribute( QStringLiteral( "cascadedDelete" ) ).toInt() );
363
364 QDomElement subsetElem = infoElem.firstChildElement( QStringLiteral( "joinFieldsSubset" ) );
365 if ( !subsetElem.isNull() )
366 {
367 QStringList *fieldNames = new QStringList;
368 QDomNodeList fieldNodes = infoElem.elementsByTagName( QStringLiteral( "field" ) );
369 fieldNames->reserve( fieldNodes.count() );
370 for ( int i = 0; i < fieldNodes.count(); ++i )
371 *fieldNames << fieldNodes.at( i ).toElement().attribute( QStringLiteral( "name" ) );
372 info.setJoinFieldNamesSubset( fieldNames );
373 }
374
375 if ( infoElem.attribute( QStringLiteral( "hasCustomPrefix" ) ).toInt() )
376 info.setPrefix( infoElem.attribute( QStringLiteral( "customPrefix" ) ) );
377 else
378 info.setPrefix( QString() );
379
380 addJoin( info );
381 }
382 }
383}
384
386{
387 bool resolved = false;
388 for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
389 {
390 if ( it->joinLayer() )
391 continue; // already resolved
392
393 if ( QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( project->mapLayer( it->joinLayerId() ) ) )
394 {
395 it->setJoinLayer( joinedLayer );
396 connectJoinedLayer( joinedLayer );
397 resolved = true;
398 }
399 }
400
401 if ( resolved )
402 emit joinedFieldsChanged();
403}
404
406{
407 if ( !info )
408 return -1;
409
410 int joinIndex = mVectorJoins.indexOf( *info );
411 if ( joinIndex == -1 )
412 return -1;
413
414 for ( int i = 0; i < fields.count(); ++i )
415 {
416 if ( fields.fieldOrigin( i ) != QgsFields::OriginJoin )
417 continue;
418
419 if ( fields.fieldOriginIndex( i ) / 1000 == joinIndex )
420 return i;
421 }
422 return -1;
423}
424
425const QgsVectorLayerJoinInfo *QgsVectorLayerJoinBuffer::joinForFieldIndex( int index, const QgsFields &fields, int &sourceFieldIndex ) const
426{
427 if ( fields.fieldOrigin( index ) != QgsFields::OriginJoin )
428 return nullptr;
429
430 int originIndex = fields.fieldOriginIndex( index );
431 int sourceJoinIndex = originIndex / 1000;
432 sourceFieldIndex = originIndex % 1000;
433
434 if ( sourceJoinIndex < 0 || sourceJoinIndex >= mVectorJoins.count() )
435 return nullptr;
436
437 return &( mVectorJoins[sourceJoinIndex] );
438}
439
440QList<const QgsVectorLayerJoinInfo *> QgsVectorLayerJoinBuffer::joinsWhereFieldIsId( const QgsField &field ) const
441{
442 QList<const QgsVectorLayerJoinInfo *> infos;
443
444 const auto constMVectorJoins = mVectorJoins;
445 for ( const QgsVectorLayerJoinInfo &info : constMVectorJoins )
446 {
447 if ( infos.contains( &info ) )
448 continue;
449
450 if ( info.targetFieldName() == field.name() )
451 infos.append( &info );
452 }
453
454 return infos;
455}
456
458{
459 QgsFeature joinedFeature;
460
461 if ( info->joinLayer() )
462 {
463 joinedFeature.initAttributes( info->joinLayer()->fields().count() );
464 joinedFeature.setFields( info->joinLayer()->fields() );
465
466 QString joinFieldName = info->joinFieldName();
467 const QVariant targetValue = feature.attribute( info->targetFieldName() );
468 QString filter = QgsExpression::createFieldEqualityExpression( joinFieldName, targetValue );
469
470 QgsFeatureRequest request;
471 request.setFilterExpression( filter );
472 request.setLimit( 1 );
473
474 QgsFeatureIterator it = info->joinLayer()->getFeatures( request );
475 it.nextFeature( joinedFeature );
476 }
477
478 return joinedFeature;
479}
480
482{
483 QgsFeature targetedFeature;
484
485 if ( info->joinLayer() )
486 {
487 const QVariant targetValue = feature.attribute( info->joinFieldName() );
488 const QString filter = QgsExpression::createFieldEqualityExpression( info->targetFieldName(), targetValue );
489
490 QgsFeatureRequest request;
491 request.setFilterExpression( filter );
492 request.setLimit( 1 );
493
494 QgsFeatureIterator it = mLayer->getFeatures( request );
495 it.nextFeature( targetedFeature );
496 }
497
498 return targetedFeature;
499}
500
502{
504 cloned->mVectorJoins = mVectorJoins;
505 return cloned;
506}
507
508void QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields()
509{
510 // TODO - check - this whole method is probably not needed anymore,
511 // since the cache handling is covered by joinedLayerModified()
512
513 QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
514 Q_ASSERT( joinedLayer );
515
516 // recache the joined layer
517 for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
518 {
519 if ( joinedLayer == it->joinLayer() )
520 {
521 it->cachedAttributes.clear();
522 cacheJoinLayer( *it );
523 }
524 }
525
526 emit joinedFieldsChanged();
527}
528
529void QgsVectorLayerJoinBuffer::joinedLayerModified()
530{
531 QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
532 Q_ASSERT( joinedLayer );
533
534 // recache the joined layer
535 for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
536 {
537 if ( joinedLayer == it->joinLayer() )
538 {
539 it->cacheDirty = true;
540 }
541 }
542}
543
544void QgsVectorLayerJoinBuffer::joinedLayerWillBeDeleted()
545{
546 QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
547 Q_ASSERT( joinedLayer );
548
549 removeJoin( joinedLayer->id() );
550}
551
552void QgsVectorLayerJoinBuffer::connectJoinedLayer( QgsVectorLayer *vl )
553{
554 connect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields, Qt::UniqueConnection );
555 connect( vl, &QgsVectorLayer::layerModified, this, &QgsVectorLayerJoinBuffer::joinedLayerModified, Qt::UniqueConnection );
556 connect( vl, &QgsVectorLayer::willBeDeleted, this, &QgsVectorLayerJoinBuffer::joinedLayerWillBeDeleted, Qt::UniqueConnection );
557}
558
559bool QgsVectorLayerJoinBuffer::addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags )
560{
561 if ( !containsJoins() )
562 return false;
563
564 // try to add/update a feature in each joined layer
565 const QgsVectorJoinList joins = vectorJoins();
566 for ( const QgsVectorLayerJoinInfo &info : joins )
567 {
568 QgsVectorLayer *joinLayer = info.joinLayer();
569
570 if ( joinLayer && joinLayer->isEditable() && info.isEditable() && info.hasUpsertOnEdit() )
571 {
572 QgsFeatureList joinFeatures;
573
574 for ( const QgsFeature &feature : std::as_const( features ) )
575 {
576 const QgsFeature joinFeature = info.extractJoinedFeature( feature );
577
578 // we don't want to add a new feature in joined layer when the id
579 // column value yet exist, we just want to update the existing one
580 const QVariant idFieldValue = feature.attribute( info.targetFieldName() );
581 const QString filter = QgsExpression::createFieldEqualityExpression( info.joinFieldName(), idFieldValue.toString() );
582
583 QgsFeatureRequest request;
585 request.setNoAttributes();
586 request.setFilterExpression( filter );
587 request.setLimit( 1 );
588
589 QgsFeatureIterator it = info.joinLayer()->getFeatures( request );
590 QgsFeature existingFeature;
591 it.nextFeature( existingFeature );
592
593 if ( existingFeature.isValid() )
594 {
595 if ( info.hasSubset() )
596 {
597 const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( info );
598 const auto constSubsetNames = subsetNames;
599 for ( const QString &field : constSubsetNames )
600 {
601 QVariant newValue = joinFeature.attribute( field );
602 int fieldIndex = joinLayer->fields().indexOf( field );
603 joinLayer->changeAttributeValue( existingFeature.id(), fieldIndex, newValue );
604 }
605 }
606 else
607 {
608 const QgsFields joinFields = joinFeature.fields();
609 for ( const auto &field : joinFields )
610 {
611 QVariant newValue = joinFeature.attribute( field.name() );
612 int fieldIndex = joinLayer->fields().indexOf( field.name() );
613 joinLayer->changeAttributeValue( existingFeature.id(), fieldIndex, newValue );
614 }
615 }
616 }
617 else
618 {
619 // joined feature is added only if one of its field is not null
620 bool notNullFields = false;
621 const QgsFields joinFields = joinFeature.fields();
622 for ( const auto &field : joinFields )
623 {
624 if ( field.name() == info.joinFieldName() )
625 continue;
626
627 if ( !QgsVariantUtils::isNull( joinFeature.attribute( field.name() ) ) )
628 {
629 notNullFields = true;
630 break;
631 }
632 }
633
634 if ( notNullFields )
635 joinFeatures << joinFeature;
636 }
637 }
638
639 joinLayer->addFeatures( joinFeatures );
640 }
641 }
642
643 return true;
644}
645
646bool QgsVectorLayerJoinBuffer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue )
647{
648 if ( mLayer->fields().fieldOrigin( field ) != QgsFields::OriginJoin )
649 return false;
650
651 int srcFieldIndex;
652 const QgsVectorLayerJoinInfo *info = joinForFieldIndex( field, mLayer->fields(), srcFieldIndex );
653 if ( info && info->joinLayer() && info->isEditable() )
654 {
655 QgsFeature feature = mLayer->getFeature( fid );
656
657 if ( !feature.isValid() )
658 return false;
659
660 const QgsFeature joinFeature = joinedFeatureOf( info, feature );
661
662 if ( joinFeature.isValid() )
663 return info->joinLayer()->changeAttributeValue( joinFeature.id(), srcFieldIndex, newValue, oldValue );
664 else
665 {
666 feature.setAttribute( field, newValue );
667 return addFeatures( QgsFeatureList() << feature );
668 }
669 }
670 else
671 return false;
672}
673
675{
676 bool success = true;
677
678 for ( auto it = newValues.constBegin(); it != newValues.constEnd(); ++it )
679 {
680 const int field = it.key();
681 const QVariant newValue = it.value();
682 QVariant oldValue;
683
684 if ( oldValues.contains( field ) )
685 oldValue = oldValues[field];
686
687 success &= changeAttributeValue( fid, field, newValue, oldValue );
688 }
689
690 return success;
691}
692
694{
695 return deleteFeatures( QgsFeatureIds() << fid, context );
696}
697
699{
700 if ( !containsJoins() )
701 return false;
702
703 const auto constFids = fids;
704 for ( const QgsFeatureId &fid : constFids )
705 {
706 const auto constVectorJoins = vectorJoins();
707 for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
708 {
709 if ( info.isEditable() && info.hasCascadedDelete() )
710 {
711 const QgsFeature joinFeature = joinedFeatureOf( &info, mLayer->getFeature( fid ) );
712 if ( joinFeature.isValid() )
713 info.joinLayer()->deleteFeature( joinFeature.id(), context );
714 }
715 }
716 }
717
718 return true;
719}
720
722{
723 const QgsAuxiliaryLayer *al = mLayer->auxiliaryLayer();
724
725 return al && al->id() == info.joinLayerId();
726}
A vector of attributes.
Definition: qgsattributes.h:59
Class allowing to manage the auxiliary storage for a vector layer.
static QString createFieldEqualityExpression(const QString &fieldName, const QVariant &value, QVariant::Type fieldType=QVariant::Type::Invalid)
Create an expression allowing to evaluate if a field is equal to a value.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setLimit(long long limit)
Set the maximum number of features to request.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Definition: qgsfeature.cpp:265
QgsAttributes attributes
Definition: qgsfeature.h:65
QgsFields fields
Definition: qgsfeature.h:66
void initAttributes(int fieldCount)
Initialize this feature with the given number of fields.
Definition: qgsfeature.cpp:238
void setFields(const QgsFields &fields, bool initAttributes=false)
Assigns a field map with the feature to allow attribute access by attribute name.
Definition: qgsfeature.cpp:198
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:219
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:338
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString name
Definition: qgsfield.h:60
void setName(const QString &name)
Set the field name.
Definition: qgsfield.cpp:176
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
@ OriginJoin
Field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
Definition: qgsfields.h:52
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Definition: qgsfields.cpp:189
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
Definition: qgsfields.cpp:197
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
QStringList names() const
Returns a list with field names.
Definition: qgsfields.cpp:143
QString name
Definition: qgsmaplayer.h:76
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
void willBeDeleted()
Emitted in the destructor when the layer is about to be deleted, but it is still in a perfectly valid...
void layerModified()
Emitted when modifications has been done on layer.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:104
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
Manages joined fields for a vector layer.
void resolveReferences(QgsProject *project)
Resolves layer IDs of joined layers using given project's available layers.
QgsFeature targetedFeatureOf(const QgsVectorLayerJoinInfo *info, const QgsFeature &feature) const
Returns the targeted feature corresponding to the joined feature.
bool addJoin(const QgsVectorLayerJoinInfo &joinInfo)
Joins another vector layer to this layer.
void readXml(const QDomNode &layer_node)
Reads joins from project file.
QgsVectorLayerJoinBuffer(QgsVectorLayer *layer=nullptr)
void writeXml(QDomNode &layer_node, QDomDocument &document) const
Saves mVectorJoins to xml under the layer node.
int joinedFieldsOffset(const QgsVectorLayerJoinInfo *info, const QgsFields &fields)
Find out what is the first index of the join within fields.
const QgsVectorLayerJoinInfo * joinForFieldIndex(int index, const QgsFields &fields, int &sourceFieldIndex) const
Finds the vector join for a layer field index.
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant())
Changes attribute value in joined layers.
bool deleteFeatures(const QgsFeatureIds &fids, QgsVectorLayer::DeleteContext *context=nullptr) const
Deletes a list of features from joined layers.
QList< const QgsVectorLayerJoinInfo * > joinsWhereFieldIsId(const QgsField &field) const
Returns joins where the field of a target layer is considered as an id.
bool removeJoin(const QString &joinLayerId)
Removes a vector layer join.
bool containsJoins() const
Quick way to test if there is any join at all.
bool changeAttributeValues(QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues=QgsAttributeMap())
Changes attributes' values in joined layers.
bool addFeatures(QgsFeatureList &features, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) override
Adds a list of features in joined layers.
QgsVectorLayerJoinBuffer * clone() const
Create a copy of the join buffer.
void joinedFieldsChanged()
Emitted whenever the list of joined fields changes (e.g.
void createJoinCaches()
Calls cacheJoinLayer() for all vector joins.
void updateFields(QgsFields &fields)
Updates field map with joined attributes.
static QVector< int > joinSubsetIndices(QgsVectorLayer *joinLayer, const QStringList &joinFieldsSubset)
Returns a vector of indices for use in join based on field names from the layer.
bool deleteFeature(QgsFeatureId fid, QgsVectorLayer::DeleteContext *context=nullptr) const
Deletes a feature from joined layers.
QgsFeature joinedFeatureOf(const QgsVectorLayerJoinInfo *info, const QgsFeature &feature) const
Returns the joined feature corresponding to the feature.
const QgsVectorJoinList & vectorJoins() const
bool isAuxiliaryJoin(const QgsVectorLayerJoinInfo &info) const
Returns true if the join information is about auxiliary layer, false otherwise.
Defines left outer join from our vector layer to some other vector layer.
QStringList * joinFieldNamesSubset() const
Returns the subset of fields to be used from joined layer.
void setDynamicFormEnabled(bool enabled)
Sets whether the form has to be dynamically updated with joined fields when a feature is being create...
void setUsingMemoryCache(bool enabled)
Sets whether values from the joined layer should be cached in memory to speed up lookups.
void setJoinLayerId(const QString &layerId)
Sets ID of the joined layer. It will need to be overwritten by setJoinLayer() to a reference to real ...
void setEditable(bool enabled)
Sets whether the form of the target layer allows editing joined fields.
bool cacheDirty
True if the cached join attributes need to be updated.
bool isEditable() const
Returns whether joined fields may be edited through the form of the target layer.
void setCascadedDelete(bool enabled)
Sets whether a feature deleted on the target layer has to impact the joined layer by deleting the cor...
void setJoinFieldName(const QString &fieldName)
Sets name of the field of joined layer that will be used for join.
bool isUsingMemoryCache() const
Returns whether values from the joined layer should be cached in memory to speed up lookups.
QString prefix() const
Returns prefix of fields from the joined layer. If nullptr, joined layer's name will be used.
void setTargetFieldName(const QString &fieldName)
Sets name of the field of our layer that will be used for join.
QString joinFieldName() const
Returns name of the field of joined layer that will be used for join.
void setUpsertOnEdit(bool enabled)
Sets whether a feature created on the target layer has to impact the joined layer by creating a new f...
QString targetFieldName() const
Returns name of the field of our layer that will be used for join.
QString joinLayerId() const
ID of the joined layer - may be used to resolve reference to the joined layer.
void setPrefix(const QString &prefix)
Sets prefix of fields from the joined layer. If nullptr, joined layer's name will be used.
bool hasSubset(bool blocklisted=true) const
Returns true if blocklisted fields is not empty or if a subset of names has been set.
QgsVectorLayer * joinLayer() const
Returns joined layer (may be nullptr if the reference was set by layer ID and not resolved yet)
QHash< QString, QgsAttributes > cachedAttributes
Cache for joined attributes to provide fast lookup (size is 0 if no memory caching)
void setJoinFieldNamesSubset(QStringList *fieldNamesSubset)
Sets the subset of fields to be used from joined layer.
Represents a vector layer which manages a vector based data sets.
void updateFields()
Will regenerate the fields property of this layer by obtaining all fields from the dataProvider,...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
bool addFeatures(QgsFeatureList &features, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a list of features to the sink.
QgsAuxiliaryLayer * auxiliaryLayer()
Returns the current auxiliary layer.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
QgsFeature getFeature(QgsFeatureId fid) const
Queries the layer for the feature with the given id.
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
void updatedFields()
Emitted whenever the fields available from this layer have been changed.
const QList< QgsVectorLayerJoinInfo > vectorJoins() const
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:42
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:922
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
QList< int > QgsAttributeList
Definition: qgsfield.h:26
const QgsField & field
Definition: qgsfield.h:463
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsVectorLayerJoinInfo > QgsVectorJoinList
Context for cascade delete features.