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