Skip to content

Commit

Permalink
[FEATURE]: Transactional editing for postgres layers (other datasourc…
Browse files Browse the repository at this point in the history
…es possible in the future). Funded by City of Uster
  • Loading branch information
manisandro committed Jan 19, 2015
1 parent 7e3bef6 commit 40ad3b0
Show file tree
Hide file tree
Showing 23 changed files with 1,052 additions and 185 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -12,6 +12,7 @@

%Include qgis.sip

%Include qgstransaction.sip
%Include qgsapplication.sip
%Include qgsattributeaction.sip
%Include qgsbrowsermodel.sip
Expand Down
51 changes: 51 additions & 0 deletions python/core/qgstransaction.sip
@@ -0,0 +1,51 @@
/**
* This class allows to include a set of layers in a database-side transaction,
* provided the layer data providers support transactions and are compatible
* with each other.
*
* Only layers which are not in edit mode can be included in a transaction,
* and all layers need to be in read-only mode for a transaction to be committed
* or rolled back.
*
* Layers cannot only be included in one transaction at a time.
*
* When editing layers which are part of a transaction group, all changes are
* sent directly to the data provider (bypassing the undo/redo stack), and the
* changes can either be committed or rolled back on the database side via the
* QgsTransaction::commit and QgsTransaction::rollback methods.
*
* As long as the transaction is active, the state of all layer features reflects
* the current state in the transaction.
*
* Edits on features can get rejected if another conflicting transaction is active.
*/
class QgsTransaction /Abstract/
{
%TypeHeaderCode
#include <qgstransaction.h>
%End
public:
/** Creates a transaction for the specified connection string and provider */
static QgsTransaction* create( const QString& connString, const QString& providerKey ) /Factory/;

/** Creates a transaction which includes the specified layers. Connection string
* and data provider are taken from the first layer */
static QgsTransaction* create( const QStringList& layerIds ) /Factory/;

virtual ~QgsTransaction();

/** Add layer to the transaction. The layer must not be in edit mode. The transaction must not be active. */
bool addLayer( const QString& layerId );

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Dec 1, 2015

Member

@manisandro do you remember why the transaction must not be active when a layer is added?

This comment has been minimized.

Copy link
@manisandro

manisandro Dec 1, 2015

Author Member

@m-kuhn For consistency and simplicity: first you add all layers to the transaction, then you start the transaction, and then you do the editing. This way, when you rollback the transaction, the state of all layers returns to the state before editing. This would be pretty hard to handle if a layer is in editing mode, because you have to figure out to which undo step to roll back to in the layer edit buffer. And if editing of the layer was stopped in the meantime, you have no way to return to the prior state really.

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Dec 1, 2015

Member

I agree on the "layer must not be in edit mode" part. I was only referring to "transaction must not be active".

It's not a big deal, I was just wondering if it's a necessary precondition. E.g. if a new layer is added in the same db while the transaction is active, I think it would be fine to just throw it into the transaction as well.

This comment has been minimized.

Copy link
@manisandro

manisandro Dec 1, 2015

Author Member

@m-kuhn Aha right. Uhm I can't remember right now if I actually encountered a show-stopper without that precondition or whether I introduced it "just to be sure".

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Dec 1, 2015

Member

Have you ever thought about directly connecting the edit state of all layers in a transaction with the transaction being active?

Whenever beginning a transaction, the layers are put in edit mode, whenever committing or rolling back a transaction, the layer editing is stopped.
I think that would make it much easier to use.

This comment has been minimized.

Copy link
@manisandro

manisandro Dec 1, 2015

Author Member

I suppose this could be done, question is whether you want to allow the user to explicitly activate/deactivate the edit mode if the layer is in a transaction.

Also one would need to check with the original client, whether such a change would still fit with their vision of the feature. Perhaps @andreasneumann can say something about this?


/** Begin transaction */
bool begin( QString& errorMsg );

/** Commit transaction. All layers need to be in read-only mode. */
bool commit( QString& errorMsg );

/** Roll back transaction. All layers need to be in read-only mode. */
bool rollback( QString& errorMsg );

/** Executes sql */
virtual bool executeSql( const QString& sql, QString& error ) = 0;

This comment has been minimized.

Copy link
@m-kuhn

m-kuhn Dec 1, 2015

Member

Is there a way to get access to the information returned from the database?
E.g. the return value of an executed function?

This comment has been minimized.

Copy link
@manisandro

manisandro Dec 1, 2015

Author Member

Currently only true/false for success resp failure in QgsPostgresTransaction::executeSql and, if failure, the corresponding error message:

bool QgsPostgresTransaction::executeSql( const QString &sql, QString &errorMsg )
};
2 changes: 2 additions & 0 deletions python/core/qgsvectordataprovider.sip
Expand Up @@ -313,6 +313,8 @@ class QgsVectorDataProvider : QgsDataProvider

