Skip to content

Commit bc98a32

Browse files
committed
[FEATURE] Add QgsVectorDataProvider::truncate for clearing layers
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.
1 parent b256075 commit bc98a32

8 files changed

Lines changed: 164 additions & 46 deletions

File tree

‎python/core/qgsvectordataprovider.sip‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class QgsVectorDataProvider : QgsDataProvider
4949
ChangeFeatures,
5050
/** Supports renaming attributes (fields). Added in QGIS 2.16 */
5151
RenameAttributes,
52+
//! Supports fast truncation of the layer (removing all features). Added in QGIS 3.0
53+
FastTruncate,
5254
};
5355
typedef QFlags<QgsVectorDataProvider::Capability> Capabilities;
5456

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

193195
/**
194-
* Deletes one or more features
196+
* Deletes one or more features from the provider. This requires the DeleteFeatures capability.
195197
* @param id list containing feature ids to delete
196198
* @return true in case of success and false in case of failure
199+
* @see truncate()
197200
*/
198-
virtual bool deleteFeatures( const QSet<qint64> &id );
201+
virtual bool deleteFeatures( const QgsFeatureIds &id );
202+
203+
/**
204+
* Removes all features from the layer. This requires either the FastTruncate or DeleteFeatures capability.
205+
* Providers with the FastTruncate capability will use an optimised method to truncate the layer.
206+
* @returns true in case of success and false in case of failure.
207+
* @note added in QGIS 3.0
208+
* @see deleteFeatures()
209+
*/
210+
virtual bool truncate();
199211

200212
/**
201213
* Adds new attributes

‎src/core/qgsvectordataprovider.cpp‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ bool QgsVectorDataProvider::deleteFeatures( const QgsFeatureIds &ids )
6969
return false;
7070
}
7171

72+
bool QgsVectorDataProvider::truncate()
73+
{
74+
if ( !( capabilities() & DeleteFeatures ) )
75+
return false;
76+
77+
QgsFeatureIds toDelete;
78+
QgsFeatureIterator it = getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setSubsetOfAttributes( QgsAttributeList() ) );
79+
QgsFeature f;
80+
while ( it.nextFeature( f ) )
81+
toDelete << f.id();
82+
83+
return deleteFeatures( toDelete );
84+
}
85+
7286
bool QgsVectorDataProvider::addAttributes( const QList<QgsField> &attributes )
7387
{
7488
Q_UNUSED( attributes );

‎src/core/qgsvectordataprovider.h‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
100100
ChangeFeatures = 1 << 18,
101101
//! Supports renaming attributes (fields). Added in QGIS 2.16
102102
RenameAttributes = 1 << 19,
103+
//! Supports fast truncation of the layer (removing all features). Added in QGIS 3.0
104+
FastTruncate = 1 << 20,
103105
};
104106

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

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

257+
/**
258+
* Removes all features from the layer. This requires either the FastTruncate or DeleteFeatures capability.
259+
* Providers with the FastTruncate capability will use an optimised method to truncate the layer.
260+
* @returns true in case of success and false in case of failure.
261+
* @note added in QGIS 3.0
262+
* @see deleteFeatures()
263+
*/
264+
virtual bool truncate();
265+
254266
/**
255267
* Adds new attributes
256268
* @param attributes list of new attributes

‎src/providers/postgres/qgspostgresprovider.cpp‎

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,7 @@ bool QgsPostgresProvider::hasSufficientPermsAndCapabilities()
11351135
if ( testAccess.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) )
11361136
{
11371137
// DELETE
1138-
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures;
1138+
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures | QgsVectorDataProvider::Truncate;
11391139
}
11401140

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

2263+
bool QgsPostgresProvider::truncate()
2264+
{
2265+
bool returnvalue = true;
2266+
2267+
if ( mIsQuery )
2268+
{
2269+
QgsDebugMsg( "Cannot truncate (is a query)" );
2270+
return false;
2271+
}
2272+
2273+
QgsPostgresConn* conn = connectionRW();
2274+
if ( !conn )
2275+
{
2276+
return false;
2277+
}
2278+
conn->lock();
2279+
2280+
try
2281+
{
2282+
conn->begin();
2283+
2284+
QString sql = QStringLiteral( "TRUNCATE %1" ).arg( mQuery );
2285+
QgsDebugMsg( "truncate sql: " + sql );
2286+
2287+
//send truncate statement and do error handling
2288+
QgsPostgresResult result( conn->PQexec( sql ) );
2289+
if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK )
2290+
throw PGException( result );
2291+
2292+
returnvalue &= conn->commit();
2293+
2294+
if ( returnvalue )
2295+
{
2296+
if ( mSpatialColType == sctTopoGeometry )
2297+
{
2298+
// NOTE: in presence of multiple TopoGeometry objects
2299+
// for the same table or when deleting a Geometry
2300+
// layer _also_ having a TopoGeometry component,
2301+
// orphans would still be left.
2302+
// TODO: decouple layer from table and signal table when
2303+
// records are added or removed
2304+
dropOrphanedTopoGeoms();
2305+
}
2306+
mShared->clear();
2307+
}
2308+
}
2309+
catch ( PGException &e )
2310+
{
2311+
pushError( tr( "PostGIS error while truncating: %1" ).arg( e.errorMessage() ) );
2312+
conn->rollback();
2313+
returnvalue = false;
2314+
}
2315+
2316+
conn->unlock();
2317+
return returnvalue;
2318+
}
2319+
22632320
bool QgsPostgresProvider::addAttributes( const QList<QgsField> &attributes )
22642321
{
22652322
bool returnvalue = true;
@@ -4706,3 +4763,12 @@ QVariantList QgsPostgresSharedData::lookupKey( QgsFeatureId featureId )
47064763
return it.value();
47074764
return QVariantList();
47084765
}
4766+
4767+
void QgsPostgresSharedData::clear()
4768+
{
4769+
QMutexLocker locker( &mMutex );
4770+
mFidToKey.clear();
4771+
mKeyToFid.clear();
4772+
mFeaturesCounted = -1;
4773+
mFidCounter = 0;
4774+
}

‎src/providers/postgres/qgspostgresprovider.h‎

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,6 @@ class QgsPostgresProvider : public QgsVectorDataProvider
126126
void setExtent( QgsRectangle& newExtent );
127127

128128
virtual QgsRectangle extent() const override;
129-
130-
/** Update the extent
131-
*/
132129
virtual void updateExtents() override;
133130

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

