Skip to content

Commit

Permalink
[FEATURE] Add QgsVectorDataProvider::truncate for clearing layers
Browse files Browse the repository at this point in the history
Adds a new method to QgsVectorDataProvider to truncate the layer.
The base implementation requires DeleteFeatures capability and
is not optimised. Providers can return the FastTruncate capability
and override the base implementation with a provider specific
optimised version. This is done in this commit for the Postgres
and Spatialite providers.
  • Loading branch information
nyalldawson committed Dec 27, 2016
1 parent b256075 commit bc98a32
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 46 deletions.
16 changes: 14 additions & 2 deletions python/core/qgsvectordataprovider.sip
Expand Up @@ -49,6 +49,8 @@ class QgsVectorDataProvider : QgsDataProvider
ChangeFeatures,
/** Supports renaming attributes (fields). Added in QGIS 2.16 */
RenameAttributes,
//! Supports fast truncation of the layer (removing all features). Added in QGIS 3.0
FastTruncate,
};
typedef QFlags<QgsVectorDataProvider::Capability> Capabilities;

Expand Down Expand Up @@ -191,11 +193,21 @@ class QgsVectorDataProvider : QgsDataProvider
virtual bool addFeatures( QList<QgsFeature> &flist /In,Out/ );

/**
* Deletes one or more features
* Deletes one or more features from the provider. This requires the DeleteFeatures capability.
* @param id list containing feature ids to delete
* @return true in case of success and false in case of failure
* @see truncate()
*/
virtual bool deleteFeatures( const QSet<qint64> &id );
virtual bool deleteFeatures( const QgsFeatureIds &id );

/**
* Removes all features from the layer. This requires either the FastTruncate or DeleteFeatures capability.
* Providers with the FastTruncate capability will use an optimised method to truncate the layer.
* @returns true in case of success and false in case of failure.
* @note added in QGIS 3.0
* @see deleteFeatures()
*/
virtual bool truncate();

/**
* Adds new attributes
Expand Down
14 changes: 14 additions & 0 deletions src/core/qgsvectordataprovider.cpp
Expand Up @@ -69,6 +69,20 @@ bool QgsVectorDataProvider::deleteFeatures( const QgsFeatureIds &ids )
return false;
}

bool QgsVectorDataProvider::truncate()
{
if ( !( capabilities() & DeleteFeatures ) )
return false;

QgsFeatureIds toDelete;
QgsFeatureIterator it = getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
QgsFeature f;
while ( it.nextFeature( f ) )
toDelete << f.id();

return deleteFeatures( toDelete );
}

bool QgsVectorDataProvider::addAttributes( const QList<QgsField> &attributes )
{
Q_UNUSED( attributes );
Expand Down
14 changes: 13 additions & 1 deletion src/core/qgsvectordataprovider.h
Expand Up @@ -100,6 +100,8 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
ChangeFeatures = 1 << 18,
//! Supports renaming attributes (fields). Added in QGIS 2.16
RenameAttributes = 1 << 19,
//! Supports fast truncation of the layer (removing all features). Added in QGIS 3.0
FastTruncate = 1 << 20,
};

Q_DECLARE_FLAGS( Capabilities, Capability )
Expand Down Expand Up @@ -245,12 +247,22 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
virtual bool addFeatures( QgsFeatureList &flist );

/**
* Deletes one or more features
* Deletes one or more features from the provider. This requires the DeleteFeatures capability.
* @param id list containing feature ids to delete
* @return true in case of success and false in case of failure
* @see truncate()
*/
virtual bool deleteFeatures( const QgsFeatureIds &id );

/**
* Removes all features from the layer. This requires either the FastTruncate or DeleteFeatures capability.
* Providers with the FastTruncate capability will use an optimised method to truncate the layer.
* @returns true in case of success and false in case of failure.
* @note added in QGIS 3.0
* @see deleteFeatures()
*/
virtual bool truncate();

