diff --git a/python/core/qgsvectorlayerjoinbuffer.sip b/python/core/qgsvectorlayerjoinbuffer.sip index bdc45784c346..0484c3eab3ed 100644 --- a/python/core/qgsvectorlayerjoinbuffer.sip +++ b/python/core/qgsvectorlayerjoinbuffer.sip @@ -132,6 +132,82 @@ Quick way to test if there is any join at all :rtype: QgsVectorLayerJoinBuffer %End + bool addFeature( const QgsFeature &feature ) const; +%Docstring + Adds a feature in joined layers. The feature given in parameter is the + one added in target layer. If a corresponding joined feature yet exists + in a joined layer, then this feature is just updated. Note that if a + corresponding joined feature has only empty fields, then it's not + created nor added. + + \param feature The feature added in the target layer. + + :return: false if an error happened, true otherwise + +.. versionadded:: 3.0 + :rtype: bool +%End + + bool addFeatures( const QgsFeatureList &features ) const; +%Docstring + Adds a list of features in joined layers. Features given in parameter + are those added in target layer. If a corresponding joined feature yet + exists in a joined layer, then this feature is just updated. Note that + if a corresponding joined feature has only empty fields, then it's not + created nor added. + + \param features The list of features added in the target layer + + :return: false if an error happened, true otherwise + +.. versionadded:: 3.0 + :rtype: bool +%End + + bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant() ) const; +%Docstring + Changes attribute value in joined layers. The feature id given in + parameter is the one added in target layer. If the corresponding joined + feature does not exist in a joined layer, then it's automatically + created if its fields are not empty. + + \param fid The feature id + \param field The field to update + \param newValue The new value of the attribute + \param oldValue The old value of the attribute + + :return: false if an error happened, true otherwise + +.. versionadded:: 3.0 + :rtype: bool +%End + + bool deleteFeature( QgsFeatureId fid ) const; +%Docstring + Deletes a feature from joined layers. The feature id given in + parameter is the one coming from the target layer. + + \param fid The feature id from the target layer to delete + + :return: false if an error happened, true otherwise + +.. versionadded:: 3.0 + :rtype: bool +%End + + bool deleteFeatures( const QgsFeatureIds &fids ) const; +%Docstring + Deletes a list of features from joined layers. Feature ids given + in aprameter are those coming from the target layer. + + \param fids Feature ids from the target layer to delete + + :return: false if an error happened, true otherwise + +.. versionadded:: 3.0 + :rtype: bool +%End + signals: void joinedFieldsChanged(); %Docstring diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index e8f1002455dc..b105ae6bba3f 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -951,7 +951,7 @@ bool QgsVectorLayer::addFeature( QgsFeature &feature, Flags ) updateExtents(); if ( mJoinBuffer->containsJoins() ) - success = addFeaturesToJoinedLayers( QgsFeatureList() << feature ); + success = mJoinBuffer->addFeature( feature ); } return success; @@ -2265,27 +2265,7 @@ bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, const QV { if ( fields().fieldOrigin( field ) == QgsFields::OriginJoin ) { - int srcFieldIndex; - const QgsVectorLayerJoinInfo *info = mJoinBuffer->joinForFieldIndex( field, fields(), srcFieldIndex ); - if ( info && info->joinLayer() && info->isEditable() ) - { - QgsFeature feature = getFeature( fid ); - - if ( !feature.isValid() ) - return false; - - const QgsFeature joinFeature = mJoinBuffer->joinedFeatureOf( info, feature ); - - if ( joinFeature.isValid() ) - return info->joinLayer()->changeAttributeValue( joinFeature.id(), srcFieldIndex, newValue, oldValue ); - else - { - feature.setAttribute( field, newValue ); - return addFeaturesToJoinedLayers( QgsFeatureList() << feature ); - } - } - else - return false; + return mJoinBuffer->changeAttributeValue( fid, field, newValue, oldValue ); } else { @@ -2442,7 +2422,7 @@ bool QgsVectorLayer::deleteFeature( QgsFeatureId fid ) return false; if ( mJoinBuffer->containsJoins() ) - deleteFeaturesFromJoinedLayers( QgsFeatureIds() << fid ); + mJoinBuffer->deleteFeature( fid ); bool res = mEditBuffer->deleteFeature( fid ); if ( res ) @@ -2463,7 +2443,7 @@ bool QgsVectorLayer::deleteFeatures( const QgsFeatureIds &fids ) } if ( mJoinBuffer->containsJoins() ) - deleteFeaturesFromJoinedLayers( fids ); + mJoinBuffer->deleteFeatures( fids ); bool res = mEditBuffer->deleteFeatures( fids ); @@ -2476,26 +2456,6 @@ bool QgsVectorLayer::deleteFeatures( const QgsFeatureIds &fids ) return res; } -bool QgsVectorLayer::deleteFeaturesFromJoinedLayers( QgsFeatureIds fids ) -{ - bool rc = false; - - Q_FOREACH ( const QgsFeatureId &fid, fids ) - { - Q_FOREACH ( const QgsVectorLayerJoinInfo &info, vectorJoins() ) - { - if ( info.isEditable() && info.hasCascadedDelete() ) - { - const QgsFeature joinFeature = mJoinBuffer->joinedFeatureOf( &info, getFeature( fid ) ); - if ( joinFeature.isValid() ) - info.joinLayer()->deleteFeature( joinFeature.id() ); - } - } - } - - return rc; -} - QgsAttributeList QgsVectorLayer::pkAttributeList() const { QgsAttributeList pkAttributesList; @@ -2668,83 +2628,11 @@ bool QgsVectorLayer::addFeatures( QgsFeatureList &features, Flags ) updateExtents(); if ( res && mJoinBuffer->containsJoins() ) - res = addFeaturesToJoinedLayers( features ); + res = mJoinBuffer->addFeatures( features ); return res; } -bool QgsVectorLayer::addFeaturesToJoinedLayers( QgsFeatureList &features, Flags ) -{ - // try to add/update a feature in each joined layer - Q_FOREACH ( const QgsVectorLayerJoinInfo &info, vectorJoins() ) - { - QgsVectorLayer *joinLayer = info.joinLayer(); - - if ( joinLayer && joinLayer->isEditable() && info.isEditable() && info.hasUpsertOnEdit() ) - { - QgsFeatureList joinFeatures; - - Q_FOREACH ( const QgsFeature &feature, features ) - { - const QgsFeature joinFeature = info.extractJoinedFeature( feature ); - - // we don't want to add a new feature in joined layer when the id - // column value yet exist, we just want to update the existing one - const QVariant idFieldValue = feature.attribute( info.targetFieldName() ); - const QString filter = QgsExpression::createFieldEqualityExpression( info.joinFieldName(), idFieldValue.toString() ); - - QgsFeatureRequest request; - request.setFilterExpression( filter ); - request.setLimit( 1 ); - - QgsFeatureIterator it = info.joinLayer()->getFeatures( request ); - QgsFeature existingFeature; - it.nextFeature( existingFeature ); - - if ( existingFeature.isValid() ) - { - const QStringList *subsetFields = info.joinFieldNamesSubset(); - if ( subsetFields ) - { - Q_FOREACH ( const QString &field, *subsetFields ) - existingFeature.setAttribute( field, joinFeature.attribute( field ) ); - } - else - { - Q_FOREACH ( const QgsField &field, joinFeature.fields() ) - existingFeature.setAttribute( field.name(), joinFeature.attribute( field.name() ) ); - } - - joinLayer->updateFeature( existingFeature ); - } - else - { - // joined feature is added only if one of its field is not null - bool notNullFields = false; - Q_FOREACH ( const QgsField &field, joinFeature.fields() ) - { - if ( field.name() == info.joinFieldName() ) - continue; - - if ( !joinFeature.attribute( field.name() ).isNull() ) - { - notNullFields = true; - break; - } - } - - if ( notNullFields ) - joinFeatures << joinFeature; - } - } - - joinLayer->addFeatures( joinFeatures ); - } - } - - return true; -} - void QgsVectorLayer::setCoordinateSystem() { QgsDebugMsg( "----- Computing Coordinate System" ); diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index 28fdc70b64df..69d1488bcd4b 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -1932,9 +1932,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte //! Read simple labeling from layer's custom properties (QGIS 2.x projects) QgsAbstractVectorLayerLabeling *readLabelingFromCustomProperties(); - bool addFeaturesToJoinedLayers( QgsFeatureList &features, Flags flags = 0 ); - bool deleteFeaturesFromJoinedLayers( QgsFeatureIds fids ); - #ifdef SIP_RUN QgsVectorLayer( const QgsVectorLayer &rhs ); #endif diff --git a/src/core/qgsvectorlayerjoinbuffer.cpp b/src/core/qgsvectorlayerjoinbuffer.cpp index 86ed5c154449..1f1d81348721 100644 --- a/src/core/qgsvectorlayerjoinbuffer.cpp +++ b/src/core/qgsvectorlayerjoinbuffer.cpp @@ -517,3 +517,137 @@ void QgsVectorLayerJoinBuffer::connectJoinedLayer( QgsVectorLayer *vl ) connect( vl, &QgsVectorLayer::layerModified, this, &QgsVectorLayerJoinBuffer::joinedLayerModified, Qt::UniqueConnection ); connect( vl, &QgsVectorLayer::willBeDeleted, this, &QgsVectorLayerJoinBuffer::joinedLayerWillBeDeleted, Qt::UniqueConnection ); } + +bool QgsVectorLayerJoinBuffer::addFeature( const QgsFeature &feature ) const +{ + return addFeatures( QgsFeatureList() << feature ); +} + +bool QgsVectorLayerJoinBuffer::addFeatures( const QgsFeatureList &features ) const +{ + if ( !containsJoins() ) + return false; + + // try to add/update a feature in each joined layer + Q_FOREACH ( const QgsVectorLayerJoinInfo &info, vectorJoins() ) + { + QgsVectorLayer *joinLayer = info.joinLayer(); + + if ( joinLayer && joinLayer->isEditable() && info.isEditable() && info.hasUpsertOnEdit() ) + { + QgsFeatureList joinFeatures; + + Q_FOREACH ( const QgsFeature &feature, features ) + { + const QgsFeature joinFeature = info.extractJoinedFeature( feature ); + + // we don't want to add a new feature in joined layer when the id + // column value yet exist, we just want to update the existing one + const QVariant idFieldValue = feature.attribute( info.targetFieldName() ); + const QString filter = QgsExpression::createFieldEqualityExpression( info.joinFieldName(), idFieldValue.toString() ); + + QgsFeatureRequest request; + request.setFilterExpression( filter ); + request.setLimit( 1 ); + + QgsFeatureIterator it = info.joinLayer()->getFeatures( request ); + QgsFeature existingFeature; + it.nextFeature( existingFeature ); + + if ( existingFeature.isValid() ) + { + const QStringList *subsetFields = info.joinFieldNamesSubset(); + if ( subsetFields ) + { + Q_FOREACH ( const QString &field, *subsetFields ) + existingFeature.setAttribute( field, joinFeature.attribute( field ) ); + } + else + { + Q_FOREACH ( const QgsField &field, joinFeature.fields() ) + existingFeature.setAttribute( field.name(), joinFeature.attribute( field.name() ) ); + } + + joinLayer->updateFeature( existingFeature ); + } + else + { + // joined feature is added only if one of its field is not null + bool notNullFields = false; + Q_FOREACH ( const QgsField &field, joinFeature.fields() ) + { + if ( field.name() == info.joinFieldName() ) + continue; + + if ( !joinFeature.attribute( field.name() ).isNull() ) + { + notNullFields = true; + break; + } + } + + if ( notNullFields ) + joinFeatures << joinFeature; + } + } + + joinLayer->addFeatures( joinFeatures ); + } + } + + return true; +} + +bool QgsVectorLayerJoinBuffer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue ) const +{ + if ( mLayer->fields().fieldOrigin( field ) != QgsFields::OriginJoin ) + return false; + + int srcFieldIndex; + const QgsVectorLayerJoinInfo *info = joinForFieldIndex( field, mLayer->fields(), srcFieldIndex ); + if ( info && info->joinLayer() && info->isEditable() ) + { + QgsFeature feature = mLayer->getFeature( fid ); + + if ( !feature.isValid() ) + return false; + + const QgsFeature joinFeature = joinedFeatureOf( info, feature ); + + if ( joinFeature.isValid() ) + return info->joinLayer()->changeAttributeValue( joinFeature.id(), srcFieldIndex, newValue, oldValue ); + else + { + feature.setAttribute( field, newValue ); + return addFeatures( QgsFeatureList() << feature ); + } + } + else + return false; +} + +bool QgsVectorLayerJoinBuffer::deleteFeature( QgsFeatureId fid ) const +{ + return deleteFeatures( QgsFeatureIds() << fid ); +} + +bool QgsVectorLayerJoinBuffer::deleteFeatures( const QgsFeatureIds &fids ) const +{ + if ( !containsJoins() ) + return false; + + Q_FOREACH ( const QgsFeatureId &fid, fids ) + { + Q_FOREACH ( const QgsVectorLayerJoinInfo &info, vectorJoins() ) + { + if ( info.isEditable() && info.hasCascadedDelete() ) + { + const QgsFeature joinFeature = joinedFeatureOf( &info, mLayer->getFeature( fid ) ); + if ( joinFeature.isValid() ) + info.joinLayer()->deleteFeature( joinFeature.id() ); + } + } + } + + return true; +} diff --git a/src/core/qgsvectorlayerjoinbuffer.h b/src/core/qgsvectorlayerjoinbuffer.h index 02ed58194a79..e38dcefe704d 100644 --- a/src/core/qgsvectorlayerjoinbuffer.h +++ b/src/core/qgsvectorlayerjoinbuffer.h @@ -109,6 +109,77 @@ class CORE_EXPORT QgsVectorLayerJoinBuffer : public QObject //! \since QGIS 2.6 QgsVectorLayerJoinBuffer *clone() const SIP_FACTORY; + /** + * Adds a feature in joined layers. The feature given in parameter is the + * one added in target layer. If a corresponding joined feature yet exists + * in a joined layer, then this feature is just updated. Note that if a + * corresponding joined feature has only empty fields, then it's not + * created nor added. + * + * \param feature The feature added in the target layer. + * + * \returns false if an error happened, true otherwise + * + * \since QGIS 3.0 + */ + bool addFeature( const QgsFeature &feature ) const; + + /** + * Adds a list of features in joined layers. Features given in parameter + * are those added in target layer. If a corresponding joined feature yet + * exists in a joined layer, then this feature is just updated. Note that + * if a corresponding joined feature has only empty fields, then it's not + * created nor added. + * + * \param features The list of features added in the target layer + * + * \returns false if an error happened, true otherwise + * + * \since QGIS 3.0 + */ + bool addFeatures( const QgsFeatureList &features ) const; + + /** + * Changes attribute value in joined layers. The feature id given in + * parameter is the one added in target layer. If the corresponding joined + * feature does not exist in a joined layer, then it's automatically + * created if its fields are not empty. + * + * \param fid The feature id + * \param field The field to update + * \param newValue The new value of the attribute + * \param oldValue The old value of the attribute + * + * \returns false if an error happened, true otherwise + * + * \since QGIS 3.0 + */ + bool changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue = QVariant() ) const; + + /** + * Deletes a feature from joined layers. The feature id given in + * parameter is the one coming from the target layer. + * + * \param fid The feature id from the target layer to delete + * + * \returns false if an error happened, true otherwise + * + * \since QGIS 3.0 + */ + bool deleteFeature( QgsFeatureId fid ) const; + + /** + * Deletes a list of features from joined layers. Feature ids given + * in aprameter are those coming from the target layer. + * + * \param fids Feature ids from the target layer to delete + * + * \returns false if an error happened, true otherwise + * + * \since QGIS 3.0 + */ + bool deleteFeatures( const QgsFeatureIds &fids ) const; + signals: //! Emitted whenever the list of joined fields changes (e.g. added join or joined layer's fields change) //! \since QGIS 2.6