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