Skip to content

Commit

Permalink
Tests for style DB storage
Browse files Browse the repository at this point in the history
  • Loading branch information
elpaso committed Nov 7, 2022
1 parent 9c2923a commit c80a0d6
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 30 deletions.
5 changes: 3 additions & 2 deletions src/core/providers/ogr/qgsogrprovidermetadata.cpp
Expand Up @@ -805,6 +805,7 @@ int QgsOgrProviderMetadata::listStyles(
QMap<int, QString> mapIdToDescription;
QMap<qlonglong, QList<int> > mapTimestampToId;
int numberOfRelatedStyles = 0;

while ( true )
{
gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) );
Expand Down Expand Up @@ -839,8 +840,8 @@ int QgsOgrProviderMetadata::listStyles(
int year, month, day, hour, minute, second, TZ;
OGR_F_GetFieldAsDateTime( hFeature.get(), OGR_FD_GetFieldIndex( hLayerDefn, "update_time" ),
&year, &month, &day, &hour, &minute, &second, &TZ );
qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 +
static_cast<qlonglong>( month ) * 31 * 24 * 3600 + static_cast<qlonglong>( year ) * 12 * 31 * 24 * 3600;
const qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 +
static_cast<qlonglong>( month ) * 31 * 24 * 3600 + static_cast<qlonglong>( year ) * 12 * 31 * 24 * 3600;

listTimestamp.append( ts );
mapIdToStyleName[fid] = styleName;
Expand Down
41 changes: 17 additions & 24 deletions src/core/vector/qgsvectorlayer.cpp
Expand Up @@ -1761,37 +1761,30 @@ QString QgsVectorLayer::loadDefaultStyle( bool &resultFlag )

if ( resultFlag )
{
// Try to load all stored styles
if ( mLoadAllStoredStyle )
// Try to load all stored styles from DB
if ( mLoadAllStoredStyle && mDataProvider && mDataProvider->isSaveAndLoadStyleToDatabaseSupported() )
{
QStringList ids, names, descriptions;
QString errorMessage;
listStylesInDatabase( ids, names, descriptions, errorMessage );
if ( ids.count() == names.count() )
Q_ASSERT( ids.count() == names.count() );
const QString currentStyleName { mStyleManager->currentStyle() };
for ( int i = 0; i < static_cast<int>( ids.count() ); ++i )
{
const QString currentStyleName { mStyleManager->currentStyle() };
for ( int i = 0; i < ids.count(); ++i )
if ( names.at( i ) == currentStyleName )
{
if ( names.at( i ) == currentStyleName )
{
continue;
}
errorMessage.clear();
const QString styleXml { getStyleFromDatabase( ids.at( i ), errorMessage ) };
if ( ! styleXml.isEmpty() && errorMessage.isEmpty() )
{
mStyleManager->addStyle( names.at( i ), QgsMapLayerStyle( styleXml ) );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Error retrieving style %1 from DB: %2" ).arg( ids.at( i ), errorMessage ), 2 );
}
continue;
}
errorMessage.clear();
const QString styleXml { getStyleFromDatabase( ids.at( i ), errorMessage ) };
if ( ! styleXml.isEmpty() && errorMessage.isEmpty() )
{
mStyleManager->addStyle( names.at( i ), QgsMapLayerStyle( styleXml ) );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Error retrieving style %1 from DB: %2" ).arg( ids.at( i ), errorMessage ), 2 );
}
}
else
{
// Something is wery wrong!
QgsDebugMsg( "Number of ids and names differs after fetching styles from DB!" );
}
}
return styleXml ;
Expand Down
8 changes: 4 additions & 4 deletions src/providers/postgres/qgspostgresprovider.cpp
Expand Up @@ -5547,7 +5547,7 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString &
" WHERE f_table_catalog=%1"
" AND f_table_schema=%2"
" AND f_table_name=%3"
" AND f_geometry_column=%4"
" AND f_geometry_column %4"
" AND (type=%5 OR type IS NULL)"
" AND styleName=%6" )
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
Expand Down Expand Up @@ -5580,7 +5580,7 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString &
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) )
.arg( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) )
.arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) )
// Must be the final .arg replacement - see above
.arg( QgsPostgresConn::quotedValue( qmlStyle ),
Expand Down Expand Up @@ -5676,8 +5676,8 @@ QString QgsPostgresProviderMetadata::loadStoredStyle( const QString &uri, QStrin
}
else
{
selectQmlQuery = QString( "SELECT styleQML"
" FROM styleName, layer_styles"
selectQmlQuery = QString( "SELECT styleName, styleQML"
" FROM layer_styles"
" WHERE f_table_catalog=%1"
" AND f_table_schema=%2"
" AND f_table_name=%3"
Expand Down
5 changes: 5 additions & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -284,6 +284,8 @@ ADD_PYTHON_TEST(PyQgsProviderConnectionComboBox test_qgsproviderconnectioncombob
ADD_PYTHON_TEST(PyQgsProviderConnectionModel test_qgsproviderconnectionmodel.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionGpkg test_qgsproviderconnection_ogr_gpkg.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionSpatialite test_qgsproviderconnection_spatialite.py)
ADD_PYTHON_TEST(PyQgsStyleStorageSpatialite test_stylestorage_spatialite.py)
ADD_PYTHON_TEST(PyQgsStyleStorageGpkg test_stylestorage_gpkg.py)
ADD_PYTHON_TEST(PyQgsProviderRegistry test_qgsproviderregistry.py)
ADD_PYTHON_TEST(PyQgsProviderSourceWidgetProviderRegistry test_qgssourcewidgetproviderregistry.py)
ADD_PYTHON_TEST(PyQgsProviderSqlQueryBuilder test_qgsprovidersqlquerybuilder.py)
Expand Down Expand Up @@ -468,6 +470,7 @@ if (ENABLE_PGTEST)
ADD_PYTHON_TEST(PyQgsDatabaseSchemaComboBox test_qgsdatabaseschemacombobox.py)
ADD_PYTHON_TEST(PyQgsDatabaseTableComboBox test_qgsdatabasetablecombobox.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionPostgres test_qgsproviderconnection_postgres.py)
ADD_PYTHON_TEST(PyQgsStyleStoragePostgres test_stylestorage_postgres.py)
if (WITH_SERVER)
ADD_PYTHON_TEST(PyQgsServerWMSGetFeatureInfoPG test_qgsserver_wms_getfeatureinfo_postgres.py)
ADD_PYTHON_TEST(PyQgsServerAccessControlWMSGetPrintPG test_qgsserver_accesscontrol_wms_getprint_postgres.py)
Expand All @@ -487,13 +490,15 @@ endif()
if (ENABLE_MSSQLTEST)
ADD_PYTHON_TEST(PyQgsMssqlProvider test_provider_mssql.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionMssql test_qgsproviderconnection_mssql.py)
ADD_PYTHON_TEST(PyQgsStyleStorageMssql test_stylestorage_mssql.py)
SET_TESTS_PROPERTIES(PyQgsMssqlProvider PyQgsProviderConnectionMssql PROPERTIES LABELS "MSSQL")
endif()

if (ENABLE_ORACLETEST)
ADD_PYTHON_TEST(PyQgsOracleProvider test_provider_oracle.py TEST_TIMEOUT=300)
ADD_PYTHON_TEST(PyQgsProviderConnectionOracle test_qgsproviderconnection_oracle.py)
ADD_PYTHON_TEST(PyQgsProjectStorageOracle test_project_storage_oracle.py)
ADD_PYTHON_TEST(PyQgsStyleStorageOracle test_stylestorage_oracle.py)
SET_TESTS_PROPERTIES(PyQgsOracleProvider PyQgsProviderConnectionOracle PyQgsProjectStorageOracle PROPERTIES LABELS "ORACLE")
endif()

Expand Down
132 changes: 132 additions & 0 deletions tests/src/python/stylestoragebase.py
@@ -0,0 +1,132 @@
# coding=utf-8
""""Base test for provider style DB storage
.. 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__ = 'elpaso@itopen.it'
__date__ = '2022-11-07'
__copyright__ = 'Copyright 2022, ItOpen'

from qgis.testing import start_app, unittest
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.PyQt.QtGui import QColor

from qgis.core import (
QgsProviderRegistry,
QgsSettings,
QgsAbstractDatabaseProviderConnection,
QgsField,
QgsFields,
QgsCoordinateReferenceSystem,
QgsWkbTypes,
QgsVectorLayer,
)

import time


class StyleStorageTestCaseBase(unittest.TestCase):

@classmethod
def setUpClass(cls):
"""Run before all tests"""

QCoreApplication.setOrganizationName("QGIS_Test")
QCoreApplication.setOrganizationDomain("%s.com" % __name__)
QCoreApplication.setApplicationName(__name__)
QgsSettings().clear()
start_app()


class StyleStorageTestBase():

def layerUri(self, conn, schema_name, table_name):
"""Providers may override if they need more complex URI generation than
what tableUri() offers"""

return conn.tableUri(schema_name, table_name)

def testMultipleStyles(self):

md = QgsProviderRegistry.instance().providerMetadata(self.providerKey)

conn = md.createConnection(self.uri, {})
md.saveConnection(conn, 'qgis_test1')

schema = None
capabilities = conn.capabilities()

if (capabilities & QgsAbstractDatabaseProviderConnection.CreateSchema
and capabilities & QgsAbstractDatabaseProviderConnection.Schemas
and capabilities & QgsAbstractDatabaseProviderConnection.DropSchema):

schema = 'testStyles'
# Start clean
if schema in conn.schemas():
conn.dropSchema(schema, True)

# Create
conn.createSchema(schema)
schemas = conn.schemas()
self.assertTrue(schema in schemas)

fields = QgsFields()
fields.append(QgsField("string_t", QVariant.String))
options = {}
crs = QgsCoordinateReferenceSystem.fromEpsgId(4326)
typ = QgsWkbTypes.Point

# Create table
conn.createVectorTable(schema, 'test_styles', fields, typ, crs, True, options)
uri = self.layerUri(conn, schema, 'test_styles')

vl = QgsVectorLayer(uri, 'vl', self.providerKey)
self.assertTrue(vl.isValid())
renderer = vl.renderer()
symbol = renderer.symbol().clone()
symbol.setColor(QColor('#ff0000'))
renderer.setSymbol(symbol)

vl.saveStyleToDatabase('style1', 'style1', False, None)

symbol = renderer.symbol().clone()
symbol.setColor(QColor('#00ff00'))
renderer.setSymbol(symbol)

vl.saveStyleToDatabase('style2', 'style2', True, None)

symbol = renderer.symbol().clone()
symbol.setColor(QColor('#0000ff'))
renderer.setSymbol(symbol)

vl.saveStyleToDatabase('style3', 'style3', False, None)
num, ids, names, desc, err = vl.listStylesInDatabase()

self.assertTrue(set(['style2', 'style3', 'style1']).issubset(set(names)))

del vl
vl = QgsVectorLayer(uri, 'vl', self.providerKey)
self.assertTrue(vl.isValid())
renderer = vl.renderer()
symbol = renderer.symbol()
self.assertEqual(symbol.color().name(), '#00ff00')

mgr = vl.styleManager()
self.assertEqual(mgr.styles(), ['style2'])

del vl
options = QgsVectorLayer.LayerOptions()
options.loadAllStoredStyles = True
vl = QgsVectorLayer(uri, 'vl', self.providerKey, options)
self.assertTrue(vl.isValid())
renderer = vl.renderer()
symbol = renderer.symbol()
self.assertEqual(symbol.color().name(), '#00ff00')

mgr = vl.styleManager()
self.assertTrue(set(['style2', 'style3', 'style1']).issubset(set(names)))
41 changes: 41 additions & 0 deletions tests/src/python/test_stylestorage_gpkg.py
@@ -0,0 +1,41 @@
# coding=utf-8
""""Style storage tests for GPKG
.. 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__ = 'elpaso@itopen.it'
__date__ = '2022-11-07'
__copyright__ = 'Copyright 2022, ItOpen'

import os
from stylestoragebase import StyleStorageTestBase
from qgis.PyQt.QtCore import QTemporaryDir
from qgis.core import (
QgsProviderRegistry,
)
from qgis.testing import unittest


class StyleStorageTest(unittest.TestCase, StyleStorageTestBase):

# Provider test cases must define the provider name (e.g. "postgres" or "ogr")
providerKey = 'ogr'

def setUp(self):

super().setUp()
self.temp_dir = QTemporaryDir()
self.temp_path = self.temp_dir.path()
md = QgsProviderRegistry.instance().providerMetadata(self.providerKey)
self.test_uri = os.path.join(self.temp_path, 'test.gpkg')
self.assertTrue(md.createDatabase(self.test_uri)[0])
self.uri = self.test_uri


if __name__ == '__main__':
unittest.main()
53 changes: 53 additions & 0 deletions tests/src/python/test_stylestorage_mssql.py
@@ -0,0 +1,53 @@
# coding=utf-8
""""Style storage tests for MSSQL
.. 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__ = 'elpaso@itopen.it'
__date__ = '2022-11-07'
__copyright__ = 'Copyright 2022, ItOpen'

import os
from stylestoragebase import StyleStorageTestBase, StyleStorageTestCaseBase
from qgis.PyQt.QtCore import QTemporaryDir
from qgis.core import (
QgsDataSourceUri
)
from qgis.testing import unittest


class StyleStorageTest(StyleStorageTestCaseBase, StyleStorageTestBase):

# 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 = 'mssql'

def setUp(self):

super().setUp()

dbconn = "service='testsqlserver' user=sa password='<YourStrong!Passw0rd>' "
if 'QGIS_MSSQLTEST_DB' in os.environ:
dbconn = os.environ['QGIS_MSSQLTEST_DB']

self.uri = dbconn

def layerUri(self, conn, schema_name, table_name):
"""Providers may override if they need more complex URI generation than
what tableUri() offers"""

uri = QgsDataSourceUri(conn.tableUri(schema_name, table_name))
uri.setGeometryColumn('geom')
uri.setParam('srid', '4326')
uri.setParam('type', 'POINT')
return uri.uri()


if __name__ == '__main__':
unittest.main()

0 comments on commit c80a0d6

Please sign in to comment.