QGIS API Documentation  2.99.0-Master (69af2f5)
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 
25 #include <QDomElement>
26 
28  : mLayer( layer )
29 {
30 }
31 
32 static QList<QgsVectorLayer *> _outEdges( QgsVectorLayer *vl )
33 {
34  QList<QgsVectorLayer *> lst;
35  Q_FOREACH ( const QgsVectorLayerJoinInfo &info, vl->vectorJoins() )
36  {
37  if ( QgsVectorLayer *joinVl = info.joinLayer() )
38  lst << joinVl;
39  }
40  return lst;
41 }
42 
43 static bool _hasCycleDFS( QgsVectorLayer *n, QHash<QgsVectorLayer *, int> &mark )
44 {
45  if ( mark.value( n ) == 1 ) // temporary
46  return true;
47  if ( mark.value( n ) == 0 ) // not visited
48  {
49  mark[n] = 1; // temporary
50  Q_FOREACH ( QgsVectorLayer *m, _outEdges( n ) )
51  {
52  if ( _hasCycleDFS( m, mark ) )
53  return true;
54  }
55  mark[n] = 2; // permanent
56  }
57  return false;
58 }
59 
60 
62 {
63  QMutexLocker locker( &mMutex );
64  mVectorJoins.push_back( joinInfo );
65 
66  // run depth-first search to detect cycles in the graph of joins between layers.
67  // any cycle would cause infinite recursion when updating fields
68  QHash<QgsVectorLayer *, int> markDFS;
69  if ( mLayer && _hasCycleDFS( mLayer, markDFS ) )
70  {
71  // we have to reject this one
72  mVectorJoins.pop_back();
73  return false;
74  }
75 
76  //cache joined layer to virtual memory if specified by user
77  if ( joinInfo.isUsingMemoryCache() )
78  {
79  cacheJoinLayer( mVectorJoins.last() );
80  }
81 
82  // Wait for notifications about changed fields in joined layer to propagate them.
83  // During project load the joined layers possibly do not exist yet so the connection will not be created,
84  // but then QgsProject makes sure to call createJoinCaches() which will do the connection.
85  // Unique connection makes sure we do not respond to one layer's update more times (in case of multiple join)
86  if ( QgsVectorLayer *vl = joinInfo.joinLayer() )
87  {
88  connectJoinedLayer( vl );
89  }
90 
91  emit joinedFieldsChanged();
92  return true;
93 }
94 
95 
96 bool QgsVectorLayerJoinBuffer::removeJoin( const QString &joinLayerId )
97 {
98  QMutexLocker locker( &mMutex );
99  bool res = false;
100  for ( int i = 0; i < mVectorJoins.size(); ++i )
101  {
102  if ( mVectorJoins.at( i ).joinLayerId() == joinLayerId )
103  {
104  if ( QgsVectorLayer *vl = mVectorJoins.at( i ).joinLayer() )
105  {
106  disconnect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields );
107  }
108 
109  mVectorJoins.removeAt( i );
110  res = true;
111  }
112  }
113 
114  emit joinedFieldsChanged();
115  return res;
116 }
117 
118 void QgsVectorLayerJoinBuffer::cacheJoinLayer( QgsVectorLayerJoinInfo &joinInfo )
119 {
120  //memory cache not required or already done
121  if ( !joinInfo.isUsingMemoryCache() || !joinInfo.cacheDirty )
122  {
123  return;
124  }
125 
126  QgsVectorLayer *cacheLayer = joinInfo.joinLayer();
127  if ( cacheLayer )
128  {
129  int joinFieldIndex = cacheLayer->fields().indexFromName( joinInfo.joinFieldName() );
130 
131  if ( joinFieldIndex < 0 || joinFieldIndex >= cacheLayer->fields().count() )
132  return;
133 
134  joinInfo.cachedAttributes.clear();
135 
136  QgsFeatureRequest request;
138 
139  // maybe user requested just a subset of layer's attributes
140  // so we do not have to cache everything
141  bool hasSubset = joinInfo.joinFieldNamesSubset();
142  QVector<int> subsetIndices;
143  if ( hasSubset )
144  {
145  subsetIndices = joinSubsetIndices( cacheLayer, *joinInfo.joinFieldNamesSubset() );
146 
147  // we need just subset of attributes - but make sure to include join field name
148  QgsAttributeList cacheLayerAttrs = subsetIndices.toList();
149  if ( !cacheLayerAttrs.contains( joinFieldIndex ) )
150  cacheLayerAttrs.append( joinFieldIndex );
151  request.setSubsetOfAttributes( cacheLayerAttrs );
152  }
153 
154  QgsFeatureIterator fit = cacheLayer->getFeatures( request );
155  QgsFeature f;
156  while ( fit.nextFeature( f ) )
157  {
158  QgsAttributes attrs = f.attributes();
159  QString key = attrs.at( joinFieldIndex ).toString();
160  if ( hasSubset )
161  {
162  QgsAttributes subsetAttrs( subsetIndices.count() );
163  for ( int i = 0; i < subsetIndices.count(); ++i )
164  subsetAttrs[i] = attrs.at( subsetIndices.at( i ) );
165  joinInfo.cachedAttributes.insert( key, subsetAttrs );
166  }
167  else
168  {
169  QgsAttributes attrs2 = attrs;
170  attrs2.remove( joinFieldIndex ); // skip the join field to avoid double field names (fields often have the same name)
171  joinInfo.cachedAttributes.insert( key, attrs2 );
172  }
173  }
174  joinInfo.cacheDirty = false;
175  }
176 }
177 
178 
179 QVector<int> QgsVectorLayerJoinBuffer::joinSubsetIndices( QgsVectorLayer *joinLayer, const QStringList &joinFieldsSubset )
180 {
181  QVector<int> subsetIndices;
182  const QgsFields &fields = joinLayer->fields();
183  for ( int i = 0; i < joinFieldsSubset.count(); ++i )
184  {
185  QString joinedFieldName = joinFieldsSubset.at( i );
186  int index = fields.lookupField( joinedFieldName );
187  if ( index != -1 )
188  {
189  subsetIndices.append( index );
190  }
191  else
192  {
193  QgsDebugMsg( "Join layer subset field not found: " + joinedFieldName );
194  }
195  }
196 
197  return subsetIndices;
198 }
199 
201 {
202  QString prefix;
203 
204  QList< QgsVectorLayerJoinInfo>::const_iterator joinIt = mVectorJoins.constBegin();
205  for ( int joinIdx = 0 ; joinIt != mVectorJoins.constEnd(); ++joinIt, ++joinIdx )
206  {
207  QgsVectorLayer *joinLayer = joinIt->joinLayer();
208  if ( !joinLayer )
209  {
210  continue;
211  }
212 
213  const QgsFields &joinFields = joinLayer->fields();
214  QString joinFieldName = joinIt->joinFieldName();
215 
216  QSet<QString> subset;
217  bool hasSubset = false;
218  if ( joinIt->joinFieldNamesSubset() )
219  {
220  hasSubset = true;
221  subset = QSet<QString>::fromList( *joinIt->joinFieldNamesSubset() );
222  }
223 
224  if ( joinIt->prefix().isNull() )
225  {
226  prefix = joinLayer->name() + '_';
227  }
228  else
229  {
230  prefix = joinIt->prefix();
231  }
232 
233  for ( int idx = 0; idx < joinFields.count(); ++idx )
234  {
235  // if using just a subset of fields, filter some of them out
236  if ( hasSubset && !subset.contains( joinFields.at( idx ).name() ) )
237  continue;
238 
239  //skip the join field to avoid double field names (fields often have the same name)
240  // when using subset of field, use all the selected fields
241  if ( hasSubset || joinFields.at( idx ).name() != joinFieldName )
242  {
243  QgsField f = joinFields.at( idx );
244  f.setName( prefix + f.name() );
245  fields.append( f, QgsFields::OriginJoin, idx + ( joinIdx * 1000 ) );
246  }
247  }
248  }
249 }
250 
252 {
253  QMutexLocker locker( &mMutex );
254  QList< QgsVectorLayerJoinInfo >::iterator joinIt = mVectorJoins.begin();
255  for ( ; joinIt != mVectorJoins.end(); ++joinIt )
256  {
257  if ( joinIt->isUsingMemoryCache() && joinIt->cacheDirty )
258  cacheJoinLayer( *joinIt );
259  }
260 }
261 
262 
263 void QgsVectorLayerJoinBuffer::writeXml( QDomNode &layer_node, QDomDocument &document ) const
264 {
265  QDomElement vectorJoinsElem = document.createElement( QStringLiteral( "vectorjoins" ) );
266  layer_node.appendChild( vectorJoinsElem );
267  QList< QgsVectorLayerJoinInfo >::const_iterator joinIt = mVectorJoins.constBegin();
268  for ( ; joinIt != mVectorJoins.constEnd(); ++joinIt )
269  {
270  QDomElement joinElem = document.createElement( QStringLiteral( "join" ) );
271 
272  joinElem.setAttribute( QStringLiteral( "targetFieldName" ), joinIt->targetFieldName() );
273 
274  joinElem.setAttribute( QStringLiteral( "joinLayerId" ), joinIt->joinLayerId() );
275  joinElem.setAttribute( QStringLiteral( "joinFieldName" ), joinIt->joinFieldName() );
276 
277  joinElem.setAttribute( QStringLiteral( "memoryCache" ), joinIt->isUsingMemoryCache() );
278 
279  if ( joinIt->joinFieldNamesSubset() )
280  {
281  QDomElement subsetElem = document.createElement( QStringLiteral( "joinFieldsSubset" ) );
282  Q_FOREACH ( const QString &fieldName, *joinIt->joinFieldNamesSubset() )
283  {
284  QDomElement fieldElem = document.createElement( QStringLiteral( "field" ) );
285  fieldElem.setAttribute( QStringLiteral( "name" ), fieldName );
286  subsetElem.appendChild( fieldElem );
287  }
288 
289  joinElem.appendChild( subsetElem );
290  }
291 
292  if ( !joinIt->prefix().isNull() )
293  {
294  joinElem.setAttribute( QStringLiteral( "customPrefix" ), joinIt->prefix() );
295  joinElem.setAttribute( QStringLiteral( "hasCustomPrefix" ), 1 );
296  }
297 
298  vectorJoinsElem.appendChild( joinElem );
299  }
300 }
301 
302 void QgsVectorLayerJoinBuffer::readXml( const QDomNode &layer_node )
303 {
304  mVectorJoins.clear();
305  QDomElement vectorJoinsElem = layer_node.firstChildElement( QStringLiteral( "vectorjoins" ) );
306  if ( !vectorJoinsElem.isNull() )
307  {
308  QDomNodeList joinList = vectorJoinsElem.elementsByTagName( QStringLiteral( "join" ) );
309  for ( int i = 0; i < joinList.size(); ++i )
310  {
311  QDomElement infoElem = joinList.at( i ).toElement();
313  info.setJoinFieldName( infoElem.attribute( QStringLiteral( "joinFieldName" ) ) );
314  // read layer ID - to turn it into layer object, caller will need to call resolveReferences() later
315  info.setJoinLayerId( infoElem.attribute( QStringLiteral( "joinLayerId" ) ) );
316  info.setTargetFieldName( infoElem.attribute( QStringLiteral( "targetFieldName" ) ) );
317  info.setUsingMemoryCache( infoElem.attribute( QStringLiteral( "memoryCache" ) ).toInt() );
318 
319  QDomElement subsetElem = infoElem.firstChildElement( QStringLiteral( "joinFieldsSubset" ) );
320  if ( !subsetElem.isNull() )
321  {
322  QStringList *fieldNames = new QStringList;
323  QDomNodeList fieldNodes = infoElem.elementsByTagName( QStringLiteral( "field" ) );
324  fieldNames->reserve( fieldNodes.count() );
325  for ( int i = 0; i < fieldNodes.count(); ++i )
326  *fieldNames << fieldNodes.at( i ).toElement().attribute( QStringLiteral( "name" ) );
327  info.setJoinFieldNamesSubset( fieldNames );
328  }
329 
330  if ( infoElem.attribute( QStringLiteral( "hasCustomPrefix" ) ).toInt() )
331  info.setPrefix( infoElem.attribute( QStringLiteral( "customPrefix" ) ) );
332  else
333  info.setPrefix( QString() );
334 
335  addJoin( info );
336  }
337  }
338 }
339 
341 {
342  bool resolved = false;
343  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
344  {
345  if ( it->joinLayer() )
346  continue; // already resolved
347 
348  if ( QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( project->mapLayer( it->joinLayerId() ) ) )
349  {
350  it->setJoinLayer( joinedLayer );
351  resolved = true;
352  }
353  }
354 
355  if ( resolved )
356  emit joinedFieldsChanged();
357 }
358 
360 {
361  if ( !info )
362  return -1;
363 
364  int joinIndex = mVectorJoins.indexOf( *info );
365  if ( joinIndex == -1 )
366  return -1;
367 
368  for ( int i = 0; i < fields.count(); ++i )
369  {
370  if ( fields.fieldOrigin( i ) != QgsFields::OriginJoin )
371  continue;
372 
373  if ( fields.fieldOriginIndex( i ) / 1000 == joinIndex )
374  return i;
375  }
376  return -1;
377 }
378 
379 const QgsVectorLayerJoinInfo *QgsVectorLayerJoinBuffer::joinForFieldIndex( int index, const QgsFields &fields, int &sourceFieldIndex ) const
380 {
381  if ( fields.fieldOrigin( index ) != QgsFields::OriginJoin )
382  return nullptr;
383 
384  int originIndex = fields.fieldOriginIndex( index );
385  int sourceJoinIndex = originIndex / 1000;
386  sourceFieldIndex = originIndex % 1000;
387 
388  if ( sourceJoinIndex < 0 || sourceJoinIndex >= mVectorJoins.count() )
389  return nullptr;
390 
391  return &( mVectorJoins[sourceJoinIndex] );
392 }
393 
395 {
396  QgsVectorLayerJoinBuffer *cloned = new QgsVectorLayerJoinBuffer( mLayer );
397  cloned->mVectorJoins = mVectorJoins;
398  return cloned;
399 }
400 
401 void QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields()
402 {
403  // TODO - check - this whole method is probably not needed anymore,
404  // since the cache handling is covered by joinedLayerModified()
405 
406  QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
407  Q_ASSERT( joinedLayer );
408 
409  // recache the joined layer
410  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
411  {
412  if ( joinedLayer == it->joinLayer() )
413  {
414  it->cachedAttributes.clear();
415  cacheJoinLayer( *it );
416  }
417  }
418 
419  emit joinedFieldsChanged();
420 }
421 
422 void QgsVectorLayerJoinBuffer::joinedLayerModified()
423 {
424  QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
425  Q_ASSERT( joinedLayer );
426 
427  // recache the joined layer
428  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
429  {
430  if ( joinedLayer == it->joinLayer() )
431  {
432  it->cacheDirty = true;
433  }
434  }
435 }
436 
437 void QgsVectorLayerJoinBuffer::joinedLayerWillBeDeleted()
438 {
439  QgsVectorLayer *joinedLayer = qobject_cast<QgsVectorLayer *>( sender() );
440  Q_ASSERT( joinedLayer );
441 
442  removeJoin( joinedLayer->id() );
443 }
444 
445 void QgsVectorLayerJoinBuffer::connectJoinedLayer( QgsVectorLayer *vl )
446 {
447  connect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields, Qt::UniqueConnection );
448  connect( vl, &QgsVectorLayer::layerModified, this, &QgsVectorLayerJoinBuffer::joinedLayerModified, Qt::UniqueConnection );
449  connect( vl, &QgsVectorLayer::willBeDeleted, this, &QgsVectorLayerJoinBuffer::joinedLayerWillBeDeleted, Qt::UniqueConnection );
450 }
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:289
void writeXml(QDomNode &layer_node, QDomDocument &document) const
Saves mVectorJoins to xml under the layer node.
Wrapper for iterator of features from vector data provider or vector layer.
Field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
Definition: qgsfields.h:49
FieldOrigin fieldOrigin(int fieldIdx) const
Get field&#39;s origin (value from an enumeration)
Definition: qgsfields.cpp:161
void createJoinCaches()
Calls cacheJoinLayer() for all vector joins.
QString name
Definition: qgsfield.h:54
#define QgsDebugMsg(str)
Definition: qgslogger.h:37
void willBeDeleted()
Emitted in the destructor when the layer is about to be deleted, but it is still in a perfectly valid...
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:41
void setName(const QString &name)
Set the field name.
Definition: qgsfield.cpp:135
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:61
QgsVectorLayerJoinBuffer(QgsVectorLayer *layer=nullptr)
int count() const
Return number of items.
Definition: qgsfields.cpp:115
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
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:135
int fieldOriginIndex(int fieldIdx) const
Get field&#39;s origin index (its meaning is specific to each type of origin)
Definition: qgsfields.cpp:169
int indexFromName(const QString &fieldName) const
Get the field index from the field name.
Definition: qgsfields.cpp:174
const QList< QgsVectorLayerJoinInfo > vectorJoins() const
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QgsVectorLayer * joinLayer() const
Returns joined layer (may be null if the reference was set by layer ID and not resolved yet) ...
QgsFields fields() const override
Returns the list of fields of this layer.
Defines left outer join from our vector layer to some other vector layer.
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)
Append a field. The field must have unique name, otherwise it is rejected (returns false) ...
Definition: qgsfields.cpp:59
Reads and writes project states.
Definition: qgsproject.h:78
void setUsingMemoryCache(bool enabled)
Sets whether values from the joined layer should be cached in memory to speed up lookups.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:46
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.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override
Query the layer for features specified in request.
void setPrefix(const QString &prefix)
Sets prefix of fields from the joined layer. If null, joined layer&#39;s name will be used...
bool addJoin(const QgsVectorLayerJoinInfo &joinInfo)
Joins another vector layer to this layer.
void updateFields(QgsFields &fields)
Updates field map with joined attributes.
static QVector< int > joinSubsetIndices(QgsVectorLayer *joinLayer, const QStringList &joinFieldsSubset)
Return a vector of indices for use in join based on field names from the layer.
const QgsVectorLayerJoinInfo * joinForFieldIndex(int index, const QgsFields &fields, int &sourceFieldIndex) const
Finds the vector join for a layer field index.
bool isUsingMemoryCache() const
Returns whether values from the joined layer should be cached in memory to speed up lookups...
void joinedFieldsChanged()
Emitted whenever the list of joined fields changes (e.g.
QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
QString name
Definition: qgsmaplayer.h:58
QList< int > QgsAttributeList
Definition: qgsfield.h:27
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.
void updatedFields()
Is 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()
This signal is emitted when modifications has been done on layer.
void setJoinFieldNamesSubset(QStringList *fieldNamesSubset)
Set subset of fields to be used from joined layer.
QStringList * joinFieldNamesSubset() const
Get subset of fields to be used from joined layer.
QgsAttributes attributes
Definition: qgsfeature.h:71
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)
Set flags that affect how features will be fetched.