Skip to content

Commit

Permalink
Unit tests for OGR provider querySublayers, minor fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jun 28, 2021
1 parent 15e1e23 commit a5d7fe2
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 13 deletions.
9 changes: 5 additions & 4 deletions src/core/providers/ogr/qgsogrprovider.cpp
Expand Up @@ -627,9 +627,10 @@ QList<QgsProviderSublayerDetails> QgsOgrProvider::_subLayers( Qgis::SublayerQuer
if ( !mSubLayerList.isEmpty() )
return mSubLayerList;

if ( mOgrLayer && ( mIsSubLayer || layerCount() == 1 ) )
const size_t totalLayerCount = layerCount();
if ( mOgrLayer && ( mIsSubLayer || totalLayerCount == 1 ) )
{
mSubLayerList << QgsOgrProviderUtils::querySubLayerList( mLayerIndex, mOgrLayer, mGDALDriverName, flags, mIsSubLayer, dataSourceUri() );
mSubLayerList << QgsOgrProviderUtils::querySubLayerList( mLayerIndex, mOgrLayer, mGDALDriverName, flags, mIsSubLayer, dataSourceUri(), totalLayerCount == 1 );
}
else
{
Expand All @@ -638,7 +639,7 @@ QList<QgsProviderSublayerDetails> QgsOgrProvider::_subLayers( Qgis::SublayerQuer
// reuse the same dataset. Can help in a particular with a FileGDB with
// the FileGDB driver
QgsOgrLayerUniquePtr firstLayer;
for ( unsigned int i = 0; i < layerCount() ; i++ )
for ( size_t i = 0; i < totalLayerCount ; i++ )
{
QString errCause;
QgsOgrLayerUniquePtr layer = QgsOgrProviderUtils::getLayer( mOgrOrigLayer->datasetName(),
Expand All @@ -652,7 +653,7 @@ QList<QgsProviderSublayerDetails> QgsOgrProvider::_subLayers( Qgis::SublayerQuer
if ( !layer )
continue;

mSubLayerList << QgsOgrProviderUtils::querySubLayerList( i, layer.get(), mGDALDriverName, flags, mIsSubLayer, dataSourceUri() );
mSubLayerList << QgsOgrProviderUtils::querySubLayerList( i, layer.get(), mGDALDriverName, flags, mIsSubLayer, dataSourceUri(), totalLayerCount == 1 );
if ( firstLayer == nullptr )
{
firstLayer = std::move( layer );
Expand Down
8 changes: 4 additions & 4 deletions src/core/providers/ogr/qgsogrprovidermetadata.cpp
Expand Up @@ -1046,11 +1046,11 @@ QList<QgsProviderSublayerDetails> QgsOgrProviderMetadata::querySublayers( const
skippedLayerNames = QgsSqliteUtils::systemTables();
}

const unsigned int layerCount = firstLayer->GetLayerCount();
const int layerCount = firstLayer->GetLayerCount();

if ( layerCount == 1 )
{
return QgsOgrProviderUtils::querySubLayerList( 0, firstLayer.get(), driverName, flags, false, uri, feedback );
return QgsOgrProviderUtils::querySubLayerList( 0, firstLayer.get(), driverName, flags, false, uri, true, feedback );
}
else
{
Expand All @@ -1059,7 +1059,7 @@ QList<QgsProviderSublayerDetails> QgsOgrProviderMetadata::querySublayers( const
// layer alive while we iterate over the other layers, so that we can
// reuse the same dataset. Can help in a particular with a FileGDB with
// the FileGDB driver
for ( unsigned int i = 0; i < layerCount; i++ )
for ( int i = 0; i < layerCount; i++ )
{
if ( feedback && feedback->isCanceled() )
break;
Expand All @@ -1081,7 +1081,7 @@ QList<QgsProviderSublayerDetails> QgsOgrProviderMetadata::querySublayers( const
continue;
}

res << QgsOgrProviderUtils::querySubLayerList( i, i == 0 ? firstLayer.get() : layer.get(), driverName, flags, false, uri, feedback );
res << QgsOgrProviderUtils::querySubLayerList( i, i == 0 ? firstLayer.get() : layer.get(), driverName, flags, false, uri, false, feedback );
}
return res;
}
Expand Down
9 changes: 6 additions & 3 deletions src/core/providers/ogr/qgsogrproviderutils.cpp
Expand Up @@ -2292,7 +2292,7 @@ bool QgsOgrProviderUtils::canDriverShareSameDatasetAmongLayers( const QString &d
!( updateMode && dsName.endsWith( QLatin1String( ".shp.zip" ), Qt::CaseInsensitive ) );
}

QList< QgsProviderSublayerDetails > QgsOgrProviderUtils::querySubLayerList( int i, QgsOgrLayer *layer, const QString &driverName, Qgis::SublayerQueryFlags flags, bool isSubLayer, const QString &baseUri, QgsFeedback *feedback )
QList< QgsProviderSublayerDetails > QgsOgrProviderUtils::querySubLayerList( int i, QgsOgrLayer *layer, const QString &driverName, Qgis::SublayerQueryFlags flags, bool isSubLayer, const QString &baseUri, bool hasSingleLayerOnly, QgsFeedback *feedback )
{
QString layerName = QString::fromUtf8( layer->name() );

Expand Down Expand Up @@ -2329,9 +2329,12 @@ QList< QgsProviderSublayerDetails > QgsOgrProviderUtils::querySubLayerList( int


QVariantMap parts = QgsOgrProviderMetadata().decodeUri( baseUri );
parts.insert( QStringLiteral( "layerName" ), layerName );
if ( !hasSingleLayerOnly )
parts.insert( QStringLiteral( "layerName" ), layerName );
else
parts.remove( QStringLiteral( "layerName" ) );

if ( slowGeomTypeRetrieval || wkbFlatten( layerGeomType ) != wkbUnknown )
if ( slowGeomTypeRetrieval || wkbFlatten( layerGeomType ) != wkbUnknown || !( flags & Qgis::SublayerQueryFlag::ResolveGeometryType ) )
{
const long long layerFeatureCount = flags & Qgis::SublayerQueryFlag::CountFeatures ? layer->GetApproxFeatureCount() : static_cast< long >( Qgis::FeatureCountState::Uncounted );

Expand Down
3 changes: 2 additions & 1 deletion src/core/providers/ogr/qgsogrproviderutils.h
Expand Up @@ -257,7 +257,8 @@ class CORE_EXPORT QgsOgrProviderUtils
bool updateMode,
const QString &dsName );

static QList<QgsProviderSublayerDetails> querySubLayerList( int i, QgsOgrLayer *layer, const QString &driverName, Qgis::SublayerQueryFlags flags, bool isSubLayer, const QString &baseUri, QgsFeedback *feedback = nullptr );
static QList<QgsProviderSublayerDetails> querySubLayerList( int i, QgsOgrLayer *layer, const QString &driverName, Qgis::SublayerQueryFlags flags, bool isSubLayer,
const QString &baseUri, bool hasSingleLayerOnly, QgsFeedback *feedback = nullptr );

};

Expand Down
134 changes: 133 additions & 1 deletion tests/src/python/test_provider_ogr.py
Expand Up @@ -42,7 +42,10 @@
QgsWkbTypes,
QgsNetworkAccessManager,
QgsLayerMetadata,
QgsNotSupportedException
QgsNotSupportedException,
QgsMapLayerType,
QgsProviderSublayerDetails,
Qgis
)
from qgis.testing import start_app, unittest
from qgis.utils import spatialite_connect
Expand Down Expand Up @@ -1501,6 +1504,135 @@ def testFieldDomains(self):
self.assertTrue(enum_setup.config()['map'], [{'one': '1'}, {'2': '2'}])
self.assertEqual(vl.editorWidgetSetup(fields.lookupField('with_enum_domain')).type(), 'ValueMap')

def test_provider_sublayer_details(self):
"""
Test retrieving sublayer details from data provider metadata
"""
metadata = QgsProviderRegistry.instance().providerMetadata('ogr')

# invalid uri
res = metadata.querySublayers('')
self.assertFalse(res)

# not a vector
res = metadata.querySublayers(os.path.join(TEST_DATA_DIR, 'landsat.tif'))
self.assertFalse(res)

# single layer vector
res = metadata.querySublayers(os.path.join(TEST_DATA_DIR, 'lines.shp'))
self.assertEqual(len(res), 1)
self.assertEqual(res[0].layerNumber(), 0)
self.assertEqual(res[0].name(), "lines")
self.assertEqual(res[0].description(), '')
self.assertEqual(res[0].uri(), TEST_DATA_DIR + "/lines.shp")
self.assertEqual(res[0].providerKey(), "ogr")
self.assertEqual(res[0].type(), QgsMapLayerType.VectorLayer)

# make sure result is valid to load layer from
options = QgsProviderSublayerDetails.LayerOptions(QgsCoordinateTransformContext())
vl = res[0].toLayer(options)
self.assertTrue(vl.isValid())

# geopackage with two vector layers
res = metadata.querySublayers(os.path.join(TEST_DATA_DIR, "mixed_layers.gpkg"))
self.assertEqual(len(res), 2)
self.assertEqual(res[0].layerNumber(), 0)
self.assertEqual(res[0].name(), "points")
self.assertEqual(res[0].description(), "")
self.assertEqual(res[0].uri(), "{}/mixed_layers.gpkg|layername=points".format(TEST_DATA_DIR))
self.assertEqual(res[0].providerKey(), "ogr")
self.assertEqual(res[0].type(), QgsMapLayerType.VectorLayer)
self.assertEqual(res[0].featureCount(), Qgis.FeatureCountState.Uncounted)
self.assertEqual(res[0].wkbType(), QgsWkbTypes.Point)
vl = res[0].toLayer(options)
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.Point)

self.assertEqual(res[1].layerNumber(), 1)
self.assertEqual(res[1].name(), "lines")
self.assertEqual(res[1].description(), "")
self.assertEqual(res[1].uri(), "{}/mixed_layers.gpkg|layername=lines".format(TEST_DATA_DIR))
self.assertEqual(res[1].providerKey(), "ogr")
self.assertEqual(res[1].type(), QgsMapLayerType.VectorLayer)
self.assertEqual(res[1].featureCount(), Qgis.FeatureCountState.Uncounted)
self.assertEqual(res[1].wkbType(), QgsWkbTypes.MultiLineString)
vl = res[1].toLayer(options)
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.MultiLineString)

# request feature count
res = metadata.querySublayers(os.path.join(TEST_DATA_DIR, "mixed_layers.gpkg"), Qgis.SublayerQueryFlag.CountFeatures)
self.assertEqual(len(res), 2)
self.assertEqual(res[0].name(), "points")
self.assertEqual(res[0].featureCount(), 0)
self.assertEqual(res[1].name(), "lines")
self.assertEqual(res[1].featureCount(), 6)

# layer with mixed geometry types - without resolving geometry types
res = metadata.querySublayers(os.path.join(TEST_DATA_DIR, "mixed_types.TAB"))
self.assertEqual(len(res), 1)
self.assertEqual(res[0].layerNumber(), 0)
self.assertEqual(res[0].name(), "mixed_types")
self.assertEqual(res[0].description(), "")
self.assertEqual(res[0].uri(), "{}/mixed_types.TAB".format(TEST_DATA_DIR))
self.assertEqual(res[0].providerKey(), "ogr")
self.assertEqual(res[0].type(), QgsMapLayerType.VectorLayer)
self.assertEqual(res[0].featureCount(), Qgis.FeatureCountState.Uncounted)
self.assertEqual(res[0].wkbType(), QgsWkbTypes.Unknown)
vl = res[0].toLayer(options)
self.assertTrue(vl.isValid())

# layer with mixed geometry types - without resolving geometry types, but with feature count
res = metadata.querySublayers(os.path.join(TEST_DATA_DIR, "mixed_types.TAB"), Qgis.SublayerQueryFlag.CountFeatures)
self.assertEqual(len(res), 1)
self.assertEqual(res[0].layerNumber(), 0)
self.assertEqual(res[0].name(), "mixed_types")
self.assertEqual(res[0].description(), "")
self.assertEqual(res[0].uri(), "{}/mixed_types.TAB".format(TEST_DATA_DIR))
self.assertEqual(res[0].providerKey(), "ogr")
self.assertEqual(res[0].type(), QgsMapLayerType.VectorLayer)
self.assertEqual(res[0].featureCount(), 13)
self.assertEqual(res[0].wkbType(), QgsWkbTypes.Unknown)

# layer with mixed geometry types - resolve geometry type (for OGR provider this implies also that we count features!)
res = metadata.querySublayers(os.path.join(TEST_DATA_DIR, "mixed_types.TAB"), Qgis.SublayerQueryFlag.ResolveGeometryType)
self.assertEqual(len(res), 3)
self.assertEqual(res[0].layerNumber(), 0)
self.assertEqual(res[0].name(), "mixed_types")
self.assertEqual(res[0].description(), "")
self.assertEqual(res[0].uri(), "{}/mixed_types.TAB|geometrytype=Point".format(TEST_DATA_DIR))
self.assertEqual(res[0].providerKey(), "ogr")
self.assertEqual(res[0].type(), QgsMapLayerType.VectorLayer)
self.assertEqual(res[0].featureCount(), 4)
self.assertEqual(res[0].wkbType(), QgsWkbTypes.Point)
vl = res[0].toLayer(options)
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.Point)

self.assertEqual(res[1].layerNumber(), 0)
self.assertEqual(res[1].name(), "mixed_types")
self.assertEqual(res[1].description(), "")
self.assertEqual(res[1].uri(), "{}/mixed_types.TAB|geometrytype=LineString".format(TEST_DATA_DIR))
self.assertEqual(res[1].providerKey(), "ogr")
self.assertEqual(res[1].type(), QgsMapLayerType.VectorLayer)
self.assertEqual(res[1].featureCount(), 4)
self.assertEqual(res[1].wkbType(), QgsWkbTypes.LineString)
vl = res[1].toLayer(options)
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.LineString)

self.assertEqual(res[2].layerNumber(), 0)
self.assertEqual(res[2].name(), "mixed_types")
self.assertEqual(res[2].description(), "")
self.assertEqual(res[2].uri(), "{}/mixed_types.TAB|geometrytype=Polygon".format(TEST_DATA_DIR))
self.assertEqual(res[2].providerKey(), "ogr")
self.assertEqual(res[2].type(), QgsMapLayerType.VectorLayer)
self.assertEqual(res[2].featureCount(), 3)
self.assertEqual(res[2].wkbType(), QgsWkbTypes.Polygon)
vl = res[2].toLayer(options)
self.assertTrue(vl.isValid())
self.assertEqual(vl.wkbType(), QgsWkbTypes.Polygon)


if __name__ == '__main__':
unittest.main()
Binary file added tests/testdata/mixed_types.DAT
Binary file not shown.
Binary file added tests/testdata/mixed_types.ID
Binary file not shown.
Binary file added tests/testdata/mixed_types.MAP
Binary file not shown.
8 changes: 8 additions & 0 deletions tests/testdata/mixed_types.TAB
@@ -0,0 +1,8 @@
!table
!version 300
!charset WindowsLatin1

Definition Table
Type NATIVE Charset "WindowsLatin1"
Fields 1
Field1 Char (10) ;

0 comments on commit a5d7fe2

Please sign in to comment.