mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
[OGR provider] [FEATURE] Add support for transactions on GPKG databases
For complete support, it requires two GDAL fixes:
- One to avoid feature count to be invalid when using ROLLBACK TO SAVEPOINT
f73ec8cd1d
- Another one to avoid nasty issues, at least on Linux, with the POSIX
advisory locks used by libsqlite that could be invalidated due to how GDAL
could open files behind the back of libsqlite. The consequence of this
could be the deletion of -wal and -shm files, which caused issues in QGIS
(non working iterators when the edit is finished, and later edits in the
same session not working). Those issues could appear for example if doing
ogrinfo on the .gpkg opened by QGIS, or if opening two QGIS session on the
.gpkg
Both fixes are queued for GDAL 2.3.1
This commit is contained in:
parent
370bac9935
commit
6a987f913b
@ -146,6 +146,7 @@ returns the last created savepoint
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
void afterRollback();
|
||||
|
@ -51,7 +51,7 @@ QgsTransaction *QgsTransaction::create( const QSet<QgsVectorLayer *> &layers )
|
||||
|
||||
QgsVectorLayer *firstLayer = *layers.constBegin();
|
||||
|
||||
QString connStr = QgsDataSourceUri( firstLayer->source() ).connectionInfo( false );
|
||||
QString connStr = connectionString( firstLayer->source() );
|
||||
QString providerKey = firstLayer->dataProvider()->name();
|
||||
std::unique_ptr<QgsTransaction> transaction( QgsTransaction::create( connStr, providerKey ) );
|
||||
if ( transaction )
|
||||
@ -81,6 +81,46 @@ QgsTransaction::~QgsTransaction()
|
||||
setLayerTransactionIds( nullptr );
|
||||
}
|
||||
|
||||
// For the needs of the OGR provider with GeoPackage datasources, remove
|
||||
// any reference to layers in the connection string
|
||||
QString QgsTransaction::removeLayerIdOrName( const QString &str )
|
||||
{
|
||||
QString res( str );
|
||||
|
||||
for ( int i = 0; i < 2; i++ )
|
||||
{
|
||||
int pos = res.indexOf( i == 0 ? QLatin1String( "|layername=" ) : QLatin1String( "|layerid=" ) );
|
||||
if ( pos >= 0 )
|
||||
{
|
||||
int end = res.indexOf( '|', pos + 1 );
|
||||
if ( end >= 0 )
|
||||
{
|
||||
res = res.mid( 0, pos ) + res.mid( end );
|
||||
}
|
||||
else
|
||||
{
|
||||
res = res.mid( 0, pos );
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
///@cond PRIVATE
|
||||
QString QgsTransaction::connectionString( const QString &layerName )
|
||||
{
|
||||
QString connString = QgsDataSourceUri( layerName ).connectionInfo( false );
|
||||
// In the case of a OGR datasource, connectionInfo() will return an empty
|
||||
// string. In that case, use the layer->source() itself, and strip any
|
||||
// reference to layers from it.
|
||||
if ( connString.isEmpty() )
|
||||
{
|
||||
connString = removeLayerIdOrName( layerName );
|
||||
}
|
||||
return connString;
|
||||
}
|
||||
///@endcond
|
||||
|
||||
bool QgsTransaction::addLayer( QgsVectorLayer *layer )
|
||||
{
|
||||
if ( !layer )
|
||||
@ -90,17 +130,18 @@ bool QgsTransaction::addLayer( QgsVectorLayer *layer )
|
||||
return false;
|
||||
|
||||
//test if provider supports transactions
|
||||
if ( !layer->dataProvider() || ( layer->dataProvider()->capabilities() & QgsVectorDataProvider::TransactionSupport ) == 0 )
|
||||
if ( !supportsTransaction( layer ) )
|
||||
return false;
|
||||
|
||||
if ( layer->dataProvider()->transaction() )
|
||||
return false;
|
||||
|
||||
//connection string not compatible
|
||||
if ( QgsDataSourceUri( layer->source() ).connectionInfo( false ) != mConnString )
|
||||
|
||||
if ( connectionString( layer->source() ) != mConnString )
|
||||
{
|
||||
QgsDebugMsg( QString( "Couldn't start transaction because connection string for layer %1 : '%2' does not match '%3'" ).arg(
|
||||
layer->id(), QgsDataSourceUri( layer->source() ).connectionInfo( false ), mConnString ) );
|
||||
layer->id(), connectionString( layer->source() ), mConnString ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -162,11 +203,11 @@ bool QgsTransaction::rollback( QString &errorMsg )
|
||||
|
||||
bool QgsTransaction::supportsTransaction( const QgsVectorLayer *layer )
|
||||
{
|
||||
std::unique_ptr< QLibrary > lib( QgsProviderRegistry::instance()->createProviderLibrary( layer->providerType() ) );
|
||||
if ( !lib )
|
||||
//test if provider supports transactions
|
||||
if ( !layer->dataProvider() || ( layer->dataProvider()->capabilities() & QgsVectorDataProvider::TransactionSupport ) == 0 )
|
||||
return false;
|
||||
|
||||
return lib->resolve( "createTransaction" );
|
||||
return true;
|
||||
}
|
||||
|
||||
void QgsTransaction::onLayerDeleted()
|
||||
|
@ -158,6 +158,11 @@ class CORE_EXPORT QgsTransaction : public QObject SIP_ABSTRACT
|
||||
*/
|
||||
bool lastSavePointIsDirty() const { return mLastSavePointIsDirty; }
|
||||
|
||||
///@cond PRIVATE
|
||||
// For internal use only, or by QgsTransactionGroup
|
||||
static QString connectionString( const QString &layerName ) SIP_SKIP;
|
||||
///@endcond
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
@ -188,6 +193,8 @@ class CORE_EXPORT QgsTransaction : public QObject SIP_ABSTRACT
|
||||
|
||||
void setLayerTransactionIds( QgsTransaction *transaction );
|
||||
|
||||
static QString removeLayerIdOrName( const QString &str );
|
||||
|
||||
virtual bool beginTransaction( QString &error, int statementTimeout ) = 0;
|
||||
virtual bool commitTransaction( QString &error ) = 0;
|
||||
virtual bool rollbackTransaction( QString &error ) = 0;
|
||||
|
@ -33,8 +33,7 @@ bool QgsTransactionGroup::addLayer( QgsVectorLayer *layer )
|
||||
if ( !QgsTransaction::supportsTransaction( layer ) )
|
||||
return false;
|
||||
|
||||
QString connString = QgsDataSourceUri( layer->source() ).connectionInfo();
|
||||
|
||||
QString connString = QgsTransaction::connectionString( layer->source() );
|
||||
if ( mConnString.isEmpty() )
|
||||
{
|
||||
mConnString = connString;
|
||||
@ -74,6 +73,8 @@ void QgsTransactionGroup::onEditingStarted()
|
||||
return;
|
||||
|
||||
mTransaction.reset( QgsTransaction::create( mConnString, mProviderKey ) );
|
||||
if ( !mTransaction )
|
||||
return;
|
||||
|
||||
QString errorMsg;
|
||||
mTransaction->begin( errorMsg );
|
||||
|
@ -9,6 +9,7 @@ SET (OGR_SRCS
|
||||
qgsgeopackagerasterwritertask.cpp
|
||||
qgsogrdbconnection.cpp
|
||||
qgsogrdbtablemodel.cpp
|
||||
qgsogrtransaction.cpp
|
||||
)
|
||||
|
||||
SET(OGR_MOC_HDRS
|
||||
@ -19,6 +20,7 @@ SET(OGR_MOC_HDRS
|
||||
qgsgeopackagerasterwritertask.h
|
||||
qgsogrdbconnection.h
|
||||
qgsogrdbtablemodel.h
|
||||
qgsogrtransaction.h
|
||||
)
|
||||
|
||||
IF (WITH_GUI)
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "qgssettings.h"
|
||||
#include "qgsexception.h"
|
||||
#include "qgswkbtypes.h"
|
||||
#include "qgsogrtransaction.h"
|
||||
|
||||
#include <QTextCodec>
|
||||
#include <QFile>
|
||||
@ -42,38 +43,51 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource *source, bool
|
||||
: QgsAbstractFeatureIteratorFromSource<QgsOgrFeatureSource>( source, ownSource, request )
|
||||
, mFilterFids( mRequest.filterFids() )
|
||||
, mFilterFidsIt( mFilterFids.constBegin() )
|
||||
, mSharedDS( source->mSharedDS )
|
||||
{
|
||||
//QgsDebugMsg( "Feature iterator of " + mSource->mLayerName + ": acquiring connection");
|
||||
mConn = QgsOgrConnPool::instance()->acquireConnection( QgsOgrProviderUtils::connectionPoolId( mSource->mDataSource ) );
|
||||
if ( !mConn->ds )
|
||||
if ( mSharedDS )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( mSource->mLayerName.isNull() )
|
||||
{
|
||||
mOgrLayer = GDALDatasetGetLayer( mConn->ds, mSource->mLayerIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
mOgrLayer = GDALDatasetGetLayerByName( mConn->ds, mSource->mLayerName.toUtf8().constData() );
|
||||
}
|
||||
if ( !mOgrLayer )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !mSource->mSubsetString.isEmpty() )
|
||||
{
|
||||
mOgrOrigLayer = mOgrLayer;
|
||||
mOgrLayerWithFid = QgsOgrProviderUtils::setSubsetString( mOgrLayer, mConn->ds, mSource->mEncoding, QString(), true, &mOrigFidAdded );
|
||||
mOgrLayer = QgsOgrProviderUtils::setSubsetString( mOgrLayer, mConn->ds, mSource->mEncoding, mSource->mSubsetString, true, &mOrigFidAdded );
|
||||
mOgrLayer = mSharedDS->createSQLResultLayer( mSource->mEncoding, mSource->mLayerName, mSource->mLayerIndex );
|
||||
if ( !mOgrLayer )
|
||||
{
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//QgsDebugMsg( "Feature iterator of " + mSource->mLayerName + ": acquiring connection");
|
||||
mConn = QgsOgrConnPool::instance()->acquireConnection( QgsOgrProviderUtils::connectionPoolId( mSource->mDataSource ) );
|
||||
if ( !mConn->ds )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( mSource->mLayerName.isNull() )
|
||||
{
|
||||
mOgrLayer = GDALDatasetGetLayer( mConn->ds, mSource->mLayerIndex );
|
||||
}
|
||||
else
|
||||
{
|
||||
mOgrLayer = GDALDatasetGetLayerByName( mConn->ds, mSource->mLayerName.toUtf8().constData() );
|
||||
}
|
||||
if ( !mOgrLayer )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !mSource->mSubsetString.isEmpty() )
|
||||
{
|
||||
mOgrOrigLayer = mOgrLayer;
|
||||
mOgrLayerWithFid = QgsOgrProviderUtils::setSubsetString( mOgrLayer, mConn->ds, mSource->mEncoding, QString(), true, &mOrigFidAdded );
|
||||
mOgrLayer = QgsOgrProviderUtils::setSubsetString( mOgrLayer, mConn->ds, mSource->mEncoding, mSource->mSubsetString, true, &mOrigFidAdded );
|
||||
if ( !mOgrLayer )
|
||||
{
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
QMutexLocker locker( mSharedDS ? &mSharedDS->mutex() : nullptr );
|
||||
|
||||
if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->mCrs )
|
||||
{
|
||||
@ -237,6 +251,8 @@ bool QgsOgrFeatureIterator::fetchFeatureWithId( QgsFeatureId id, QgsFeature &fea
|
||||
|
||||
bool QgsOgrFeatureIterator::fetchFeature( QgsFeature &feature )
|
||||
{
|
||||
QMutexLocker locker( mSharedDS ? &mSharedDS->mutex() : nullptr );
|
||||
|
||||
feature.setValid( false );
|
||||
|
||||
if ( mClosed || !mOgrLayer )
|
||||
@ -286,6 +302,8 @@ bool QgsOgrFeatureIterator::fetchFeature( QgsFeature &feature )
|
||||
|
||||
bool QgsOgrFeatureIterator::rewind()
|
||||
{
|
||||
QMutexLocker locker( mSharedDS ? &mSharedDS->mutex() : nullptr );
|
||||
|
||||
if ( mClosed || !mOgrLayer )
|
||||
return false;
|
||||
|
||||
@ -299,6 +317,20 @@ bool QgsOgrFeatureIterator::rewind()
|
||||
|
||||
bool QgsOgrFeatureIterator::close()
|
||||
{
|
||||
if ( mSharedDS )
|
||||
{
|
||||
iteratorClosed();
|
||||
|
||||
if ( mOgrLayer )
|
||||
{
|
||||
mSharedDS->releaseResultSet( mOgrLayer );
|
||||
mOgrLayer = nullptr;
|
||||
}
|
||||
mSharedDS.reset();
|
||||
mClosed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( !mConn )
|
||||
return false;
|
||||
|
||||
@ -444,7 +476,12 @@ QgsOgrFeatureSource::QgsOgrFeatureSource( const QgsOgrProvider *p )
|
||||
, mDriverName( p->mGDALDriverName )
|
||||
, mCrs( p->crs() )
|
||||
, mWkbType( p->wkbType() )
|
||||
, mSharedDS( nullptr )
|
||||
{
|
||||
if ( p->mTransaction )
|
||||
{
|
||||
mSharedDS = p->mTransaction->sharedDS();
|
||||
}
|
||||
for ( int i = ( p->mFirstFieldIsFid ) ? 1 : 0; i < mFields.size(); i++ )
|
||||
mFieldsWithoutFid.append( mFields.at( i ) );
|
||||
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( mDataSource ) );
|
||||
|
@ -21,8 +21,12 @@
|
||||
|
||||
#include <ogr_api.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QgsOgrFeatureIterator;
|
||||
class QgsOgrProvider;
|
||||
class QgsOgrDataset;
|
||||
using QgsOgrDatasetSharedPtr = std::shared_ptr< QgsOgrDataset>;
|
||||
|
||||
class QgsOgrFeatureSource : public QgsAbstractFeatureSource
|
||||
{
|
||||
@ -45,6 +49,7 @@ class QgsOgrFeatureSource : public QgsAbstractFeatureSource
|
||||
QString mDriverName;
|
||||
QgsCoordinateReferenceSystem mCrs;
|
||||
QgsWkbTypes::Type mWkbType = QgsWkbTypes::Unknown;
|
||||
QgsOgrDatasetSharedPtr mSharedDS = nullptr;
|
||||
|
||||
friend class QgsOgrFeatureIterator;
|
||||
friend class QgsOgrExpressionCompiler;
|
||||
@ -87,6 +92,7 @@ class QgsOgrFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsOgr
|
||||
|
||||
QgsRectangle mFilterRect;
|
||||
QgsCoordinateTransform mTransform;
|
||||
QgsOgrDatasetSharedPtr mSharedDS = nullptr;
|
||||
|
||||
bool fetchFeatureWithId( QgsFeatureId id, QgsFeature &feature ) const;
|
||||
};
|
||||
|
@ -37,6 +37,7 @@ email : sherman at mrcc.com
|
||||
#include "qgsgeopackagedataitems.h"
|
||||
#include "qgswkbtypes.h"
|
||||
#include "qgsnetworkaccessmanager.h"
|
||||
#include "qgsogrtransaction.h"
|
||||
|
||||
#ifdef HAVE_GUI
|
||||
#include "qgssourceselectprovider.h"
|
||||
@ -564,6 +565,18 @@ QString QgsOgrProvider::dataSourceUri( bool expandAuthConfig ) const
|
||||
}
|
||||
}
|
||||
|
||||
QgsTransaction *QgsOgrProvider::transaction() const
|
||||
{
|
||||
return static_cast<QgsTransaction *>( mTransaction );
|
||||
}
|
||||
|
||||
void QgsOgrProvider::setTransaction( QgsTransaction *transaction )
|
||||
{
|
||||
QgsDebugMsg( QString( "set transaction %1" ).arg( transaction != nullptr ) );
|
||||
// static_cast since layers cannot be added to a transaction of a non-matching provider
|
||||
mTransaction = static_cast<QgsOgrTransaction *>( transaction );
|
||||
}
|
||||
|
||||
QgsAbstractFeatureSource *QgsOgrProvider::featureSource() const
|
||||
{
|
||||
return new QgsOgrFeatureSource( this );
|
||||
@ -1545,6 +1558,9 @@ bool QgsOgrProvider::addFeatures( QgsFeatureList &flist, Flags flags )
|
||||
if ( returnvalue )
|
||||
clearMinMaxCache();
|
||||
|
||||
if ( mTransaction )
|
||||
mTransaction->dirtyLastSavePoint();
|
||||
|
||||
return returnvalue;
|
||||
}
|
||||
|
||||
@ -1672,6 +1688,9 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
|
||||
}
|
||||
}
|
||||
|
||||
if ( mTransaction )
|
||||
mTransaction->dirtyLastSavePoint();
|
||||
|
||||
return returnvalue;
|
||||
}
|
||||
|
||||
@ -1706,6 +1725,10 @@ bool QgsOgrProvider::deleteAttributes( const QgsAttributeIds &attributes )
|
||||
}
|
||||
}
|
||||
loadFields();
|
||||
|
||||
if ( mTransaction )
|
||||
mTransaction->dirtyLastSavePoint();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -1753,13 +1776,17 @@ bool QgsOgrProvider::renameAttributes( const QgsFieldNameMap &renamedAttributes
|
||||
}
|
||||
}
|
||||
loadFields();
|
||||
|
||||
if ( mTransaction )
|
||||
mTransaction->dirtyLastSavePoint();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool QgsOgrProvider::startTransaction()
|
||||
{
|
||||
bool inTransaction = false;
|
||||
if ( mOgrLayer->TestCapability( OLCTransactions ) )
|
||||
if ( mTransaction == nullptr && mOgrLayer->TestCapability( OLCTransactions ) )
|
||||
{
|
||||
// A transaction might already be active, so be robust on failed
|
||||
// StartTransaction.
|
||||
@ -2009,6 +2036,9 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
|
||||
commitTransaction();
|
||||
}
|
||||
|
||||
if ( mTransaction )
|
||||
mTransaction->dirtyLastSavePoint();
|
||||
|
||||
if ( mOgrLayer->SyncToDisk() != OGRERR_NONE )
|
||||
{
|
||||
pushError( tr( "OGR error syncing to disk: %1" ).arg( CPLGetLastErrorMsg() ) );
|
||||
@ -2089,6 +2119,9 @@ bool QgsOgrProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
|
||||
commitTransaction();
|
||||
}
|
||||
|
||||
if ( mTransaction )
|
||||
mTransaction->dirtyLastSavePoint();
|
||||
|
||||
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
|
||||
return syncToDisc();
|
||||
}
|
||||
@ -2191,6 +2224,9 @@ bool QgsOgrProvider::deleteFeatures( const QgsFeatureIds &id )
|
||||
commitTransaction();
|
||||
}
|
||||
|
||||
if ( mTransaction )
|
||||
mTransaction->dirtyLastSavePoint();
|
||||
|
||||
if ( !syncToDisc() )
|
||||
{
|
||||
returnvalue = false;
|
||||
@ -2216,6 +2252,9 @@ bool QgsOgrProvider::deleteFeature( QgsFeatureId id )
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( mTransaction )
|
||||
mTransaction->dirtyLastSavePoint();
|
||||
|
||||
mShapefileMayBeCorrupted = true;
|
||||
|
||||
return true;
|
||||
@ -2434,6 +2473,12 @@ void QgsOgrProvider::computeCapabilities()
|
||||
{
|
||||
ability |= CircularGeometries;
|
||||
}
|
||||
|
||||
if ( mGDALDriverName == QLatin1String( "GPKG" ) )
|
||||
{
|
||||
//supports transactions
|
||||
ability |= TransactionSupport;
|
||||
}
|
||||
}
|
||||
|
||||
if ( updateModeActivated )
|
||||
@ -4348,6 +4393,29 @@ void QgsOgrProviderUtils::invalidateCachedDatasets( const QString &dsName )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QgsOgrDatasetSharedPtr QgsOgrProviderUtils::getAlreadyOpenedDataset( const QString &dsName )
|
||||
{
|
||||
QMutexLocker locker( &sGlobalMutex );
|
||||
for ( auto iter = sMapSharedDS.begin(); iter != sMapSharedDS.end(); ++iter )
|
||||
{
|
||||
auto ident = iter.key();
|
||||
if ( ident.dsName == dsName && ident.updateMode )
|
||||
{
|
||||
// Browse through this list, to look for the first available DatasetWithLayers*
|
||||
// instance that is in update mode (hoping there's only one...)
|
||||
auto &datasetList = iter.value();
|
||||
for ( const auto &ds : datasetList )
|
||||
{
|
||||
Q_ASSERT( ds->refCount > 0 );
|
||||
return QgsOgrDataset::create( ident, ds );
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName,
|
||||
int layerIndex,
|
||||
QString &errCause )
|
||||
@ -4987,6 +5055,45 @@ QgsOgrLayerUniquePtr QgsOgrProviderUtils::getSqlLayer( QgsOgrLayer *baseLayer,
|
||||
return QgsOgrLayer::CreateForSql( ident, sql, baseLayer->ds, hSqlLayer );
|
||||
}
|
||||
|
||||
void QgsOgrProviderUtils::releaseInternal( const DatasetIdentification &ident,
|
||||
DatasetWithLayers *ds,
|
||||
bool removeFromDatasetList )
|
||||
{
|
||||
|
||||
ds->refCount --;
|
||||
if ( ds->refCount == 0 )
|
||||
{
|
||||
Q_ASSERT( ds->setLayers.isEmpty() );
|
||||
|
||||
if ( removeFromDatasetList )
|
||||
{
|
||||
auto iter = sMapSharedDS.find( ident );
|
||||
if ( iter != sMapSharedDS.end() )
|
||||
{
|
||||
auto &datasetList = iter.value();
|
||||
int i = 0;
|
||||
|
||||
// Normally there should be a match, except for datasets that
|
||||
// have been invalidated
|
||||
Q_FOREACH ( QgsOgrProviderUtils::DatasetWithLayers *dsIter, datasetList )
|
||||
{
|
||||
if ( dsIter == ds )
|
||||
{
|
||||
datasetList.removeAt( i );
|
||||
break;
|
||||
}
|
||||
i ++;
|
||||
}
|
||||
|
||||
if ( datasetList.isEmpty() )
|
||||
sMapSharedDS.erase( iter );
|
||||
}
|
||||
}
|
||||
QgsOgrProviderUtils::GDALCloseWrapper( ds->hDS );
|
||||
delete ds;
|
||||
}
|
||||
}
|
||||
|
||||
void QgsOgrProviderUtils::release( QgsOgrLayer *&layer )
|
||||
{
|
||||
if ( !layer )
|
||||
@ -5004,42 +5111,71 @@ void QgsOgrProviderUtils::release( QgsOgrLayer *&layer )
|
||||
GDALDatasetReleaseResultSet( layer->ds->hDS, layer->hLayer );
|
||||
}
|
||||
|
||||
layer->ds->refCount --;
|
||||
if ( layer->ds->refCount == 0 )
|
||||
{
|
||||
Q_ASSERT( layer->ds->setLayers.isEmpty() );
|
||||
releaseInternal( layer->ident, layer->ds, !layer->isSqlLayer );
|
||||
|
||||
if ( !layer->isSqlLayer )
|
||||
{
|
||||
auto iter = sMapSharedDS.find( layer->ident );
|
||||
if ( iter != sMapSharedDS.end() )
|
||||
{
|
||||
auto &datasetList = iter.value();
|
||||
int i = 0;
|
||||
|
||||
// Normally there should be a match, except for datasets that
|
||||
// have been invalidated
|
||||
Q_FOREACH ( QgsOgrProviderUtils::DatasetWithLayers *ds, datasetList )
|
||||
{
|
||||
if ( ds == layer->ds )
|
||||
{
|
||||
datasetList.removeAt( i );
|
||||
break;
|
||||
}
|
||||
i ++;
|
||||
}
|
||||
|
||||
if ( datasetList.isEmpty() )
|
||||
sMapSharedDS.erase( iter );
|
||||
}
|
||||
}
|
||||
QgsOgrProviderUtils::GDALCloseWrapper( layer->ds->hDS );
|
||||
delete layer->ds;
|
||||
}
|
||||
delete layer;
|
||||
layer = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void QgsOgrProviderUtils::releaseDataset( QgsOgrDataset *&ds )
|
||||
{
|
||||
if ( !ds )
|
||||
return;
|
||||
|
||||
QMutexLocker locker( &sGlobalMutex );
|
||||
releaseInternal( ds->mIdent, ds->mDs, true );
|
||||
delete ds;
|
||||
ds = nullptr;
|
||||
}
|
||||
|
||||
|
||||
QgsOgrDatasetSharedPtr QgsOgrDataset::create( const QgsOgrProviderUtils::DatasetIdentification &ident,
|
||||
QgsOgrProviderUtils::DatasetWithLayers *ds )
|
||||
{
|
||||
QgsOgrDatasetSharedPtr dsRet = QgsOgrDatasetSharedPtr( new QgsOgrDataset(), QgsOgrProviderUtils::releaseDataset );
|
||||
dsRet->mIdent = ident;
|
||||
dsRet->mDs = ds;
|
||||
dsRet->mDs->refCount ++;
|
||||
return dsRet;
|
||||
}
|
||||
|
||||
bool QgsOgrDataset::executeSQLNoReturn( const QString &sql )
|
||||
{
|
||||
QMutexLocker locker( &mutex() );
|
||||
CPLErrorReset();
|
||||
OGRLayerH hSqlLayer = GDALDatasetExecuteSQL(
|
||||
mDs->hDS, sql.toUtf8().constData(), nullptr, nullptr );
|
||||
bool ret = CPLGetLastErrorType() == CE_None;
|
||||
GDALDatasetReleaseResultSet( mDs->hDS, hSqlLayer );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
OGRLayerH QgsOgrDataset::createSQLResultLayer( QTextCodec *encoding, const QString &layerName, int layerIndex )
|
||||
{
|
||||
QMutexLocker locker( &mutex() );
|
||||
|
||||
OGRLayerH layer;
|
||||
if ( !layerName.isEmpty() )
|
||||
{
|
||||
layer = GDALDatasetGetLayerByName( mDs->hDS, layerName.toUtf8().constData() );
|
||||
}
|
||||
else
|
||||
{
|
||||
layer = GDALDatasetGetLayer( mDs->hDS, layerIndex );
|
||||
}
|
||||
if ( !layer )
|
||||
return nullptr;
|
||||
return QgsOgrProviderUtils::setSubsetString( layer, mDs->hDS, encoding, QString(), false, nullptr );
|
||||
}
|
||||
|
||||
void QgsOgrDataset::releaseResultSet( OGRLayerH hSqlLayer )
|
||||
{
|
||||
QMutexLocker locker( &mutex() );
|
||||
GDALDatasetReleaseResultSet( mDs->hDS, hSqlLayer );
|
||||
}
|
||||
|
||||
QgsOgrLayer::QgsOgrLayer()
|
||||
{
|
||||
oFDefn.layer = this;
|
||||
@ -6188,3 +6324,16 @@ void QgsOgrLayerReleaser::operator()( QgsOgrLayer *layer )
|
||||
{
|
||||
QgsOgrProviderUtils::release( layer );
|
||||
}
|
||||
|
||||
QGISEXTERN QgsTransaction *createTransaction( const QString &connString )
|
||||
{
|
||||
auto ds = QgsOgrProviderUtils::getAlreadyOpenedDataset( connString );
|
||||
if ( !ds )
|
||||
{
|
||||
QgsMessageLog::logMessage( QObject::tr( "Cannot open transaction on %1, since it is is not currently opened" ).arg( connString ),
|
||||
QObject::tr( "OGR" ), Qgis::Critical );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new QgsOgrTransaction( connString, ds );
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ class QgsOgrFeatureIterator;
|
||||
#include <gdal.h>
|
||||
|
||||
class QgsOgrLayer;
|
||||
class QgsOgrTransaction;
|
||||
|
||||
/**
|
||||
* Releases a QgsOgrLayer
|
||||
@ -146,6 +147,7 @@ class QgsOgrProvider : public QgsVectorDataProvider
|
||||
|
||||
QString name() const override;
|
||||
QString description() const override;
|
||||
QgsTransaction *transaction() const override;
|
||||
bool doesStrictFeatureTypeCheck() const override;
|
||||
|
||||
//! Returns OGR geometry type
|
||||
@ -317,14 +319,27 @@ class QgsOgrProvider : public QgsVectorDataProvider
|
||||
void setupProxy();
|
||||
#endif
|
||||
|
||||
QgsOgrTransaction *mTransaction = nullptr;
|
||||
|
||||
void setTransaction( QgsTransaction *transaction ) override;
|
||||
|
||||
};
|
||||
|
||||
class QgsOgrDataset;
|
||||
|
||||
/**
|
||||
* Scoped QgsOgrDataset.
|
||||
*/
|
||||
using QgsOgrDatasetSharedPtr = std::shared_ptr< QgsOgrDataset>;
|
||||
|
||||
|
||||
/**
|
||||
\class QgsOgrProviderUtils
|
||||
\brief Utility class with static methods
|
||||
*/
|
||||
class QgsOgrProviderUtils
|
||||
{
|
||||
friend class QgsOgrDataset;
|
||||
friend class QgsOgrLayer;
|
||||
|
||||
//! Identifies a dataset by name, updateMode and options
|
||||
@ -370,6 +385,10 @@ class QgsOgrProviderUtils
|
||||
|
||||
static bool canUseOpenedDatasets( const QString &dsName );
|
||||
|
||||
static void releaseInternal( const DatasetIdentification &ident,
|
||||
DatasetWithLayers *ds,
|
||||
bool removeFromDatasetList );
|
||||
|
||||
public:
|
||||
|
||||
//! Inject credentials into the dsName (if any)
|
||||
@ -397,6 +416,9 @@ class QgsOgrProviderUtils
|
||||
//! Wrapper for GDALClose()
|
||||
static void GDALCloseWrapper( GDALDatasetH mhDS );
|
||||
|
||||
//! Return a QgsOgrDataset wrapping an already opened GDALDataset. Typical use: by QgsOgrTransaction
|
||||
static QgsOgrDatasetSharedPtr getAlreadyOpenedDataset( const QString &dsName );
|
||||
|
||||
//! Open a layer given by name, potentially reusing an existing GDALDatasetH if it doesn't already use that layer.
|
||||
static QgsOgrLayerUniquePtr getLayer( const QString &dsName,
|
||||
const QString &layerName,
|
||||
@ -430,6 +452,9 @@ class QgsOgrProviderUtils
|
||||
//! Release a QgsOgrLayer*
|
||||
static void release( QgsOgrLayer *&layer );
|
||||
|
||||
//! Release a QgsOgrDataset*
|
||||
static void releaseDataset( QgsOgrDataset *&ds );
|
||||
|
||||
//! Make sure that the existing pool of opened datasets on dsName is not accessible for new getLayer() attempts
|
||||
static void invalidateCachedDatasets( const QString &dsName );
|
||||
|
||||
@ -448,6 +473,34 @@ class QgsOgrProviderUtils
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
\class QgsOgrDataset
|
||||
\brief Wrap a GDALDatasetH object in a thread-safe way
|
||||
*/
|
||||
class QgsOgrDataset
|
||||
{
|
||||
friend class QgsOgrProviderUtils;
|
||||
QgsOgrProviderUtils::DatasetIdentification mIdent;
|
||||
QgsOgrProviderUtils::DatasetWithLayers *mDs;
|
||||
|
||||
QgsOgrDataset() = default;
|
||||
~QgsOgrDataset() = default;
|
||||
|
||||
public:
|
||||
|
||||
static QgsOgrDatasetSharedPtr create( const QgsOgrProviderUtils::DatasetIdentification &ident,
|
||||
QgsOgrProviderUtils::DatasetWithLayers *ds );
|
||||
|
||||
QMutex &mutex() { return mDs->mutex; }
|
||||
|
||||
bool executeSQLNoReturn( const QString &sql );
|
||||
|
||||
OGRLayerH createSQLResultLayer( QTextCodec *encoding, const QString &layerName, int layerIndex );
|
||||
|
||||
void releaseResultSet( OGRLayerH hSqlLayer );
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
\class QgsOgrFeatureDefn
|
||||
\brief Wrap a OGRFieldDefnH object in a thread-safe way
|
||||
@ -502,7 +555,7 @@ class QgsOgrLayer
|
||||
QgsOgrProviderUtils::DatasetIdentification ident;
|
||||
bool isSqlLayer = false;
|
||||
QString layerName;
|
||||
QString sql;
|
||||
QString sql; // not really used. Just set at QgsOgrLayer::CreateForLayer() time
|
||||
QgsOgrProviderUtils::DatasetWithLayers *ds = nullptr;
|
||||
OGRLayerH hLayer = nullptr;
|
||||
QgsOgrFeatureDefn oFDefn;
|
||||
@ -622,7 +675,7 @@ class QgsOgrLayer
|
||||
//! Wrapper of GDALDatasetReleaseResultSet( GDALDatasetExecuteSQL( ... ) )
|
||||
void ExecuteSQLNoReturn( const QByteArray &sql );
|
||||
|
||||
//! Wrapper of GDALDatasetExecuteSQL(). Returned layer must be released with QgsOgrProviderUtils::release()
|
||||
//! Wrapper of GDALDatasetExecuteSQL().
|
||||
QgsOgrLayerUniquePtr ExecuteSQL( const QByteArray &sql );
|
||||
};
|
||||
|
||||
|
85
src/providers/ogr/qgsogrtransaction.cpp
Normal file
85
src/providers/ogr/qgsogrtransaction.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/***************************************************************************
|
||||
QgsOgrTransaction.cpp - Transaction support for OGR layers
|
||||
-------------------
|
||||
begin : June 13, 2018
|
||||
copyright : (C) 2018 by Even Rouault
|
||||
email : even.rouault @ spatialys.com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* 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 "qgsogrtransaction.h"
|
||||
#include "qgsogrprovider.h"
|
||||
#include "qgslogger.h"
|
||||
#include "qgis.h"
|
||||
|
||||
QgsOgrTransaction::QgsOgrTransaction( const QString &connString, QgsOgrDatasetSharedPtr ds )
|
||||
: QgsTransaction( connString ), mSharedDS( ds )
|
||||
|
||||
{
|
||||
Q_ASSERT( mSharedDS );
|
||||
}
|
||||
|
||||
bool QgsOgrTransaction::beginTransaction( QString &error, int /* statementTimeout */ )
|
||||
{
|
||||
return executeSql( QStringLiteral( "BEGIN" ), error );
|
||||
}
|
||||
|
||||
bool QgsOgrTransaction::commitTransaction( QString &error )
|
||||
{
|
||||
if ( executeSql( QStringLiteral( "COMMIT" ), error ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QgsOgrTransaction::rollbackTransaction( QString &error )
|
||||
{
|
||||
if ( executeSql( QStringLiteral( "ROLLBACK" ), error ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QgsOgrTransaction::executeSql( const QString &sql, QString &errorMsg, bool isDirty, const QString &name )
|
||||
{
|
||||
|
||||
QString err;
|
||||
if ( isDirty )
|
||||
{
|
||||
createSavepoint( err );
|
||||
}
|
||||
|
||||
QgsDebugMsg( QString( "Transaction sql: %1" ).arg( sql ) );
|
||||
if ( !mSharedDS->executeSQLNoReturn( sql ) )
|
||||
{
|
||||
errorMsg = CPLGetLastErrorMsg();
|
||||
QgsDebugMsg( errorMsg );
|
||||
|
||||
if ( isDirty )
|
||||
{
|
||||
rollbackToSavepoint( savePoints().last(), err );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isDirty )
|
||||
{
|
||||
dirtyLastSavePoint();
|
||||
emit dirtied( sql, name );
|
||||
}
|
||||
|
||||
QgsDebugMsg( QString( "... ok" ) );
|
||||
return true;
|
||||
}
|
53
src/providers/ogr/qgsogrtransaction.h
Normal file
53
src/providers/ogr/qgsogrtransaction.h
Normal file
@ -0,0 +1,53 @@
|
||||
/***************************************************************************
|
||||
qgsogrtransaction.h - Transaction support for OGR layers
|
||||
-------------------
|
||||
begin : June 13, 2018
|
||||
copyright : (C) 2018 by Even Rouault
|
||||
email : even.rouault @ spatialys.com
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
* 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef QGSOGRTRANSACTION_H
|
||||
#define QGSOGRTRANSACTION_H
|
||||
|
||||
#include "qgstransaction.h"
|
||||
#include "qgsogrprovider.h"
|
||||
|
||||
class QgsOgrTransaction : public QgsTransaction
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QgsOgrTransaction( const QString &connString, QgsOgrDatasetSharedPtr ds );
|
||||
|
||||
/**
|
||||
* Executes the SQL query in database.
|
||||
*
|
||||
* \param sql The SQL query to execute
|
||||
* \param error The error or an empty string if none
|
||||
* \param isDirty True to add an undo/redo command in the edition buffer, false otherwise
|
||||
* \param name Name of the operation ( only used if `isDirty` is true)
|
||||
*/
|
||||
bool executeSql( const QString &sql, QString &error, bool isDirty = false, const QString &name = QString() ) override;
|
||||
|
||||
QgsOgrDatasetSharedPtr sharedDS() const { return mSharedDS; }
|
||||
|
||||
private:
|
||||
|
||||
QgsOgrDatasetSharedPtr mSharedDS = nullptr;
|
||||
|
||||
bool beginTransaction( QString &error, int statementTimeout ) override;
|
||||
bool commitTransaction( QString &error ) override;
|
||||
bool rollbackTransaction( QString &error ) override;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSOGRTRANSACTION_H
|
@ -983,6 +983,76 @@ class TestPyQgsOGRProviderGpkg(unittest.TestCase):
|
||||
self.assertTrue(QgsGeometry.compare(provider_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001),
|
||||
provider_extent.asPolygon()[0])
|
||||
|
||||
def testTransaction(self):
|
||||
|
||||
tmpfile = os.path.join(self.basetestpath, 'testTransaction.gpkg')
|
||||
ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
|
||||
lyr = ds.CreateLayer('lyr1', geom_type=ogr.wkbPoint)
|
||||
f = ogr.Feature(lyr.GetLayerDefn())
|
||||
f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(0 1)'))
|
||||
lyr.CreateFeature(f)
|
||||
lyr = ds.CreateLayer('lyr2', geom_type=ogr.wkbPoint)
|
||||
f = ogr.Feature(lyr.GetLayerDefn())
|
||||
f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(2 3)'))
|
||||
lyr.CreateFeature(f)
|
||||
f = ogr.Feature(lyr.GetLayerDefn())
|
||||
f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(4 5)'))
|
||||
lyr.CreateFeature(f)
|
||||
ds = None
|
||||
|
||||
vl1 = QgsVectorLayer(u'{}'.format(tmpfile) + "|layername=" + "lyr1", 'test', u'ogr')
|
||||
self.assertTrue(vl1.isValid())
|
||||
vl2 = QgsVectorLayer(u'{}'.format(tmpfile) + "|layername=" + "lyr2", 'test', u'ogr')
|
||||
self.assertTrue(vl2.isValid())
|
||||
|
||||
# prepare a project with transactions enabled
|
||||
p = QgsProject()
|
||||
p.setAutoTransaction(True)
|
||||
p.addMapLayers([vl1, vl2])
|
||||
|
||||
self.assertTrue(vl1.startEditing())
|
||||
self.assertIsNotNone(vl1.dataProvider().transaction())
|
||||
self.assertTrue(vl1.deleteFeature(1))
|
||||
|
||||
# An iterator opened on the layer should see the feature deleted
|
||||
self.assertEqual(len([f for f in vl1.getFeatures(QgsFeatureRequest())]), 0)
|
||||
|
||||
# But not if opened from another connection
|
||||
vl1_external = QgsVectorLayer(u'{}'.format(tmpfile) + "|layername=" + "lyr1", 'test', u'ogr')
|
||||
self.assertTrue(vl1_external.isValid())
|
||||
self.assertEqual(len([f for f in vl1_external.getFeatures(QgsFeatureRequest())]), 1)
|
||||
del vl1_external
|
||||
|
||||
self.assertTrue(vl1.commitChanges())
|
||||
|
||||
# Should still get zero features on vl1
|
||||
self.assertEqual(len([f for f in vl1.getFeatures(QgsFeatureRequest())]), 0)
|
||||
self.assertEqual(len([f for f in vl2.getFeatures(QgsFeatureRequest())]), 2)
|
||||
|
||||
# Test undo/redo
|
||||
self.assertTrue(vl2.startEditing())
|
||||
self.assertIsNotNone(vl2.dataProvider().transaction())
|
||||
self.assertTrue(vl2.editBuffer().deleteFeature(1))
|
||||
self.assertEqual(len([f for f in vl2.getFeatures(QgsFeatureRequest())]), 1)
|
||||
self.assertTrue(vl2.editBuffer().deleteFeature(2))
|
||||
self.assertEqual(len([f for f in vl2.getFeatures(QgsFeatureRequest())]), 0)
|
||||
vl2.undoStack().undo()
|
||||
self.assertEqual(len([f for f in vl2.getFeatures(QgsFeatureRequest())]), 1)
|
||||
vl2.undoStack().undo()
|
||||
self.assertEqual(len([f for f in vl2.getFeatures(QgsFeatureRequest())]), 2)
|
||||
vl2.undoStack().redo()
|
||||
self.assertEqual(len([f for f in vl2.getFeatures(QgsFeatureRequest())]), 1)
|
||||
self.assertTrue(vl2.commitChanges())
|
||||
|
||||
self.assertEqual(len([f for f in vl2.getFeatures(QgsFeatureRequest())]), 1)
|
||||
del vl1
|
||||
del vl2
|
||||
|
||||
vl2_external = QgsVectorLayer(u'{}'.format(tmpfile) + "|layername=" + "lyr2", 'test', u'ogr')
|
||||
self.assertTrue(vl2_external.isValid())
|
||||
self.assertEqual(len([f for f in vl2_external.getFeatures(QgsFeatureRequest())]), 1)
|
||||
del vl2_external
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user