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