QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  QString connStr = connectionString( firstLayer->source() );
40  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 ) )
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 
69 // For the needs of the OGR provider with GeoPackage datasources, remove
70 // any reference to layers in the connection string
71 QString QgsTransaction::removeLayerIdOrName( const QString &str )
72 {
73  QString res( str );
74 
75  for ( int i = 0; i < 2; i++ )
76  {
77  int pos = res.indexOf( i == 0 ? QLatin1String( "|layername=" ) : QLatin1String( "|layerid=" ) );
78  if ( pos >= 0 )
79  {
80  int end = res.indexOf( '|', pos + 1 );
81  if ( end >= 0 )
82  {
83  res = res.mid( 0, pos ) + res.mid( end );
84  }
85  else
86  {
87  res = res.mid( 0, pos );
88  }
89  }
90  }
91  return res;
92 }
93 
95 QString QgsTransaction::connectionString( const QString &layerUri )
96 {
97  QString connString = QgsDataSourceUri( layerUri ).connectionInfo( false );
98  // In the case of a OGR datasource, connectionInfo() will return an empty
99  // string. In that case, use the layer->source() itself, and strip any
100  // reference to layers from it.
101  if ( connString.isEmpty() )
102  {
103  connString = removeLayerIdOrName( layerUri );
104  }
105  return connString;
106 }
108 
110 {
111  if ( !layer )
112  return false;
113 
114  if ( layer->isEditable() )
115  return false;
116 
117  //test if provider supports transactions
118  if ( !supportsTransaction( layer ) )
119  return false;
120 
121  if ( layer->dataProvider()->transaction() )
122  return false;
123 
124  //connection string not compatible
125 
126  if ( connectionString( layer->source() ) != mConnString )
127  {
128  QgsDebugMsg( QStringLiteral( "Couldn't start transaction because connection string for layer %1 : '%2' does not match '%3'" ).arg(
129  layer->id(), connectionString( layer->source() ), mConnString ) );
130  return false;
131  }
132 
134  connect( layer, &QgsVectorLayer::destroyed, this, &QgsTransaction::onLayerDeleted );
135  mLayers.insert( layer );
136 
137  if ( mTransactionActive )
138  layer->dataProvider()->setTransaction( this );
139 
140  return true;
141 }
142 
143 bool QgsTransaction::begin( QString &errorMsg, int statementTimeout )
144 {
145  if ( mTransactionActive )
146  return false;
147 
148  //Set all layers to direct edit mode
149  if ( !beginTransaction( errorMsg, statementTimeout ) )
150  return false;
151 
152  setLayerTransactionIds( this );
153  mTransactionActive = true;
154  mSavepoints.clear();
155  return true;
156 }
157 
158 bool QgsTransaction::commit( QString &errorMsg )
159 {
160  if ( !mTransactionActive )
161  return false;
162 
163  if ( !commitTransaction( errorMsg ) )
164  return false;
165 
166  setLayerTransactionIds( nullptr );
167  mTransactionActive = false;
168  mSavepoints.clear();
169  return true;
170 }
171 
172 bool QgsTransaction::rollback( QString &errorMsg )
173 {
174  if ( !mTransactionActive )
175  return false;
176 
177  if ( !rollbackTransaction( errorMsg ) )
178  return false;
179 
180  setLayerTransactionIds( nullptr );
181  mTransactionActive = false;
182  mSavepoints.clear();
183 
184  emit afterRollback();
185 
186  return true;
187 }
188 
190 {
191  //test if provider supports transactions
192  if ( !layer->dataProvider() || ( layer->dataProvider()->capabilities() & QgsVectorDataProvider::TransactionSupport ) == 0 )
193  return false;
194 
195  return true;
196 }
197 
198 void QgsTransaction::onLayerDeleted()
199 {
200  mLayers.remove( static_cast<QgsVectorLayer *>( sender() ) );
201 }
202 
203 void QgsTransaction::setLayerTransactionIds( QgsTransaction *transaction )
204 {
205  const auto constMLayers = mLayers;
206  for ( QgsVectorLayer *vl : constMLayers )
207  {
208  if ( vl->dataProvider() )
209  {
210  vl->dataProvider()->setTransaction( transaction );
211  }
212  }
213 }
214 
215 QString QgsTransaction::createSavepoint( QString &error SIP_OUT )
216 {
217  if ( !mTransactionActive )
218  return QString();
219 
220  if ( !mLastSavePointIsDirty && !mSavepoints.isEmpty() )
221  {
222  return mSavepoints.top();
223  }
224 
225  const QString name( QStringLiteral( "qgis" ) + ( QUuid::createUuid().toString().mid( 1, 24 ).replace( '-', QString() ) ) );
226 
227  if ( !executeSql( QStringLiteral( "SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( name ) ), error ) )
228  {
229  QgsMessageLog::logMessage( tr( "Could not create savepoint (%1)" ).arg( error ) );
230  return QString();
231  }
232 
233  mSavepoints.push( name );
234  mLastSavePointIsDirty = false;
235  return name;
236 }
237 
238 QString QgsTransaction::createSavepoint( const QString &savePointId, QString &error SIP_OUT )
239 {
240  if ( !mTransactionActive )
241  return QString();
242 
243  if ( !executeSql( QStringLiteral( "SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( savePointId ) ), error ) )
244  {
245  QgsMessageLog::logMessage( tr( "Could not create savepoint (%1)" ).arg( error ) );
246  return QString();
247  }
248 
249  mSavepoints.push( savePointId );
250  mLastSavePointIsDirty = false;
251  return savePointId;
252 }
253 
254 bool QgsTransaction::rollbackToSavepoint( const QString &name, QString &error SIP_OUT )
255 {
256  if ( !mTransactionActive )
257  return false;
258 
259  const int idx = mSavepoints.indexOf( name );
260 
261  if ( idx == -1 )
262  return false;
263 
264  mSavepoints.resize( idx );
265  // Rolling back always dirties the previous savepoint because
266  // the status of the DB has changed between the previous savepoint and the
267  // one we are rolling back to.
268  mLastSavePointIsDirty = true;
269  return executeSql( QStringLiteral( "ROLLBACK TO SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( name ) ), error );
270 }
271 
273 {
274  mLastSavePointIsDirty = true;
275 }
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...
void afterRollback()
Emitted after a rollback.
bool rollback(QString &errorMsg)
Roll back transaction.
bool begin(QString &errorMsg, int statementTimeout=20)
Begin transaction The statementTimeout (in seconds) specifies how long an sql statement is allowed to...
bool addLayer(QgsVectorLayer *layer)
Add the layer to the transaction.
static bool supportsTransaction(const QgsVectorLayer *layer)
Checks if the provider of a given layer supports transactions.
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)
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