QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 : [email protected]
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 
42 QSet<QgsVectorLayer *> QgsVectorLayerEditBufferGroup::layers() const
43 {
44  return mLayers;
45 }
46 
47 QSet<QgsVectorLayer *> QgsVectorLayerEditBufferGroup::modifiedLayers() const
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 
115 bool 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 
326 bool 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 
383 QList<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 
477 void 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 }
QgsProject::relationManager
QgsRelationManager relationManager
Definition: qgsproject.h:114
qgsvectorlayereditbuffergroup.h
QgsVectorLayerEditBufferGroup::addLayer
void addLayer(QgsVectorLayer *layer)
Add a layer to this edit buffer group.
Definition: qgsvectorlayereditbuffergroup.cpp:32
QgsMapLayer::editingStopped
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
QgsVectorLayer::dataProvider
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
Definition: qgsvectorlayer.cpp:676
QgsVectorLayer::updateFields
void updateFields()
Will regenerate the fields property of this layer by obtaining all fields from the dataProvider,...
Definition: qgsvectorlayer.cpp:3963
QgsVectorLayer::featuresDeleted
void featuresDeleted(const QgsFeatureIds &fids)
Emitted when features have been deleted.
QgsVectorDataProvider::fields
QgsFields fields() const override=0
Returns the fields associated with this data provider.
QgsFields
Container of fields for a vector layer.
Definition: qgsfields.h:44
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:480
QgsVectorLayer::afterCommitChanges
void afterCommitChanges()
Emitted after changes are committed to the data provider.
QgsMapLayer::triggerRepaint
void triggerRepaint(bool deferredUpdate=false)
Will advise the map canvas (and any other interested party) that this layer requires to be repainted.
Definition: qgsmaplayer.cpp:2114
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3436
QgsVectorLayerEditBufferGroup::clear
void clear()
Remove all layers from this edit buffer group.
Definition: qgsvectorlayereditbuffergroup.cpp:37
QgsRelationManager::relations
QMap< QString, QgsRelation > relations() const
Gets access to the relations managed by this class.
Definition: qgsrelationmanager.cpp:54
QgsTransaction::supportsTransaction
static bool supportsTransaction(const QgsVectorLayer *layer)
Checks if the provider of a given layer supports transactions.
Definition: qgstransaction.cpp:195
QgsVectorLayerEditBufferGroup::rollBack
bool rollBack(QStringList &rollbackErrors, bool stopEditing=true)
Stop editing and discard the edits.
Definition: qgsvectorlayereditbuffergroup.cpp:326
QgsLogger::warning
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
QgsVectorLayerEditBufferGroup::modifiedLayers
QSet< QgsVectorLayer * > modifiedLayers() const
Gets the set of modified layers currently managed by this edit buffer group.
Definition: qgsvectorlayereditbuffergroup.cpp:47
QgsVectorLayerEditBufferGroup::isEditing
bool isEditing() const
Returns true if the layers are in editing mode.
Definition: qgsvectorlayereditbuffergroup.cpp:378
QgsVectorLayerEditBufferGroup::startEditing
bool startEditing()
Start editing.
Definition: qgsvectorlayereditbuffergroup.cpp:58
QgsLogger::debug
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
qgsvectorlayer.h
QgsVectorLayerEditBufferGroup::commitChanges
bool commitChanges(QStringList &commitErrors, bool stopEditing=true)
Attempts to commit any changes to disk.
Definition: qgsvectorlayereditbuffergroup.cpp:115
qgstransaction.h
QgsTransaction::connectionString
QString connectionString() const
Returns the connection string of the transaction.
Definition: qgstransaction.cpp:69
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsRelationManager::referencingRelations
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....
Definition: qgsrelationmanager.cpp:119
QgsVectorLayerEditBufferGroup::QgsVectorLayerEditBufferGroup
QgsVectorLayerEditBufferGroup(QObject *parent=nullptr)
Constructor for QgsEditBufferGroup.
Definition: qgsvectorlayereditbuffergroup.cpp:26
QgsTransaction::create
static QgsTransaction * create(const QString &connString, const QString &providerKey)
Create a transaction for the specified connection string connString and provider with providerKey.
Definition: qgstransaction.cpp:27
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:76
QgsRelation
Definition: qgsrelation.h:42
QgsMapLayer::undoStack
QUndoStack * undoStack()
Returns pointer to layer's undo stack.
Definition: qgsmaplayer.cpp:1961
QgsDataProvider::leaveUpdateMode
virtual bool leaveUpdateMode()
Leave update mode.
Definition: qgsdataprovider.h:488
QgsFields::constEnd
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
QgsVectorLayerEditBufferGroup::layers
QSet< QgsVectorLayer * > layers() const
Gets the set of layers currently managed by this edit buffer group.
Definition: qgsvectorlayereditbuffergroup.cpp:42
qgsproject.h
qgsvectorlayereditbuffer.h
QgsDataProvider::updateExtents
virtual void updateExtents()
Update the extents of the layer.
Definition: qgsdataprovider.h:250
QgsFields::names
QStringList names() const
Returns a list with field names.
Definition: qgsfields.cpp:143