/**
* Adds new attributes
* @param attributes list of new attributes
Expand Down
68 changes: 67 additions & 1 deletion src/providers/postgres/qgspostgresprovider.cpp
Expand Up @@ -1135,7 +1135,7 @@ bool QgsPostgresProvider::hasSufficientPermsAndCapabilities()
if ( testAccess.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) )
{
// DELETE
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures;
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures | QgsVectorDataProvider::Truncate;
}

if ( testAccess.PQgetvalue( 0, 1 ) == QLatin1String( "t" ) )
Expand Down Expand Up @@ -2260,6 +2260,63 @@ bool QgsPostgresProvider::deleteFeatures( const QgsFeatureIds & id )
return returnvalue;
}

bool QgsPostgresProvider::truncate()
{
bool returnvalue = true;

if ( mIsQuery )
{
QgsDebugMsg( "Cannot truncate (is a query)" );
return false;
}

QgsPostgresConn* conn = connectionRW();
if ( !conn )
{
return false;
}
conn->lock();

try
{
conn->begin();

QString sql = QStringLiteral( "TRUNCATE %1" ).arg( mQuery );
QgsDebugMsg( "truncate sql: " + sql );

//send truncate statement and do error handling
QgsPostgresResult result( conn->PQexec( sql ) );
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
throw PGException( result );

returnvalue &= conn->commit();

if ( returnvalue )
{
if ( mSpatialColType == sctTopoGeometry )
{
// NOTE: in presence of multiple TopoGeometry objects
// for the same table or when deleting a Geometry
// layer _also_ having a TopoGeometry component,
// orphans would still be left.
// TODO: decouple layer from table and signal table when
// records are added or removed
dropOrphanedTopoGeoms();
}
mShared->clear();
}
}
catch ( PGException &e )
{
pushError( tr( "PostGIS error while truncating: %1" ).arg( e.errorMessage() ) );
conn->rollback();
returnvalue = false;
}

conn->unlock();
return returnvalue;
}

bool QgsPostgresProvider::addAttributes( const QList<QgsField> &attributes )
{
bool returnvalue = true;
Expand Down Expand Up @@ -4706,3 +4763,12 @@ QVariantList QgsPostgresSharedData::lookupKey( QgsFeatureId featureId )
return it.value();
return QVariantList();
}

void QgsPostgresSharedData::clear()
{
QMutexLocker locker( &mMutex );
mFidToKey.clear();
mKeyToFid.clear();
mFeaturesCounted = -1;
mFidCounter = 0;
}
40 changes: 2 additions & 38 deletions src/providers/postgres/qgspostgresprovider.h
Expand Up @@ -126,9 +126,6 @@ class QgsPostgresProvider : public QgsVectorDataProvider
void setExtent( QgsRectangle& newExtent );

virtual QgsRectangle extent() const override;

/** Update the extent
*/
virtual void updateExtents() override;

/**
Expand All @@ -145,13 +142,7 @@ class QgsPostgresProvider : public QgsVectorDataProvider
void determinePrimaryKeyFromUriKeyColumn();

QgsFields fields() const override;

/**
* Return a short comment for the data that this provider is
* providing access to (e.g. the comment for postgres table).
*/
QString dataComment() const override;

QVariant minimumValue( int index ) const override;
QVariant maximumValue( int index ) const override;
virtual void uniqueValues( int index, QList<QVariant> &uniqueValues, int limit = -1 ) const override;
Expand All @@ -165,42 +156,14 @@ class QgsPostgresProvider : public QgsVectorDataProvider
QString defaultValueClause( int fieldId ) const override;
QVariant defaultValue( int fieldId ) const override;
bool skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant& value = QVariant() ) const override;

/** Adds a list of features
@return true in case of success and false in case of failure*/
bool addFeatures( QgsFeatureList & flist ) override;

/** Deletes a list of features
@param id list of feature ids
@return true in case of success and false in case of failure*/
bool deleteFeatures( const QgsFeatureIds & id ) override;