static QVariant convertValue( QVariant::Type type, QString value );

virtual QgsTransaction* transaction() const;

protected:
void clearMinMaxCache();
void fillMinMaxCache();
Expand Down
20 changes: 14 additions & 6 deletions src/core/CMakeLists.txt
Expand Up @@ -153,12 +153,14 @@ SET(QGIS_CORE_SRCS
qgssimplifymethod.cpp
qgssnapper.cpp
qgsspatialindex.cpp
qgstransaction.cpp
qgstolerance.cpp
qgsvectordataprovider.cpp
qgsvectorfilewriter.cpp
qgsvectorlayer.cpp
qgsvectorlayercache.cpp
qgsvectorlayereditbuffer.cpp
qgsvectorlayereditpassthrough.cpp
qgsvectorlayereditutils.cpp
qgsvectorlayerfeatureiterator.cpp
qgsvectorlayerimport.cpp
Expand Down Expand Up @@ -188,7 +190,7 @@ SET(QGIS_CORE_SRCS
composer/qgscomposermapoverview.cpp
composer/qgscomposertable.cpp
composer/qgscomposertablev2.cpp
composer/qgscomposertablecolumn.cpp
composer/qgscomposertablecolumn.cpp
composer/qgscomposerattributetable.cpp
composer/qgscomposerattributetablev2.cpp
composer/qgscomposerattributetablemodel.cpp
Expand All @@ -211,7 +213,7 @@ SET(QGIS_CORE_SRCS
composer/qgscomposermultiframe.cpp
composer/qgscomposermodel.cpp
composer/qgscomposition.cpp

dxf/qgsdxfexport.cpp
dxf/qgsdxfpaintdevice.cpp
dxf/qgsdxfpaintengine.cpp
Expand Down Expand Up @@ -269,7 +271,7 @@ SET(QGIS_CORE_SRCS
raster/qgssinglebandgrayrenderer.cpp
raster/qgssinglebandpseudocolorrenderer.cpp
raster/qgsbrightnesscontrastfilter.cpp
raster/qgshuesaturationfilter.cpp
raster/qgshuesaturationfilter.cpp
)

IF(ENABLE_MODELTEST)
Expand Down Expand Up @@ -360,6 +362,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsrunprocess.h
qgsrelationmanager.h
qgsvectorlayer.h
qgsvectorlayereditpassthrough.h
qgsvectorlayereditbuffer.h
qgsnetworkaccessmanager.h
qgsvectordataprovider.h
Expand All @@ -378,14 +381,14 @@ SET(QGIS_CORE_MOC_HDRS
composer/qgscomposerobject.h
composer/qgscomposeritem.h
composer/qgscomposeritemgroup.h
composer/qgscomposermousehandles.h
composer/qgscomposermousehandles.h
composer/qgscomposerlabel.h
composer/qgscomposershape.h
composer/qgscomposerattributetable.h
composer/qgscomposerattributetablev2.h
composer/qgscomposerattributetablemodel.h
composer/qgscomposerattributetablemodelv2.h
composer/qgscomposertable.h
composer/qgscomposerattributetablemodelv2.h
composer/qgscomposertable.h
composer/qgscomposertablev2.h
composer/qgscomposertablecolumn.h
composer/qgscomposerhtml.h
Expand Down Expand Up @@ -459,6 +462,8 @@ SET(QGIS_CORE_HDRS
qgsclipper.h
qgscolorscheme.h
qgscolorschemeregistry.h
qgsconnectionpool.h
qgscontexthelp.h
qgscoordinatereferencesystem.h
qgscrscache.h
qgscsexception.h
Expand Down Expand Up @@ -526,6 +531,9 @@ SET(QGIS_CORE_HDRS
qgssnapper.h
qgsspatialindex.h
qgstolerance.h
qgstransaction.h
qgsvectordataprovider.h
qgsvectorlayercache.h
qgsvectorfilewriter.h
qgsvectorlayereditutils.h
qgsvectorlayerfeatureiterator.h
Expand Down
213 changes: 213 additions & 0 deletions src/core/qgstransaction.cpp
@@ -0,0 +1,213 @@
/***************************************************************************
qgstransaction.cpp
------------------
begin : May 5, 2014
copyright : (C) 2014 by Marco Hugentobler
email : marco dot hugentobler at sourcepole dot ch
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include <QLibrary>

#include "qgstransaction.h"
#include "qgsdatasourceuri.h"
#include "qgsmaplayerregistry.h"
#include "qgsproviderregistry.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayer.h"

typedef QgsTransaction* createTransaction_t( const QString& connString );

QgsTransaction* QgsTransaction::create( const QString& connString, const QString& providerKey )
{

QLibrary* lib = QgsProviderRegistry::instance()->providerLibrary( providerKey );
if ( !lib )
{
return 0;
}

createTransaction_t* createTransaction = ( createTransaction_t* ) cast_to_fptr( lib->resolve( "createTransaction" ) );
if ( !createTransaction )
{
return 0;
}

QgsTransaction* ts = createTransaction( connString );

delete lib;

return ts;
}

QgsTransaction* QgsTransaction::create( const QStringList& layerIds )
{
if ( layerIds.isEmpty() )
{
return 0;
}

QgsVectorLayer* layer = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerIds.first() ) );
if ( !layer )
{
return 0;
}

QString connStr = QgsDataSourceURI( layer->source() ).connectionInfo();
QString providerKey = layer->dataProvider()->name();
QgsTransaction* ts = QgsTransaction::create( connStr, providerKey );
if ( !ts )
{
return 0;
}

foreach ( const QString& layerId, layerIds )
{
if ( !ts->addLayer( layerId ) )
{
delete ts;
return 0;
}
}
return ts;
}


QgsTransaction::QgsTransaction( const QString& connString )
: mConnString( connString ), mTransactionActive( false )
{
}

QgsTransaction::~QgsTransaction()
{
setLayerTransactionIds( 0 );
}

bool QgsTransaction::addLayer( const QString& layerId )
{
if ( mTransactionActive )
{
return false;
}

QgsVectorLayer* layer = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerId ) );
if ( !layer )
{
return false;
}

if ( layer->isEditable() )
{
return false;
}

//test if provider supports transactions
if ( !layer->dataProvider() || !layer->dataProvider()->capabilities() & QgsVectorDataProvider::TransactionSupport )
{
return false;
}

if ( layer->dataProvider()->transaction() != 0 )
{
return false;
}

//connection string not compatible
if ( QgsDataSourceURI( layer->source() ).connectionInfo() != mConnString )
{
return false;
}

mLayers.insert( layerId );
return true;
}

bool QgsTransaction::begin( QString& errorMsg, int statementTimeout )
{
if ( mTransactionActive )
{
return false;
}

//Set all layers to direct edit mode
if ( !beginTransaction( errorMsg, statementTimeout ) )
{
return false;
}

setLayerTransactionIds( this );
mTransactionActive = true;
return true;
}

bool QgsTransaction::commit( QString& errorMsg )
{
if ( !mTransactionActive )
{
return false;
}

foreach ( const QString& layerid, mLayers )
{
QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( layerid );
if ( !l || l->isEditable() )
{
return false;
}
}

if ( !commitTransaction( errorMsg ) )
{
return false;
}

setLayerTransactionIds( 0 );
mTransactionActive = false;
return true;
}

bool QgsTransaction::rollback( QString& errorMsg )
{
if ( !mTransactionActive )
{
return false;
}

foreach ( const QString& layerid, mLayers )
{
QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( layerid );
if ( !l || l->isEditable() )
{
return false;
}
}

if ( !rollbackTransaction( errorMsg ) )
{
return false;
}

setLayerTransactionIds( 0 );
mTransactionActive = false;
return true;
}

void QgsTransaction::setLayerTransactionIds( QgsTransaction* transaction )
{
foreach ( const QString& layerid, mLayers )
{
QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerid ) );
if ( vl && vl->dataProvider() )
{
vl->dataProvider()->setTransaction( transaction );
}
}
}

0 comments on commit 40ad3b0

Please sign in to comment.