QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
qgsvectorlayereditbuffergroup.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectorlayereditbuffergroup.cpp - QgsVectorLayerEditBufferGroup
3
4 ---------------------
5 begin : 22.12.2021
6 copyright : (C) 2021 by Damiano Lombardi
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
18#include "moc_qgsvectorlayereditbuffergroup.cpp"
19
20#include "qgsproject.h"
21#include "qgstransaction.h"
23#include "qgsvectorlayer.h"
24
25#include <QQueue>
26
28 : QObject( parent )
29{
30
31}
32
34{
35 mLayers.insert( layer );
36}
37
39{
40 mLayers.remove( layer );
41}
42
44{
45 mLayers.clear();
46}
47
48QSet<QgsVectorLayer *> QgsVectorLayerEditBufferGroup::layers() const
49{
50 return mLayers;
51}
52
54{
55 QSet<QgsVectorLayer *> modifiedLayers;
56
57 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
58 if ( layer->isModified() )
59 modifiedLayers.insert( layer );
60
61 return modifiedLayers;
62}
63
65{
66 if ( mIsEditing )
67 return true;
68
69 bool editingStarted = true;
70 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
71 {
72 if ( !layer->isValid() )
73 {
74 editingStarted = false;
75 QgsLogger::debug( tr( "Can't start editing invalid layer '%1'." ).arg( layer->name() ) );
76 break;
77 }
78
79 if ( !layer->dataProvider() )
80 {
81 editingStarted = false;
82 QgsLogger::debug( tr( "Can't start editing layer '%1' with invalid data provider." ).arg( layer->name() ) );
83 break;
84 }
85
86 // allow editing if provider supports any of the capabilities
87 if ( !layer->supportsEditing() )
88 {
89 editingStarted = false;
90 QgsLogger::debug( tr( "Can't start editing. Layer '%1' doesn't support editing." ).arg( layer->name() ) );
91 break;
92 }
93
94 if ( layer->editBuffer() )
95 {
96 // editing already underway
97 layer->editBuffer()->setEditBufferGroup( this );
98 continue;
99 }
100
101
102 emit layer->beforeEditingStarted();
103 layer->dataProvider()->enterUpdateMode();
104 layer->createEditBuffer();
105 layer->editBuffer()->setEditBufferGroup( this );
106 layer->updateFields();
107 emit layer->editingStarted();
108 }
109
110 if ( ! editingStarted )
111 {
112 QStringList rollbackErrors;
113 if ( ! rollBack( rollbackErrors, true ) )
114 QgsLogger::debug( tr( "Can't rollback after start editing failure. Roll back detailed errors: %1" ).arg( rollbackErrors.join( " / " ) ) );
115 }
116
117 mIsEditing = editingStarted;
118 return mIsEditing;
119}
120
121bool QgsVectorLayerEditBufferGroup::commitChanges( QStringList &commitErrors, bool stopEditing )
122{
123 bool success = true;
124
125 const QSet<QgsVectorLayer *> constModifiedLayers = modifiedLayers();
126 if ( constModifiedLayers.isEmpty() )
127 {
128 editingFinished( stopEditing );
129 mIsEditing = !stopEditing;
130 return success;
131 }
132
133 QMap<QString, QSet<QgsVectorLayer *> > connectionStringsLayers;
134 for ( QgsVectorLayer *modifiedLayer : constModifiedLayers )
135 if ( QgsTransaction::supportsTransaction( modifiedLayer ) )
136 connectionStringsLayers[QgsTransaction::connectionString( modifiedLayer->source() )].insert( modifiedLayer );
137
138 QList<QgsVectorLayer *> transactionLayers;
139 QList<std::shared_ptr<QgsTransaction> > openTransactions;
140 const QStringList connectionStrings = connectionStringsLayers.keys();
141 for ( const QString &connectionString : connectionStrings )
142 {
143 const QString providerKey = ( *connectionStringsLayers.value( connectionString ).begin() )->providerType();
144
145 std::shared_ptr<QgsTransaction> transaction;
146 transaction.reset( QgsTransaction::create( connectionString, providerKey ) );
147 if ( !transaction )
148 {
149 commitErrors << tr( "ERROR: data source '%1', is not available for transactions." ).arg( connectionString );
150 success = false;
151 break;
152 }
153
154 QString errorMsg;
155 if ( ! transaction->begin( errorMsg ) )
156 {
157 commitErrors << tr( "ERROR: could not start a transaction on data provider '%1', detailed error: '%2'." ).arg( providerKey, errorMsg );
158 success = false;
159 break;
160 }
161
162 const auto constLayers = connectionStringsLayers.value( connectionString );
163 for ( QgsVectorLayer *layer : constLayers )
164 {
165 if ( ! transaction->addLayer( layer, true ) )
166 {
167 commitErrors << tr( "ERROR: could not add layer '%1' to transaction on data provider '%2'." ).arg( layer->name(), providerKey );
168 success = false;
169 break;
170 }
171
172 transactionLayers.append( layer );
173 }
174
175 openTransactions.append( transaction );
176
177 if ( !success )
178 break;
179 }
180
181 // Order layers children to parents
182 const QList<QgsVectorLayer *> orderedLayers = orderLayersParentsToChildren( constModifiedLayers );
183 QList<QgsVectorLayer *>::const_iterator orderedLayersIterator;
184
185 // Check geometry types
186 if ( success )
187 {
188 for ( orderedLayersIterator = orderedLayers.constBegin(); orderedLayersIterator != orderedLayers.constEnd(); ++orderedLayersIterator )
189 {
190 if ( !( *orderedLayersIterator )->editBuffer() )
191 {
192 commitErrors << tr( "ERROR: edit buffer of layer '%1' is not valid." ).arg( ( *orderedLayersIterator )->name() );
193 success = false;
194 break;
195 }
196
197 success = ( *orderedLayersIterator )->editBuffer()->commitChangesCheckGeometryTypeCompatibility( commitErrors );
198 if ( ! success )
199 break;
200 }
201 }
202
203 QSet<QgsVectorLayer *> modifiedLayersOnProviderSide;
204
205 // Change fields (add new fields, delete fields)
206 if ( success )
207 {
208 for ( orderedLayersIterator = orderedLayers.constBegin(); orderedLayersIterator != orderedLayers.constEnd(); ++orderedLayersIterator )
209 {
210 QgsFields oldFields = ( *orderedLayersIterator )->fields();
211
212 bool attributesDeleted = false;
213 success = ( *orderedLayersIterator )->editBuffer()->commitChangesDeleteAttributes( attributesDeleted, commitErrors );
214 if ( ! success )
215 break;
216
217 bool attributesRenamed = false;
218 success = ( *orderedLayersIterator )->editBuffer()->commitChangesRenameAttributes( attributesRenamed, commitErrors );
219 if ( ! success )
220 break;
221
222 bool attributesAdded = false;
223 success = ( *orderedLayersIterator )->editBuffer()->commitChangesAddAttributes( attributesAdded, commitErrors );
224 if ( ! success )
225 break;
226
227 if ( attributesDeleted || attributesRenamed || attributesAdded )
228 {
229 if ( ! transactionLayers.contains( ( *orderedLayersIterator ) ) )
230 modifiedLayersOnProviderSide.insert( ( *orderedLayersIterator ) );
231
232 success = ( *orderedLayersIterator )->editBuffer()->commitChangesCheckAttributesModifications( oldFields, commitErrors );
233 if ( ! success )
234 break;
235 }
236 }
237 }
238
239 // delete all features, in reverse dependency order (children first)
240 if ( success )
241 {
242 orderedLayersIterator = orderedLayers.constEnd();
243 while ( orderedLayersIterator != orderedLayers.constBegin() )
244 {
245 --orderedLayersIterator;
246 bool featuresDeleted;
247 success = ( *orderedLayersIterator )->editBuffer()->commitChangesDeleteFeatures( featuresDeleted, commitErrors );
248 if ( ! success )
249 break;
250
251 if ( featuresDeleted && transactionLayers.contains( ( *orderedLayersIterator ) ) )
252 modifiedLayersOnProviderSide.insert( ( *orderedLayersIterator ) );
253 }
254 }
255
256 // add all features, in forward dependency order (parents first)
257 if ( success )
258 {
259 for ( orderedLayersIterator = orderedLayers.constBegin(); orderedLayersIterator != orderedLayers.constEnd(); ++orderedLayersIterator )
260 {
261 bool featuresAdded;
262 ( *orderedLayersIterator )->editBuffer()->commitChangesAddFeatures( featuresAdded, commitErrors );
263 if ( ! success )
264 break;
265
266 if ( featuresAdded && transactionLayers.contains( ( *orderedLayersIterator ) ) )
267 modifiedLayersOnProviderSide.insert( ( *orderedLayersIterator ) );
268 }
269 }
270
271 // change all attributes and geometries in reverse dependency order (children first)
272 if ( success )
273 {
274 orderedLayersIterator = orderedLayers.constEnd();
275 while ( orderedLayersIterator != orderedLayers.constBegin() )
276 {
277 --orderedLayersIterator;
278
279 bool attributesChanged;
280 success = ( *orderedLayersIterator )->editBuffer()->commitChangesChangeAttributes( attributesChanged, commitErrors );
281 if ( ! success )
282 break;
283
284 if ( attributesChanged && transactionLayers.contains( ( *orderedLayersIterator ) ) )
285 modifiedLayersOnProviderSide.insert( ( *orderedLayersIterator ) );
286 }
287 }
288
289 // if everything went well, commit
290 if ( success )
291 {
292 QList<std::shared_ptr<QgsTransaction> >::iterator openTransactionsIterator = openTransactions.begin();
293 while ( openTransactionsIterator != openTransactions.end() )
294 {
295 QString errorMsg;
296 if ( !( *openTransactionsIterator )->commit( errorMsg ) )
297 {
298 success = false;
299 commitErrors << tr( "ERROR: could not commit a transaction, detailed error: '%1'." ).arg( errorMsg );
300 break;
301 }
302
303 modifiedLayersOnProviderSide += connectionStringsLayers.value( ( *openTransactionsIterator )->connectionString() );
304 openTransactionsIterator = openTransactions.erase( openTransactionsIterator );
305 }
306 }
307
308 // Otherwise rollback
309 if ( !success )
310 {
311 // Append additional information about layer which can't be rollbacked
312 if ( ! modifiedLayersOnProviderSide.isEmpty() )
313 {
314 if ( modifiedLayersOnProviderSide.size() == 1 )
315 commitErrors << tr( "WARNING: changes to layer '%1' were already sent to data provider and cannot be rolled back." ).arg( ( *modifiedLayersOnProviderSide.begin() )->name() );
316 else
317 {
318 commitErrors << tr( "WARNING: changes to following layers were already sent to data provider and cannot be rolled back:" );
319 for ( QgsVectorLayer *layer : std::as_const( modifiedLayersOnProviderSide ) )
320 commitErrors << tr( "- '%1'" ).arg( layer->name() );
321 }
322 }
323
324 QString rollbackError;
325 for ( const std::shared_ptr<QgsTransaction> &transaction : openTransactions )
326 transaction->rollback( rollbackError );
327 }
328
329 // Stop editing
330 if ( success )
331 editingFinished( stopEditing );
332
333 if ( success && stopEditing )
334 mIsEditing = false;
335
336 return success;
337}
338
339bool QgsVectorLayerEditBufferGroup::rollBack( QStringList &rollbackErrors, bool stopEditing )
340{
341 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
342 {
343 if ( ! layer->editBuffer() )
344 continue;
345
346 if ( !layer->dataProvider() )
347 {
348 rollbackErrors << tr( "Layer '%1' doesn't have a valid data provider" ).arg( layer->name() );
349 return false;
350 }
351
352 bool rollbackExtent = !layer->editBuffer()->deletedFeatureIds().isEmpty() ||
353 !layer->editBuffer()->addedFeatures().isEmpty() ||
354 !layer->editBuffer()->changedGeometries().isEmpty();
355
356 emit layer->beforeRollBack();
357
358 layer->editBuffer()->rollBack();
359
360 emit layer->afterRollBack();
361
362 if ( layer->isModified() )
363 {
364 // new undo stack roll back method
365 // old method of calling every undo could cause many canvas refreshes
366 layer->undoStack()->setIndex( 0 );
367 }
368
369 layer->updateFields();
370
371 if ( stopEditing )
372 {
373 layer->clearEditBuffer();
374 layer->undoStack()->clear();
375 emit layer->editingStopped();
376 }
377
378 if ( rollbackExtent )
379 layer->updateExtents();
380
381 if ( stopEditing )
382 layer->dataProvider()->leaveUpdateMode();
383
384 layer->triggerRepaint();
385 }
386
387 mIsEditing = ! stopEditing;
388 return true;
389}
390
392{
393 return mIsEditing;
394}
395
396QList<QgsVectorLayer *> QgsVectorLayerEditBufferGroup::orderLayersParentsToChildren( QSet<QgsVectorLayer *> layers )
397{
398 QList<QgsVectorLayer *> orderedLayers;
399 QSet<QgsVectorLayer *> unorderedLayers = layers;
400
401 bool layerOrdered = true;
402 while ( ! unorderedLayers.isEmpty() && layerOrdered )
403 {
404 layerOrdered = false;
405 QSet<QgsVectorLayer *>::iterator unorderedLayerIterator = unorderedLayers.begin();
406 while ( unorderedLayerIterator != unorderedLayers.end() )
407 {
408 // Get referencing relation to find referenced layers
409 const QList<QgsRelation> referencingRelations = QgsProject::instance()->relationManager()->referencingRelations( *unorderedLayerIterator ); // skip-keyword-check
410
411 // If this layer references at least one modified layer continue
412 bool layerReferencingModifiedLayer = false;
413 for ( const QgsRelation &relation : referencingRelations )
414 {
415 if ( unorderedLayers.contains( relation.referencedLayer() ) )
416 {
417 layerReferencingModifiedLayer = true;
418 break;
419 }
420 }
421 if ( layerReferencingModifiedLayer )
422 {
423 ++unorderedLayerIterator;
424 continue;
425 }
426
427 // No modified layer is referencing this layer
428 orderedLayers.append( *unorderedLayerIterator );
429 unorderedLayerIterator = unorderedLayers.erase( unorderedLayerIterator );
430 layerOrdered = true;
431 }
432 }
433
434 if ( ! unorderedLayers.isEmpty() )
435 {
436 QgsLogger::warning( tr( "Circular relation between some layers. Correct saving order of layers can't be guaranteed" ) );
437 orderedLayers.append( unorderedLayers.values() );
438 }
439
440 return orderedLayers;
441}
442
443void QgsVectorLayerEditBufferGroup::editingFinished( bool stopEditing )
444{
445 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
446 {
447 if ( !layer->mDeletedFids.empty() )
448 {
449 emit layer->featuresDeleted( layer->mDeletedFids );
450 layer->mDeletedFids.clear();
451 }
452
453 if ( stopEditing )
454 layer->clearEditBuffer();
455
456 layer->undoStack()->clear();
457 emit layer->afterCommitChanges();
458 if ( stopEditing )
459 emit layer->editingStopped();
460
461 layer->updateFields();
462
463 layer->dataProvider()->updateExtents();
464
465 if ( stopEditing )
466 {
467 layer->dataProvider()->leaveUpdateMode();
468 }
469
470 // This second call is required because OGR provider with JSON
471 // driver might have changed fields order after the call to
472 // leaveUpdateMode
473 if ( layer->fields().names() != layer->dataProvider()->fields().names() )
474 {
475 layer->updateFields();
476 }
477
478 layer->triggerRepaint();
479 }
480}
Container of fields for a vector layer.
Definition qgsfields.h:46
const_iterator constEnd() const noexcept
Returns a const STL-style iterator pointing to the imaginary item after the last item in the list.
static void debug(const QString &msg, int debuglevel=1, const char *file=nullptr, const char *function=nullptr, int line=-1)
Goes to qDebug.
Definition qgslogger.cpp:59
static void warning(const QString &msg)
Goes to qWarning.
QgsRelationManager * relationManager
Definition qgsproject.h:117
static QgsProject * instance()
Returns the QgsProject singleton instance.
QList< QgsRelation > referencingRelations(const QgsVectorLayer *layer=nullptr, int fieldIdx=-2) const
Gets all relations where the specified layer (and field) is the referencing part (i....
Represents a relationship between two vector layers.
Definition qgsrelation.h:44
static bool supportsTransaction(const QgsVectorLayer *layer)
Checks if the provider of a given layer supports transactions.
QString connectionString() const
Returns the connection string of the transaction.
static QgsTransaction * create(const QString &connString, const QString &providerKey)
Create a transaction for the specified connection string connString and provider with providerKey.
bool commitChanges(QStringList &commitErrors, bool stopEditing=true)
Attempts to commit any changes to disk.
void clear()
Remove all layers from this edit buffer group.
bool rollBack(QStringList &rollbackErrors, bool stopEditing=true)
Stop editing and discard the edits.
void removeLayer(QgsVectorLayer *layer)
Remove a layer from this edit buffer group.
void addLayer(QgsVectorLayer *layer)
Add a layer to this edit buffer group.
QSet< QgsVectorLayer * > layers() const
Gets the set of layers currently managed by this edit buffer group.
QSet< QgsVectorLayer * > modifiedLayers() const
Gets the set of modified layers currently managed by this edit buffer group.
QgsVectorLayerEditBufferGroup(QObject *parent=nullptr)
Constructor for QgsEditBufferGroup.
bool isEditing() const
Returns true if the layers are in editing mode.
Represents a vector layer which manages a vector based data sets.