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