Merge pull request #32464 from elpaso/connections-db-api-spatialite

Connections db api spatialite
This commit is contained in:
Alessandro Pasotti 2019-10-29 14:08:31 +01:00 committed by GitHub
commit 4eaab04204
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 636 additions and 19 deletions

View File

@ -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;

View File

@ -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." )

View File

@ -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}
)

View File

@ -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();

View File

@ -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

View 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();
}

View 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

View File

@ -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')

View File

@ -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)

View 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()

Binary file not shown.