mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-04 00:30:59 -05:00
Merge pull request #32464 from elpaso/connections-db-api-spatialite
Connections db api spatialite
This commit is contained in:
commit
4eaab04204
@ -23,8 +23,8 @@
|
||||
// List of GPKG quoted system and dummy tables names to be excluded from the tables listing
|
||||
static const QStringList excludedTableNames { { QStringLiteral( "\"ogr_empty_table\"" ) } };
|
||||
|
||||
QgsGeoPackageProviderConnection::QgsGeoPackageProviderConnection( const QString &name ):
|
||||
QgsAbstractDatabaseProviderConnection( name )
|
||||
QgsGeoPackageProviderConnection::QgsGeoPackageProviderConnection( const QString &name )
|
||||
: QgsAbstractDatabaseProviderConnection( name )
|
||||
{
|
||||
setDefaultCapabilities();
|
||||
QgsSettings settings;
|
||||
|
@ -26,11 +26,11 @@ extern "C"
|
||||
#include <libpq-fe.h>
|
||||
}
|
||||
|
||||
QgsPostgresProviderConnection::QgsPostgresProviderConnection( const QString &name ):
|
||||
QgsAbstractDatabaseProviderConnection( name )
|
||||
QgsPostgresProviderConnection::QgsPostgresProviderConnection( const QString &name )
|
||||
: QgsAbstractDatabaseProviderConnection( name )
|
||||
{
|
||||
// Remove the sql and table empty parts
|
||||
static const QRegularExpression removePartsRe { R"raw(\s*sql=\s*|\s*table=""\s*)raw" };
|
||||
const QRegularExpression removePartsRe { R"raw(\s*sql=\s*|\s*table=""\s*)raw" };
|
||||
setUri( QgsPostgresConn::connUri( name ).uri().replace( removePartsRe, QString() ) );
|
||||
setDefaultCapabilities();
|
||||
}
|
||||
@ -118,7 +118,7 @@ QString QgsPostgresProviderConnection::tableUri( const QString &schema, const QS
|
||||
dsUri.setSchema( schema );
|
||||
if ( tableInfo.flags().testFlag( QgsAbstractDatabaseProviderConnection::TableFlag::Raster ) )
|
||||
{
|
||||
static const QRegularExpression removePartsRe { R"raw(\s*sql=\s*|\s*table=("[^"]+"\.?)*\s*)raw" };
|
||||
const QRegularExpression removePartsRe { R"raw(\s*sql=\s*|\s*table=("[^"]+"\.?)*\s*)raw" };
|
||||
if ( tableInfo.geometryColumn().isEmpty() )
|
||||
{
|
||||
throw QgsProviderConnectionException( QObject::tr( "Raster table '%1' in schema '%2' has no geometry column." )
|
||||
|
@ -12,6 +12,7 @@ SET(SPATIALITE_SRCS
|
||||
qgsspatialiteconnpool.cpp
|
||||
qgsspatialitefeatureiterator.cpp
|
||||
qgsspatialitetablemodel.cpp
|
||||
qgsspatialiteproviderconnection.cpp
|
||||
)
|
||||
|
||||
SET(SPATIALITE_MOC_HDRS
|
||||
@ -49,7 +50,9 @@ ENDIF ()
|
||||
INCLUDE_DIRECTORIES(
|
||||
${CMAKE_SOURCE_DIR}/external
|
||||
${CMAKE_SOURCE_DIR}/src/core
|
||||
${CMAKE_SOURCE_DIR}/src/core/providers/ogr
|
||||
${CMAKE_SOURCE_DIR}/src/core/expression
|
||||
${CMAKE_SOURCE_DIR}/src/core/symbology
|
||||
${CMAKE_SOURCE_DIR}/src/core/geometry
|
||||
${CMAKE_SOURCE_DIR}/src/core/metadata
|
||||
${CMAKE_SOURCE_DIR}/src/gui
|
||||
@ -61,6 +64,7 @@ INCLUDE_DIRECTORIES(
|
||||
${CMAKE_BINARY_DIR}/src/ui
|
||||
)
|
||||
INCLUDE_DIRECTORIES(SYSTEM
|
||||
${GDAL_INCLUDE_DIR}
|
||||
${SQLITE3_INCLUDE_DIR}
|
||||
${SPATIALITE_INCLUDE_DIR}
|
||||
)
|
||||
|
@ -30,6 +30,8 @@ email : a.furieri@lqt.it
|
||||
#include "qgsspatialitefeatureiterator.h"
|
||||
#include "qgsfeedback.h"
|
||||
#include "qgsspatialitedataitems.h"
|
||||
#include "qgsspatialiteconnection.h"
|
||||
#include "qgsspatialiteproviderconnection.h"
|
||||
|
||||
#include "qgsjsonutils.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
@ -6054,6 +6056,34 @@ QList< QgsDataItemProvider * > QgsSpatiaLiteProviderMetadata::dataItemProviders(
|
||||
return providers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
QMap<QString, QgsAbstractProviderConnection *> QgsSpatiaLiteProviderMetadata::connections( bool cached )
|
||||
{
|
||||
return connectionsProtected< QgsSpatiaLiteProviderConnection, QgsSpatiaLiteConnection>( cached );
|
||||
}
|
||||
|
||||
QgsAbstractProviderConnection *QgsSpatiaLiteProviderMetadata::createConnection( const QString &connName )
|
||||
{
|
||||
return new QgsSpatiaLiteProviderConnection( connName );
|
||||
}
|
||||
|
||||
QgsAbstractProviderConnection *QgsSpatiaLiteProviderMetadata::createConnection( const QString &uri, const QVariantMap &configuration )
|
||||
{
|
||||
return new QgsSpatiaLiteProviderConnection( uri, configuration );
|
||||
}
|
||||
|
||||
void QgsSpatiaLiteProviderMetadata::deleteConnection( const QString &name )
|
||||
{
|
||||
deleteConnectionProtected<QgsSpatiaLiteProviderConnection>( name );
|
||||
}
|
||||
|
||||
void QgsSpatiaLiteProviderMetadata::saveConnection( const QgsAbstractProviderConnection *conn, const QString &name )
|
||||
{
|
||||
saveConnectionProtected( conn, name );
|
||||
}
|
||||
|
||||
|
||||
QGISEXTERN QgsProviderMetadata *providerMetadataFactory()
|
||||
{
|
||||
return new QgsSpatiaLiteProviderMetadata();
|
||||
|
@ -406,6 +406,19 @@ class QgsSpatiaLiteProviderMetadata: public QgsProviderMetadata
|
||||
const QMap<QString, QVariant> *options ) override;
|
||||
bool createDb( const QString &dbPath, QString &errCause ) override;
|
||||
QList< QgsDataItemProvider * > dataItemProviders() const override;
|
||||
|
||||
// QgsProviderMetadata interface
|
||||
public:
|
||||
QMap<QString, QgsAbstractProviderConnection *> connections( bool cached ) override;
|
||||
QgsAbstractProviderConnection *createConnection( const QString &name ) override;
|
||||
void deleteConnection( const QString &name ) override;
|
||||
void saveConnection( const QgsAbstractProviderConnection *connection, const QString &name ) override;
|
||||
|
||||
protected:
|
||||
|
||||
QgsAbstractProviderConnection *createConnection( const QString &uri, const QVariantMap &configuration ) override;
|
||||
|
||||
|
||||
};
|
||||
|
||||
// clazy:excludeall=qstring-allocations
|
||||
|
395
src/providers/spatialite/qgsspatialiteproviderconnection.cpp
Normal file
395
src/providers/spatialite/qgsspatialiteproviderconnection.cpp
Normal file
@ -0,0 +1,395 @@
|
||||
/***************************************************************************
|
||||
QgsSpatialiteProviderConnection.cpp - QgsSpatialiteProviderConnection
|
||||
|
||||
---------------------
|
||||
begin : 6.8.2019
|
||||
copyright : (C) 2019 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 "qgsspatialiteproviderconnection.h"
|
||||
#include "qgsspatialiteconnection.h"
|
||||
#include "qgsspatialiteprovider.h"
|
||||
#include "qgsogrprovider.h"
|
||||
#include "qgssettings.h"
|
||||
#include "qgsmessagelog.h"
|
||||
#include "qgsproviderregistry.h"
|
||||
|
||||
|
||||
QgsSpatiaLiteProviderConnection::QgsSpatiaLiteProviderConnection( const QString &name )
|
||||
: QgsAbstractDatabaseProviderConnection( name )
|
||||
{
|
||||
setDefaultCapabilities();
|
||||
// TODO: QGIS 4: move into QgsSettings::Section::Providers group
|
||||
QgsSettings settings;
|
||||
settings.beginGroup( QStringLiteral( "SpatiaLite" ) );
|
||||
settings.beginGroup( QStringLiteral( "connections" ) );
|
||||
settings.beginGroup( name );
|
||||
QgsDataSourceUri dsUri;
|
||||
dsUri.setDatabase( settings.value( QStringLiteral( "sqlitepath" ) ).toString() );
|
||||
setUri( dsUri.uri() );
|
||||
}
|
||||
|
||||
QgsSpatiaLiteProviderConnection::QgsSpatiaLiteProviderConnection( const QString &uri, const QVariantMap &configuration ):
|
||||
QgsAbstractDatabaseProviderConnection( uri, configuration )
|
||||
{
|
||||
const QRegularExpression removePartsRe { R"raw(\s*sql=\s*|\s*table=""\s*|\([^\)]+\))raw" };
|
||||
// Cleanup the URI in case it contains other information other than the file path
|
||||
setUri( QString( uri ).replace( removePartsRe, QString() ) );
|
||||
setDefaultCapabilities();
|
||||
}
|
||||
|
||||
void QgsSpatiaLiteProviderConnection::store( const QString &name ) const
|
||||
{
|
||||
// TODO: QGIS 4: move into QgsSettings::Section::Providers group
|
||||
QgsSettings settings;
|
||||
settings.beginGroup( QStringLiteral( "SpatiaLite" ) );
|
||||
settings.beginGroup( QStringLiteral( "connections" ) );
|
||||
settings.beginGroup( name );
|
||||
settings.setValue( QStringLiteral( "sqlitepath" ), pathFromUri() );
|
||||
}
|
||||
|
||||
void QgsSpatiaLiteProviderConnection::remove( const QString &name ) const
|
||||
{
|
||||
// TODO: QGIS 4: move into QgsSettings::Section::Providers group
|
||||
QgsSettings settings;
|
||||
settings.beginGroup( QStringLiteral( "SpatiaLite" ) );
|
||||
settings.beginGroup( QStringLiteral( "connections" ) );
|
||||
settings.remove( name );
|
||||
}
|
||||
|
||||
QString QgsSpatiaLiteProviderConnection::tableUri( const QString &schema, const QString &name ) const
|
||||
{
|
||||
const auto tableInfo { table( schema, name ) };
|
||||
return uri() + QStringLiteral( " table=%1" ).arg( QgsSqliteUtils::quotedIdentifier( name ) );
|
||||
}
|
||||
|
||||
void QgsSpatiaLiteProviderConnection::createVectorTable( const QString &schema,
|
||||
const QString &name,
|
||||
const QgsFields &fields,
|
||||
QgsWkbTypes::Type wkbType,
|
||||
const QgsCoordinateReferenceSystem &srs,
|
||||
bool overwrite,
|
||||
const QMap<QString, QVariant> *options ) const
|
||||
{
|
||||
checkCapability( Capability::CreateVectorTable );
|
||||
if ( ! schema.isEmpty() )
|
||||
{
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by Spatialite, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
|
||||
}
|
||||
QMap<QString, QVariant> opts { *options };
|
||||
opts[ QStringLiteral( "layerName" ) ] = QVariant( name );
|
||||
opts[ QStringLiteral( "update" ) ] = true;
|
||||
QMap<int, int> map;
|
||||
QString errCause;
|
||||
QgsVectorLayerExporter::ExportError errCode = QgsSpatiaLiteProvider::createEmptyLayer(
|
||||
uri() + QStringLiteral( " table=%1 (geom)" ).arg( QgsSqliteUtils::quotedIdentifier( name ) ),
|
||||
fields,
|
||||
wkbType,
|
||||
srs,
|
||||
overwrite,
|
||||
&map,
|
||||
&errCause,
|
||||
&opts
|
||||
);
|
||||
if ( errCode != QgsVectorLayerExporter::ExportError::NoError )
|
||||
{
|
||||
throw QgsProviderConnectionException( QObject::tr( "An error occurred while creating the vector layer: %1" ).arg( errCause ) );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsSpatiaLiteProviderConnection::dropVectorTable( const QString &schema, const QString &name ) const
|
||||
{
|
||||
checkCapability( Capability::DropVectorTable );
|
||||
if ( ! schema.isEmpty() )
|
||||
{
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by Spatialite, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
|
||||
}
|
||||
QString errCause;
|
||||
|
||||
QgsSqliteHandle *hndl = QgsSqliteHandle::openDb( pathFromUri() );
|
||||
if ( !hndl )
|
||||
{
|
||||
errCause = QObject::tr( "Connection to database failed" );
|
||||
}
|
||||
|
||||
if ( errCause.isEmpty() )
|
||||
{
|
||||
sqlite3 *sqlite_handle = hndl->handle();
|
||||
int ret;
|
||||
if ( !gaiaDropTable( sqlite_handle, name.toUtf8().constData() ) )
|
||||
{
|
||||
// unexpected error
|
||||
errCause = QObject::tr( "Unable to delete table %1\n" ).arg( name );
|
||||
QgsSqliteHandle::closeDb( hndl );
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: remove spatial indexes?
|
||||
// run VACUUM to free unused space and compact the database
|
||||
ret = sqlite3_exec( sqlite_handle, "VACUUM", nullptr, nullptr, nullptr );
|
||||
if ( ret != SQLITE_OK )
|
||||
{
|
||||
QgsDebugMsg( QStringLiteral( "Failed to run VACUUM after deleting table on database %1" )
|
||||
.arg( pathFromUri() ) );
|
||||
}
|
||||
|
||||
QgsSqliteHandle::closeDb( hndl );
|
||||
}
|
||||
}
|
||||
if ( ! errCause.isEmpty() )
|
||||
{
|
||||
throw QgsProviderConnectionException( QObject::tr( "Error deleting vector/aspatial table %1: %2" ).arg( name ).arg( errCause ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QgsSpatiaLiteProviderConnection::renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const
|
||||
{
|
||||
checkCapability( Capability::RenameVectorTable );
|
||||
if ( ! schema.isEmpty() )
|
||||
{
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by Spatialite, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
|
||||
}
|
||||
// TODO: maybe an index?
|
||||
QString sql( QStringLiteral( "ALTER TABLE %1 RENAME TO %2" )
|
||||
.arg( QgsSqliteUtils::quotedIdentifier( name ),
|
||||
QgsSqliteUtils::quotedIdentifier( newName ) ) );
|
||||
executeSqlPrivate( sql );
|
||||
sql = QStringLiteral( "UPDATE geometry_columns SET f_table_name = lower(%2) WHERE lower(f_table_name) = lower(%1)" )
|
||||
.arg( QgsSqliteUtils::quotedString( name ),
|
||||
QgsSqliteUtils::quotedString( newName ) );
|
||||
executeSqlPrivate( sql );
|
||||
sql = QStringLiteral( "UPDATE layer_styles SET f_table_name = lower(%2) WHERE f_table_name = lower(%1)" )
|
||||
.arg( QgsSqliteUtils::quotedString( name ),
|
||||
QgsSqliteUtils::quotedString( newName ) );
|
||||
try
|
||||
{
|
||||
executeSqlPrivate( sql );
|
||||
}
|
||||
catch ( QgsProviderConnectionException &ex )
|
||||
{
|
||||
QgsDebugMsgLevel( QStringLiteral( "Warning: error while updating the styles, perhaps there are no styles stored in this GPKG: %1" ).arg( ex.what() ), 4 );
|
||||
}
|
||||
}
|
||||
|
||||
QList<QList<QVariant>> QgsSpatiaLiteProviderConnection::executeSql( const QString &sql ) const
|
||||
{
|
||||
checkCapability( Capability::ExecuteSql );
|
||||
return executeSqlPrivate( sql );
|
||||
}
|
||||
|
||||
void QgsSpatiaLiteProviderConnection::vacuum( const QString &schema, const QString &name ) const
|
||||
{
|
||||
Q_UNUSED( name )
|
||||
checkCapability( Capability::Vacuum );
|
||||
if ( ! schema.isEmpty() )
|
||||
{
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by Spatialite, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
|
||||
}
|
||||
executeSqlPrivate( QStringLiteral( "VACUUM" ) );
|
||||
}
|
||||
|
||||
|
||||
QList<QgsSpatiaLiteProviderConnection::TableProperty> QgsSpatiaLiteProviderConnection::tables( const QString &schema, const TableFlags &flags ) const
|
||||
{
|
||||
checkCapability( Capability::Tables );
|
||||
if ( ! schema.isEmpty() )
|
||||
{
|
||||
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by Spatialite, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
|
||||
}
|
||||
QList<QgsSpatiaLiteProviderConnection::TableProperty> tableInfo;
|
||||
QString errCause;
|
||||
QList<QVariantList> results;
|
||||
try
|
||||
{
|
||||
QgsSpatiaLiteConnection connection( pathFromUri() );
|
||||
QgsSpatiaLiteConnection::Error err = connection.fetchTables( true );
|
||||
if ( err != QgsSpatiaLiteConnection::NoError )
|
||||
{
|
||||
QString msg;
|
||||
switch ( err )
|
||||
{
|
||||
case QgsSpatiaLiteConnection::NotExists:
|
||||
msg = QObject::tr( "Database does not exist" );
|
||||
break;
|
||||
case QgsSpatiaLiteConnection::FailedToOpen:
|
||||
msg = QObject::tr( "Failed to open database" );
|
||||
break;
|
||||
case QgsSpatiaLiteConnection::FailedToCheckMetadata:
|
||||
msg = QObject::tr( "Failed to check metadata" );
|
||||
break;
|
||||
case QgsSpatiaLiteConnection::FailedToGetTables:
|
||||
msg = QObject::tr( "Failed to get list of tables" );
|
||||
break;
|
||||
default:
|
||||
msg = QObject::tr( "Unknown error" );
|
||||
break;
|
||||
}
|
||||
QString msgDetails = connection.errorMessage();
|
||||
if ( !msgDetails.isEmpty() )
|
||||
{
|
||||
msg = QStringLiteral( "%1 (%2)" ).arg( msg, msgDetails );
|
||||
}
|
||||
throw QgsProviderConnectionException( QObject::tr( "Error fetching table information for connection: %1" ).arg( pathFromUri() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
const QString connectionInfo = QStringLiteral( "dbname='%1'" ).arg( QString( connection.path() ).replace( '\'', QLatin1String( "\\'" ) ) );
|
||||
QgsDataSourceUri dsUri( connectionInfo );
|
||||
|
||||
// Need to store it here because provider (and underlying gaia library) returns views as spatial table if they have geometries
|
||||
QStringList viewNames;
|
||||
for ( const auto &tn : executeSqlPrivate( QStringLiteral( "SELECT name FROM sqlite_master WHERE type = 'view'" ) ) )
|
||||
{
|
||||
viewNames.push_back( tn.first().toString() );
|
||||
}
|
||||
|
||||
// Another wierdness: table names are converted to lowercase when out of spatialite gaia functions, let's get them back to their real case here,
|
||||
// may need LAUNDER on open, but let's try to make it consistent with how GPKG works.
|
||||
QgsStringMap tableNotLowercaseNames;
|
||||
for ( const auto &tn : executeSqlPrivate( QStringLiteral( "SELECT name FROM sqlite_master WHERE LOWER(name) != name" ) ) )
|
||||
{
|
||||
const QString tName { tn.first().toString() };
|
||||
tableNotLowercaseNames.insert( tName.toLower(), tName );
|
||||
}
|
||||
|
||||
const auto constTables = connection.tables();
|
||||
for ( const QgsSpatiaLiteConnection::TableEntry &entry : constTables )
|
||||
{
|
||||
QString tableName { tableNotLowercaseNames.value( entry.tableName, entry.tableName ) };
|
||||
dsUri.setDataSource( QString(), tableName, entry.column, QString(), QString() );
|
||||
QgsSpatiaLiteProviderConnection::TableProperty property;
|
||||
property.setTableName( tableName );
|
||||
// Create a layer and get information from it
|
||||
std::unique_ptr< QgsVectorLayer > vl = qgis::make_unique<QgsVectorLayer>( dsUri.uri(), QString(), QLatin1Literal( "spatialite" ) );
|
||||
if ( vl->isValid() )
|
||||
{
|
||||
if ( vl->isSpatial() )
|
||||
{
|
||||
property.setGeometryColumnCount( 1 );
|
||||
property.setGeometryColumn( entry.column );
|
||||
property.setFlag( QgsSpatiaLiteProviderConnection::TableFlag::Vector );
|
||||
property.setGeometryColumnTypes( {{ vl->wkbType(), vl->crs() }} );
|
||||
}
|
||||
else
|
||||
{
|
||||
property.setGeometryColumnCount( 0 );
|
||||
property.setGeometryColumnTypes( {{ QgsWkbTypes::NoGeometry, QgsCoordinateReferenceSystem() }} );
|
||||
property.setFlag( QgsSpatiaLiteProviderConnection::TableFlag::Aspatial );
|
||||
}
|
||||
if ( viewNames.contains( tableName ) )
|
||||
{
|
||||
property.setFlag( QgsSpatiaLiteProviderConnection::TableFlag::View );
|
||||
}
|
||||
tableInfo.push_back( property );
|
||||
}
|
||||
else
|
||||
{
|
||||
QgsDebugMsgLevel( QStringLiteral( "Layer is not valid: %1" ).arg( dsUri.uri() ), 2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( QgsProviderConnectionException &ex )
|
||||
{
|
||||
errCause = ex.what();
|
||||
}
|
||||
|
||||
if ( ! errCause.isEmpty() )
|
||||
{
|
||||
throw QgsProviderConnectionException( QObject::tr( "Error listing tables from %1: %2" ).arg( pathFromUri() ).arg( errCause ) );
|
||||
}
|
||||
// Filters
|
||||
if ( flags )
|
||||
{
|
||||
tableInfo.erase( std::remove_if( tableInfo.begin(), tableInfo.end(), [ & ]( const QgsAbstractDatabaseProviderConnection::TableProperty & ti )
|
||||
{
|
||||
return !( ti.flags() & flags );
|
||||
} ), tableInfo.end() );
|
||||
}
|
||||
return tableInfo ;
|
||||
}
|
||||
|
||||
void QgsSpatiaLiteProviderConnection::setDefaultCapabilities()
|
||||
{
|
||||
mCapabilities =
|
||||
{
|
||||
Capability::Tables,
|
||||
Capability::CreateVectorTable,
|
||||
Capability::DropVectorTable,
|
||||
Capability::RenameVectorTable,
|
||||
Capability::Vacuum,
|
||||
Capability::Spatial,
|
||||
Capability::TableExists,
|
||||
Capability::ExecuteSql,
|
||||
};
|
||||
}
|
||||
|
||||
QList<QVariantList> QgsSpatiaLiteProviderConnection::executeSqlPrivate( const QString &sql ) const
|
||||
{
|
||||
QString errCause;
|
||||
QList<QVariantList> results;
|
||||
gdal::ogr_datasource_unique_ptr hDS( GDALOpenEx( pathFromUri().toUtf8().constData(), GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr, nullptr, nullptr ) );
|
||||
if ( hDS )
|
||||
{
|
||||
OGRLayerH ogrLayer( GDALDatasetExecuteSQL( hDS.get(), sql.toUtf8().constData(), nullptr, nullptr ) );
|
||||
if ( ogrLayer )
|
||||
{
|
||||
gdal::ogr_feature_unique_ptr fet;
|
||||
QgsFields fields;
|
||||
while ( fet.reset( OGR_L_GetNextFeature( ogrLayer ) ), fet )
|
||||
{
|
||||
QVariantList row;
|
||||
// Try to get the right type for the returned values
|
||||
if ( fields.isEmpty() )
|
||||
{
|
||||
fields = QgsOgrUtils::readOgrFields( fet.get(), QTextCodec::codecForName( "UTF-8" ) );
|
||||
}
|
||||
if ( ! fields.isEmpty() )
|
||||
{
|
||||
QgsFeature f { QgsOgrUtils::readOgrFeature( fet.get(), fields, QTextCodec::codecForName( "UTF-8" ) ) };
|
||||
const QgsAttributes &constAttrs { f.attributes() };
|
||||
for ( int i = 0; i < constAttrs.length(); i++ )
|
||||
{
|
||||
row.push_back( constAttrs.at( i ) );
|
||||
}
|
||||
}
|
||||
else // Fallback to strings
|
||||
{
|
||||
for ( int i = 0; i < OGR_F_GetFieldCount( fet.get() ); i++ )
|
||||
{
|
||||
row.push_back( QVariant( QString::fromUtf8( OGR_F_GetFieldAsString( fet.get(), i ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
results.push_back( row );
|
||||
}
|
||||
GDALDatasetReleaseResultSet( hDS.get(), ogrLayer );
|
||||
}
|
||||
errCause = CPLGetLastErrorMsg( );
|
||||
}
|
||||
else
|
||||
{
|
||||
errCause = QObject::tr( "There was an error opening Spatialite %1!" ).arg( pathFromUri() );
|
||||
}
|
||||
if ( ! errCause.isEmpty() )
|
||||
{
|
||||
throw QgsProviderConnectionException( QObject::tr( "Error executing SQL %1: %2" ).arg( sql ).arg( errCause ) );
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
QString QgsSpatiaLiteProviderConnection::pathFromUri() const
|
||||
{
|
||||
const QgsDataSourceUri dsUri( uri() );
|
||||
return dsUri.database();
|
||||
}
|
||||
|
58
src/providers/spatialite/qgsspatialiteproviderconnection.h
Normal file
58
src/providers/spatialite/qgsspatialiteproviderconnection.h
Normal file
@ -0,0 +1,58 @@
|
||||
/***************************************************************************
|
||||
QgsSpatialiteProviderConnection.h - QgsSpatialiteProviderConnection
|
||||
|
||||
---------------------
|
||||
begin : 6.8.2019
|
||||
copyright : (C) 2019 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 QGSSPATIALITEPROVIDERCONNECTION_H
|
||||
#define QGSSPATIALITEPROVIDERCONNECTION_H
|
||||
|
||||
#include "qgsabstractdatabaseproviderconnection.h"
|
||||
|
||||
///@cond PRIVATE
|
||||
#define SIP_NO_FILE
|
||||
|
||||
class QgsSpatiaLiteProviderConnection : public QgsAbstractDatabaseProviderConnection
|
||||
{
|
||||
public:
|
||||
|
||||
QgsSpatiaLiteProviderConnection( const QString &name );
|
||||
// Note: URI must be in PG QgsDataSourceUri format ( "dbname='path_to_sqlite.db'" )
|
||||
QgsSpatiaLiteProviderConnection( const QString &uri, const QVariantMap &configuration );
|
||||
|
||||
|
||||
// QgsAbstractProviderConnection interface
|
||||
public:
|
||||
void store( const QString &name ) const override;
|
||||
void remove( const QString &name ) const override;
|
||||
QString tableUri( const QString &schema, const QString &name ) const override;
|
||||
void createVectorTable( const QString &schema, const QString &name, const QgsFields &fields, QgsWkbTypes::Type wkbType, const QgsCoordinateReferenceSystem &srs, bool overwrite, const QMap<QString, QVariant> *options ) const override;
|
||||
void dropVectorTable( const QString &schema, const QString &name ) const override;
|
||||
void renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const override;
|
||||
QList<QList<QVariant>> executeSql( const QString &sql ) const override;
|
||||
void vacuum( const QString &schema, const QString &name ) const override;
|
||||
QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables( const QString &schema = QString(),
|
||||
const TableFlags &flags = nullptr ) const override;
|
||||
|
||||
private:
|
||||
|
||||
void setDefaultCapabilities();
|
||||
//! Use GDAL to execute SQL
|
||||
QList<QVariantList> executeSqlPrivate( const QString &sql ) const;
|
||||
|
||||
//! extract the path from the DS URI (which is in "PG" form: 'dbname=\'/path_to.sqlite\' table="table_name" (geom_col_name)')
|
||||
QString pathFromUri() const;
|
||||
|
||||
};
|
||||
|
||||
///@endcond
|
||||
#endif // QGSSPATIALITEPROVIDERCONNECTION_H
|
@ -176,8 +176,11 @@ class TestPyQgsProviderConnectionBase():
|
||||
self.assertEqual(res, [])
|
||||
sql = "SELECT string, long, double, integer, date, datetime FROM %s" % table
|
||||
res = conn.executeSql(sql)
|
||||
# GPKG has no type for time
|
||||
self.assertEqual(res, [['QGIS Rocks - \U0001f604', 666, 1.234, 1234, QtCore.QDate(2019, 7, 8), QtCore.QDateTime(2019, 7, 8, 12, 0, 12)]])
|
||||
# GPKG has no type for time and spatialite has no support for dates and time ...
|
||||
if self.providerKey == 'spatialite':
|
||||
self.assertEqual(res, [['QGIS Rocks - \U0001f604', 666, 1.234, 1234, '2019-07-08', '2019-07-08T12:00:12']])
|
||||
else:
|
||||
self.assertEqual(res, [['QGIS Rocks - \U0001f604', 666, 1.234, 1234, QtCore.QDate(2019, 7, 8), QtCore.QDateTime(2019, 7, 8, 12, 0, 12)]])
|
||||
sql = "SELECT time FROM %s" % table
|
||||
res = conn.executeSql(sql)
|
||||
self.assertIn(res, ([[QtCore.QTime(12, 0, 13)]], [['12:00:13.00']]))
|
||||
@ -193,14 +196,15 @@ class TestPyQgsProviderConnectionBase():
|
||||
self.assertTrue('myNewTable' in table_names)
|
||||
self.assertFalse('myNewAspatialTable' in table_names)
|
||||
|
||||
# Query for rasters (in qgis_test schema or no schema for GPKG)
|
||||
table_properties = conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster)
|
||||
# At leasy one raster should be there
|
||||
self.assertTrue(len(table_properties) >= 1)
|
||||
table_property = table_properties[0]
|
||||
self.assertTrue(table_property.flags() & QgsAbstractDatabaseProviderConnection.Raster)
|
||||
self.assertFalse(table_property.flags() & QgsAbstractDatabaseProviderConnection.Vector)
|
||||
self.assertFalse(table_property.flags() & QgsAbstractDatabaseProviderConnection.Aspatial)
|
||||
# Query for rasters (in qgis_test schema or no schema for GPKG, spatialite has no support)
|
||||
if self.providerKey != 'spatialite':
|
||||
table_properties = conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster)
|
||||
# At least one raster should be there (except for spatialite)
|
||||
self.assertTrue(len(table_properties) >= 1)
|
||||
table_property = table_properties[0]
|
||||
self.assertTrue(table_property.flags() & QgsAbstractDatabaseProviderConnection.Raster)
|
||||
self.assertFalse(table_property.flags() & QgsAbstractDatabaseProviderConnection.Vector)
|
||||
self.assertFalse(table_property.flags() & QgsAbstractDatabaseProviderConnection.Aspatial)
|
||||
|
||||
# Rename
|
||||
conn.renameVectorTable(schema, 'myNewTable', 'myVeryNewTable')
|
||||
|
@ -103,17 +103,17 @@ class TestPyQgsProviderConnectionGpkg(unittest.TestCase, TestPyQgsProviderConnec
|
||||
conn.createVectorTable('', 'myNewTable', QgsFields(), typ, crs, True, {})
|
||||
|
||||
# Check filters and special cases
|
||||
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster))
|
||||
table_names = self._table_names(conn.tables('', QgsAbstractDatabaseProviderConnection.Raster))
|
||||
self.assertTrue('osm' in table_names)
|
||||
self.assertFalse('myNewTable' in table_names)
|
||||
self.assertFalse('myNewAspatialTable' in table_names)
|
||||
|
||||
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.View))
|
||||
table_names = self._table_names(conn.tables('', QgsAbstractDatabaseProviderConnection.View))
|
||||
self.assertFalse('osm' in table_names)
|
||||
self.assertFalse('myNewTable' in table_names)
|
||||
self.assertFalse('myNewAspatialTable' in table_names)
|
||||
|
||||
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Aspatial))
|
||||
table_names = self._table_names(conn.tables('', QgsAbstractDatabaseProviderConnection.Aspatial))
|
||||
self.assertFalse('osm' in table_names)
|
||||
self.assertFalse('myNewTable' in table_names)
|
||||
self.assertTrue('myNewAspatialTable' in table_names)
|
||||
|
113
tests/src/python/test_qgsproviderconnection_spatialite.py
Normal file
113
tests/src/python/test_qgsproviderconnection_spatialite.py
Normal file
@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""QGIS Unit tests for Spatialite QgsAbastractProviderConnection API.
|
||||
|
||||
.. note:: 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.
|
||||
|
||||
"""
|
||||
__author__ = 'Alessandro Pasotti'
|
||||
__date__ = '28/10/2019'
|
||||
__copyright__ = 'Copyright 2019, The QGIS Project'
|
||||
# This will get replaced with a git SHA1 when you do a git archive
|
||||
__revision__ = '$Format:%H$'
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from test_qgsproviderconnection_base import TestPyQgsProviderConnectionBase
|
||||
from qgis.core import (
|
||||
QgsWkbTypes,
|
||||
QgsAbstractDatabaseProviderConnection,
|
||||
QgsProviderConnectionException,
|
||||
QgsVectorLayer,
|
||||
QgsRasterLayer,
|
||||
QgsProviderRegistry,
|
||||
QgsFields,
|
||||
QgsCoordinateReferenceSystem,
|
||||
)
|
||||
from qgis.testing import unittest
|
||||
from utilities import unitTestDataPath
|
||||
|
||||
TEST_DATA_DIR = unitTestDataPath()
|
||||
|
||||
|
||||
class TestPyQgsProviderConnectionSpatialite(unittest.TestCase, TestPyQgsProviderConnectionBase):
|
||||
|
||||
# Provider test cases must define the string URI for the test
|
||||
uri = ''
|
||||
# Provider test cases must define the provider name (e.g. "postgres" or "ogr")
|
||||
providerKey = 'spatialite'
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Run before all tests"""
|
||||
TestPyQgsProviderConnectionBase.setUpClass()
|
||||
spatialite_original_path = '{}/qgis_server/test_project_wms_grouped_layers.sqlite'.format(TEST_DATA_DIR)
|
||||
cls.spatialite_path = '{}/qgis_server/test_project_wms_grouped_layers_test.sqlite'.format(TEST_DATA_DIR)
|
||||
shutil.copy(spatialite_original_path, cls.spatialite_path)
|
||||
cls.uri = "dbname=\'%s\'" % cls.spatialite_path
|
||||
vl = QgsVectorLayer('{} table=\'cdb_lines\''.format(cls.uri), 'test', 'spatialite')
|
||||
assert vl.isValid()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""Run after all tests"""
|
||||
os.unlink(cls.spatialite_path)
|
||||
|
||||
def test_spatialite_connections_from_uri(self):
|
||||
"""Create a connection from a layer uri and retrieve it"""
|
||||
|
||||
md = QgsProviderRegistry.instance().providerMetadata('spatialite')
|
||||
vl = QgsVectorLayer('{} table=\'cdb_lines\''.format(self.uri), 'test', 'spatialite')
|
||||
self.assertTrue(vl.isValid())
|
||||
conn = md.createConnection(vl.dataProvider().uri().uri(), {})
|
||||
self.assertEqual(conn.uri(), self.uri + ' table="cdb_lines"')
|
||||
conn.tables()
|
||||
|
||||
def test_spatialite_table_uri(self):
|
||||
"""Create a connection from a layer uri and create a table URI"""
|
||||
|
||||
md = QgsProviderRegistry.instance().providerMetadata('spatialite')
|
||||
conn = md.createConnection(self.uri, {})
|
||||
self.assertEqual(conn.tableUri('', 'cdb_lines'), '{} table="cdb_lines"'.format(self.uri))
|
||||
vl = QgsVectorLayer(conn.tableUri('', 'cdb_lines'), 'lines', 'spatialite')
|
||||
self.assertTrue(vl.isValid())
|
||||
|
||||
# Test table(), throws if not found
|
||||
table_info = conn.table('', 'cdb_lines')
|
||||
|
||||
def test_spatialite_connections(self):
|
||||
"""Create some connections and retrieve them"""
|
||||
|
||||
md = QgsProviderRegistry.instance().providerMetadata('spatialite')
|
||||
|
||||
conn = md.createConnection(self.uri, {})
|
||||
md.saveConnection(conn, 'qgis_test1')
|
||||
|
||||
# Retrieve capabilities
|
||||
capabilities = conn.capabilities()
|
||||
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.Tables))
|
||||
self.assertFalse(bool(capabilities & QgsAbstractDatabaseProviderConnection.Schemas))
|
||||
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.CreateVectorTable))
|
||||
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.DropVectorTable))
|
||||
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.RenameVectorTable))
|
||||
self.assertFalse(bool(capabilities & QgsAbstractDatabaseProviderConnection.RenameRasterTable))
|
||||
|
||||
crs = QgsCoordinateReferenceSystem.fromEpsgId(3857)
|
||||
typ = QgsWkbTypes.LineString
|
||||
conn.createVectorTable('', 'myNewAspatialTable', QgsFields(), QgsWkbTypes.NoGeometry, crs, True, {})
|
||||
conn.createVectorTable('', 'myNewTable', QgsFields(), typ, crs, True, {})
|
||||
|
||||
table_names = self._table_names(conn.tables('', QgsAbstractDatabaseProviderConnection.View))
|
||||
self.assertTrue('my_view' in table_names)
|
||||
self.assertFalse('myNewTable' in table_names)
|
||||
self.assertFalse('myNewAspatialTable' in table_names)
|
||||
|
||||
table_names = self._table_names(conn.tables('', QgsAbstractDatabaseProviderConnection.Aspatial))
|
||||
self.assertFalse('myNewTable' in table_names)
|
||||
self.assertTrue('myNewAspatialTable' in table_names)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
BIN
tests/testdata/qgis_server/test_project_wms_grouped_layers.sqlite
vendored
Normal file
BIN
tests/testdata/qgis_server/test_project_wms_grouped_layers.sqlite
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user