QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
18 #include <QLibrary>
19 
20 #include "qgstransaction.h"
21 #include "qgslogger.h"
22 #include "qgsdatasourceuri.h"
23 #include "qgsproviderregistry.h"
24 #include "qgsvectordataprovider.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsexpression.h"
27 #include "qgsmessagelog.h"
28 #include <QUuid>
29 
30 typedef QgsTransaction *createTransaction_t( const QString &connString );
31 
32 QgsTransaction *QgsTransaction::create( const QString &connString, const QString &providerKey )
33 {
34  std::unique_ptr< QLibrary > lib( QgsProviderRegistry::instance()->createProviderLibrary( providerKey ) );
35  if ( !lib )
36  return nullptr;
37 
38  createTransaction_t *createTransaction = reinterpret_cast< createTransaction_t * >( cast_to_fptr( lib->resolve( "createTransaction" ) ) );
39  if ( !createTransaction )
40  return nullptr;
41 
42  QgsTransaction *ts = createTransaction( connString );
43 
44  return ts;
45 }
46 
47 QgsTransaction *QgsTransaction::create( const QSet<QgsVectorLayer *> &layers )
48 {
49  if ( layers.isEmpty() )
50  return nullptr;
51 
52  QgsVectorLayer *firstLayer = *layers.constBegin();
53 
54  QString connStr = connectionString( firstLayer->source() );
55  QString providerKey = firstLayer->dataProvider()->name();
56  std::unique_ptr<QgsTransaction> transaction( QgsTransaction::create( connStr, providerKey ) );
57  if ( transaction )
58  {
59  for ( QgsVectorLayer *layer : layers )
60  {
61  if ( !transaction->addLayer( layer ) )
62  {
63  transaction.reset();
64  break;
65  }
66  }
67  }
68  return transaction.release();
69 }
70 
71 
72 QgsTransaction::QgsTransaction( const QString &connString )
73  : mConnString( connString )
74  , mTransactionActive( false )
75  , mLastSavePointIsDirty( true )
76 {
77 }
78 
80 {
81  setLayerTransactionIds( nullptr );
82 }
83 
84 // For the needs of the OGR provider with GeoPackage datasources, remove
85 // any reference to layers in the connection string
86 QString QgsTransaction::removeLayerIdOrName( const QString &str )
87 {
88  QString res( str );
89 
90  for ( int i = 0; i < 2; i++ )
91  {
92  int pos = res.indexOf( i == 0 ? QLatin1String( "|layername=" ) : QLatin1String( "|layerid=" ) );
93  if ( pos >= 0 )
94  {
95  int end = res.indexOf( '|', pos + 1 );
96  if ( end >= 0 )
97  {
98  res = res.mid( 0, pos ) + res.mid( end );
99  }
100  else
101  {
102  res = res.mid( 0, pos );
103  }
104  }
105  }
106  return res;
107 }
108 
110 QString QgsTransaction::connectionString( const QString &layerName )
111 {
112  QString connString = QgsDataSourceUri( layerName ).connectionInfo( false );
113  // In the case of a OGR datasource, connectionInfo() will return an empty
114  // string. In that case, use the layer->source() itself, and strip any
115  // reference to layers from it.
116  if ( connString.isEmpty() )
117  {
118  connString = removeLayerIdOrName( layerName );
119  }
120  return connString;
121 }
123 
125 {
126  if ( !layer )
127  return false;
128 
129  if ( layer->isEditable() )
130  return false;
131 
132  //test if provider supports transactions
133  if ( !supportsTransaction( layer ) )
134  return false;
135 
136  if ( layer->dataProvider()->transaction() )
137  return false;
138 
139  //connection string not compatible
140 
141  if ( connectionString( layer->source() ) != mConnString )
142  {
143  QgsDebugMsg( QStringLiteral( "Couldn't start transaction because connection string for layer %1 : '%2' does not match '%3'" ).arg(
144  layer->id(), connectionString( layer->source() ), mConnString ) );
145  return false;
146  }
147 
149  connect( layer, &QgsVectorLayer::destroyed, this, &QgsTransaction::onLayerDeleted );
150  mLayers.insert( layer );
151 
152  if ( mTransactionActive )
153  layer->dataProvider()->setTransaction( this );
154 
155  return true;
156 }
157 
158 bool QgsTransaction::begin( QString &errorMsg, int statementTimeout )
159 {
160  if ( mTransactionActive )
161  return false;
162 
163  //Set all layers to direct edit mode
164  if ( !beginTransaction( errorMsg, statementTimeout ) )
165  return false;
166 
167  setLayerTransactionIds( this );
168  mTransactionActive = true;
169  mSavepoints.clear();
170  return true;
171 }
172 
173 bool QgsTransaction::commit( QString &errorMsg )
174 {
175  if ( !mTransactionActive )
176  return false;
177 
178  if ( !commitTransaction( errorMsg ) )
179  return false;
180 
181  setLayerTransactionIds( nullptr );
182  mTransactionActive = false;
183  mSavepoints.clear();
184  return true;
185 }
186 
187 bool QgsTransaction::rollback( QString &errorMsg )
188 {
189  if ( !mTransactionActive )
190  return false;
191 
192  if ( !rollbackTransaction( errorMsg ) )
193  return false;
194 
195  setLayerTransactionIds( nullptr );
196  mTransactionActive = false;
197  mSavepoints.clear();
198 
199  emit afterRollback();
200 
201  return true;
202 }
203 
205 {
206  //test if provider supports transactions
207  if ( !layer->dataProvider() || ( layer->dataProvider()->capabilities() & QgsVectorDataProvider::TransactionSupport ) == 0 )
208  return false;
209 
210  return true;
211 }
212 
213 void QgsTransaction::onLayerDeleted()
214 {
215  mLayers.remove( static_cast<QgsVectorLayer *>( sender() ) );
216 }
217 
218 void QgsTransaction::setLayerTransactionIds( QgsTransaction *transaction )
219 {
220  Q_FOREACH ( QgsVectorLayer *vl, mLayers )
221  {
222  if ( vl->dataProvider() )
223  {
224  vl->dataProvider()->setTransaction( transaction );
225  }
226  }
227 }
228 
229 QString QgsTransaction::createSavepoint( QString &error SIP_OUT )
230 {
231  if ( !mTransactionActive )
232  return QString();
233 
234  if ( !mLastSavePointIsDirty && !mSavepoints.isEmpty() )
235  return mSavepoints.top();
236 
237  const QString name( QUuid::createUuid().toString() );
238 
239  if ( !executeSql( QStringLiteral( "SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( name ) ), error ) )
240  {
241  QgsMessageLog::logMessage( tr( "Could not create savepoint (%1)" ).arg( error ) );
242  return QString();
243  }
244 
245  mSavepoints.push( name );
246  mLastSavePointIsDirty = false;
247  return name;
248 }
249 
250 QString QgsTransaction::createSavepoint( const QString &savePointId, QString &error SIP_OUT )
251 {
252  if ( !mTransactionActive )
253  return QString();
254 
255  if ( !executeSql( QStringLiteral( "SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( savePointId ) ), error ) )
256  {
257  QgsMessageLog::logMessage( tr( "Could not create savepoint (%1)" ).arg( error ) );
258  return QString();
259  }
260 
261  mSavepoints.push( savePointId );
262  mLastSavePointIsDirty = false;
263  return savePointId;
264 }
265 
266 bool QgsTransaction::rollbackToSavepoint( const QString &name, QString &error SIP_OUT )
267 {
268  if ( !mTransactionActive )
269  return false;
270 
271  const int idx = mSavepoints.indexOf( name );
272 
273  if ( idx == -1 )
274  return false;
275 
276  mSavepoints.resize( idx );
277  mLastSavePointIsDirty = false;
278  return executeSql( QStringLiteral( "ROLLBACK TO SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( name ) ), error );
279 }
280 
282 {
283  mLastSavePointIsDirty = true;
284 }
static QgsTransaction * create(const QString &connString, const QString &providerKey)
Create a transaction for the specified connection string connString and provider with providerKey...
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsTransaction * createTransaction_t(const QString &connString)
QString source() const
Returns the source for the layer.
bool rollbackToSavepoint(const QString &name, QString &error)
rollback to save point, the save point is maintained and is "undertied"
bool commit(QString &errorMsg)
Commit transaction.
virtual bool executeSql(const QString &sql, QString &error, bool isDirty=false, const QString &name=QString())=0
Execute the sql string.
virtual QString name() const =0
Returns a provider name.
void dataChanged()
This is emitted whenever an asynchronous operation has finished and the data should be redrawn...
QString connectionInfo(bool expandAuthConfig=true) const
Returns connection part of URI.
bool isEditable() const FINAL
Returns true if the provider is in editing mode.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
~QgsTransaction() override
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
static bool supportsTransaction(const QgsVectorLayer *layer)
Checks if the provider of a given layer supports transactions.
#define cast_to_fptr(f)
Definition: qgis.h:171
void afterRollback()
Emitted after a rollback.
virtual QgsTransaction * transaction() const
Returns the transaction this data provider is included in, if any.
QgsTransaction(const QString &connString)
virtual QgsVectorDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities.
This class allows including a set of layers in a database-side transaction, provided the layer data p...
#define SIP_OUT
Definition: qgis_sip.h:51
bool addLayer(QgsVectorLayer *layer)
Add the layer to the transaction.
bool rollback(QString &errorMsg)
Roll back transaction.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer&#39;s data provider.
Class for storing the component parts of a PostgreSQL/RDBMS datasource URI.
QString createSavepoint(QString &error)
creates a save point returns empty string on error returns the last created savepoint if it&#39;s not dir...
Represents a vector layer which manages a vector based data sets.
void dirtyLastSavePoint()
dirty save point such that next call to createSavepoint will create a new one
bool begin(QString &errorMsg, int statementTimeout=20)
Begin transaction The statementTimeout (in seconds) specifies how long an sql statement is allowed to...