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