Skip to content

Commit

Permalink
Joins: optionally use just a subset of fields from joined layer
Browse files Browse the repository at this point in the history
  • Loading branch information
wonder-sk committed Sep 13, 2014
1 parent 02a9bc8 commit ffcd07b
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 22 deletions.
9 changes: 9 additions & 0 deletions python/core/qgsvectorlayer.sip
Expand Up @@ -103,6 +103,15 @@ struct QgsVectorJoinInfo
@note not available in python bindings
*/
// QHash< QString, QgsAttributeMap> cachedAttributes;

bool operator==( const QgsVectorJoinInfo& other ) const;

/** Set subset of fields to be used from joined layer. Takes ownership of the passed pointer. Null pointer tells to use all fields.
@note added in 2.6 */
void setJoinFieldNamesSubset( QStringList* fieldNamesSubset /Transfer/ );
/** Get subset of fields to be used from joined layer. All fields will be used if null is returned.
@note added in 2.6 */
QStringList* joinFieldNamesSubset() const;
};


Expand Down
8 changes: 8 additions & 0 deletions python/core/qgsvectorlayerjoinbuffer.sip
Expand Up @@ -39,6 +39,14 @@ class QgsVectorLayerJoinBuffer : QObject
@param sourceFieldIndex Output: field's index in source layer */
const QgsVectorJoinInfo* joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex /Out/ ) const;

//! Find out what is the first index of the join within fields. Returns -1 if join is not present
//! @note added in 2.6
int joinedFieldsOffset( const QgsVectorJoinInfo* info, const QgsFields& fields );

//! Return a vector of indices for use in join based on field names from the layer
//! @note added in 2.6
static QVector<int> joinSubsetIndices( QgsVectorLayer* joinLayer, const QStringList& joinFieldsSubset );

//! Create a copy of the join buffer
//! @note added in 2.6
QgsVectorLayerJoinBuffer* clone() const /Factory/;
Expand Down
20 changes: 20 additions & 0 deletions src/core/qgsvectorlayer.h
Expand Up @@ -183,6 +183,26 @@ struct CORE_EXPORT QgsVectorJoinInfo
int targetFieldIndex;
/**Join field index in the source layer. For backward compatibility with 1.x (x>=7)*/
int joinFieldIndex;

bool operator==( const QgsVectorJoinInfo& other ) const
{
return targetFieldName == other.targetFieldName &&
joinLayerId == other.joinLayerId &&
joinFieldName == other.joinFieldName &&
joinFieldsSubset == other.joinFieldsSubset &&
memoryCache == other.memoryCache;
}

/** Set subset of fields to be used from joined layer. Takes ownership of the passed pointer. Null pointer tells to use all fields.
@note added in 2.6 */
void setJoinFieldNamesSubset( QStringList* fieldNamesSubset ) { joinFieldsSubset = QSharedPointer<QStringList>( fieldNamesSubset ); }
/** Get subset of fields to be used from joined layer. All fields will be used if null is returned.
@note added in 2.6 */
QStringList* joinFieldNamesSubset() const { return joinFieldsSubset.data(); }

protected:
/**Subset of fields to use from joined layer. null = use all fields*/
QSharedPointer<QStringList> joinFieldsSubset;
};

