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