147144
QgsFields fields() const override;
148-
149-
/**
150-
* Return a short comment for the data that this provider is
151-
* providing access to (e.g. the comment for postgres table).
152-
*/
153145
QString dataComment() const override;
154-
155146
QVariant minimumValue( int index ) const override;
156147
QVariant maximumValue( int index ) const override;
157148
virtual void uniqueValues( int index, QList<QVariant> &uniqueValues, int limit = -1 ) const override;
@@ -165,42 +156,14 @@ class QgsPostgresProvider : public QgsVectorDataProvider
165156
QString defaultValueClause( int fieldId ) const override;
166157
QVariant defaultValue( int fieldId ) const override;
167158
bool skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant& value = QVariant() ) const override;
168-
169-
/** Adds a list of features
170-
@return true in case of success and false in case of failure*/
171159
bool addFeatures( QgsFeatureList & flist ) override;
172-
173-
/** Deletes a list of features
174-
@param id list of feature ids
175-
@return true in case of success and false in case of failure*/
176160
bool deleteFeatures( const QgsFeatureIds & id ) override;
177-
161+
bool truncate() override;
178162
bool addAttributes( const QList<QgsField> &attributes ) override;
179163
bool deleteAttributes( const QgsAttributeIds &name ) override;
180164
virtual bool renameAttributes( const QgsFieldNameMap& renamedAttributes ) override;
181-
182-
/** Changes attribute values of existing features
183-
@param attr_map a map containing the new attributes. The integer is the feature id,
184-
the first QString is the attribute name and the second one is the new attribute value
185-
@return true in case of success and false in case of failure*/
186165
bool changeAttributeValues( const QgsChangedAttributesMap &attr_map ) override;
187-
188-
/**
189-
Changes geometries of existing features
190-
@param geometry_map A QMap containing the feature IDs to change the geometries of.
191-
the second map parameter being the new geometries themselves
192-
@return true in case of success and false in case of failure
193-
*/
194166
bool changeGeometryValues( const QgsGeometryMap &geometry_map ) override;
195-
196-
/**
197-
* Changes attribute values and geometries of existing features.
198-
* @param attr_map a map containing changed attributes
199-
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs
200-
* that will have their geometries changed.
201-
* The second map parameter being the new geometries themselves
202-
* @return true in case of success and false in case of failure
203-
*/
204167
bool changeFeatures( const QgsChangedAttributesMap &attr_map, const QgsGeometryMap &geometry_map ) override;
205168