/** \ingroup core
Expand Down
33 changes: 21 additions & 12 deletions src/core/qgsvectorlayerfeatureiterator.cpp
Expand Up @@ -451,6 +451,7 @@ void QgsVectorLayerFeatureIterator::prepareJoins()
FetchJoinInfo info;
info.joinInfo = joinInfo;
info.joinLayer = joinLayer;
info.indexOffset = mSource->mJoinBuffer->joinedFieldsOffset( joinInfo, mSource->mFields );

if ( joinInfo->targetFieldName.isEmpty() )
info.targetField = joinInfo->targetFieldIndex; //for compatibility with 1.x
Expand All @@ -462,10 +463,6 @@ void QgsVectorLayerFeatureIterator::prepareJoins()
else
info.joinField = joinLayer->pendingFields().indexFromName( joinInfo->joinFieldName );

info.indexOffset = *attIt - sourceLayerIndex;
if ( info.joinField < sourceLayerIndex )
info.indexOffset++;

// for joined fields, we always need to request the targetField from the provider too
if ( !fetchAttributes.contains( info.targetField ) )
sourceJoinFields << info.targetField;
Expand Down Expand Up @@ -610,10 +607,6 @@ void QgsVectorLayerFeatureIterator::FetchJoinInfo::addJoinedAttributesCached( Qg
const QgsAttributes& featureAttributes = it.value();
for ( int i = 0; i < featureAttributes.count(); ++i )
{
// skip the join field to avoid double field names (fields often have the same name)
if ( i == joinField )
continue;

f.setAttribute( index++, featureAttributes[i] );
}
}
Expand Down Expand Up @@ -663,6 +656,13 @@ void QgsVectorLayerFeatureIterator::FetchJoinInfo::addJoinedAttributesDirect( Qg

joinLayer->dataProvider()->setSubsetString( subsetString, false );

// maybe user requested just a subset of layer's attributes
// so we do not have to cache everything
bool hasSubset = joinInfo->joinFieldNamesSubset();
QVector<int> subsetIndices;
if ( hasSubset )
subsetIndices = QgsVectorLayerJoinBuffer::joinSubsetIndices( joinLayer, *joinInfo->joinFieldNamesSubset() );

// select (no geometry)
QgsFeatureRequest request;
request.setFlags( QgsFeatureRequest::NoGeometry );
Expand All @@ -675,12 +675,21 @@ void QgsVectorLayerFeatureIterator::FetchJoinInfo::addJoinedAttributesDirect( Qg
{
int index = indexOffset;
const QgsAttributes& attr = fet.attributes();
for ( int i = 0; i < attr.count(); ++i )
if ( hasSubset )
{
if ( i == joinField )
continue;
for ( int i = 0; i < subsetIndices.count(); ++i )
f.setAttribute( index++, attr[ subsetIndices[i] ] );
}
else
{
// use all fields except for the one used for join (has same value as exiting field in target layer)
for ( int i = 0; i < attr.count(); ++i )
{
if ( i == joinField )
continue;

f.setAttribute( index++, attr[i] );
f.setAttribute( index++, attr[i] );
}
}
}
else
Expand Down
109 changes: 107 additions & 2 deletions src/core/qgsvectorlayerjoinbuffer.cpp
Expand Up @@ -89,16 +89,65 @@ void QgsVectorLayerJoinBuffer::cacheJoinLayer( QgsVectorJoinInfo& joinInfo )

joinInfo.cachedAttributes.clear();

QgsFeatureIterator fit = cacheLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ) );
QgsFeatureRequest request;
request.setFlags( QgsFeatureRequest::NoGeometry );

// maybe user requested just a subset of layer's attributes
// so we do not have to cache everything
bool hasSubset = joinInfo.joinFieldNamesSubset();
QVector<int> subsetIndices;
if ( hasSubset )
{
subsetIndices = joinSubsetIndices( cacheLayer, *joinInfo.joinFieldNamesSubset() );

// we need just subset of attributes - but make sure to include join field name
QgsAttributeList cacheLayerAttrs = subsetIndices.toList();
if ( !cacheLayerAttrs.contains( joinFieldIndex ) )
cacheLayerAttrs.append( joinFieldIndex );
request.setSubsetOfAttributes( cacheLayerAttrs );
}

QgsFeatureIterator fit = cacheLayer->getFeatures( request );
QgsFeature f;
while ( fit.nextFeature( f ) )
{
const QgsAttributes& attrs = f.attributes();
joinInfo.cachedAttributes.insert( attrs[joinFieldIndex].toString(), attrs );
QString key = attrs[joinFieldIndex].toString();
if ( hasSubset )
{
QgsAttributes subsetAttrs( subsetIndices.count() );
for ( int i = 0; i < subsetIndices.count(); ++i )
subsetAttrs[i] = attrs[ subsetIndices[i] ];
joinInfo.cachedAttributes.insert( key, subsetAttrs );
}
else
{
QgsAttributes attrs2 = attrs;
attrs2.remove( joinFieldIndex ); // skip the join field to avoid double field names (fields often have the same name)
joinInfo.cachedAttributes.insert( key, attrs2 );
}
}
}
}


QVector<int> QgsVectorLayerJoinBuffer::joinSubsetIndices( QgsVectorLayer* joinLayer, const QStringList& joinFieldsSubset )
{
QVector<int> subsetIndices;
const QgsFields& fields = joinLayer->pendingFields();
for ( int i = 0; i < joinFieldsSubset.count(); ++i )
{
QString joinedFieldName = joinFieldsSubset.at( i );
int index = fields.fieldNameIndex( joinedFieldName );
if ( index != -1 )
subsetIndices.append( index );
else
QgsDebugMsg( "Join layer subset field not found: " + joinedFieldName );
}

return subsetIndices;
}

void QgsVectorLayerJoinBuffer::updateFields( QgsFields& fields )
{
QList< QgsVectorJoinInfo>::const_iterator joinIt = mVectorJoins.constBegin();
Expand All @@ -117,8 +166,20 @@ void QgsVectorLayerJoinBuffer::updateFields( QgsFields& fields )
else
joinFieldName = joinIt->joinFieldName;

QSet<QString> subset;
bool hasSubset = false;
if ( joinIt->joinFieldNamesSubset() )
{
hasSubset = true;
subset = QSet<QString>::fromList( *joinIt->joinFieldNamesSubset() );
}

for ( int idx = 0; idx < joinFields.count(); ++idx )
{
// if using just a subset of fields, filter some of them out
if ( hasSubset && !subset.contains( joinFields[idx].name() ) )
continue;

//skip the join field to avoid double field names (fields often have the same name)
if ( joinFields[idx].name() != joinFieldName )
{
Expand Down Expand Up @@ -165,6 +226,20 @@ void QgsVectorLayerJoinBuffer::writeXml( QDomNode& layer_node, QDomDocument& doc
joinElem.setAttribute( "joinFieldName", joinIt->joinFieldName );

joinElem.setAttribute( "memoryCache", !joinIt->cachedAttributes.isEmpty() );

if ( joinIt->joinFieldNamesSubset() )
{
QDomElement subsetElem = document.createElement( "joinFieldsSubset" );
foreach ( QString fieldName, *joinIt->joinFieldNamesSubset() )
{
QDomElement fieldElem = document.createElement( "field" );
fieldElem.setAttribute( "name", fieldName );
subsetElem.appendChild( fieldElem );
}

joinElem.appendChild( subsetElem );
}

vectorJoinsElem.appendChild( joinElem );
}
}
Expand All @@ -188,11 +263,41 @@ void QgsVectorLayerJoinBuffer::readXml( const QDomNode& layer_node )
info.joinFieldIndex = infoElem.attribute( "joinField" ).toInt(); //for compatibility with 1.x
info.targetFieldIndex = infoElem.attribute( "targetField" ).toInt(); //for compatibility with 1.x

QDomElement subsetElem = infoElem.firstChildElement( "joinFieldsSubset" );
if ( !subsetElem.isNull() )
{
QStringList* fieldNames = new QStringList;
QDomNodeList fieldNodes = infoElem.elementsByTagName( "field" );
for ( int i = 0; i < fieldNodes.count(); ++i )
*fieldNames << fieldNodes.at( i ).toElement().attribute( "name" );
info.setJoinFieldNamesSubset( fieldNames );
}

addJoin( info );
}
}
}

int QgsVectorLayerJoinBuffer::joinedFieldsOffset( const QgsVectorJoinInfo* info, const QgsFields& fields )
{
if ( !info )
return -1;

int joinIndex = mVectorJoins.indexOf( *info );
if ( joinIndex == -1 )
return -1;

for ( int i = 0; i < fields.count(); ++i )
{
if ( fields.fieldOrigin( i ) != QgsFields::OriginJoin )
continue;

if ( fields.fieldOriginIndex( i ) / 1000 == joinIndex )
return i;
}
return -1;
}

const QgsVectorJoinInfo* QgsVectorLayerJoinBuffer::joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex ) const
{
if ( fields.fieldOrigin( index ) != QgsFields::OriginJoin )
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsvectorlayerjoinbuffer.h
Expand Up @@ -68,6 +68,14 @@ class CORE_EXPORT QgsVectorLayerJoinBuffer : public QObject
@param sourceFieldIndex Output: field's index in source layer */
const QgsVectorJoinInfo* joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex ) const;

//! Find out what is the first index of the join within fields. Returns -1 if join is not present
//! @note added in 2.6
int joinedFieldsOffset( const QgsVectorJoinInfo* info, const QgsFields& fields );

//! Return a vector of indices for use in join based on field names from the layer
//! @note added in 2.6
static QVector<int> joinSubsetIndices( QgsVectorLayer* joinLayer, const QStringList& joinFieldsSubset );

//! Create a copy of the join buffer
//! @note added in 2.6
QgsVectorLayerJoinBuffer* clone() const;
Expand Down

0 comments on commit ffcd07b

Please sign in to comment.