QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 childrens 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 success = ( *orderedLayersIterator )->editBuffer()->commitChangesCheckGeometryTypeCompatibility( commitErrors );
185 if ( ! success )
186 break;
187 }
188 }
189
190 QSet<QgsVectorLayer *> modifiedLayersOnProviderSide;
191
192 // Change fields (add new fields, delete fields)
193 if ( success )
194 {
195 for ( orderedLayersIterator = orderedLayers.constBegin(); orderedLayersIterator != orderedLayers.constEnd(); ++orderedLayersIterator )
196 {
197 QgsFields oldFields = ( *orderedLayersIterator )->fields();
198
199 bool attributesDeleted = false;
200 success = ( *orderedLayersIterator )->editBuffer()->commitChangesDeleteAttributes( attributesDeleted, commitErrors );
201 if ( ! success )
202 break;
203
204 bool attributesRenamed = false;
205 success = ( *orderedLayersIterator )->editBuffer()->commitChangesRenameAttributes( attributesRenamed, commitErrors );
206 if ( ! success )
207 break;
208
209 bool attributesAdded = false;
210 success = ( *orderedLayersIterator )->editBuffer()->commitChangesAddAttributes( attributesAdded, commitErrors );
211 if ( ! success )
212 break;
213
214 if ( attributesDeleted || attributesRenamed || attributesAdded )
215 {
216 if ( ! transactionLayers.contains( ( *orderedLayersIterator ) ) )
217 modifiedLayersOnProviderSide.insert( ( *orderedLayersIterator ) );
218
219 success = ( *orderedLayersIterator )->editBuffer()->commitChangesCheckAttributesModifications( oldFields, commitErrors );
220 if ( ! success )
221 break;
222 }
223 }
224 }
225
226 // delete all features, in reverse dependency order (children first)
227 if ( success )
228 {
229 orderedLayersIterator = orderedLayers.constEnd();
230 while ( orderedLayersIterator != orderedLayers.constBegin() )
231 {
232 --orderedLayersIterator;
233 bool featuresDeleted;
234 success = ( *orderedLayersIterator )->editBuffer()->commitChangesDeleteFeatures( featuresDeleted, commitErrors );
235 if ( ! success )
236 break;
237
238 if ( featuresDeleted && transactionLayers.contains( ( *orderedLayersIterator ) ) )
239 modifiedLayersOnProviderSide.insert( ( *orderedLayersIterator ) );
240 }
241 }
242
243 // add all features, in forward dependency order (parents first)
244 if ( success )
245 {
246 for ( orderedLayersIterator = orderedLayers.constBegin(); orderedLayersIterator != orderedLayers.constEnd(); ++orderedLayersIterator )
247 {
248 bool featuresAdded;
249 ( *orderedLayersIterator )->editBuffer()->commitChangesAddFeatures( featuresAdded, commitErrors );
250 if ( ! success )
251 break;
252
253 if ( featuresAdded && transactionLayers.contains( ( *orderedLayersIterator ) ) )
254 modifiedLayersOnProviderSide.insert( ( *orderedLayersIterator ) );
255 }
256 }
257
258 // change all attributes and geometries in reverse dependency order (children first)
259 if ( success )
260 {
261 orderedLayersIterator = orderedLayers.constEnd();
262 while ( orderedLayersIterator != orderedLayers.constBegin() )
263 {
264 --orderedLayersIterator;
265
266 bool attributesChanged;
267 success = ( *orderedLayersIterator )->editBuffer()->commitChangesChangeAttributes( attributesChanged, commitErrors );
268 if ( ! success )
269 break;
270
271 if ( attributesChanged && transactionLayers.contains( ( *orderedLayersIterator ) ) )
272 modifiedLayersOnProviderSide.insert( ( *orderedLayersIterator ) );
273 }
274 }
275
276 // if everything went well, commit
277 if ( success )
278 {
279 QList<std::shared_ptr<QgsTransaction> >::iterator openTransactionsIterator = openTransactions.begin();
280 while ( openTransactionsIterator != openTransactions.end() )
281 {
282 QString errorMsg;
283 if ( !( *openTransactionsIterator )->commit( errorMsg ) )
284 {
285 success = false;
286 commitErrors << tr( "ERROR: could not commit a transaction, detailed error: '%1'." ).arg( errorMsg );
287 break;
288 }
289
290 modifiedLayersOnProviderSide += connectionStringsLayers.value( ( *openTransactionsIterator )->connectionString() );
291 openTransactionsIterator = openTransactions.erase( openTransactionsIterator );
292 }
293 }
294
295 // Otherwise rollback
296 if ( !success )
297 {
298 // Append additional information about layer which can't be rollbacked
299 if ( ! modifiedLayersOnProviderSide.isEmpty() )
300 {
301 if ( modifiedLayersOnProviderSide.size() == 1 )
302 commitErrors << tr( "WARNING: changes to layer '%1' where already sent to data provider and cannot be rolled back." ).arg( ( *modifiedLayersOnProviderSide.begin() )->name() );
303 else
304 {
305 commitErrors << tr( "WARNING: changes to following layers where already sent to data provider and cannot be rolled back:" );
306 for ( QgsVectorLayer *layer : std::as_const( modifiedLayersOnProviderSide ) )
307 commitErrors << tr( "- '%1'" ).arg( layer->name() );
308 }
309 }
310
311 QString rollbackError;
312 for ( const std::shared_ptr<QgsTransaction> &transaction : openTransactions )
313 transaction->rollback( rollbackError );
314 }
315
316 // Stop editing
317 if ( success )
318 editingFinished( stopEditing );
319
320 if ( success && stopEditing )
321 mIsEditing = false;
322
323 return success;
324}
325
326bool QgsVectorLayerEditBufferGroup::rollBack( QStringList &rollbackErrors, bool stopEditing )
327{
328 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
329 {
330 if ( ! layer->editBuffer() )
331 continue;
332
333 if ( !layer->dataProvider() )
334 {
335 rollbackErrors << tr( "Layer '%1' doesn't have a valid data provider" ).arg( layer->name() );
336 return false;
337 }
338
339 bool rollbackExtent = !layer->editBuffer()->deletedFeatureIds().isEmpty() ||
340 !layer->editBuffer()->addedFeatures().isEmpty() ||
341 !layer->editBuffer()->changedGeometries().isEmpty();
342
343 emit layer->beforeRollBack();
344
345 layer->editBuffer()->rollBack();
346
347 emit layer->afterRollBack();
348
349 if ( layer->isModified() )
350 {
351 // new undo stack roll back method
352 // old method of calling every undo could cause many canvas refreshes
353 layer->undoStack()->setIndex( 0 );
354 }
355
356 layer->updateFields();
357
358 if ( stopEditing )
359 {
360 layer->clearEditBuffer();
361 layer->undoStack()->clear();
362 emit layer->editingStopped();
363 }
364
365 if ( rollbackExtent )
366 layer->updateExtents();
367
368 if ( stopEditing )
369 layer->dataProvider()->leaveUpdateMode();
370
371 layer->triggerRepaint();
372 }
373
374 mIsEditing = ! stopEditing;
375 return true;
376}
377
379{
380 return mIsEditing;
381}
382
383QList<QgsVectorLayer *> QgsVectorLayerEditBufferGroup::orderLayersParentsToChildren( QSet<QgsVectorLayer *> layers )
384{
385 QSet<QgsVectorLayer *> referencingLayers;
386 QSet<QgsVectorLayer *> referencedLayers;
387
388 {
389 const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->relations().values();
390 for ( const QgsRelation &relation : relations )
391 {
392 referencingLayers.insert( relation.referencingLayer() );
393 referencedLayers.insert( relation.referencedLayer() );
394 }
395 }
396
397 QList<QgsVectorLayer *> orderedLayers;
398
399 // Layers that are only parents
400 {
401 QSet<QgsVectorLayer *> onlyParents = referencedLayers - referencingLayers;
402 orderedLayers.append( onlyParents.values() );
403 }
404
405 // Other related layers
406 {
407 QSet<QgsVectorLayer *> intersection = referencedLayers;
408 intersection.intersect( referencingLayers );
409
410 QQueue<QgsVectorLayer *> otherLayersQueue;
411 otherLayersQueue.append( intersection.values() );
412 while ( ! otherLayersQueue.isEmpty() )
413 {
414 QgsVectorLayer *layer = otherLayersQueue.dequeue();
415
416 int insertIndex = -1;
417 const QList<QgsRelation> relations = QgsProject::instance()->relationManager()->referencingRelations( layer );
418 for ( const QgsRelation &relation : relations )
419 {
420 QgsVectorLayer *referencedLayer = relation.referencedLayer();
421 int index = orderedLayers.indexOf( referencedLayer );
422 if ( index >= 0 )
423 {
424 insertIndex = std::max( insertIndex, index + 1 );
425 }
426 else
427 {
428 // Check if there is a circular relation
429 bool circularRelation = false;
430 const QList<QgsRelation> backRelations = QgsProject::instance()->relationManager()->referencingRelations( referencedLayer );
431 for ( const QgsRelation &backRelation : backRelations )
432 {
433 if ( backRelation.referencedLayer() == layer )
434 {
435 QgsLogger::warning( tr( "Circular relation between layers '%1' and '%2'. Correct saving order of layers can't be guaranteed" ).arg( layer->name(), referencedLayer->name() ) );
436 insertIndex = orderedLayers.size();
437 circularRelation = true;
438 break;
439 }
440 }
441
442 if ( !circularRelation )
443 {
444 insertIndex = -1;
445 break;
446 }
447 }
448 }
449
450 // No place found this cycle
451 if ( insertIndex == -1 )
452 {
453 otherLayersQueue.enqueue( layer );
454 continue;
455 }
456
457 orderedLayers.insert( insertIndex, layer );
458 }
459 }
460
461 // Layers that are only children
462 {
463 QSet<QgsVectorLayer *> onlyChildren = referencingLayers - referencedLayers;
464 orderedLayers.append( onlyChildren.values() );
465 }
466
467 // Layers without relations (all other layers)
468 {
469 QSet<QgsVectorLayer *> layersWithoutRelations = layers - referencedLayers;
470 layersWithoutRelations -= referencingLayers;
471 orderedLayers.append( layersWithoutRelations.values() );
472 }
473
474 return orderedLayers;
475}
476
477void QgsVectorLayerEditBufferGroup::editingFinished( bool stopEditing )
478{
479 for ( QgsVectorLayer *layer : std::as_const( mLayers ) )
480 {
481 if ( !layer->mDeletedFids.empty() )
482 {
483 emit layer->featuresDeleted( layer->mDeletedFids );
484 layer->mDeletedFids.clear();
485 }
486
487 if ( stopEditing )
488 layer->clearEditBuffer();
489
490 layer->undoStack()->clear();
491 emit layer->afterCommitChanges();
492 if ( stopEditing )
493 emit layer->editingStopped();
494
495 layer->updateFields();
496
497 layer->dataProvider()->updateExtents();
498 layer->dataProvider()->leaveUpdateMode();
499
500 // This second call is required because OGR provider with JSON
501 // driver might have changed fields order after the call to
502 // leaveUpdateMode
503 if ( layer->fields().names() != layer->dataProvider()->fields().names() )
504 {
505 layer->updateFields();
506 }
507
508 layer->triggerRepaint();
509 }
510}
virtual bool leaveUpdateMode()
Leave update mode.
virtual void updateExtents()
Update the extents of the layer.
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
QStringList names() const
Returns a list with field names.
Definition: qgsfields.cpp:143
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:58
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
QString name
Definition: qgsmaplayer.h:76
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
QUndoStack * undoStack()
Returns pointer to layer's undo stack.
QgsRelationManager * relationManager
Definition: qgsproject.h:114
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:477
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....
QMap< QString, QgsRelation > relations() const
Gets access to the relations managed by this class.
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.
QgsFields fields() const override=0
Returns the fields associated with this data provider.
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.
void updateFields()
Will regenerate the fields property of this layer by obtaining all fields from the dataProvider,...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
void featuresDeleted(const QgsFeatureIds &fids)
Emitted when features have been deleted.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
void afterCommitChanges()
Emitted after changes are committed to the data provider.