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