LCOV - code coverage report
Current view: top level - core/vector - qgsvectorlayerjoinbuffer.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 8 425 1.9 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

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

Generated by: LCOV version 1.14