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