bool truncate() override;
bool addAttributes( const QList<QgsField> &attributes ) override;
bool deleteAttributes( const QgsAttributeIds &name ) override;
virtual bool renameAttributes( const QgsFieldNameMap& renamedAttributes ) 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;

/**
Changes geometries of existing features
@param geometry_map A QMap containing the feature IDs to change the geometries of.
the second map parameter being the new geometries themselves
@return true in case of success and false in case of failure
*/
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
Expand Down Expand Up @@ -559,6 +522,7 @@ class QgsPostgresSharedData
QVariantList removeFid( QgsFeatureId fid );
void insertFid( QgsFeatureId fid, const QVariantList& k );
QVariantList lookupKey( QgsFeatureId featureId );
void clear();

protected:
QMutex mMutex; //!< Access to all data members is guarded by the mutex
Expand Down
32 changes: 31 additions & 1 deletion src/providers/spatialite/qgsspatialiteprovider.cpp
Expand Up @@ -532,7 +532,7 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri )
if (( mTableBased || mViewBased ) && !mReadOnly )
{
// enabling editing only for Tables [excluding Views and VirtualShapes]
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures;
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures | QgsVectorDataProvider::Truncate;
if ( !mGeometryColumn.isEmpty() )
mEnabledCapabilities |= QgsVectorDataProvider::ChangeGeometries;
mEnabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues;
Expand Down Expand Up @@ -4119,6 +4119,36 @@ bool QgsSpatiaLiteProvider::deleteFeatures( const QgsFeatureIds &id )
return true;
}

bool QgsSpatiaLiteProvider::truncate()
{
char *errMsg = nullptr;
QString sql;

int ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
return false;
}

sql = QStringLiteral( "DELETE FROM %1" ).arg( quotedIdentifier( mTableName ) );
ret = sqlite3_exec( mSqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
return false;
}

ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
return false;
}

return true;
}

bool QgsSpatiaLiteProvider::addAttributes( const QList<QgsField> &attributes )
{
char *errMsg = nullptr;
Expand Down
4 changes: 1 addition & 3 deletions src/providers/spatialite/qgsspatialiteprovider.h
Expand Up @@ -141,10 +141,8 @@ class QgsSpatiaLiteProvider: public QgsVectorDataProvider
@return true in case of success and false in case of failure*/
bool addFeatures( QgsFeatureList & flist ) override;

/** Deletes a list of features
@param id list of feature ids
@return true in case of success and false in case of failure*/
bool deleteFeatures( const QgsFeatureIds & id ) override;
bool truncate() override;

/** Adds new attributes
@param name map with attribute name as key and type as value
Expand Down
22 changes: 22 additions & 0 deletions tests/src/python/providertestbase.py
Expand Up @@ -737,6 +737,28 @@ def testDeleteFeatures(self):
self.assertFalse(l.dataProvider().deleteFeatures(to_delete),
'Provider reported no DeleteFeatures capability, but returned true to deleteFeatures')

def testTruncate(self):
if not getattr(self, 'getEditableLayer', None):
return

l = self.getEditableLayer()
self.assertTrue(l.isValid())

features = [f['pk'] for f in l.dataProvider().getFeatures()]

if l.dataProvider().capabilities() & QgsVectorDataProvider.FastTruncate or l.dataProvider().capabilities() & QgsVectorDataProvider.DeleteFeatures:
# expect success
result = l.dataProvider().truncate()
self.assertTrue(result, 'Provider reported FastTruncate or DeleteFeatures capability, but returned False to truncate()')

# check result
features = [f['pk'] for f in l.dataProvider().getFeatures()]
self.assertEqual(len(features), 0)
else:
# expect fail
self.assertFalse(l.dataProvider().truncate(),
'Provider reported no FastTruncate or DeleteFeatures capability, but returned true to truncate()')

def testChangeAttributes(self):
if not getattr(self, 'getEditableLayer', None):
return
Expand Down

0 comments on commit bc98a32

Please sign in to comment.