From ce1e74e5fcdba6302eb1a7af1250dcd23779528e Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Wed, 20 Jan 2021 14:24:22 +0100 Subject: [PATCH 1/3] Partial fix for ESRI server typename(s) bug --- .../ogr/qgsgeopackageproviderconnection.cpp | 14 ++- .../wfs/qgswfsdescribefeaturetype.cpp | 10 +- src/providers/wfs/qgswfsfeatureiterator.cpp | 7 +- tests/src/python/test_provider_wfs.py | 109 +++++++++--------- .../bug_gh30264_empty_layer_wrong_bbox.gpkg | Bin 98304 -> 98304 bytes 5 files changed, 81 insertions(+), 59 deletions(-) diff --git a/src/core/providers/ogr/qgsgeopackageproviderconnection.cpp b/src/core/providers/ogr/qgsgeopackageproviderconnection.cpp index 77891e155305..988d5ea8d243 100644 --- a/src/core/providers/ogr/qgsgeopackageproviderconnection.cpp +++ b/src/core/providers/ogr/qgsgeopackageproviderconnection.cpp @@ -238,35 +238,43 @@ void QgsGeoPackageProviderConnection::deleteSpatialIndex( const QString &schema, QList QgsGeoPackageProviderConnection::tables( const QString &schema, const TableFlags &flags ) const { -// List of GPKG quoted system and dummy tables names to be excluded from the tables listing + + // List of GPKG quoted system and dummy tables names to be excluded from the tables listing static const QStringList excludedTableNames { { QStringLiteral( "\"ogr_empty_table\"" ) } }; checkCapability( Capability::Tables ); + if ( ! schema.isEmpty() ) { QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by GPKG, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info ); } + QList tableInfo; QString errCause; QList results; + try { const QString sql { QStringLiteral( "SELECT c.table_name, data_type, description, c.srs_id, g.geometry_type_name, g.column_name " "FROM gpkg_contents c LEFT JOIN gpkg_geometry_columns g ON (c.table_name = g.table_name) " "WHERE c.table_name NOT IN (%1)" ).arg( excludedTableNames.join( ',' ) ) }; results = executeSql( sql ); + for ( const auto &row : qgis::as_const( results ) ) { + if ( row.size() != 6 ) { throw QgsProviderConnectionException( QObject::tr( "Error listing tables from %1: wrong number of columns returned by query" ).arg( uri() ) ); } + QgsGeoPackageProviderConnection::TableProperty property; property.setTableName( row.at( 0 ).toString() ); property.setPrimaryKeyColumns( { QStringLiteral( "fid" ) } ); property.setGeometryColumnCount( 0 ); static const QStringList aspatialTypes = { QStringLiteral( "attributes" ), QStringLiteral( "aspatial" ) }; const QString dataType = row.at( 1 ).toString(); + // Table type if ( dataType == QLatin1String( "tiles" ) || dataType == QLatin1String( "2d-gridded-coverage" ) ) { @@ -278,6 +286,7 @@ QList QgsGeoPackageProviderConne property.setGeometryColumn( row.at( 5 ).toString() ); property.setGeometryColumnCount( 1 ); } + if ( aspatialTypes.contains( dataType ) ) { property.setFlag( QgsGeoPackageProviderConnection::Aspatial ); @@ -287,13 +296,16 @@ QList QgsGeoPackageProviderConne { bool ok; int srid = row.at( 3 ).toInt( &ok ); + if ( !ok ) { throw QgsProviderConnectionException( QObject::tr( "Error fetching srs_id table information: %1" ).arg( row.at( 3 ).toString() ) ); } + QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromEpsgId( srid ); property.addGeometryColumnType( QgsWkbTypes::parseType( row.at( 4 ).toString() ), crs ); } + property.setComment( row.at( 4 ).toString() ); tableInfo.push_back( property ); } diff --git a/src/providers/wfs/qgswfsdescribefeaturetype.cpp b/src/providers/wfs/qgswfsdescribefeaturetype.cpp index b799ad4c451e..967423d09549 100644 --- a/src/providers/wfs/qgswfsdescribefeaturetype.cpp +++ b/src/providers/wfs/qgswfsdescribefeaturetype.cpp @@ -39,11 +39,13 @@ bool QgsWFSDescribeFeatureType::requestFeatureType( const QString &WFSVersion, query.addQueryItem( QStringLiteral( "NAMESPACES" ), namespaceValue ); } } - - query.addQueryItem( QStringLiteral( "TYPENAME" ), typeName ); - if ( !namespaceValue.isEmpty() ) + else { - query.addQueryItem( QStringLiteral( "NAMESPACE" ), namespaceValue ); + query.addQueryItem( QStringLiteral( "TYPENAME" ), typeName ); + if ( !namespaceValue.isEmpty() ) + { + query.addQueryItem( QStringLiteral( "NAMESPACE" ), namespaceValue ); + } } url.setQuery( query ); diff --git a/src/providers/wfs/qgswfsfeatureiterator.cpp b/src/providers/wfs/qgswfsfeatureiterator.cpp index a46ee33c4dbc..cd54f5ac6aef 100644 --- a/src/providers/wfs/qgswfsfeatureiterator.cpp +++ b/src/providers/wfs/qgswfsfeatureiterator.cpp @@ -129,8 +129,13 @@ QUrl QgsWFSFeatureDownloaderImpl::buildURL( qint64 startIndex, int maxFeatures, } } if ( mShared->mWFSVersion.startsWith( QLatin1String( "2.0" ) ) ) + { query.addQueryItem( QStringLiteral( "TYPENAMES" ), typenames ); - query.addQueryItem( QStringLiteral( "TYPENAME" ), typenames ); + } + else + { + query.addQueryItem( QStringLiteral( "TYPENAME" ), typenames ); + } if ( forHits ) { diff --git a/tests/src/python/test_provider_wfs.py b/tests/src/python/test_provider_wfs.py index fc62710f81ea..1892df1bf9cd 100644 --- a/tests/src/python/test_provider_wfs.py +++ b/tests/src/python/test_provider_wfs.py @@ -124,7 +124,7 @@ def setUpClass(cls): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -160,7 +160,7 @@ def setUpClass(cls): cls.source = cls.vl.dataProvider() with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&RESULTTYPE=hits'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&RESULTTYPE=hits'), 'wb') as f: f.write(""" """.encode('UTF-8')) - with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&FILTER= + with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&FILTER= cnt @@ -265,7 +265,7 @@ def setUpClass(cls): numberMatched="3" numberReturned="0" timeStamp="2016-03-25T14:51:48.998Z"> """.encode('UTF-8')) - with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= + with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= cnt @@ -317,7 +317,7 @@ def setUpClass(cls): """.encode('UTF-8')) - with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&FILTER= + with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&FILTER= cnt @@ -337,7 +337,7 @@ def setUpClass(cls): numberMatched="2" numberReturned="0" timeStamp="2016-03-25T14:51:48.998Z"> """.encode('UTF-8')) - with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= + with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= cnt @@ -378,7 +378,7 @@ def setUpClass(cls): """.encode('UTF-8')) - with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&FILTER= + with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&FILTER= name Apple @@ -392,7 +392,7 @@ def setUpClass(cls): numberMatched="1" numberReturned="0" timeStamp="2016-03-25T14:51:48.998Z"> """.encode('UTF-8')) - with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= + with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= name Apple @@ -418,7 +418,7 @@ def setUpClass(cls): """.encode('UTF-8')) - with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&FILTER= + with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&FILTER= name AppleBearOrangePear @@ -432,7 +432,7 @@ def setUpClass(cls): numberMatched="0" numberReturned="0" timeStamp="2016-03-25T14:51:48.998Z"> """.encode('UTF-8')) - with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= + with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= name AppleBearOrangePear @@ -463,6 +463,9 @@ def testWkbType(self): """N/A for WFS provider""" pass + +class TestPyQgsWFSProvider(): + def testInconsistentUri(self): """Test a URI with a typename that doesn't match a type of the capabilities""" @@ -1126,7 +1129,7 @@ def testWFS20Paging(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -1146,7 +1149,7 @@ def testWFS20Paging(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&STARTINDEX=0&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&STARTINDEX=2&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=2&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -1290,7 +1293,7 @@ def testWFS20PagingPageSizeOverride(self): self.assertEqual(vl.wkbType(), QgsWkbTypes.Point) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&STARTINDEX=0&COUNT=2&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=2&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&STARTINDEX=2&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=2&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(""" server pagesize vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' pageSize='100'", 'test', 'WFS') @@ -1367,7 +1370,7 @@ def testWFS20PagingPageSizeOverride(self): self.assertEqual(vl.wkbType(), QgsWkbTypes.Point) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&STARTINDEX=0&COUNT=10&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=10&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -1777,7 +1780,7 @@ def testWFS20TruncatedResponse(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -2520,7 +2523,7 @@ def testSelectDistinct(self): """.encode('UTF-8')) with open(sanitize(endpoint, - """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326"""), + """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326"""), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -2643,7 +2646,7 @@ def testWrongCapabilityExtent(self): """.encode('UTF-8')) with open(sanitize(endpoint, - """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326"""), + """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326"""), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -2762,7 +2765,7 @@ def testGeomedia(self): """.encode('UTF-8')) with open(sanitize(endpoint, - """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&STARTINDEX=0&COUNT=1&SRSNAME=EPSG:32631"""), + """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=1&SRSNAME=EPSG:32631"""), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=EPSG:32631"""), + """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=EPSG:32631"""), 'wb') as f: f.write(""" """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -3140,7 +3143,7 @@ def testWFS20TransactionsEnabled(self): """.format(endpoint=endpoint).encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -3285,7 +3288,7 @@ def testGetFeatureWithNamespaces(self): self.assertEqual(len(vl.fields()), 1) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::32631&NAMESPACES=xmlns(my,http://my)&NAMESPACE=xmlns(my,http://my)'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::32631&NAMESPACES=xmlns(my,http://my)&NAMESPACE=xmlns(my,http://my)'), 'wb') as f: f.write(""" """.encode('UTF-8')) with open( - sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=EC422&TYPENAME=EC422'), + sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=EC422'), 'wb') as f: f.write("""""" with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=EC422&TYPENAME=EC422&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=EC422&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(feature_content.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=EC422&TYPENAME=EC422&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=EC422&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(feature_content.encode('UTF-8')) @@ -3827,7 +3830,7 @@ def testDescribeFeatureTypeWithSingleInclude(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write((""" @@ -4416,7 +4419,7 @@ def testRetryLogicOnExceptionLackOfPrimaryKey(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), 'wb') as f: f.write(""" @@ -4441,7 +4444,7 @@ def testRetryLogicOnExceptionLackOfPrimaryKey(self): # Initial request: exception with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&STARTINDEX=0&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&STARTINDEX=0&COUNT=1&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f: f.write(""" @@ -4453,7 +4456,7 @@ def testRetryLogicOnExceptionLackOfPrimaryKey(self): # Retry with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&RETRY=1'), + '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326&RETRY=1'), 'wb') as f: f.write(""" Date: Mon, 25 Jan 2021 18:08:00 +0100 Subject: [PATCH 2/3] Use TYPENAMES for WFS 2 getFeature Leave both forms for describeFeatureType, fix tests --- .../wfs/qgswfsdescribefeaturetype.cpp | 12 +++--- src/providers/wfs/qgswfsshareddata.cpp | 18 +++++++-- tests/src/python/test_provider_wfs.py | 38 +++++++++--------- .../bug_gh30264_empty_layer_wrong_bbox.gpkg | Bin 98304 -> 98304 bytes 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/providers/wfs/qgswfsdescribefeaturetype.cpp b/src/providers/wfs/qgswfsdescribefeaturetype.cpp index 967423d09549..df7d874689a6 100644 --- a/src/providers/wfs/qgswfsdescribefeaturetype.cpp +++ b/src/providers/wfs/qgswfsdescribefeaturetype.cpp @@ -39,13 +39,13 @@ bool QgsWFSDescribeFeatureType::requestFeatureType( const QString &WFSVersion, query.addQueryItem( QStringLiteral( "NAMESPACES" ), namespaceValue ); } } - else + + // Always add singular form for broken servers (ESRI) + // See: https://github.com/qgis/QGIS/issues/41087 + query.addQueryItem( QStringLiteral( "TYPENAME" ), typeName ); + if ( !namespaceValue.isEmpty() ) { - query.addQueryItem( QStringLiteral( "TYPENAME" ), typeName ); - if ( !namespaceValue.isEmpty() ) - { - query.addQueryItem( QStringLiteral( "NAMESPACE" ), namespaceValue ); - } + query.addQueryItem( QStringLiteral( "NAMESPACE" ), namespaceValue ); } url.setQuery( query ); diff --git a/src/providers/wfs/qgswfsshareddata.cpp b/src/providers/wfs/qgswfsshareddata.cpp index 5fbbcc556c69..99302ff35765 100644 --- a/src/providers/wfs/qgswfsshareddata.cpp +++ b/src/providers/wfs/qgswfsshareddata.cpp @@ -257,14 +257,22 @@ int QgsWFSFeatureHitsRequest::getFeatureCount( const QString &WFSVersion, { query.addQueryItem( QStringLiteral( "TYPENAMES" ), typeName ); } - query.addQueryItem( QStringLiteral( "TYPENAME" ), typeName ); + else + { + query.addQueryItem( QStringLiteral( "TYPENAME" ), typeName ); + } QString namespaceValue( caps.getNamespaceParameterValue( WFSVersion, typeName ) ); if ( !namespaceValue.isEmpty() ) { if ( WFSVersion.startsWith( QLatin1String( "2.0" ) ) ) + { query.addQueryItem( QStringLiteral( "NAMESPACES" ), namespaceValue ); - query.addQueryItem( QStringLiteral( "NAMESPACE" ), namespaceValue ); + } + else + { + query.addQueryItem( QStringLiteral( "NAMESPACE" ), namespaceValue ); + } } if ( !filter.isEmpty() ) @@ -327,14 +335,16 @@ QgsRectangle QgsWFSSingleFeatureRequest::getExtent() query.addQueryItem( QStringLiteral( "VERSION" ), mShared->mWFSVersion ); if ( mShared->mWFSVersion .startsWith( QLatin1String( "2.0" ) ) ) query.addQueryItem( QStringLiteral( "TYPENAMES" ), mUri.typeName() ); - query.addQueryItem( QStringLiteral( "TYPENAME" ), mUri.typeName() ); + else + query.addQueryItem( QStringLiteral( "TYPENAME" ), mUri.typeName() ); QString namespaceValue( mShared->mCaps.getNamespaceParameterValue( mShared->mWFSVersion, mUri.typeName() ) ); if ( !namespaceValue.isEmpty() ) { if ( mShared->mWFSVersion.startsWith( QLatin1String( "2.0" ) ) ) query.addQueryItem( QStringLiteral( "NAMESPACES" ), namespaceValue ); - query.addQueryItem( QStringLiteral( "NAMESPACE" ), namespaceValue ); + else + query.addQueryItem( QStringLiteral( "NAMESPACE" ), namespaceValue ); } if ( mShared->mWFSVersion .startsWith( QLatin1String( "2.0" ) ) ) diff --git a/tests/src/python/test_provider_wfs.py b/tests/src/python/test_provider_wfs.py index 1892df1bf9cd..0b011a392335 100644 --- a/tests/src/python/test_provider_wfs.py +++ b/tests/src/python/test_provider_wfs.py @@ -124,7 +124,7 @@ def setUpClass(cls): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -463,9 +463,6 @@ def testWkbType(self): """N/A for WFS provider""" pass - -class TestPyQgsWFSProvider(): - def testInconsistentUri(self): """Test a URI with a typename that doesn't match a type of the capabilities""" @@ -1129,7 +1126,7 @@ def testWFS20Paging(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -1267,7 +1264,7 @@ def testWFS20PagingPageSizeOverride(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -1761,7 +1758,7 @@ def testWFS20TruncatedResponse(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -2069,7 +2066,7 @@ def testJoins(self): 'wb') as f: f.write(schema.encode('UTF-8')) - with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename,my:othertypename&TYPENAME=my:typename,my:othertypename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= + with open(sanitize(endpoint, """?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename,my:othertypename&SRSNAME=urn:ogc:def:crs:EPSG::4326&FILTER= my:typename/id @@ -2195,7 +2192,7 @@ def testJoins(self): self.assertEqual(fields[0].name(), 'id') with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(schema.encode('UTF-8')) @@ -2414,7 +2411,7 @@ def testFunctionValidation(self): 'wb') as f: f.write(schema.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(schema.encode('UTF-8')) @@ -2501,7 +2498,7 @@ def testSelectDistinct(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -2626,7 +2623,7 @@ def testWrongCapabilityExtent(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -2745,7 +2742,7 @@ def testGeomedia(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -3020,6 +3017,11 @@ def testDescribeFeatureTypeWithInlineType(self): """.encode('UTF-8')) + shutil.copyfile(sanitize(endpoint, + """?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.1.0&TYPENAME=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326"""), + sanitize(endpoint, + """?SERVICE=WFS&REQUEST=GetFeature&VERSION=1.1.0&TYPENAME=my:typename&MAXFEATURES=1&SRSNAME=urn:ogc:def:crs:EPSG::4326""")) + vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename' version='1.1.0'", 'test', 'WFS') self.assertTrue(vl.isValid()) @@ -3063,7 +3065,7 @@ def testWFS20TransactionsDisabled(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -3143,7 +3145,7 @@ def testWFS20TransactionsEnabled(self): """.format(endpoint=endpoint).encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" @@ -3615,7 +3617,7 @@ def testWfs20SamServer(self): """.encode('UTF-8')) with open( - sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=EC422'), + sanitize(endpoint, '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=EC422&TYPENAME=EC422'), 'wb') as f: f.write("""""".encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write((""" @@ -4419,7 +4421,7 @@ def testRetryLogicOnExceptionLackOfPrimaryKey(self): """.encode('UTF-8')) with open(sanitize(endpoint, - '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename'), + '?SERVICE=WFS&REQUEST=DescribeFeatureType&VERSION=2.0.0&TYPENAMES=my:typename&TYPENAME=my:typename'), 'wb') as f: f.write(""" diff --git a/tests/testdata/qgis_server/bug_gh30264_empty_layer_wrong_bbox.gpkg b/tests/testdata/qgis_server/bug_gh30264_empty_layer_wrong_bbox.gpkg index 39e0c0bd8dd752a5685565ecc5d6105eddfaaaca..ad4ab8c613a6eb20dc556ecf9382eb0a18b2998b 100644 GIT binary patch delta 26 hcmZo@U~6b#n;^}oI8nx#RgpnYW Date: Tue, 26 Jan 2021 08:58:40 +0100 Subject: [PATCH 3/3] Update src/providers/wfs/qgswfsdescribefeaturetype.cpp --- src/providers/wfs/qgswfsdescribefeaturetype.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/wfs/qgswfsdescribefeaturetype.cpp b/src/providers/wfs/qgswfsdescribefeaturetype.cpp index df7d874689a6..f5df5d53f574 100644 --- a/src/providers/wfs/qgswfsdescribefeaturetype.cpp +++ b/src/providers/wfs/qgswfsdescribefeaturetype.cpp @@ -40,7 +40,7 @@ bool QgsWFSDescribeFeatureType::requestFeatureType( const QString &WFSVersion, } } - // Always add singular form for broken servers (ESRI) + // Always add singular form for broken servers // See: https://github.com/qgis/QGIS/issues/41087 query.addQueryItem( QStringLiteral( "TYPENAME" ), typeName ); if ( !namespaceValue.isEmpty() )