QGIS API Documentation  3.25.0-Master (10b47c2603)
qgstransaction.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstransaction.cpp
3  ------------------
4  begin : May 5, 2014
5  copyright : (C) 2014 by Marco Hugentobler
6  email : marco dot hugentobler at sourcepole dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 #include "qgstransaction.h"
18 #include "qgslogger.h"
19 #include "qgsdatasourceuri.h"
20 #include "qgsproviderregistry.h"
21 #include "qgsvectordataprovider.h"
22 #include "qgsvectorlayer.h"
23 #include "qgsexpression.h"
24 #include "qgsmessagelog.h"
25 #include <QUuid>
26 
27 QgsTransaction *QgsTransaction::create( const QString &connString, const QString &providerKey )
28 {
29  return QgsProviderRegistry::instance()->createTransaction( providerKey, connString );
30 }
31 
32 QgsTransaction *QgsTransaction::create( const QSet<QgsVectorLayer *> &layers )
33 {
34  if ( layers.isEmpty() )
35  return nullptr;
36 
37  QgsVectorLayer *firstLayer = *layers.constBegin();
38 
39  const QString connStr = connectionString( firstLayer->source() );
40  const QString providerKey = firstLayer->providerType();
41  std::unique_ptr<QgsTransaction> transaction( QgsTransaction::create( connStr, providerKey ) );
42  if ( transaction )
43  {
44  for ( QgsVectorLayer *layer : layers )
45  {
46  if ( !transaction->addLayer( layer, false ) )
47  {
48  transaction.reset();
49  break;
50  }
51  }
52  }
53  return transaction.release();
54 }
55 
56 
57 QgsTransaction::QgsTransaction( const QString &connString )
58  : mConnString( connString )
59  , mTransactionActive( false )
60  , mLastSavePointIsDirty( true )
61 {
62 }
63 
65 {
66  setLayerTransactionIds( nullptr );
67 }
68 
70 {
71  return mConnString;
72 }
73 
74 // For the needs of the OGR provider with GeoPackage datasources, remove
75 // any reference to layers in the connection string
76 QString QgsTransaction::removeLayerIdOrName( const QString &str )
77 {
78  QString res( str );
79 
80  for ( int i = 0; i < 2; i++ )
81  {
82  const int pos = res.indexOf( i == 0 ? QLatin1String( "|layername=" ) : QLatin1String( "|layerid=" ) );
83  if ( pos >= 0 )
84  {
85  const int end = res.indexOf( '|', pos + 1 );
86  if ( end >= 0 )
87  {
88  res = res.mid( 0, pos ) + res.mid( end );
89  }
90  else
91  {
92  res = res.mid( 0, pos );
93  }
94  }
95  }
96  return res;
97 }
98 
100 QString QgsTransaction::connectionString( const QString &layerUri )
101 {
102  QString connString = QgsDataSourceUri( layerUri ).connectionInfo( false );
103  // In the case of a OGR datasource, connectionInfo() will return an empty
104  // string. In that case, use the layer->source() itself, and strip any
105  // reference to layers from it.
106  if ( connString.isEmpty() )
107  {
108  connString = removeLayerIdOrName( layerUri );
109  }
110  return connString;
111 }
113 
114 bool QgsTransaction::addLayer( QgsVectorLayer *layer, bool addLayersInEditMode )
115 {
116  if ( !layer )
117  return false;
118 
119  if ( ! addLayersInEditMode
120  && layer->isEditable() )
121  return false;
122 
123  //test if provider supports transactions
124  if ( !supportsTransaction( layer ) )
125  return false;
126 
127  if ( layer->dataProvider()->transaction() )
128  return false;
129 
130  //connection string not compatible
131 
132  if ( connectionString( layer->source() ) != mConnString )
133  {
134  QgsDebugMsg( QStringLiteral( "Couldn't start transaction because connection string for layer %1 : '%2' does not match '%3'" ).arg(
135  layer->id(), connectionString( layer->source() ), mConnString ) );
136  return false;
137  }
138 
140  connect( layer, &QgsVectorLayer::destroyed, this, &QgsTransaction::onLayerDeleted );
141  mLayers.insert( layer );
142 
143  if ( mTransactionActive )
144  layer->dataProvider()->setTransaction( this );
145 
146  return true;
147 }
148 
149 bool QgsTransaction::begin( QString &errorMsg, int statementTimeout )
150 {
151  if ( mTransactionActive )
152  return false;
153 
154  //Set all layers to direct edit mode
155  if ( !beginTransaction( errorMsg, statementTimeout ) )
156  return false;
157 
158  setLayerTransactionIds( this );
159  mTransactionActive = true;
160  mSavepoints.clear();
161  return true;
162 }
163 
164 bool QgsTransaction::commit( QString &errorMsg )
165 {
166  if ( !mTransactionActive )
167  return false;
168 
169  if ( !commitTransaction( errorMsg ) )
170  return false;
171 
172  setLayerTransactionIds( nullptr );
173  mTransactionActive = false;
174  mSavepoints.clear();
175  return true;
176 }
177 
178 bool QgsTransaction::rollback( QString &errorMsg )
179 {
180  if ( !mTransactionActive )
181  return false;
182 
183  if ( !rollbackTransaction( errorMsg ) )
184  return false;
185 
186  setLayerTransactionIds( nullptr );
187  mTransactionActive = false;
188  mSavepoints.clear();
189 
190  emit afterRollback();
191 
192  return true;
193 }
194 
196 {
197  //test if provider supports transactions
198  if ( !layer->dataProvider() || ( layer->dataProvider()->capabilities() & QgsVectorDataProvider::TransactionSupport ) == 0 )
199  return false;
200 
201  return true;
202 }
203 
204 void QgsTransaction::onLayerDeleted()
205 {
206  mLayers.remove( static_cast<QgsVectorLayer *>( sender() ) );
207 }
208 
209 void QgsTransaction::setLayerTransactionIds( QgsTransaction *transaction )
210 {
211  const auto constMLayers = mLayers;
212  for ( QgsVectorLayer *vl : constMLayers )
213  {
214  if ( vl->dataProvider() )
215  {
216  vl->dataProvider()->setTransaction( transaction );
217  }
218  }
219 }
220 
221 QString QgsTransaction::createSavepoint( QString &error SIP_OUT )
222 {
223  if ( !mTransactionActive )
224  return QString();
225 
226  if ( !mLastSavePointIsDirty && !mSavepoints.isEmpty() )
227  {
228  return mSavepoints.top();
229  }
230 
231  const QString name( QStringLiteral( "qgis" ) + ( QUuid::createUuid().toString().mid( 1, 24 ).replace( '-', QString() ) ) );
232  return createSavepoint( name, error );
233 }
234 
235 QString QgsTransaction::createSavepoint( const QString &savePointId, QString &error SIP_OUT )
236 {
237  if ( !mTransactionActive )
238  return QString();
239 
240  if ( !executeSql( QStringLiteral( "SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( savePointId ) ), error ) )
241  {
242  QgsMessageLog::logMessage( tr( "Could not create savepoint (%1)" ).arg( error ) );
243  return QString();
244  }
245 
246  mSavepoints.push( savePointId );
247  mLastSavePointIsDirty = false;
248  return savePointId;
249 }
250 
251 bool QgsTransaction::rollbackToSavepoint( const QString &name, QString &error SIP_OUT )
252 {
253  if ( !mTransactionActive )
254  return false;
255 
256  const int idx = mSavepoints.indexOf( name );
257 
258  if ( idx == -1 )
259  return false;
260 
261  mSavepoints.resize( idx );
262  // Rolling back always dirties the previous savepoint because
263  // the status of the DB has changed between the previous savepoint and the
264  // one we are rolling back to.
265  mLastSavePointIsDirty = true;
266  return executeSql( QStringLiteral( "ROLLBACK TO SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( name ) ), error );
267 }
268 
270 {
271  mLastSavePointIsDirty = true;
272 }
void dataChanged()
Emitted whenever a change is made to the data provider which may have caused changes in the provider'...
Class for storing the component parts of a RDBMS data source URI (e.g.
QString connectionInfo(bool expandAuthConfig=true) const
Returns the connection part of the URI.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
QString source() const
Returns the source for the layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
QgsTransaction * createTransaction(const QString &providerKey, const QString &connString)
Returns new instance of transaction.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
This class allows including a set of layers in a database-side transaction, provided the layer data p...
QStack< QString > mSavepoints
bool mLastSavePointIsDirty
void afterRollback()
Emitted after a rollback.
bool rollback(QString &errorMsg)
Roll back transaction.
bool addLayer(QgsVectorLayer *layer, bool addLayersInEditMode=false)
Add the layer to the transaction.
bool begin(QString &errorMsg, int statementTimeout=20)
Begin transaction The statementTimeout (in seconds) specifies how long an sql statement is allowed to...
static bool supportsTransaction(const QgsVectorLayer *layer)
Checks if the provider of a given layer supports transactions.
virtual bool rollbackToSavepoint(const QString &name, QString &error)
rollback to save point, the save point is maintained and is "undertied"
virtual bool executeSql(const QString &sql, QString &error, bool isDirty=false, const QString &name=QString())=0
Execute the sql string.
QgsTransaction(const QString &connString)
QString connectionString() const
Returns the connection string of the transaction.
bool commit(QString &errorMsg)
Commit transaction.
~QgsTransaction() override
static QgsTransaction * create(const QString &connString, const QString &providerKey)
Create a transaction for the specified connection string connString and provider with providerKey.
QString createSavepoint(QString &error)
creates a save point returns empty string on error returns the last created savepoint if it's not dir...
void dirtyLastSavePoint()
dirty save point such that next call to createSavepoint will create a new one
@ TransactionSupport
Supports transactions.
virtual QgsTransaction * transaction() const
Returns the transaction this data provider is included in, if any.
virtual Q_INVOKABLE QgsVectorDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities.
Represents a vector layer which manages a vector based data sets.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
#define str(x)
Definition: qgis.cpp:37
#define SIP_OUT
Definition: qgis_sip.h:58
#define QgsDebugMsg(str)
Definition: qgslogger.h:38