Skip to content

Commit 6a987f9

Browse files
committed
[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 OSGeo/gdal@f73ec8c - 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
1 parent 370bac9 commit 6a987f9

12 files changed

+562
-57
lines changed

python/core/auto_generated/qgstransaction.sip.in

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ returns the last created savepoint
146146
.. versionadded:: 3.0
147147
%End
148148

149+
149150
signals:
150151

151152
void afterRollback();

src/core/qgstransaction.cpp

+48-7
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ QgsTransaction *QgsTransaction::create( const QSet<QgsVectorLayer *> &layers )
5151

5252
QgsVectorLayer *firstLayer = *layers.constBegin();
5353

54-
QString connStr = QgsDataSourceUri( firstLayer->source() ).connectionInfo( false );
54+
QString connStr = connectionString( firstLayer->source() );
5555
QString providerKey = firstLayer->dataProvider()->name();
5656
std::unique_ptr<QgsTransaction> transaction( QgsTransaction::create( connStr, providerKey ) );
5757
if ( transaction )
@@ -81,6 +81,46 @@ QgsTransaction::~QgsTransaction()
8181
setLayerTransactionIds( nullptr );
8282
}
8383

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+
109+
///@cond PRIVATE
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+
}
122+
///@endcond
123+
84124
bool QgsTransaction::addLayer( QgsVectorLayer *layer )
85125
{
86126
if ( !layer )
@@ -90,17 +130,18 @@ bool QgsTransaction::addLayer( QgsVectorLayer *layer )
90130
return false;
91131

92132
//test if provider supports transactions
93-
if ( !layer->dataProvider() || ( layer->dataProvider()->capabilities() & QgsVectorDataProvider::TransactionSupport ) == 0 )
133+
if ( !supportsTransaction( layer ) )
94134
return false;
95135

96136
if ( layer->dataProvider()->transaction() )
97137
return false;
98138

99139
//connection string not compatible
100-
if ( QgsDataSourceUri( layer->source() ).connectionInfo( false ) != mConnString )
140+
141+
if ( connectionString( layer->source() ) != mConnString )
101142
{
102143
QgsDebugMsg( QString( "Couldn't start transaction because connection string for layer %1 : '%2' does not match '%3'" ).arg(
103-
layer->id(), QgsDataSourceUri( layer->source() ).connectionInfo( false ), mConnString ) );
144+
layer->id(), connectionString( layer->source() ), mConnString ) );
104145
return false;
105146
}
106147

@@ -162,11 +203,11 @@ bool QgsTransaction::rollback( QString &errorMsg )
162203

163204
bool QgsTransaction::supportsTransaction( const QgsVectorLayer *layer )
164205
{
165-
std::unique_ptr< QLibrary > lib( QgsProviderRegistry::instance()->createProviderLibrary( layer->providerType() ) );
166-
if ( !lib )
206+
//test if provider supports transactions
207+
if ( !layer->dataProvider() || ( layer->dataProvider()->capabilities() & QgsVectorDataProvider::TransactionSupport ) == 0 )
167208
return false;
168209

169-
return lib->resolve( "createTransaction" );
210+
return true;
170211
}
171212

172213
void QgsTransaction::onLayerDeleted()

src/core/qgstransaction.h

+7
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ class CORE_EXPORT QgsTransaction : public QObject SIP_ABSTRACT
158158
*/
159159
bool lastSavePointIsDirty() const { return mLastSavePointIsDirty; }
160160

161+
///@cond PRIVATE
162+
// For internal use only, or by QgsTransactionGroup
163+
static QString connectionString( const QString &layerName ) SIP_SKIP;
164+
///@endcond
165+
161166
signals:
162167

