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