Spatialite transactions

This commit is contained in:
Alessandro Pasotti 2020-03-30 19:12:55 +02:00
parent d2360da967
commit d680db5153
11 changed files with 522 additions and 132 deletions

View File

@ -285,8 +285,6 @@ class QgsConnectionPool
* If \a timeout is a negative value the calling thread will be blocked
* until a connection becomes available. This is the default behavior.
*
*
*
* \returns initialized connection or NULLPTR if unsuccessful
*/
T acquireConnection( const QString &connInfo, int timeout = -1, bool requestMayBeNested = false )

View File

@ -22,6 +22,22 @@
#include <sqlite3.h>
#include <spatialite.h>
// Define this variable to print all spatialite SQL statements
#ifdef SPATIALITE_PRINT_ALL_SQL
// Debugging code
#include <QDebug>
#include <QThread>
static int trace_callback( unsigned, void *ctx, void *p, void * )
{
sqlite3_stmt *stmt = ( sqlite3_stmt * )p;
char *sql = sqlite3_expanded_sql( stmt );
qDebug() << "SPATIALITE" << QThread::currentThreadId() << ( sqlite3 * ) ctx << sql;
sqlite3_free( sql );
return 0;
}
#endif
int spatialite_database_unique_ptr::open( const QString &path )
{
auto &deleter = get_deleter();
@ -54,6 +70,16 @@ int spatialite_database_unique_ptr::open_v2( const QString &path, int flags, con
if ( result == SQLITE_OK )
spatialite_init_ex( database, deleter.mSpatialiteContext, 0 );
#ifdef SPATIALITE_PRINT_ALL_SQL
// Log all queries
sqlite3_trace_v2(
database,
SQLITE_TRACE_STMT,
trace_callback,
database
);
#endif
return result;
}

View File

@ -13,6 +13,7 @@ SET(SPATIALITE_SRCS
qgsspatialitefeatureiterator.cpp
qgsspatialitetablemodel.cpp
qgsspatialiteproviderconnection.cpp
qgsspatialitetransaction.cpp
)
IF (WITH_GUI)

View File

@ -158,7 +158,13 @@ class QgsSqliteHandle
{
mIsValid = false;
}
/**
* Returns a possibly cached SQLite DB object from \a path, if \a shared is FALSE
* the DB will not be searched in the cache and a new READ ONLY connection will be returned.
*/
static QgsSqliteHandle *openDb( const QString &dbPath, bool shared = true );
static bool checkMetadata( sqlite3 *handle );
static void closeDb( QgsSqliteHandle *&handle );

View File

@ -29,7 +29,16 @@
QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeatureSource *source, bool ownSource, const QgsFeatureRequest &request )
: QgsAbstractFeatureIteratorFromSource<QgsSpatiaLiteFeatureSource>( source, ownSource, request )
{
mHandle = QgsSpatiaLiteConnPool::instance()->acquireConnection( mSource->mSqlitePath, request.timeout(), request.requestMayBeNested() );
mSqliteHandle = source->transactionHandle();
if ( ! mSqliteHandle )
{
mHandle = QgsSpatiaLiteConnPool::instance()->acquireConnection( mSource->mSqlitePath, request.timeout(), request.requestMayBeNested() );
if ( mHandle )
{
mSqliteHandle = mHandle->handle();
}
}
mFetchGeometry = !mSource->mGeometryColumn.isNull() && !( mRequest.flags() & QgsFeatureRequest::NoGeometry );
mHasPrimaryKey = !mSource->mPrimaryKey.isEmpty();
@ -290,9 +299,10 @@ bool QgsSpatiaLiteFeatureIterator::close()
iteratorClosed();
if ( !mHandle )
mClosed = true;
if ( !mSqliteHandle )
{
mClosed = true;
return false;
}
@ -302,19 +312,24 @@ bool QgsSpatiaLiteFeatureIterator::close()
sqliteStatement = nullptr;
}
QgsSpatiaLiteConnPool::instance()->releaseConnection( mHandle );
mHandle = nullptr;
if ( mHandle )
{
QgsSpatiaLiteConnPool::instance()->releaseConnection( mHandle );
mHandle = nullptr;
}
mSqliteHandle = nullptr;
mClosed = true;
return true;
}
////
bool QgsSpatiaLiteFeatureIterator::prepareStatement( const QString &whereClause, long limit, const QString &orderBy )
{
if ( !mHandle )
if ( !mSqliteHandle )
return false;
try
@ -359,10 +374,10 @@ bool QgsSpatiaLiteFeatureIterator::prepareStatement( const QString &whereClause,
QgsDebugMsgLevel( sql, 4 );
if ( sqlite3_prepare_v2( mHandle->handle(), sql.toUtf8().constData(), -1, &sqliteStatement, nullptr ) != SQLITE_OK )
if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &sqliteStatement, nullptr ) != SQLITE_OK )
{
// some error occurred
QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mHandle->handle() ) ), QObject::tr( "SpatiaLite" ) );
QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), QObject::tr( "SpatiaLite" ) );
return false;
}
}
@ -489,7 +504,7 @@ bool QgsSpatiaLiteFeatureIterator::getFeature( sqlite3_stmt *stmt, QgsFeature &f
if ( ret != SQLITE_ROW )
{
// some unexpected error occurred
QgsMessageLog::logMessage( QObject::tr( "SQLite error getting feature: %1" ).arg( QString::fromUtf8( sqlite3_errmsg( mHandle->handle() ) ) ), QObject::tr( "SpatiaLite" ) );
QgsMessageLog::logMessage( QObject::tr( "SQLite error getting feature: %1" ).arg( QString::fromUtf8( sqlite3_errmsg( mSqliteHandle ) ) ), QObject::tr( "SpatiaLite" ) );
return false;
}
@ -642,6 +657,7 @@ QgsSpatiaLiteFeatureSource::QgsSpatiaLiteFeatureSource( const QgsSpatiaLiteProvi
, mSpatialIndexMbrCache( p->mSpatialIndexMbrCache )
, mSqlitePath( p->mSqlitePath )
, mCrs( p->crs() )
, mTransactionHandle( p->transaction() ? p->sqliteHandle() : nullptr )
{
}
@ -649,3 +665,8 @@ QgsFeatureIterator QgsSpatiaLiteFeatureSource::getFeatures( const QgsFeatureRequ
{
return QgsFeatureIterator( new QgsSpatiaLiteFeatureIterator( this, false, request ) );
}
sqlite3 *QgsSpatiaLiteFeatureSource::transactionHandle()
{
return mTransactionHandle;
}

View File

@ -34,6 +34,8 @@ class QgsSpatiaLiteFeatureSource final: public QgsAbstractFeatureSource
QgsFeatureIterator getFeatures( const QgsFeatureRequest &request ) override;
sqlite3 *transactionHandle();
private:
QString mGeometryColumn;
QString mSubsetString;
@ -49,6 +51,7 @@ class QgsSpatiaLiteFeatureSource final: public QgsAbstractFeatureSource
bool mSpatialIndexMbrCache;
QString mSqlitePath;
QgsCoordinateReferenceSystem mCrs;
sqlite3 *mTransactionHandle = nullptr;
friend class QgsSpatiaLiteFeatureIterator;
friend class QgsSpatiaLiteExpressionCompiler;
@ -81,8 +84,10 @@ class QgsSpatiaLiteFeatureIterator final: public QgsAbstractFeatureIteratorFromS
QVariant getFeatureAttribute( sqlite3_stmt *stmt, int ic, QVariant::Type type, QVariant::Type subType );
void getFeatureGeometry( sqlite3_stmt *stmt, int ic, QgsFeature &feature );
//! wrapper of the SQLite database connection
//! QGIS wrapper of the SQLite database connection
QgsSqliteHandle *mHandle = nullptr;
//! The low level connection
sqlite3 *mSqliteHandle = nullptr;
/**
* SQLite statement handle

View File

@ -31,6 +31,7 @@ email : a.furieri@lqt.it
#include "qgsfeedback.h"
#include "qgsspatialitedataitems.h"
#include "qgsspatialiteconnection.h"
#include "qgsspatialitetransaction.h"
#include "qgsspatialiteproviderconnection.h"
#include "qgsjsonutils.h"
@ -51,7 +52,7 @@ const QString QgsSpatiaLiteProvider::SPATIALITE_KEY = QStringLiteral( "spatialit
const QString QgsSpatiaLiteProvider::SPATIALITE_DESCRIPTION = QStringLiteral( "SpatiaLite data provider" );
static const QString SPATIALITE_ARRAY_PREFIX = QStringLiteral( "json" );
static const QString SPATIALITE_ARRAY_SUFFIX = QStringLiteral( "list" );
QAtomicInt QgsSpatiaLiteProvider::sSavepointId = 0;
bool QgsSpatiaLiteProvider::convertField( QgsField &field )
{
@ -459,8 +460,9 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri, const Provider
mPrimaryKey = anUri.keyColumn();
mQuery = mTableName;
// trying to open the SQLite DB
// Retrieve a shared connection
mHandle = QgsSqliteHandle::openDb( mSqlitePath );
if ( !mHandle )
{
return;
@ -474,7 +476,7 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri, const Provider
for ( const auto &pragma : pragmaList )
{
char *errMsg = nullptr;
int ret = sqlite3_exec( mSqliteHandle, ( "PRAGMA " + pragma ).toUtf8(), nullptr, nullptr, &errMsg );
int ret = exec_sql( QStringLiteral( "PRAGMA %1" ).arg( pragma ), errMsg );
if ( ret != SQLITE_OK )
{
QgsDebugMsg( QStringLiteral( "PRAGMA " ) + pragma + QString( " failed : %1" ).arg( errMsg ? errMsg : "" ) );
@ -539,6 +541,7 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri, const Provider
mEnabledCapabilities |= QgsVectorDataProvider::AddFeatures;
mEnabledCapabilities |= QgsVectorDataProvider::AddAttributes;
mEnabledCapabilities |= QgsVectorDataProvider::CreateAttributeIndex;
mEnabledCapabilities |= QgsVectorDataProvider::TransactionSupport;
}
if ( lyr )
@ -880,7 +883,7 @@ void QgsSpatiaLiteProvider::fetchConstraints()
int ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return;
}
@ -929,7 +932,7 @@ void QgsSpatiaLiteProvider::fetchConstraints()
int ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return;
}
@ -1036,7 +1039,7 @@ QString QgsSpatiaLiteProvider::defaultValueClause( int fieldIndex ) const
return mDefaultValueClause.value( fieldIndex, QString() );
}
void QgsSpatiaLiteProvider::handleError( const QString &sql, char *errorMessage, bool rollback )
void QgsSpatiaLiteProvider::handleError( const QString &sql, char *errorMessage, const QString &savepointId )
{
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, errorMessage ? errorMessage : tr( "unknown cause" ) ), tr( "SpatiaLite" ) );
// unexpected error
@ -1045,13 +1048,24 @@ void QgsSpatiaLiteProvider::handleError( const QString &sql, char *errorMessage,
sqlite3_free( errorMessage );
}
if ( rollback )
if ( ! savepointId.isEmpty() )
{
// ROLLBACK after some previous error
( void )sqlite3_exec( mSqliteHandle, "ROLLBACK", nullptr, nullptr, nullptr );
( void )exec_sql( QStringLiteral( "ROLLBACK TRANSACTION TO \"%1\"" ).arg( savepointId ) );
}
}
int QgsSpatiaLiteProvider::exec_sql( const QString &sql, char *errMsg )
{
// Use transaction's handle (if any)
return sqlite3_exec( sqliteHandle(), sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
}
sqlite3 *QgsSpatiaLiteProvider::sqliteHandle() const
{
return mTransaction && mTransaction->sqliteHandle() ? mTransaction->sqliteHandle() : mSqliteHandle;
}
void QgsSpatiaLiteProvider::loadFields()
{
int ret;
@ -1075,10 +1089,10 @@ void QgsSpatiaLiteProvider::loadFields()
sql = QStringLiteral( "PRAGMA table_info(%1)" ).arg( QgsSqliteUtils::quotedIdentifier( mTableName ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return;
}
if ( rows < 1 )
@ -1135,10 +1149,10 @@ void QgsSpatiaLiteProvider::loadFields()
{
sql = QStringLiteral( "select * from %1 limit 1" ).arg( mQuery );
if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
if ( sqlite3_prepare_v2( sqliteHandle( ), sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
{
// some error occurred
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), tr( "SpatiaLite" ) );
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( sqliteHandle( ) ) ), tr( "SpatiaLite" ) );
return;
}
@ -1206,7 +1220,7 @@ void QgsSpatiaLiteProvider::determineViewPrimaryKey()
int rows;
int columns;
char *errMsg = nullptr;
int ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
int ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret == SQLITE_OK )
{
if ( rows > 0 )
@ -1229,15 +1243,15 @@ QStringList QgsSpatiaLiteProvider::tablePrimaryKeys( const QString &tableName )
int rows;
int columns;
char *errMsg = nullptr;
if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
if ( sqlite3_prepare_v2( sqliteHandle( ), sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
{
// some error occurred
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ),
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( sqliteHandle( ) ) ),
tr( "SpatiaLite" ) );
}
else
{
int ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
int ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret == SQLITE_OK )
{
for ( int row = 1; row <= rows; ++row )
@ -1275,7 +1289,7 @@ bool QgsSpatiaLiteProvider::hasTriggers()
sql = QStringLiteral( "SELECT * FROM sqlite_master WHERE type='trigger' AND tbl_name=%1" )
.arg( QgsSqliteUtils::quotedIdentifier( mTableName ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
sqlite3_free_table( results );
return ( ret == SQLITE_OK && rows > 0 );
}
@ -1288,7 +1302,7 @@ bool QgsSpatiaLiteProvider::hasRowid()
// table without rowid column
QString sql = QStringLiteral( "SELECT rowid FROM %1 WHERE 0" ).arg( QgsSqliteUtils::quotedIdentifier( mTableName ) );
char *errMsg = nullptr;
return sqlite3_exec( mSqliteHandle, sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK;
return exec_sql( sql, errMsg ) == SQLITE_OK;
}
@ -3719,7 +3733,7 @@ QVariant QgsSpatiaLiteProvider::minimumValue( int index ) const
sql += " WHERE ( " + mSubsetString + ')';
}
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, errMsg ? errMsg : tr( "unknown cause" ) ), tr( "SpatiaLite" ) );
@ -3782,7 +3796,7 @@ QVariant QgsSpatiaLiteProvider::maximumValue( int index ) const
sql += " WHERE ( " + mSubsetString + ')';
}
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, errMsg ? errMsg : tr( "unknown cause" ) ), tr( "SpatiaLite" ) );
@ -3852,10 +3866,10 @@ QSet<QVariant> QgsSpatiaLiteProvider::uniqueValues( int index, int limit ) const
}
// SQLite prepared statement
if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
if ( sqlite3_prepare_v2( sqliteHandle( ), sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
{
// some error occurred
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), tr( "SpatiaLite" ) );
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( sqliteHandle( ) ) ), tr( "SpatiaLite" ) );
}
else
{
@ -3891,7 +3905,7 @@ QSet<QVariant> QgsSpatiaLiteProvider::uniqueValues( int index, int limit ) const
}
else
{
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), tr( "SpatiaLite" ) );
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( sqliteHandle( ) ) ), tr( "SpatiaLite" ) );
sqlite3_finalize( stmt );
return uniqueValues;
}
@ -3932,10 +3946,10 @@ QStringList QgsSpatiaLiteProvider::uniqueStringsMatching( int index, const QStri
}
// SQLite prepared statement
if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
if ( sqlite3_prepare_v2( sqliteHandle( ), sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
{
// some error occurred
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), tr( "SpatiaLite" ) );
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( sqliteHandle( ) ) ), tr( "SpatiaLite" ) );
}
else
@ -3965,7 +3979,7 @@ QStringList QgsSpatiaLiteProvider::uniqueStringsMatching( int index, const QStri
}
else
{
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), tr( "SpatiaLite" ) );
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( sqliteHandle( ) ) ), tr( "SpatiaLite" ) );
sqlite3_finalize( stmt );
return results;
}
@ -4020,7 +4034,9 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags )
QgsAttributes attributevec = flist[0].attributes();
ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) };
ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret == SQLITE_OK )
{
toCommit = true;
@ -4078,7 +4094,7 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags )
sql += ')';
// SQLite prepared statement
ret = sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr );
ret = sqlite3_prepare_v2( sqliteHandle( ), sql.toUtf8().constData(), -1, &stmt, nullptr );
if ( ret == SQLITE_OK )
{
@ -4185,14 +4201,14 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags )
// update feature id
if ( !( flags & QgsFeatureSink::FastInsert ) )
{
feature->setId( sqlite3_last_insert_rowid( mSqliteHandle ) );
feature->setId( sqlite3_last_insert_rowid( sqliteHandle( ) ) );
}
mNumberFeatures++;
}
else
{
// some unexpected error occurred
const char *err = sqlite3_errmsg( mSqliteHandle );
const char *err = sqlite3_errmsg( sqliteHandle( ) );
errMsg = ( char * ) sqlite3_malloc( ( int ) strlen( err ) + 1 );
strcpy( errMsg, err );
break;
@ -4202,7 +4218,7 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags )
if ( ret == SQLITE_DONE || ret == SQLITE_ROW )
{
ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
}
} // BEGIN statement
@ -4218,9 +4234,14 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags )
if ( toCommit )
{
// ROLLBACK after some previous error
( void )sqlite3_exec( mSqliteHandle, "ROLLBACK", nullptr, nullptr, nullptr );
( void )exec_sql( QStringLiteral( "ROLLBACK TRANSACTION TO SAVEPOINT \"%1\"" ).arg( savepointId ) );
}
}
else
{
if ( mTransaction )
mTransaction->dirtyLastSavePoint();
}
return ret == SQLITE_OK;
}
@ -4243,10 +4264,12 @@ bool QgsSpatiaLiteProvider::createAttributeIndex( int field )
QString sql;
QString fieldName;
int ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) };
int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -4256,20 +4279,23 @@ bool QgsSpatiaLiteProvider::createAttributeIndex( int field )
.arg( createIndexName( mTableName, fieldName ),
mTableName,
QgsSqliteUtils::quotedIdentifier( fieldName ) );
ret = sqlite3_exec( mSqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
ret = exec_sql( sql, errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
if ( mTransaction )
mTransaction->dirtyLastSavePoint();
return true;
}
@ -4293,20 +4319,22 @@ bool QgsSpatiaLiteProvider::deleteFeatures( const QgsFeatureIds &id )
char *errMsg = nullptr;
QString sql;
int ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) };
int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
sql = QStringLiteral( "DELETE FROM %1 WHERE %2=?" ).arg( QgsSqliteUtils::quotedIdentifier( mTableName ), QgsSqliteUtils::quotedIdentifier( mPrimaryKey ) );
// SQLite prepared statement
if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
if ( sqlite3_prepare_v2( sqliteHandle( ), sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
{
// some error occurred
pushError( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ) );
pushError( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( sqliteHandle( ) ) ) );
return false;
}
else
@ -4330,10 +4358,10 @@ bool QgsSpatiaLiteProvider::deleteFeatures( const QgsFeatureIds &id )
else
{
// some unexpected error occurred
const char *err = sqlite3_errmsg( mSqliteHandle );
const char *err = sqlite3_errmsg( sqliteHandle( ) );
errMsg = ( char * ) sqlite3_malloc( ( int ) strlen( err ) + 1 );
strcpy( errMsg, err );
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
}
@ -4341,13 +4369,16 @@ bool QgsSpatiaLiteProvider::deleteFeatures( const QgsFeatureIds &id )
sqlite3_finalize( stmt );
ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
if ( mTransaction )
mTransaction->dirtyLastSavePoint();
return true;
}
@ -4356,28 +4387,33 @@ bool QgsSpatiaLiteProvider::truncate()
char *errMsg = nullptr;
QString sql;
int ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) };
int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
sql = QStringLiteral( "DELETE FROM %1" ).arg( QgsSqliteUtils::quotedIdentifier( mTableName ) );
ret = sqlite3_exec( mSqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
ret = exec_sql( sql, errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
if ( mTransaction )
mTransaction->dirtyLastSavePoint();
return true;
}
@ -4389,10 +4425,12 @@ bool QgsSpatiaLiteProvider::addAttributes( const QList<QgsField> &attributes )
if ( attributes.isEmpty() )
return true;
int ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) };
int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -4402,27 +4440,30 @@ bool QgsSpatiaLiteProvider::addAttributes( const QList<QgsField> &attributes )
.arg( mTableName,
iter->name(),
iter->typeName() );
ret = sqlite3_exec( mSqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
ret = exec_sql( sql, errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
}
ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
gaiaStatisticsInvalidate( mSqliteHandle, mTableName.toUtf8().constData(), mGeometryColumn.toUtf8().constData() );
update_layer_statistics( mSqliteHandle, mTableName.toUtf8().constData(), mGeometryColumn.toUtf8().constData() );
gaiaStatisticsInvalidate( sqliteHandle( ), mTableName.toUtf8().constData(), mGeometryColumn.toUtf8().constData() );
update_layer_statistics( sqliteHandle( ), mTableName.toUtf8().constData(), mGeometryColumn.toUtf8().constData() );
// reload columns
loadFields();
if ( mTransaction )
mTransaction->dirtyLastSavePoint();
return true;
}
@ -4434,10 +4475,12 @@ bool QgsSpatiaLiteProvider::changeAttributeValues( const QgsChangedAttributesMap
if ( attr_map.isEmpty() )
return true;
int ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) };
int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -4504,7 +4547,7 @@ bool QgsSpatiaLiteProvider::changeAttributeValues( const QgsChangedAttributesMap
auto msgPtr { static_cast<char *>( sqlite3_malloc( errM.length() + 1 ) ) };
strcpy( static_cast<char *>( msgPtr ), errM.toStdString().c_str() );
errMsg = msgPtr;
handleError( jRepr, errMsg, true );
handleError( jRepr, errMsg, savepointId );
return false;
}
}
@ -4521,21 +4564,24 @@ bool QgsSpatiaLiteProvider::changeAttributeValues( const QgsChangedAttributesMap
}
sql += QStringLiteral( " WHERE %1=%2" ).arg( QgsSqliteUtils::quotedIdentifier( mPrimaryKey ) ).arg( fid );
ret = sqlite3_exec( mSqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
ret = exec_sql( sql.toUtf8(), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
}
ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
if ( mTransaction )
mTransaction->dirtyLastSavePoint();
return true;
}
@ -4545,10 +4591,12 @@ bool QgsSpatiaLiteProvider::changeGeometryValues( const QgsGeometryMap &geometry
char *errMsg = nullptr;
QString sql;
int ret = sqlite3_exec( mSqliteHandle, "BEGIN", nullptr, nullptr, &errMsg );
const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) };
int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -4560,10 +4608,10 @@ bool QgsSpatiaLiteProvider::changeGeometryValues( const QgsGeometryMap &geometry
.arg( QgsSqliteUtils::quotedIdentifier( mPrimaryKey ) );
// SQLite prepared statement
if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
if ( sqlite3_prepare_v2( sqliteHandle( ), sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
{
// some error occurred
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), tr( "SpatiaLite" ) );
QgsMessageLog::logMessage( tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( sqliteHandle( ) ) ), tr( "SpatiaLite" ) );
}
else
{
@ -4591,10 +4639,10 @@ bool QgsSpatiaLiteProvider::changeGeometryValues( const QgsGeometryMap &geometry
else
{
// some unexpected error occurred
const char *err = sqlite3_errmsg( mSqliteHandle );
const char *err = sqlite3_errmsg( sqliteHandle( ) );
errMsg = ( char * ) sqlite3_malloc( ( int ) strlen( err ) + 1 );
strcpy( errMsg, err );
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
}
@ -4602,12 +4650,16 @@ bool QgsSpatiaLiteProvider::changeGeometryValues( const QgsGeometryMap &geometry
sqlite3_finalize( stmt );
ret = sqlite3_exec( mSqliteHandle, "COMMIT", nullptr, nullptr, &errMsg );
ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg, true );
handleError( sql, errMsg, savepointId );
return false;
}
if ( mTransaction )
mTransaction->dirtyLastSavePoint();
return true;
}
@ -4706,7 +4758,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
"WHERE lower(name) = lower(%1) "
"AND type in ('table', 'view') " ).arg( QgsSqliteUtils::quotedString( mTableName ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret == SQLITE_OK && rows == 1 )
{
QString type = QString( results[ columns + 0 ] );
@ -4751,7 +4803,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
QgsSqliteUtils::quotedIdentifier( alias ) );
sql = QStringLiteral( "SELECT 0, %1 FROM %2 LIMIT 1" ).arg( QgsSqliteUtils::quotedIdentifier( mGeometryColumn ), mQuery );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
// Try to find a PK or try to use ROWID
if ( ret == SQLITE_OK && rows == 1 )
@ -4761,7 +4813,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
// 1. find the table that provides geometry
// String containing the name of the table that provides the geometry if the layer data source is based on a query
QString queryGeomTableName;
if ( sqlite3_prepare_v2( mSqliteHandle, sql.toUtf8().constData(), -1, &stmt, nullptr ) == SQLITE_OK )
if ( sqlite3_prepare_v2( sqliteHandle( ), sql.toUtf8().constData(), -1, &stmt, nullptr ) == SQLITE_OK )
{
queryGeomTableName = sqlite3_column_table_name( stmt, 1 );
}
@ -4815,7 +4867,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
// Try first without any injection or manipulation
sql = QStringLiteral( "SELECT %1, %2 FROM %3 LIMIT 1" ).arg( QgsSqliteUtils::quotedIdentifier( pks.first( ) ), QgsSqliteUtils::quotedIdentifier( mGeometryColumn ), mQuery );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret == SQLITE_OK && rows == 1 )
{
mPrimaryKey = pks.first( );
@ -4828,7 +4880,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
.arg( QgsSqliteUtils::quotedIdentifier( tableIdentifier ) )
.arg( pks.first() ) ) );
sql = QStringLiteral( "SELECT %1 FROM %2 LIMIT 1" ).arg( pk ).arg( newSql );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret == SQLITE_OK && rows == 1 )
{
mQuery = newSql;
@ -4844,7 +4896,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
if ( ! queryGeomTableName.isEmpty() )
{
sql = QStringLiteral( "SELECT ROWID FROM %1 WHERE ROWID IS NOT NULL LIMIT 1" ).arg( QgsSqliteUtils::quotedIdentifier( queryGeomTableName ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK || rows != 1 )
{
queryGeomTableName = QString();
@ -4858,7 +4910,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
.arg( QgsSqliteUtils::quotedIdentifier( tableIdentifier ),
QStringLiteral( "ROWID" ) ) ) );
sql = QStringLiteral( "SELECT ROWID FROM %1 WHERE ROWID IS NOT NULL LIMIT 1" ).arg( newSql );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret == SQLITE_OK && rows == 1 )
{
mQuery = newSql;
@ -4897,7 +4949,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
.arg( QgsSqliteUtils::quotedString( mTableName ),
QgsSqliteUtils::quotedString( mGeometryColumn ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
if ( errMsg && strcmp( errMsg, "no such table: geometry_columns_auth" ) == 0 )
@ -4906,7 +4958,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
sql = QStringLiteral( "SELECT 0 FROM geometry_columns WHERE upper(f_table_name) = upper(%1) and upper(f_geometry_column) = upper(%2)" )
.arg( QgsSqliteUtils::quotedString( mTableName ),
QgsSqliteUtils::quotedString( mGeometryColumn ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
}
}
if ( ret == SQLITE_OK && rows == 1 )
@ -4936,7 +4988,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
" WHERE view_name=%1 and view_geometry=%2" ).arg( QgsSqliteUtils::quotedString( mTableName ),
QgsSqliteUtils::quotedString( mGeometryColumn ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret == SQLITE_OK && rows == 1 )
{
mViewBased = true;
@ -4956,7 +5008,7 @@ bool QgsSpatiaLiteProvider::checkLayerType()
" WHERE virt_name=%1 and virt_geometry=%2" ).arg( QgsSqliteUtils::quotedString( mTableName ),
QgsSqliteUtils::quotedString( mGeometryColumn ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret == SQLITE_OK && rows == 1 )
{
mVShapeBased = true;
@ -5066,10 +5118,10 @@ void QgsSpatiaLiteProvider::getViewSpatialIndexName()
"FROM views_geometry_columns "
"WHERE upper(view_name) = upper(%1) and upper(view_geometry) = upper(%2)" ).arg( QgsSqliteUtils::quotedString( mTableName ),
QgsSqliteUtils::quotedString( mGeometryColumn ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
}
if ( rows < 1 )
;
@ -5118,7 +5170,7 @@ bool QgsSpatiaLiteProvider::getTableGeometryDetails()
mIndexGeometry = mGeometryColumn;
QString sql;
if ( ! versionIsAbove( mSqliteHandle, 3, 1 ) )
if ( ! versionIsAbove( sqliteHandle( ), 3, 1 ) )
{
sql = QString( "SELECT type, srid, spatial_index_enabled, coord_dimension FROM geometry_columns"
" WHERE upper(f_table_name) = upper(%1) and upper(f_geometry_column) = upper(%2)" ).arg( QgsSqliteUtils::quotedString( mTableName ),
@ -5131,10 +5183,10 @@ bool QgsSpatiaLiteProvider::getTableGeometryDetails()
QgsSqliteUtils::quotedString( mGeometryColumn ) );
}
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
if ( rows < 1 )
@ -5208,7 +5260,7 @@ bool QgsSpatiaLiteProvider::getTableGeometryDetails()
if ( mGeomType == QgsWkbTypes::Unknown || mSrid < 0 )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -5230,10 +5282,10 @@ bool QgsSpatiaLiteProvider::getViewGeometryDetails()
" WHERE upper(view_name) = upper(%1) and upper(view_geometry) = upper(%2)" ).arg( QgsSqliteUtils::quotedString( mTableName ),
QgsSqliteUtils::quotedString( mGeometryColumn ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
if ( rows < 1 )
@ -5288,7 +5340,7 @@ bool QgsSpatiaLiteProvider::getViewGeometryDetails()
if ( mGeomType == QgsWkbTypes::Unknown || mSrid < 0 )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -5308,10 +5360,10 @@ bool QgsSpatiaLiteProvider::getVShapeGeometryDetails()
" WHERE virt_name=%1 and virt_geometry=%2" ).arg( QgsSqliteUtils::quotedString( mTableName ),
QgsSqliteUtils::quotedString( mGeometryColumn ) );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
if ( rows < 1 )
@ -5355,7 +5407,7 @@ bool QgsSpatiaLiteProvider::getVShapeGeometryDetails()
if ( mGeomType == QgsWkbTypes::Unknown || mSrid < 0 )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -5376,7 +5428,7 @@ bool QgsSpatiaLiteProvider::getQueryGeometryDetails()
// get stuff from the relevant column instead. This may (will?)
// fail if there is no data in the relevant table.
QString sql = QStringLiteral( "select srid(%1), geometrytype(%1) from %2" )
QString sql = QStringLiteral( "SELECT srid(%1), geometrytype(%1) FROM %2" )
.arg( QgsSqliteUtils::quotedIdentifier( mGeometryColumn ),
mQuery );
@ -5388,10 +5440,10 @@ bool QgsSpatiaLiteProvider::getQueryGeometryDetails()
sql += QLatin1String( " limit 1" );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -5412,23 +5464,23 @@ bool QgsSpatiaLiteProvider::getQueryGeometryDetails()
if ( fType == QLatin1String( "GEOMETRY" ) )
{
// check to see if there is a unique geometry type
sql = QString( "select distinct "
"case"
" when geometrytype(%1) IN ('POINT','MULTIPOINT') THEN 'POINT'"
" when geometrytype(%1) IN ('LINESTRING','MULTILINESTRING') THEN 'LINESTRING'"
" when geometrytype(%1) IN ('POLYGON','MULTIPOLYGON') THEN 'POLYGON'"
" end "
"from %2" )
sql = QString( "SELECT DISTINCT "
"CASE"
" WHEN geometrytype(%1) IN ('POINT','MULTIPOINT') THEN 'POINT'"
" WHEN geometrytype(%1) IN ('LINESTRING','MULTILINESTRING') THEN 'LINESTRING'"
" WHEN geometrytype(%1) IN ('POLYGON','MULTIPOLYGON') THEN 'POLYGON'"
" END "
"FROM %2" )
.arg( QgsSqliteUtils::quotedIdentifier( mGeometryColumn ),
mQuery );
if ( !mSubsetString.isEmpty() )
sql += " where " + mSubsetString;
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -5473,7 +5525,7 @@ bool QgsSpatiaLiteProvider::getQueryGeometryDetails()
if ( mGeomType == QgsWkbTypes::Unknown || mSrid < 0 )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
@ -5491,10 +5543,10 @@ bool QgsSpatiaLiteProvider::getSridDetails()
QString sql = QStringLiteral( "SELECT auth_name||':'||auth_srid,proj4text FROM spatial_ref_sys WHERE srid=%1" ).arg( mSrid );
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
if ( rows < 1 )
@ -5550,10 +5602,10 @@ bool QgsSpatiaLiteProvider::getTableSummary()
sql += " WHERE ( " + mSubsetString + ')';
}
ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
{
handleError( sql, errMsg );
handleError( sql, errMsg, QString() );
return false;
}
if ( rows < 1 )
@ -5668,6 +5720,17 @@ QList<QgsVectorLayer *> QgsSpatiaLiteProvider::searchLayers( const QList<QgsVect
return result;
}
void QgsSpatiaLiteProvider::setTransaction( QgsTransaction *transaction )
{
QgsDebugMsgLevel( QStringLiteral( "set transaction %1" ).arg( transaction != nullptr ), 1 );
// static_cast since layers cannot be added to a transaction of a non-matching provider
mTransaction = static_cast<QgsSpatiaLiteTransaction *>( transaction );
}
QgsTransaction *QgsSpatiaLiteProvider::transaction( ) const
{
return static_cast<QgsTransaction *>( mTransaction );
}
QList<QgsRelation> QgsSpatiaLiteProvider::discoverRelations( const QgsVectorLayer *self, const QList<QgsVectorLayer *> &layers ) const
{
@ -5677,7 +5740,7 @@ QList<QgsRelation> QgsSpatiaLiteProvider::discoverRelations( const QgsVectorLaye
int rows;
int columns;
char *errMsg = nullptr;
int ret = sqlite3_get_table( mSqliteHandle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
int ret = sqlite3_get_table( sqliteHandle( ), sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret == SQLITE_OK )
{
int nbFound = 0;
@ -6140,7 +6203,21 @@ QList< QgsDataItemProvider * > QgsSpatiaLiteProviderMetadata::dataItemProviders(
return providers;
}
QgsTransaction *QgsSpatiaLiteProviderMetadata::createTransaction( const QString &connString )
{
const QgsDataSourceUri dsUri{ connString };
// Cannot use QgsSpatiaLiteConnPool::instance()->acquireConnection( dsUri.database() ) };
// because it will return a read only connection, use the (cached) connection from the
// layers instead.
QgsSqliteHandle *ds { QgsSqliteHandle::openDb( dsUri.database() ) };
if ( !ds )
{
QgsMessageLog::logMessage( QObject::tr( "Cannot open transaction on %1, since it is is not currently opened" ).arg( connString ),
QObject::tr( "spatialite" ), Qgis::Critical );
return nullptr;
}
return new QgsSpatiaLiteTransaction( connString, ds );
}
QMap<QString, QgsAbstractProviderConnection *> QgsSpatiaLiteProviderMetadata::connections( bool cached )
{

View File

@ -43,6 +43,8 @@ class QgsField;
class QgsSqliteHandle;
class QgsSpatiaLiteFeatureIterator;
class QgsSpatiaLiteTransaction;
class QgsTransaction;
#include "qgsdatasourceuri.h"
@ -220,6 +222,12 @@ class QgsSpatiaLiteProvider final: public QgsVectorDataProvider
*/
static QList<QgsVectorLayer *> searchLayers( const QList<QgsVectorLayer *> &layers, const QString &connectionInfo, const QString &tableName );
QgsSpatiaLiteTransaction *mTransaction = nullptr;
QgsTransaction *transaction() const override;
void setTransaction( QgsTransaction *transaction ) override;
QgsFields mAttributeFields;
//! Map of field index to default value SQL fragments
@ -325,6 +333,10 @@ class QgsSpatiaLiteProvider final: public QgsVectorDataProvider
//! SpatiaLite minor version
int mSpatialiteVersionMinor = 0;
//! Internal transaction handling (for addFeatures etc.)
int mSavepointId;
static QAtomicInt sSavepointId;
/**
* internal utility functions used to handle common SQLite tasks
*/
@ -382,7 +394,17 @@ class QgsSpatiaLiteProvider final: public QgsVectorDataProvider
/**
* Handles an error encountered while executing an sql statement.
*/
void handleError( const QString &sql, char *errorMessage, bool rollback = false );
void handleError( const QString &sql, char *errorMessage, const QString &savepointId );
/**
* Sqlite exec sql wrapper for SQL logging
*/
int exec_sql( const QString &sql, char *errMsg = nullptr );
/**
* Returns the sqlite handle to be used, if we are inside a transaction it will be the transaction's handle
*/
sqlite3 *sqliteHandle( ) const;
friend class QgsSpatiaLiteFeatureSource;
@ -421,14 +443,14 @@ class QgsSpatiaLiteProviderMetadata final: public QgsProviderMetadata
QgsAbstractProviderConnection *createConnection( const QString &name ) override;
void deleteConnection( const QString &name ) override;
void saveConnection( const QgsAbstractProviderConnection *connection, const QString &name ) override;
QgsTransaction *createTransaction( const QString &connString ) override;
protected:
QgsAbstractProviderConnection *createConnection( const QString &uri, const QVariantMap &configuration ) override;
};
// clazy:excludeall=qstring-allocations
#endif

View File

@ -0,0 +1,89 @@
/***************************************************************************
qgsspatialitetransaction.cpp - QgsSpatialiteTransaction
---------------------
begin : 30.3.2020
copyright : (C) 2020 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* 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 "qgsspatialitetransaction.h"
#include "qgslogger.h"
#include <QDebug>
///@cond PRIVATE
QAtomicInt QgsSpatiaLiteTransaction::sSavepointId = 0;
QgsSpatiaLiteTransaction::QgsSpatiaLiteTransaction( const QString &connString, QgsSqliteHandle *sharedHandle )
: QgsTransaction( connString )
, mSharedHandle( sharedHandle )
{
if ( mSharedHandle )
mSqliteHandle = mSharedHandle->handle();
mSavepointId = ++sSavepointId;
}
bool QgsSpatiaLiteTransaction::beginTransaction( QString &error, int /* statementTimeout */ )
{
return executeSql( QStringLiteral( "BEGIN" ), error );
}
bool QgsSpatiaLiteTransaction::commitTransaction( QString &error )
{
return executeSql( QStringLiteral( "COMMIT" ), error );
}
bool QgsSpatiaLiteTransaction::rollbackTransaction( QString &error )
{
return executeSql( QStringLiteral( "ROLLBACK" ), error );
}
bool QgsSpatiaLiteTransaction::executeSql( const QString &sql, QString &errorMsg, bool isDirty, const QString &name )
{
if ( ! mSqliteHandle )
{
QgsDebugMsg( QStringLiteral( "Spatialite handle is not set" ) );
return false;
}
QString err;
if ( isDirty )
{
createSavepoint( err );
}
char *errMsg = nullptr;
if ( sqlite3_exec( mSqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
{
QgsDebugMsg( errorMsg );
if ( isDirty )
{
rollbackToSavepoint( savePoints().last(), err );
}
sqlite3_free( errMsg );
return false;
}
if ( isDirty )
{
dirtyLastSavePoint();
emit dirtied( sql, name );
}
QgsDebugMsg( QStringLiteral( "... ok" ) );
return true;
}
sqlite3 *QgsSpatiaLiteTransaction::sqliteHandle() const
{
return mSqliteHandle;
}
///@endcond

View File

@ -0,0 +1,64 @@
/***************************************************************************
qgsspatialitetransaction.h - QgsSpatialiteTransaction
---------------------
begin : 30.3.2020
copyright : (C) 2020 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* 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 QGSSPATIALITETRANSACTION_H
#define QGSSPATIALITETRANSACTION_H
#include "qgstransaction.h"
#include "qgsspatialiteconnection.h"
#include "qgis_sip.h"
///@cond PRIVATE
#define SIP_NO_FILE
class QgsSpatiaLiteTransaction : public QgsTransaction
{
Q_OBJECT
public:
explicit QgsSpatiaLiteTransaction( const QString &connString, QgsSqliteHandle *sharedHandle );
/**
* 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;
/**
* Returns the (possibly NULL) sqlite handle
*/
sqlite3 *sqliteHandle() const;
private:
QgsSqliteHandle *mSharedHandle = nullptr;
int mSavepointId;
//! SQLite handle
sqlite3 *mSqliteHandle = nullptr;
bool beginTransaction( QString &error, int statementTimeout ) override;
bool commitTransaction( QString &error ) override;
bool rollbackTransaction( QString &error ) override;
static QAtomicInt sSavepointId;
};
///@endcond
#endif // QGSSPATIALITETRANSACTION_H

View File

@ -17,9 +17,11 @@ import re
import sys
import shutil
import tempfile
from osgeo import ogr
from datetime import datetime
from qgis.core import (QgsProviderRegistry,
QgsDataSourceUri,
QgsVectorLayer,
QgsVectorDataProvider,
QgsPointXY,
@ -1295,6 +1297,85 @@ class TestQgsSpatialiteProvider(unittest.TestCase, ProviderTestCase):
self.assertEqual(vl.featureCount(), 1)
self.assertEqual(vl.getFeature(1).geometry().asWkt().upper(), 'POINT (9 45)')
def testTransaction(self):
"""Test spatialite transactions"""
tmpfile = tempfile.mktemp('.db')
ds = ogr.GetDriverByName('SQLite').CreateDataSource(tmpfile, options=['SPATIALITE=YES'])
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
uri1 = QgsDataSourceUri()
uri1.setDatabase(tmpfile)
uri1.setTable('lyr1')
uri2 = QgsDataSourceUri()
uri2.setDatabase(tmpfile)
uri2.setTable('lyr2')
vl1 = QgsVectorLayer(uri1.uri(), 'test', 'spatialite')
self.assertTrue(vl1.isValid())
vl2 = QgsVectorLayer(uri2.uri(), 'test', 'spatialite')
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(uri1.uri(), 'test', 'spatialite')
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(uri2.uri(), 'test', 'spatialite')
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()