Branch data Line data Source code
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 : 0 : QgsTransaction *QgsTransaction::create( const QString &connString, const QString &providerKey ) 28 : : { 29 : 0 : return QgsProviderRegistry::instance()->createTransaction( providerKey, connString ); 30 : 0 : } 31 : : 32 : 0 : QgsTransaction *QgsTransaction::create( const QSet<QgsVectorLayer *> &layers ) 33 : : { 34 : 0 : if ( layers.isEmpty() ) 35 : 0 : return nullptr; 36 : : 37 : 0 : QgsVectorLayer *firstLayer = *layers.constBegin(); 38 : : 39 : 0 : QString connStr = connectionString( firstLayer->source() ); 40 : 0 : QString providerKey = firstLayer->providerType(); 41 : 0 : std::unique_ptr<QgsTransaction> transaction( QgsTransaction::create( connStr, providerKey ) ); 42 : 0 : if ( transaction ) 43 : : { 44 : 0 : for ( QgsVectorLayer *layer : layers ) 45 : : { 46 : 0 : if ( !transaction->addLayer( layer ) ) 47 : : { 48 : 0 : transaction.reset(); 49 : 0 : break; 50 : : } 51 : : } 52 : 0 : } 53 : 0 : return transaction.release(); 54 : 0 : } 55 : : 56 : : 57 : 0 : QgsTransaction::QgsTransaction( const QString &connString ) 58 : 0 : : mConnString( connString ) 59 : 0 : , mTransactionActive( false ) 60 : 0 : , mLastSavePointIsDirty( true ) 61 : 0 : { 62 : 0 : } 63 : : 64 : 0 : QgsTransaction::~QgsTransaction() 65 : 0 : { 66 : 0 : setLayerTransactionIds( nullptr ); 67 : 0 : } 68 : : 69 : : // For the needs of the OGR provider with GeoPackage datasources, remove 70 : : // any reference to layers in the connection string 71 : 0 : QString QgsTransaction::removeLayerIdOrName( const QString &str ) 72 : : { 73 : 0 : QString res( str ); 74 : : 75 : 0 : for ( int i = 0; i < 2; i++ ) 76 : : { 77 : 0 : int pos = res.indexOf( i == 0 ? QLatin1String( "|layername=" ) : QLatin1String( "|layerid=" ) ); 78 : 0 : if ( pos >= 0 ) 79 : : { 80 : 0 : int end = res.indexOf( '|', pos + 1 ); 81 : 0 : if ( end >= 0 ) 82 : : { 83 : 0 : res = res.mid( 0, pos ) + res.mid( end ); 84 : 0 : } 85 : : else 86 : : { 87 : 0 : res = res.mid( 0, pos ); 88 : : } 89 : 0 : } 90 : 0 : } 91 : 0 : return res; 92 : 0 : } 93 : : 94 : : ///@cond PRIVATE 95 : 0 : QString QgsTransaction::connectionString( const QString &layerUri ) 96 : : { 97 : 0 : 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 : 0 : if ( connString.isEmpty() ) 102 : : { 103 : 0 : connString = removeLayerIdOrName( layerUri ); 104 : 0 : } 105 : 0 : return connString; 106 : 0 : } 107 : : ///@endcond 108 : : 109 : 0 : bool QgsTransaction::addLayer( QgsVectorLayer *layer ) 110 : : { 111 : 0 : if ( !layer ) 112 : 0 : return false; 113 : : 114 : 0 : if ( layer->isEditable() ) 115 : 0 : return false; 116 : : 117 : : //test if provider supports transactions 118 : 0 : if ( !supportsTransaction( layer ) ) 119 : 0 : return false; 120 : : 121 : 0 : if ( layer->dataProvider()->transaction() ) 122 : 0 : return false; 123 : : 124 : : //connection string not compatible 125 : : 126 : 0 : if ( connectionString( layer->source() ) != mConnString ) 127 : : { 128 : 0 : 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 : 0 : return false; 131 : : } 132 : : 133 : 0 : connect( this, &QgsTransaction::afterRollback, layer->dataProvider(), &QgsVectorDataProvider::dataChanged ); 134 : 0 : connect( layer, &QgsVectorLayer::destroyed, this, &QgsTransaction::onLayerDeleted ); 135 : 0 : mLayers.insert( layer ); 136 : : 137 : 0 : if ( mTransactionActive ) 138 : 0 : layer->dataProvider()->setTransaction( this ); 139 : : 140 : 0 : return true; 141 : 0 : } 142 : : 143 : 0 : bool QgsTransaction::begin( QString &errorMsg, int statementTimeout ) 144 : : { 145 : 0 : if ( mTransactionActive ) 146 : 0 : return false; 147 : : 148 : : //Set all layers to direct edit mode 149 : 0 : if ( !beginTransaction( errorMsg, statementTimeout ) ) 150 : 0 : return false; 151 : : 152 : 0 : setLayerTransactionIds( this ); 153 : 0 : mTransactionActive = true; 154 : 0 : mSavepoints.clear(); 155 : 0 : return true; 156 : 0 : } 157 : : 158 : 0 : bool QgsTransaction::commit( QString &errorMsg ) 159 : : { 160 : 0 : if ( !mTransactionActive ) 161 : 0 : return false; 162 : : 163 : 0 : if ( !commitTransaction( errorMsg ) ) 164 : 0 : return false; 165 : : 166 : 0 : setLayerTransactionIds( nullptr ); 167 : 0 : mTransactionActive = false; 168 : 0 : mSavepoints.clear(); 169 : 0 : return true; 170 : 0 : } 171 : : 172 : 0 : bool QgsTransaction::rollback( QString &errorMsg ) 173 : : { 174 : 0 : if ( !mTransactionActive ) 175 : 0 : return false; 176 : : 177 : 0 : if ( !rollbackTransaction( errorMsg ) ) 178 : 0 : return false; 179 : : 180 : 0 : setLayerTransactionIds( nullptr ); 181 : 0 : mTransactionActive = false; 182 : 0 : mSavepoints.clear(); 183 : : 184 : 0 : emit afterRollback(); 185 : : 186 : 0 : return true; 187 : 0 : } 188 : : 189 : 0 : bool QgsTransaction::supportsTransaction( const QgsVectorLayer *layer ) 190 : : { 191 : : //test if provider supports transactions 192 : 0 : if ( !layer->dataProvider() || ( layer->dataProvider()->capabilities() & QgsVectorDataProvider::TransactionSupport ) == 0 ) 193 : 0 : return false; 194 : : 195 : 0 : return true; 196 : 0 : } 197 : : 198 : 0 : void QgsTransaction::onLayerDeleted() 199 : : { 200 : 0 : mLayers.remove( static_cast<QgsVectorLayer *>( sender() ) ); 201 : 0 : } 202 : : 203 : 0 : void QgsTransaction::setLayerTransactionIds( QgsTransaction *transaction ) 204 : : { 205 : 0 : const auto constMLayers = mLayers; 206 : 0 : for ( QgsVectorLayer *vl : constMLayers ) 207 : : { 208 : 0 : if ( vl->dataProvider() ) 209 : : { 210 : 0 : vl->dataProvider()->setTransaction( transaction ); 211 : 0 : } 212 : : } 213 : 0 : } 214 : : 215 : 0 : QString QgsTransaction::createSavepoint( QString &error SIP_OUT ) 216 : : { 217 : 0 : if ( !mTransactionActive ) 218 : 0 : return QString(); 219 : : 220 : 0 : if ( !mLastSavePointIsDirty && !mSavepoints.isEmpty() ) 221 : : { 222 : 0 : return mSavepoints.top(); 223 : : } 224 : : 225 : 0 : const QString name( QStringLiteral( "qgis" ) + ( QUuid::createUuid().toString().mid( 1, 24 ).replace( '-', QString() ) ) ); 226 : : 227 : 0 : if ( !executeSql( QStringLiteral( "SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( name ) ), error ) ) 228 : : { 229 : 0 : QgsMessageLog::logMessage( tr( "Could not create savepoint (%1)" ).arg( error ) ); 230 : 0 : return QString(); 231 : : } 232 : : 233 : 0 : mSavepoints.push( name ); 234 : 0 : mLastSavePointIsDirty = false; 235 : 0 : return name; 236 : 0 : } 237 : : 238 : 0 : QString QgsTransaction::createSavepoint( const QString &savePointId, QString &error SIP_OUT ) 239 : : { 240 : 0 : if ( !mTransactionActive ) 241 : 0 : return QString(); 242 : : 243 : 0 : if ( !executeSql( QStringLiteral( "SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( savePointId ) ), error ) ) 244 : : { 245 : 0 : QgsMessageLog::logMessage( tr( "Could not create savepoint (%1)" ).arg( error ) ); 246 : 0 : return QString(); 247 : : } 248 : : 249 : 0 : mSavepoints.push( savePointId ); 250 : 0 : mLastSavePointIsDirty = false; 251 : 0 : return savePointId; 252 : 0 : } 253 : : 254 : 0 : bool QgsTransaction::rollbackToSavepoint( const QString &name, QString &error SIP_OUT ) 255 : : { 256 : 0 : if ( !mTransactionActive ) 257 : 0 : return false; 258 : : 259 : 0 : const int idx = mSavepoints.indexOf( name ); 260 : : 261 : 0 : if ( idx == -1 ) 262 : 0 : return false; 263 : : 264 : 0 : 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 : 0 : mLastSavePointIsDirty = true; 269 : 0 : return executeSql( QStringLiteral( "ROLLBACK TO SAVEPOINT %1" ).arg( QgsExpression::quotedColumnRef( name ) ), error ); 270 : 0 : } 271 : : 272 : 0 : void QgsTransaction::dirtyLastSavePoint() 273 : : { 274 : 0 : mLastSavePointIsDirty = true; 275 : 0 : }