diff --git a/python/core/qgsvectordataprovider.sip b/python/core/qgsvectordataprovider.sip index ad7cd78fab3e..098c2c940baf 100644 --- a/python/core/qgsvectordataprovider.sip +++ b/python/core/qgsvectordataprovider.sip @@ -39,6 +39,7 @@ class QgsVectorDataProvider : QgsDataProvider RandomSelectGeometryAtId, /** DEPRECATED - do not use */ SequentialSelectGeometryAtId, + /** DEPRECATED - do not use */ CreateAttributeIndex, /** Allows user to select encoding */ SelectEncoding, @@ -47,7 +48,13 @@ class QgsVectorDataProvider : QgsDataProvider /** Supports topological simplification of geometries on provider side according to a distance tolerance */ SimplifyGeometriesWithTopologicalValidation, /** Supports transactions*/ - TransactionSupport + TransactionSupport, + /** Supports circular geometry types (circularstring, compoundcurve, curvepolygon)*/ + CircularGeometries, + /** Supports joint updates for attributes and geometry + * Providers supporting this should still define ChangeGeometries | ChangeAttributeValues + */ + ChangeFeatures }; /** Bitmask of all provider's editing capabilities */ @@ -189,6 +196,17 @@ class QgsVectorDataProvider : QgsDataProvider */ virtual bool changeAttributeValues( const QMap > &attr_map ); + /** + * Changes attribute values and geometries of existing features. + * @param attr_map a map containing changed attributes + * @param geometry_map A QgsGeometryMap whose index contains the feature IDs + * that will have their geometries changed. + * The second map parameter being the new geometries themselves + * @return true in case of success and false in case of failure + */ + virtual bool changeFeatures( const QMap > &attr_map, + const QMap &geometry_map ); + /** * Returns the default value for field specified by @c fieldId */ @@ -201,7 +219,7 @@ class QgsVectorDataProvider : QgsDataProvider * The second map parameter being the new geometries themselves * @return True in case of success and false in case of failure */ - virtual bool changeGeometryValues( QMap & geometry_map ); + virtual bool changeGeometryValues( const QMap &geometry_map ); /** * Creates a spatial index on the datasource (if supported by the provider type). @@ -320,6 +338,13 @@ class QgsVectorDataProvider : QgsDataProvider */ virtual QgsTransaction* transaction() const; + /** + * Forces a reload of the underlying datasource if the provider implements this + * method. + * In particular on the OGR provider, a pooled connection will be invalidated. + * This forces QGIS to reopen a file or connection. + * This can be required if the underlying file is replaced. + */ virtual void forceReload(); /** diff --git a/src/core/qgsvectordataprovider.cpp b/src/core/qgsvectordataprovider.cpp index d573e8db8a99..372a74555991 100644 --- a/src/core/qgsvectordataprovider.cpp +++ b/src/core/qgsvectordataprovider.cpp @@ -87,12 +87,20 @@ QVariant QgsVectorDataProvider::defaultValue( int fieldId ) return QVariant(); } -bool QgsVectorDataProvider::changeGeometryValues( QgsGeometryMap &geometry_map ) +bool QgsVectorDataProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) { Q_UNUSED( geometry_map ); return false; } +bool QgsVectorDataProvider::changeFeatures( const QgsChangedAttributesMap &attr_map, + const QgsGeometryMap &geometry_map ) +{ + Q_UNUSED( attr_map ); + Q_UNUSED( geometry_map ); + return false; +} + bool QgsVectorDataProvider::createSpatialIndex() { return false; @@ -206,8 +214,13 @@ QString QgsVectorDataProvider::capabilitiesString() const QgsDebugMsg( "Capability: Simplify Geometries before fetch the feature ensuring that the result is a valid geometry" ); } - return abilitiesList.join( ", " ); + if ( abilities & QgsVectorDataProvider::ChangeFeatures ) + { + abilitiesList += tr( "Change Geometries and Attributes at once" ); + QgsDebugMsg( "Capability: change attributes and geometries at once" ); + } + return abilitiesList.join( ", " ); } diff --git a/src/core/qgsvectordataprovider.h b/src/core/qgsvectordataprovider.h index e58fbfec8287..2c5055c5e771 100644 --- a/src/core/qgsvectordataprovider.h +++ b/src/core/qgsvectordataprovider.h @@ -61,42 +61,47 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider enum Capability { /** Provider has no capabilities */ - NoCapabilities = 0, + NoCapabilities = 0, /** Allows adding features */ - AddFeatures = 1, + AddFeatures = 1, /** Allows deletion of features */ - DeleteFeatures = 1 << 1, + DeleteFeatures = 1 << 1, /** Allows modification of attribute values */ - ChangeAttributeValues = 1 << 2, + ChangeAttributeValues = 1 << 2, /** Allows addition of new attributes (fields) */ - AddAttributes = 1 << 3, + AddAttributes = 1 << 3, /** Allows deletion of attributes (fields) */ - DeleteAttributes = 1 << 4, + DeleteAttributes = 1 << 4, /** DEPRECATED - do not use */ - SaveAsShapefile = 1 << 5, + SaveAsShapefile = 1 << 5, /** Allows creation of spatial index */ - CreateSpatialIndex = 1 << 6, + CreateSpatialIndex = 1 << 6, /** Fast access to features using their ID */ - SelectAtId = 1 << 7, + SelectAtId = 1 << 7, /** Allows modifications of geometries */ - ChangeGeometries = 1 << 8, + ChangeGeometries = 1 << 8, /** DEPRECATED - do not use */ - SelectGeometryAtId = 1 << 9, + SelectGeometryAtId = 1 << 9, /** DEPRECATED - do not use */ - RandomSelectGeometryAtId = 1 << 10, + RandomSelectGeometryAtId = 1 << 10, /** DEPRECATED - do not use */ - SequentialSelectGeometryAtId = 1 << 11, - CreateAttributeIndex = 1 << 12, + SequentialSelectGeometryAtId = 1 << 11, + /** DEPRECATED - do not use */ + CreateAttributeIndex = 1 << 12, /** Allows user to select encoding */ - SelectEncoding = 1 << 13, + SelectEncoding = 1 << 13, /** Supports simplification of geometries on provider side according to a distance tolerance */ - SimplifyGeometries = 1 << 14, + SimplifyGeometries = 1 << 14, /** Supports topological simplification of geometries on provider side according to a distance tolerance */ SimplifyGeometriesWithTopologicalValidation = 1 << 15, /** Supports transactions*/ - TransactionSupport = 1 << 16, + TransactionSupport = 1 << 16, /** Supports circular geometry types (circularstring, compoundcurve, curvepolygon)*/ - CircularGeometries = 1 << 17 + CircularGeometries = 1 << 17, + /** Supports joint updates for attributes and geometry + * Providers supporting this should still define ChangeGeometries | ChangeAttributeValues + */ + ChangeFeatures = 1 << 18 }; /** Bitmask of all provider's editing capabilities */ @@ -239,6 +244,16 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider */ virtual bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ); + /** + * Changes attribute values and geometries of existing features. + * @param attr_map a map containing changed attributes + * @param geometry_map A QgsGeometryMap whose index contains the feature IDs + * that will have their geometries changed. + * The second map parameter being the new geometries themselves + * @return true in case of success and false in case of failure + */ + virtual bool changeFeatures( const QgsChangedAttributesMap &attr_map, const QgsGeometryMap &geometry_map ); + /** * Returns the default value for field specified by @c fieldId */ @@ -251,7 +266,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider * The second map parameter being the new geometries themselves * @return True in case of success and false in case of failure */ - virtual bool changeGeometryValues( QgsGeometryMap & geometry_map ); + virtual bool changeGeometryValues( const QgsGeometryMap &geometry_map ); /** * Creates a spatial index on the datasource (if supported by the provider type). diff --git a/src/core/qgsvectorlayereditbuffer.cpp b/src/core/qgsvectorlayereditbuffer.cpp index 919a4af5fc1b..60bf0dc985d6 100644 --- a/src/core/qgsvectorlayereditbuffer.cpp +++ b/src/core/qgsvectorlayereditbuffer.cpp @@ -271,17 +271,21 @@ bool QgsVectorLayerEditBuffer::commitChanges( QStringList& commitErrors ) int cap = provider->capabilities(); bool success = true; + // geometry updates attribute updates + // yes no => changeGeometryValues + // no yes => changeAttributeValues + // yes yes => changeFeatures + // // update geometries // - if ( !mChangedGeometries.isEmpty() ) + if ( !mChangedGeometries.isEmpty() && (( cap & QgsVectorDataProvider::ChangeFeatures ) == 0 || mChangedAttributeValues.isEmpty() ) ) { - if (( cap & QgsVectorDataProvider::ChangeGeometries ) && provider->changeGeometryValues( mChangedGeometries ) ) + if ( provider->changeGeometryValues( mChangedGeometries ) ) { commitErrors << tr( "SUCCESS: %n geometries were changed.", "changed geometries count", mChangedGeometries.size() ); emit committedGeometriesChanges( L->id(), mChangedGeometries ); - mChangedGeometries.clear(); } else @@ -398,36 +402,56 @@ bool QgsVectorLayerEditBuffer::commitChanges( QStringList& commitErrors ) if ( attributeChangesOk ) { - // - // change attributes - // - if ( !mChangedAttributeValues.isEmpty() ) + if ( cap & QgsVectorDataProvider::ChangeFeatures && !mChangedGeometries.isEmpty() && !mChangedAttributeValues.isEmpty() ) { - if (( cap & QgsVectorDataProvider::ChangeAttributeValues ) && provider->changeAttributeValues( mChangedAttributeValues ) ) - { - commitErrors << tr( "SUCCESS: %n attribute value(s) changed.", "changed attribute values count", mChangedAttributeValues.size() ); + Q_ASSERT(( cap & ( QgsVectorDataProvider::ChangeAttributeValues | QgsVectorDataProvider::ChangeGeometries ) ) == ( QgsVectorDataProvider::ChangeAttributeValues | QgsVectorDataProvider::ChangeGeometries ) ); + if ( provider->changeFeatures( mChangedAttributeValues, mChangedGeometries ) ) + { + commitErrors << tr( "SUCCESS: %1 attribute value(s) and %2 geometries changed." ).arg( mChangedAttributeValues.size(), mChangedGeometries.size() ); emit committedAttributeValuesChanges( L->id(), mChangedAttributeValues ); - mChangedAttributeValues.clear(); + + emit committedGeometriesChanges( L->id(), mChangedGeometries ); + mChangedGeometries.clear(); } else { - commitErrors << tr( "ERROR: %n attribute value change(s) not applied.", "not changed attribute values count", mChangedAttributeValues.size() ); -#if 0 - QString list = "ERROR: pending changes:"; - Q_FOREACH ( QgsFeatureId id, mChangedAttributeValues.keys() ) + success = false; + } + } + else + { + // + // change attributes + // + if ( !mChangedAttributeValues.isEmpty() && (( cap & QgsVectorDataProvider::ChangeFeatures ) == 0 || mChangedGeometries.isEmpty() ) ) + { + if (( cap & QgsVectorDataProvider::ChangeAttributeValues ) && provider->changeAttributeValues( mChangedAttributeValues ) ) + { + commitErrors << tr( "SUCCESS: %n attribute value(s) changed.", "changed attribute values count", mChangedAttributeValues.size() ); + + emit committedAttributeValuesChanges( L->id(), mChangedAttributeValues ); + mChangedAttributeValues.clear(); + } + else { - list.append( "\n " + FID_TO_STRING( id ) + '[' ); - Q_FOREACH ( int idx, mChangedAttributeValues[ id ].keys() ) + commitErrors << tr( "ERROR: %n attribute value change(s) not applied.", "not changed attribute values count", mChangedAttributeValues.size() ); +#if 0 + QString list = "ERROR: pending changes:"; + Q_FOREACH ( QgsFeatureId id, mChangedAttributeValues.keys() ) { - list.append( QString( " %1:%2" ).arg( L->pendingFields().at( idx ).name() ).arg( mChangedAttributeValues[id][idx].toString() ) ); + list.append( "\n " + FID_TO_STRING( id ) + '[' ); + Q_FOREACH ( int idx, mChangedAttributeValues[ id ].keys() ) + { + list.append( QString( " %1:%2" ).arg( L->pendingFields().at( idx ).name() ).arg( mChangedAttributeValues[id][idx].toString() ) ); + } + list.append( " ]" ); } - list.append( " ]" ); - } - commitErrors << list; + commitErrors << list; #endif - success = false; + success = false; + } } } diff --git a/src/providers/grass/qgsgrassprovider.h b/src/providers/grass/qgsgrassprovider.h index 5009929e6363..2dd4fde4f414 100644 --- a/src/providers/grass/qgsgrassprovider.h +++ b/src/providers/grass/qgsgrassprovider.h @@ -133,7 +133,7 @@ class GRASS_LIB_EXPORT QgsGrassProvider : public QgsVectorDataProvider virtual bool addAttributes( const QList &attributes ) override; virtual bool deleteAttributes( const QgsAttributeIds &attributes ) override; virtual bool changeAttributeValues( const QgsChangedAttributesMap & attr_map ) override { Q_UNUSED( attr_map ); return true; } - virtual bool changeGeometryValues( QgsGeometryMap & geometry_map ) override { Q_UNUSED( geometry_map ); return true; } + virtual bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override { Q_UNUSED( geometry_map ); return true; } //---------------------------------------------------------------------------- diff --git a/src/providers/memory/qgsmemoryprovider.cpp b/src/providers/memory/qgsmemoryprovider.cpp index 8710993a9f71..c0d4ad84cebe 100644 --- a/src/providers/memory/qgsmemoryprovider.cpp +++ b/src/providers/memory/qgsmemoryprovider.cpp @@ -392,7 +392,7 @@ bool QgsMemoryProvider::deleteAttributes( const QgsAttributeIds& attributes ) return true; } -bool QgsMemoryProvider::changeAttributeValues( const QgsChangedAttributesMap & attr_map ) +bool QgsMemoryProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_map ) { for ( QgsChangedAttributesMap::const_iterator it = attr_map.begin(); it != attr_map.end(); ++it ) { @@ -407,7 +407,7 @@ bool QgsMemoryProvider::changeAttributeValues( const QgsChangedAttributesMap & a return true; } -bool QgsMemoryProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) +bool QgsMemoryProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) { for ( QgsGeometryMap::const_iterator it = geometry_map.begin(); it != geometry_map.end(); ++it ) { diff --git a/src/providers/memory/qgsmemoryprovider.h b/src/providers/memory/qgsmemoryprovider.h index 7d66791c792f..78f18003738d 100644 --- a/src/providers/memory/qgsmemoryprovider.h +++ b/src/providers/memory/qgsmemoryprovider.h @@ -109,7 +109,7 @@ class QgsMemoryProvider : public QgsVectorDataProvider * the second map parameter being the new geometries themselves * @return true in case of success and false in case of failure */ - virtual bool changeGeometryValues( QgsGeometryMap & geometry_map ) override; + virtual bool changeGeometryValues( const QgsGeometryMap & geometry_map ) override; /** Accessor for sql where clause used to limit dataset */ QString subsetString() override; diff --git a/src/providers/mssql/qgsmssqlprovider.cpp b/src/providers/mssql/qgsmssqlprovider.cpp index 7d4defe8b25b..98f302780def 100644 --- a/src/providers/mssql/qgsmssqlprovider.cpp +++ b/src/providers/mssql/qgsmssqlprovider.cpp @@ -1167,7 +1167,7 @@ bool QgsMssqlProvider::changeAttributeValues( const QgsChangedAttributesMap &att return true; } -bool QgsMssqlProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) +bool QgsMssqlProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) { if ( geometry_map.isEmpty() ) return true; @@ -1175,7 +1175,7 @@ bool QgsMssqlProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) if ( mFidColName.isEmpty() ) return false; - for ( QgsGeometryMap::iterator it = geometry_map.begin(); it != geometry_map.end(); ++it ) + for ( QgsGeometryMap::const_iterator it = geometry_map.constBegin(); it != geometry_map.constEnd(); ++it ) { QgsFeatureId fid = it.key(); // skip added features diff --git a/src/providers/mssql/qgsmssqlprovider.h b/src/providers/mssql/qgsmssqlprovider.h index 83399b97131c..5046c97078af 100644 --- a/src/providers/mssql/qgsmssqlprovider.h +++ b/src/providers/mssql/qgsmssqlprovider.h @@ -207,10 +207,10 @@ class QgsMssqlProvider : public QgsVectorDataProvider virtual bool deleteAttributes( const QgsAttributeIds &attributes ) override; /** Changes attribute values of existing features */ - virtual bool changeAttributeValues( const QgsChangedAttributesMap & attr_map ) override; + virtual bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override; /** Changes existing geometries*/ - virtual bool changeGeometryValues( QgsGeometryMap & geometry_map ) override; + virtual bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override; /** * Create a spatial index for the current layer diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp index 201f090df1a3..0862748b0c73 100644 --- a/src/providers/ogr/qgsogrprovider.cpp +++ b/src/providers/ogr/qgsogrprovider.cpp @@ -1261,14 +1261,14 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_ return true; } -bool QgsOgrProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) +bool QgsOgrProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) { OGRFeatureH theOGRFeature = nullptr; OGRGeometryH theNewGeometry = nullptr; setRelevantFields( ogrLayer, true, attributeIndexes() ); - for ( QgsGeometryMap::iterator it = geometry_map.begin(); it != geometry_map.end(); ++it ) + for ( QgsGeometryMap::const_iterator it = geometry_map.constBegin(); it != geometry_map.constEnd(); ++it ) { if ( FID_TO_NUMBER( it.key() ) > std::numeric_limits::max() ) { diff --git a/src/providers/ogr/qgsogrprovider.h b/src/providers/ogr/qgsogrprovider.h index ab8f8131761f..50e7e3a1ca7b 100644 --- a/src/providers/ogr/qgsogrprovider.h +++ b/src/providers/ogr/qgsogrprovider.h @@ -152,10 +152,10 @@ class QgsOgrProvider : public QgsVectorDataProvider virtual bool deleteAttributes( const QgsAttributeIds &attributes ) override; /** Changes attribute values of existing features */ - virtual bool changeAttributeValues( const QgsChangedAttributesMap & attr_map ) override; + virtual bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override; /** Changes existing geometries*/ - virtual bool changeGeometryValues( QgsGeometryMap & geometry_map ) override; + virtual bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override; /** Tries to create a .qix index file for faster access if only a subset of the features is required @return true in case of success*/ diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp index d75c6187f693..643d11924d0c 100644 --- a/src/providers/oracle/qgsoracleprovider.cpp +++ b/src/providers/oracle/qgsoracleprovider.cpp @@ -1889,7 +1889,7 @@ void QgsOracleProvider::appendGeomParam( const QgsGeometry *geom, QSqlQuery &qry qry.addBindValue( QVariant::fromValue( g ) ); } -bool QgsOracleProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) +bool QgsOracleProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) { QgsDebugMsg( "entering." ); @@ -1919,8 +1919,8 @@ bool QgsOracleProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) throw OracleException( tr( "Could not prepare update statement." ), qry ); } - for ( QgsGeometryMap::iterator iter = geometry_map.begin(); - iter != geometry_map.end(); + for ( QgsGeometryMap::const_iterator iter = geometry_map.constBegin(); + iter != geometry_map.constEnd(); ++iter ) { appendGeomParam( &iter.value(), qry ); diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 00ff9025e03c..2b71259da7de 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -1143,6 +1143,14 @@ bool QgsPostgresProvider::hasSufficientPermsAndCapabilities() // supports circular geometries mEnabledCapabilities |= QgsVectorDataProvider::CircularGeometries; + + if (( mEnabledCapabilities & QgsVectorDataProvider::ChangeGeometries ) && + ( mEnabledCapabilities & QgsVectorDataProvider::ChangeAttributeValues ) && + mSpatialColType != sctTopoGeometry ) + { + mEnabledCapabilities |= QgsVectorDataProvider::ChangeFeatures; + } + return true; } @@ -1214,7 +1222,7 @@ bool QgsPostgresProvider::determinePrimaryKey() mPrimaryKeyType = pktTid; QgsMessageLog::logMessage( tr( "Primary key is ctid - changing of existing features disabled (%1; %2)" ).arg( mGeometryColumn, mQuery ) ); - mEnabledCapabilities &= ~( QgsVectorDataProvider::DeleteFeatures | QgsVectorDataProvider::ChangeAttributeValues | QgsVectorDataProvider::ChangeGeometries ); + mEnabledCapabilities &= ~( QgsVectorDataProvider::DeleteFeatures | QgsVectorDataProvider::ChangeAttributeValues | QgsVectorDataProvider::ChangeGeometries | QgsVectorDataProvider::ChangeFeatures ); } else { @@ -2192,7 +2200,7 @@ bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap & conn->begin(); // cycle through the features - for ( QgsChangedAttributesMap::const_iterator iter = attr_map.begin(); iter != attr_map.end(); ++iter ) + for ( QgsChangedAttributesMap::const_iterator iter = attr_map.constBegin(); iter != attr_map.constEnd(); ++iter ) { QgsFeatureId fid = iter.key(); @@ -2210,7 +2218,7 @@ bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap & // cycle through the changed attributes of the feature QString delim; - for ( QgsAttributeMap::const_iterator siter = attrs.begin(); siter != attrs.end(); ++siter ) + for ( QgsAttributeMap::const_iterator siter = attrs.constBegin(); siter != attrs.constEnd(); ++siter ) { try { @@ -2302,7 +2310,7 @@ void QgsPostgresProvider::appendGeomParam( const QgsGeometry *geom, QStringList params << param; } -bool QgsPostgresProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) +bool QgsPostgresProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) { QgsDebugMsg( "entering." ); @@ -2389,8 +2397,8 @@ bool QgsPostgresProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) QgsDebugMsg( "iterating over the map of changed geometries..." ); - for ( QgsGeometryMap::iterator iter = geometry_map.begin(); - iter != geometry_map.end(); + for ( QgsGeometryMap::const_iterator iter = geometry_map.constBegin(); + iter != geometry_map.constEnd(); ++iter ) { QgsDebugMsg( "iterating over feature id " + FID_TO_STRING( iter.key() ) ); @@ -2483,7 +2491,152 @@ bool QgsPostgresProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) conn->unlock(); - QgsDebugMsg( "exiting." ); + QgsDebugMsg( "leaving." ); + + return returnvalue; +} + +bool QgsPostgresProvider::changeFeatures( const QgsChangedAttributesMap &attr_map, + const QgsGeometryMap &geometry_map ) +{ + QgsDebugMsg( "entering." ); + Q_ASSERT( mSpatialColType != sctTopoGeometry ); + + bool returnvalue = true; + + if ( mIsQuery ) + return false; + + if ( attr_map.isEmpty() ) + return true; + + QgsPostgresConn *conn = connectionRW(); + if ( !conn ) + return false; + + conn->lock(); + + try + { + conn->begin(); + + QgsFeatureIds ids( attr_map.keys().toSet() ); + ids |= geometry_map.keys().toSet(); + + // cycle through the features + Q_FOREACH ( const QgsFeatureId &fid, ids ) + { + // skip added features + if ( FID_IS_NEW( fid ) ) + continue; + + const QgsAttributeMap &attrs = attr_map.value( fid ); + if ( attrs.isEmpty() && !geometry_map.contains( fid ) ) + continue; + + QString sql = QString( "UPDATE %1 SET " ).arg( mQuery ); + + bool pkChanged = false; + + // cycle through the changed attributes of the feature + QString delim; + for ( QgsAttributeMap::const_iterator siter = attrs.constBegin(); siter != attrs.constEnd(); ++siter ) + { + try + { + QgsField fld = field( siter.key() ); + + pkChanged = pkChanged || mPrimaryKeyAttrs.contains( siter.key() ); + + sql += delim + QString( "%1=" ).arg( quotedIdentifier( fld.name() ) ); + delim = ','; + + if ( fld.typeName() == "geometry" ) + { + sql += QString( "%1(%2)" ) + .arg( connectionRO()->majorVersion() < 2 ? "geomfromewkt" : "st_geomfromewkt", + quotedValue( siter->toString() ) ); + } + else if ( fld.typeName() == "geography" ) + { + sql += QString( "st_geographyfromewkt(%1)" ) + .arg( quotedValue( siter->toString() ) ); + } + else + { + sql += quotedValue( *siter ); + } + } + catch ( PGFieldNotFound ) + { + // Field was missing - shouldn't happen + } + } + + if ( !geometry_map.contains( fid ) ) + { + sql += QString( " WHERE %1" ).arg( whereClause( fid ) ); + + QgsPostgresResult result( conn->PQexec( sql ) ); + if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK ) + throw PGException( result ); + } + else + { + sql += QString( "%1%2=%3" ).arg( delim, quotedIdentifier( mGeometryColumn ), geomParam( 1 ) ); + sql += QString( " WHERE %1" ).arg( whereClause( fid ) ); + + QgsPostgresResult result( conn->PQprepare( "updatefeature", sql, 1, nullptr ) ); + if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK ) + { + QgsDebugMsg( QString( "Exception thrown due to PQprepare of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" ) + .arg( result.PQresultStatus() ).arg( PGRES_COMMAND_OK ).arg( sql ) ); + throw PGException( result ); + } + + QStringList params; + const QgsGeometry &geom = geometry_map[ fid ]; + appendGeomParam( &geom, params ); + + result = conn->PQexecPrepared( "updatefeature", params ); + if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK ) + throw PGException( result ); + + conn->PQexecNR( "DEALLOCATE updatefeature" ); + } + + // update feature id map if key was changed + if ( pkChanged && mPrimaryKeyType == pktFidMap ) + { + QVariant v = mShared->removeFid( fid ); + + QList k = v.toList(); + + for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ ) + { + int idx = mPrimaryKeyAttrs.at( i ); + if ( !attrs.contains( idx ) ) + continue; + + k[i] = attrs[ idx ]; + } + + mShared->insertFid( fid, k ); + } + } + + returnvalue &= conn->commit(); + } + catch ( PGException &e ) + { + pushError( tr( "PostGIS error while changing attributes: %1" ).arg( e.errorMessage() ) ); + conn->rollback(); + returnvalue = false; + } + + conn->unlock(); + + QgsDebugMsg( "leaving." ); return returnvalue; } diff --git a/src/providers/postgres/qgspostgresprovider.h b/src/providers/postgres/qgspostgresprovider.h index d006be6989ff..fb61dea691e6 100644 --- a/src/providers/postgres/qgspostgresprovider.h +++ b/src/providers/postgres/qgspostgresprovider.h @@ -205,13 +205,13 @@ class QgsPostgresProvider : public QgsVectorDataProvider /** Deletes existing attributes @param names of the attributes to delete @return true in case of success and false in case of failure*/ - bool deleteAttributes( const QgsAttributeIds & name ) override; + bool deleteAttributes( const QgsAttributeIds &name ) override; /** Changes attribute values of existing features @param attr_map a map containing the new attributes. The integer is the feature id, the first QString is the attribute name and the second one is the new attribute value @return true in case of success and false in case of failure*/ - bool changeAttributeValues( const QgsChangedAttributesMap & attr_map ) override; + bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override; /** Changes geometries of existing features @@ -219,7 +219,17 @@ class QgsPostgresProvider : public QgsVectorDataProvider the second map parameter being the new geometries themselves @return true in case of success and false in case of failure */ - bool changeGeometryValues( QgsGeometryMap & geometry_map ) override; + bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override; + + /** + * Changes attribute values and geometries of existing features. + * @param attr_map a map containing changed attributes + * @param geometry_map A QgsGeometryMap whose index contains the feature IDs + * that will have their geometries changed. + * The second map parameter being the new geometries themselves + * @return true in case of success and false in case of failure + */ + bool changeFeatures( const QgsChangedAttributesMap &attr_map, const QgsGeometryMap &geometry_map ) override; //! Get the postgres connection PGconn * pgConnection(); diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index ffb488ee1f3c..488fb3a0f5d1 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -3978,7 +3978,7 @@ bool QgsSpatiaLiteProvider::changeAttributeValues( const QgsChangedAttributesMap return false; } -bool QgsSpatiaLiteProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) +bool QgsSpatiaLiteProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) { sqlite3_stmt *stmt = nullptr; char *errMsg = nullptr; @@ -4007,7 +4007,7 @@ bool QgsSpatiaLiteProvider::changeGeometryValues( QgsGeometryMap & geometry_map return false; } - for ( QgsGeometryMap::iterator iter = geometry_map.begin(); iter != geometry_map.end(); ++iter ) + for ( QgsGeometryMap::const_iterator iter = geometry_map.constBegin(); iter != geometry_map.constEnd(); ++iter ) { // resetting Prepared Statement and bindings sqlite3_reset( stmt ); diff --git a/src/providers/spatialite/qgsspatialiteprovider.h b/src/providers/spatialite/qgsspatialiteprovider.h index 2a2cb7b24414..141efe0c710d 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.h +++ b/src/providers/spatialite/qgsspatialiteprovider.h @@ -176,7 +176,7 @@ class QgsSpatiaLiteProvider: public QgsVectorDataProvider @param attr_map a map containing the new attributes. The integer is the feature id, the first QString is the attribute name and the second one is the new attribute value @return true in case of success and false in case of failure*/ - bool changeAttributeValues( const QgsChangedAttributesMap & attr_map ) override; + bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override; /** Changes geometries of existing features @@ -184,7 +184,7 @@ class QgsSpatiaLiteProvider: public QgsVectorDataProvider the second map parameter being the new geometries themselves @return true in case of success and false in case of failure */ - bool changeGeometryValues( QgsGeometryMap & geometry_map ) override; + bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override; /** Returns a bitmask containing the supported capabilities*/ int capabilities() const override; diff --git a/src/providers/wfs/qgswfsprovider.cpp b/src/providers/wfs/qgswfsprovider.cpp index 55b2db4c1d55..dd6b17c81abf 100644 --- a/src/providers/wfs/qgswfsprovider.cpp +++ b/src/providers/wfs/qgswfsprovider.cpp @@ -466,7 +466,7 @@ bool QgsWFSProvider::deleteFeatures( const QgsFeatureIds &id ) } } -bool QgsWFSProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) +bool QgsWFSProvider::changeGeometryValues( const QgsGeometryMap &geometry_map ) { //find out typename from uri and strip namespace prefix QString tname = parameterFromUrl( "typename" ); @@ -480,8 +480,8 @@ bool QgsWFSProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) QDomElement transactionElem = createTransactionElement( transactionDoc ); transactionDoc.appendChild( transactionElem ); - QgsGeometryMap::iterator geomIt = geometry_map.begin(); - for ( ; geomIt != geometry_map.end(); ++geomIt ) + QgsGeometryMap::const_iterator geomIt = geometry_map.constBegin(); + for ( ; geomIt != geometry_map.constEnd(); ++geomIt ) { //find out feature id QMap< QgsFeatureId, QString >::const_iterator fidIt = mIdMap.constFind( geomIt.key() ); diff --git a/src/providers/wfs/qgswfsprovider.h b/src/providers/wfs/qgswfsprovider.h index 928387e70e01..9cda43788f96 100644 --- a/src/providers/wfs/qgswfsprovider.h +++ b/src/providers/wfs/qgswfsprovider.h @@ -135,7 +135,7 @@ class QgsWFSProvider : public QgsVectorDataProvider * The second map parameter being the new geometries themselves * @return True in case of success and false in case of failure */ - virtual bool changeGeometryValues( QgsGeometryMap & geometry_map ) override; + virtual bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override; /** * Changes attribute values of existing features.