QGIS API Documentation 4.1.0-Master (60fea48833c)
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 cacheJoinLayer( *it );
529 }
530 }
531
532 emit joinedFieldsChanged();
533}
534
535void QgsVectorLayerJoinBuffer::joinedLayerModified()
536{
537 QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
538 Q_ASSERT( joinedLayer );
539
540 // recache the joined layer
541 for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
542 {
543 if ( joinedLayer == it->joinLayer() )
544 {
545 it->cacheDirty = true;
546 }
547 }
548}
549
550void QgsVectorLayerJoinBuffer::joinedLayerWillBeDeleted()
551{
552 QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
553 Q_ASSERT( joinedLayer );
554
555 removeJoin( joinedLayer->id() );
556}
557
558void QgsVectorLayerJoinBuffer::connectJoinedLayer( QgsVectorLayer *vl )
559{
560 connect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields, Qt::UniqueConnection );
561 connect( vl, &QgsVectorLayer::layerModified, this, &QgsVectorLayerJoinBuffer::joinedLayerModified, Qt::UniqueConnection );
562 connect( vl, &QgsVectorLayer::willBeDeleted, this, &QgsVectorLayerJoinBuffer::joinedLayerWillBeDeleted, Qt::UniqueConnection );
563}
564
566{
567 if ( !containsJoins() )
568 return false;
569
570 // try to add/update a feature in each joined layer
571 const QgsVectorJoinList joins = vectorJoins();
572 for ( const QgsVectorLayerJoinInfo &info : joins )
573 {
574 QgsVectorLayer *joinLayer = info.joinLayer();
575
576 if ( joinLayer && joinLayer->isEditable() && info.isEditable() && info.hasUpsertOnEdit() )
577 {
578 QgsFeatureList joinFeatures;
579
580 for ( const QgsFeature &feature : std::as_const( features ) )
581 {
582 const QgsFeature joinFeature = info.extractJoinedFeature( feature );
583
584 // we don't want to add a new feature in joined layer when the id
585 // column value yet exist, we just want to update the existing one
586 const QVariant idFieldValue = feature.attribute( info.targetFieldName() );
587 const QString filter = QgsExpression::createFieldEqualityExpression( info.joinFieldName(), idFieldValue.toString() );
588
589 QgsFeatureRequest request;
591 request.setNoAttributes();
592 request.setFilterExpression( filter );
593 request.setLimit( 1 );
594
595 QgsFeatureIterator it = info.joinLayer()->getFeatures( request );
596 QgsFeature existingFeature;
597 it.nextFeature( existingFeature );
598
599 if ( existingFeature.isValid() )
600 {
601 if ( info.hasSubset() )
602 {
603 const QStringList subsetNames = QgsVectorLayerJoinInfo::joinFieldNamesSubset( info );
604 const auto constSubsetNames = subsetNames;
605 for ( const QString &field : constSubsetNames )
606 {
607 QVariant newValue = joinFeature.attribute( field );
608 int fieldIndex = joinLayer->fields().indexOf( field );
609 joinLayer->changeAttributeValue( existingFeature.id(), fieldIndex, newValue );
610 }
611 }
612 else
613 {
614 const QgsFields joinFields = joinFeature.fields();
615 for ( const auto &field : joinFields )
616 {
617 QVariant newValue = joinFeature.attribute( field.name() );
618 int fieldIndex = joinLayer->fields().indexOf( field.name() );
619 joinLayer->changeAttributeValue( existingFeature.id(), fieldIndex, newValue );
620 }
621 }
622 }
623 else
624 {
625 // joined feature is added only if one of its field is not null
626 bool notNullFields = false;
627 const QgsFields joinFields = joinFeature.fields();
628 for ( const auto &field : joinFields )
629 {
630 if ( field.name() == info.joinFieldName() )
631 continue;
632
633 if ( !QgsVariantUtils::isNull( joinFeature.attribute( field.name() ) ) )
634 {
635 notNullFields = true;
636 break;
637 }
638 }
639
640 if ( notNullFields )
641 joinFeatures << joinFeature;
642 }
643 }
644
645 joinLayer->addFeatures( joinFeatures );
646 }
647 }
648
649 return true;
650}
651
652bool QgsVectorLayerJoinBuffer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue )
653{
654 if ( mLayer->fields().fieldOrigin( field ) != Qgis::FieldOrigin::Join )
655 return false;
656
657 int srcFieldIndex;
658 const QgsVectorLayerJoinInfo *info = joinForFieldIndex( field, mLayer->fields(), srcFieldIndex );
659 if ( info && info->joinLayer() && info->isEditable() )
660 {
661 QgsFeature feature = mLayer->getFeature( fid );
662
663 if ( !feature.isValid() )
664 return false;
665
666 const QgsFeature joinFeature = joinedFeatureOf( info, feature );
667
668 if ( joinFeature.isValid() )
669 return info->joinLayer()->changeAttributeValue( joinFeature.id(), srcFieldIndex, newValue, oldValue );
670 else
671 {
672 feature.setAttribute( field, newValue );
673 return addFeatures( QgsFeatureList() << feature );
674 }
675 }
676 else
677 return false;
678}
679
681{
682 bool success = true;
683
684 for ( auto it = newValues.constBegin(); it != newValues.constEnd(); ++it )
685 {
686 const int field = it.key();
687 const QVariant newValue = it.value();
688 QVariant oldValue;
689
690 if ( oldValues.contains( field ) )
691 oldValue = oldValues[field];
692
693 success &= changeAttributeValue( fid, field, newValue, oldValue );
694 }
695
696 return success;
697}
698
700{
701 return deleteFeatures( QgsFeatureIds() << fid, context );
702}
703
705{
706 if ( !containsJoins() )
707 return false;
708
709 const auto constFids = fids;
710 for ( const QgsFeatureId &fid : constFids )
711 {
712 const auto constVectorJoins = vectorJoins();
713 for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
714 {
715 if ( info.isEditable() && info.hasCascadedDelete() )
716 {
717 const QgsFeature joinFeature = joinedFeatureOf( &info, mLayer->getFeature( fid ) );
718 if ( joinFeature.isValid() )
719 info.joinLayer()->deleteFeature( joinFeature.id(), context );
720 }
721 }
722 }
723
724 return true;
725}
726
728{
729 const QgsAuxiliaryLayer *al = mLayer->auxiliaryLayer();
730
731 return al && al->id() == info.joinLayerId();
732}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2276
@ Join
Field originates from a joined layer.
Definition qgis.h:1786
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:69
QgsFields fields
Definition qgsfeature.h:70
void initAttributes(int fieldCount)
Initialize this feature with the given number of fields.
QgsFeatureId id
Definition qgsfeature.h:68
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:46
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: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: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:113
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:59
QList< QgsVectorLayerJoinInfo > QgsVectorJoinList
Context for cascade delete features.