163168
/**
@@ -188,6 +193,8 @@ class CORE_EXPORT QgsTransaction : public QObject SIP_ABSTRACT
188193

189194
void setLayerTransactionIds( QgsTransaction *transaction );
190195

196+
static QString removeLayerIdOrName( const QString &str );
197+
191198
virtual bool beginTransaction( QString &error, int statementTimeout ) = 0;
192199
virtual bool commitTransaction( QString &error ) = 0;
193200
virtual bool rollbackTransaction( QString &error ) = 0;

src/core/qgstransactiongroup.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ bool QgsTransactionGroup::addLayer( QgsVectorLayer *layer )
3333
if ( !QgsTransaction::supportsTransaction( layer ) )
3434
return false;
3535

36-
QString connString = QgsDataSourceUri( layer->source() ).connectionInfo();
37-
36+
QString connString = QgsTransaction::connectionString( layer->source() );
3837
if ( mConnString.isEmpty() )
3938
{
4039
mConnString = connString;
@@ -74,6 +73,8 @@ void QgsTransactionGroup::onEditingStarted()
7473
return;
7574

7675
mTransaction.reset( QgsTransaction::create( mConnString, mProviderKey ) );
76+
if ( !mTransaction )
77+
return;
7778

7879
QString errorMsg;
7980
mTransaction->begin( errorMsg );

src/providers/ogr/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ SET (OGR_SRCS
99
qgsgeopackagerasterwritertask.cpp
1010
qgsogrdbconnection.cpp
1111
qgsogrdbtablemodel.cpp
12+
qgsogrtransaction.cpp
1213
)
1314

1415
SET(OGR_MOC_HDRS
@@ -19,6 +20,7 @@ SET(OGR_MOC_HDRS
1920
qgsgeopackagerasterwritertask.h
2021
qgsogrdbconnection.h
2122
qgsogrdbtablemodel.h
23+
qgsogrtransaction.h
2224
)
2325

2426
IF (WITH_GUI)

src/providers/ogr/qgsogrfeatureiterator.cpp

+58-21
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "qgssettings.h"
2727
#include "qgsexception.h"
2828
#include "qgswkbtypes.h"
29+
#include "qgsogrtransaction.h"
2930

3031
#include <QTextCodec>
3132
#include <QFile>
@@ -42,38 +43,51 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource *source, bool
4243
: QgsAbstractFeatureIteratorFromSource<QgsOgrFeatureSource>( source, ownSource, request )
4344
, mFilterFids( mRequest.filterFids() )
4445
, mFilterFidsIt( mFilterFids.constBegin() )
46+
, mSharedDS( source->mSharedDS )
4547
{
46-
//QgsDebugMsg( "Feature iterator of " + mSource->mLayerName + ": acquiring connection");
47-
mConn = QgsOgrConnPool::instance()->acquireConnection( QgsOgrProviderUtils::connectionPoolId( mSource->mDataSource ) );
48-
if ( !mConn->ds )
48+
if ( mSharedDS )
4949
{
50-
return;
51-
}
52-
53-
if ( mSource->mLayerName.isNull() )
54-
{
55-
mOgrLayer = GDALDatasetGetLayer( mConn->ds, mSource->mLayerIndex );
50+
mOgrLayer = mSharedDS->createSQLResultLayer( mSource->mEncoding, mSource->mLayerName, mSource->mLayerIndex );
51+
if ( !mOgrLayer )
52+
{
53+
return;
54+
}
5655
}
5756
else
5857
{
59-
mOgrLayer = GDALDatasetGetLayerByName( mConn->ds, mSource->mLayerName.toUtf8().constData() );
60-
}
61-
if ( !mOgrLayer )
62-
{
63-
return;
64-
}
58+
//QgsDebugMsg( "Feature iterator of " + mSource->mLayerName + ": acquiring connection");
59+
mConn = QgsOgrConnPool::instance()->acquireConnection( QgsOgrProviderUtils::connectionPoolId( mSource->mDataSource ) );
60+
if ( !mConn->ds )
61+
{
62+
return;
63+
}
6564

66-
if ( !mSource->mSubsetString.isEmpty() )
67-
{
68-
mOgrOrigLayer = mOgrLayer;
69-
mOgrLayerWithFid = QgsOgrProviderUtils::setSubsetString( mOgrLayer, mConn->ds, mSource->mEncoding, QString(), true, &mOrigFidAdded );
70-
mOgrLayer = QgsOgrProviderUtils::setSubsetString( mOgrLayer, mConn->ds, mSource->mEncoding, mSource->mSubsetString, true, &mOrigFidAdded );
65+
if ( mSource->mLayerName.isNull() )
66+
{
67+
mOgrLayer = GDALDatasetGetLayer( mConn->ds, mSource->mLayerIndex );
68+
}
69+
else
70+
{
71+
mOgrLayer = GDALDatasetGetLayerByName( mConn->ds, mSource->mLayerName.toUtf8().constData() );
72+
}
7173
if ( !mOgrLayer )
7274
{
73-
close();
7475
return;
7576
}
77+
78+
if ( !mSource->mSubsetString.isEmpty() )
79+
{
80+
mOgrOrigLayer = mOgrLayer;
81+
mOgrLayerWithFid = QgsOgrProviderUtils::setSubsetString( mOgrLayer, mConn->ds, mSource->mEncoding, QString(), true, &mOrigFidAdded );
82+
mOgrLayer = QgsOgrProviderUtils::setSubsetString( mOgrLayer, mConn->ds, mSource->mEncoding, mSource->mSubsetString, true, &mOrigFidAdded );
83+
if ( !mOgrLayer )
84+
{
85+
close();
86+
return;
87+
}
88+
}
7689
}
90+
QMutexLocker locker( mSharedDS ? &mSharedDS->mutex() : nullptr );
7791

7892
if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->mCrs )
7993
{
@@ -237,6 +251,8 @@ bool QgsOgrFeatureIterator::fetchFeatureWithId( QgsFeatureId id, QgsFeature &fea
237251

238252
bool QgsOgrFeatureIterator::fetchFeature( QgsFeature &feature )
239253
{
254+
QMutexLocker locker( mSharedDS ? &mSharedDS->mutex() : nullptr );
255+
240256
feature.setValid( false );
241257

242258
if ( mClosed || !mOgrLayer )
@@ -286,6 +302,8 @@ bool QgsOgrFeatureIterator::fetchFeature( QgsFeature &feature )
286302

287303
bool QgsOgrFeatureIterator::rewind()
288304
{
305+
QMutexLocker locker( mSharedDS ? &mSharedDS->mutex() : nullptr );
306+
289307
if ( mClosed || !mOgrLayer )
290308
return false;
291309

@@ -299,6 +317,20 @@ bool QgsOgrFeatureIterator::rewind()
299317

300318
bool QgsOgrFeatureIterator::close()
301319
{
320+
if ( mSharedDS )
321+
{
322+
iteratorClosed();
323+
324+
if ( mOgrLayer )
325+
{
326+
mSharedDS->releaseResultSet( mOgrLayer );
327+
mOgrLayer = nullptr;
328+
}
329+
mSharedDS.reset();
330+
mClosed = true;
331+
return true;
332+
}
333+
302334
if ( !mConn )
303335
return false;
304336

@@ -444,7 +476,12 @@ QgsOgrFeatureSource::QgsOgrFeatureSource( const QgsOgrProvider *p )
444476
, mDriverName( p->mGDALDriverName )
445477
, mCrs( p->crs() )
446478
, mWkbType( p->wkbType() )
479+
, mSharedDS( nullptr )
447480
{
481+
if ( p->mTransaction )
482+
{
483+
mSharedDS = p->mTransaction->sharedDS();
484+
}
448485
for ( int i = ( p->mFirstFieldIsFid ) ? 1 : 0; i < mFields.size(); i++ )
449486
mFieldsWithoutFid.append( mFields.at( i ) );
450487
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( mDataSource ) );

src/providers/ogr/qgsogrfeatureiterator.h

+6
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,12 @@
2121

2222
#include <ogr_api.h>
2323

24+
#include <memory>
25+
2426
class QgsOgrFeatureIterator;
2527
class QgsOgrProvider;
28+
class QgsOgrDataset;
29+
using QgsOgrDatasetSharedPtr = std::shared_ptr< QgsOgrDataset>;
2630

2731
class QgsOgrFeatureSource : public QgsAbstractFeatureSource
2832
{
@@ -45,6 +49,7 @@ class QgsOgrFeatureSource : public QgsAbstractFeatureSource
4549
QString mDriverName;
4650
QgsCoordinateReferenceSystem mCrs;
4751
QgsWkbTypes::Type mWkbType = QgsWkbTypes::Unknown;
52+
QgsOgrDatasetSharedPtr mSharedDS = nullptr;
4853

4954
friend class QgsOgrFeatureIterator;
5055
friend class QgsOgrExpressionCompiler;
@@ -87,6 +92,7 @@ class QgsOgrFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsOgr
8792

8893
QgsRectangle mFilterRect;
8994
QgsCoordinateTransform mTransform;
95+
QgsOgrDatasetSharedPtr mSharedDS = nullptr;
9096

9197
bool fetchFeatureWithId( QgsFeatureId id, QgsFeature &feature ) const;
9298
};

0 commit comments

Comments
 (0)