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