206169
//! Get the postgres connection
@@ -559,6 +522,7 @@ class QgsPostgresSharedData
559522
QVariantList removeFid( QgsFeatureId fid );
560523
void insertFid( QgsFeatureId fid, const QVariantList& k );
561524
QVariantList lookupKey( QgsFeatureId featureId );
525+
void clear();
562526

563527
protected:
564528
QMutex mMutex; //!< Access to all data members is guarded by the mutex

‎src/providers/spatialite/qgsspatialiteprovider.cpp‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri )
532532
if (( mTableBased || mViewBased ) && !mReadOnly )
533533
{
534534
// enabling editing only for Tables [excluding Views and VirtualShapes]
535-
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures;
535+
mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures | QgsVectorDataProvider::Truncate;
536536
if ( !mGeometryColumn.isEmpty() )
537537
mEnabledCapabilities |= QgsVectorDataProvider::ChangeGeometries;
538538
mEnabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues;
@@ -4119,6 +4119,36 @@ bool QgsSpatiaLiteProvider::deleteFeatures( const QgsFeatureIds &id )
41194119
return true;
41204120
}
41214121

4122+
bool QgsSpatiaLiteProvider::truncate()
4123+
{
4124+
char *errMsg = nullptr;
4125+
QString sql;
4126+
4127+
int ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
4128+
if ( ret != SQLITE_OK )
4129+
{
4130+
handleError( sql, errMsg );
4131+
return false;
4132+
}
4133+
4134+
sql = QStringLiteral( "DELETE FROM %1" ).arg( quotedIdentifier( mTableName ) );
4135+
ret = sqlite3_exec( mSqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
4136+
if ( ret != SQLITE_OK )
4137+
{
4138+
handleError( sql, errMsg, true );
4139+
return false;
4140+
}
4141+
4142+
ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
4143+
if ( ret != SQLITE_OK )
4144+
{
4145+
handleError( sql, errMsg, true );
4146+
return false;
4147+
}
4148+
4149+
return true;
4150+
}
4151+
41224152
bool QgsSpatiaLiteProvider::addAttributes( const QList<QgsField> &attributes )
41234153
{
41244154
char *errMsg = nullptr;

‎src/providers/spatialite/qgsspatialiteprovider.h‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,8 @@ class QgsSpatiaLiteProvider: public QgsVectorDataProvider
141141
@return true in case of success and false in case of failure*/
142142
bool addFeatures( QgsFeatureList & flist ) override;
143143

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

149147
/** Adds new attributes
150148
@param name map with attribute name as key and type as value

‎tests/src/python/providertestbase.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,28 @@ def testDeleteFeatures(self):
737737
self.assertFalse(l.dataProvider().deleteFeatures(to_delete),
738738
'Provider reported no DeleteFeatures capability, but returned true to deleteFeatures')
739739

740+
def testTruncate(self):
741+
if not getattr(self, 'getEditableLayer', None):
742+
return
743+
744+
l = self.getEditableLayer()
745+
self.assertTrue(l.isValid())
746+
747+
features = [f['pk'] for f in l.dataProvider().getFeatures()]
748+
749+
if l.dataProvider().capabilities() & QgsVectorDataProvider.FastTruncate or l.dataProvider().capabilities() & QgsVectorDataProvider.DeleteFeatures:
750+
# expect success
751+
result = l.dataProvider().truncate()
752+
self.assertTrue(result, 'Provider reported FastTruncate or DeleteFeatures capability, but returned False to truncate()')
753+
754+
# check result
755+
features = [f['pk'] for f in l.dataProvider().getFeatures()]
756+
self.assertEqual(len(features), 0)
757+
else:
758+
# expect fail
759+
self.assertFalse(l.dataProvider().truncate(),
760+
'Provider reported no FastTruncate or DeleteFeatures capability, but returned true to truncate()')
761+
740762
def testChangeAttributes(self):
741763
if not getattr(self, 'getEditableLayer', None):
742764
return

0 commit comments

Comments
 (0)