Skip to content

Commit

Permalink
[WFS provider] Handle the case where the layer schema has a ogc_fid f…
Browse files Browse the repository at this point in the history
…ield

Fixes #15062
  • Loading branch information
rouault committed Jun 22, 2016
1 parent 7c30985 commit d7414d7
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 23 deletions.
32 changes: 27 additions & 5 deletions src/providers/wfs/qgswfsshareddata.cpp
Expand Up @@ -240,15 +240,21 @@ bool QgsWFSSharedData::createCache()
cacheFields.append( QgsField( QgsWFSConstants::FIELD_MD5, QVariant::String, "string" ) );

bool ogrWaySuccessful = false;
QString fidName( "__ogc_fid" );
QString geometryFieldname( "__spatialite_geometry" );
#ifdef USE_OGR_FOR_DB_CREATION
// Only GDAL >= 2.0 can use an alternate geometry field name
// Only GDAL >= 2.0 can use an alternate geometry or FID field name
// but QgsVectorFileWriter will refuse anyway to create a ogc_fid, so we will
// do it manually
bool useReservedNames = cacheFields.fieldNameIndex( "ogc_fid" ) >= 0;
#if GDAL_VERSION_MAJOR < 2
const bool hasGeometryField = cacheFields.fieldNameIndex( "geometry" ) >= 0;
if ( !hasGeometryField )
if ( cacheFields.fieldNameIndex( "geometry" ) >= 0 )
useReservedNames = true;
#endif
if ( !useReservedNames )
{
#if GDAL_VERSION_MAJOR < 2
fidName = "ogc_fid";
geometryFieldname = "GEOMETRY";
#endif
// Creating a spatialite database can be quite slow on some file systems
Expand All @@ -260,6 +266,7 @@ bool QgsWFSSharedData::createCache()
datasourceOptions.push_back( "INIT_WITH_EPSG=NO" );
layerOptions.push_back( "LAUNDER=NO" ); // to get exact matches for field names, especially regarding case
#if GDAL_VERSION_MAJOR >= 2
layerOptions.push_back( "FID=__ogc_fid" );
layerOptions.push_back( "GEOMETRY_NAME=__spatialite_geometry" );
#endif
vsimemFilename.sprintf( "/vsimem/qgis_wfs_cache_template_%p/features.sqlite", this );
Expand Down Expand Up @@ -374,7 +381,7 @@ bool QgsWFSSharedData::createCache()
if ( !ogrWaySuccessful )
{
mCacheTablename = "features";
sql = QString( "CREATE TABLE %1 (ogc_fid INTEGER PRIMARY KEY" ).arg( mCacheTablename );
sql = QString( "CREATE TABLE %1 (%2 INTEGER PRIMARY KEY" ).arg( mCacheTablename ).arg( fidName );
Q_FOREACH ( QgsField field, cacheFields )
{
QString type( "VARCHAR" );
Expand All @@ -389,32 +396,47 @@ bool QgsWFSSharedData::createCache()
sql += ")";
rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, nullptr );
if ( rc != SQLITE_OK )
{
QgsDebugMsg( QString( "%1 failed" ).arg( sql ) );
ret = false;
}

sql = QString( "SELECT AddGeometryColumn('%1','%2',0,'POLYGON',2)" ).arg( mCacheTablename, geometryFieldname );
rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, nullptr );
if ( rc != SQLITE_OK )
{
QgsDebugMsg( QString( "%1 failed" ).arg( sql ) );
ret = false;
}

sql = QString( "SELECT CreateSpatialIndex('%1','%2')" ).arg( mCacheTablename, geometryFieldname );
rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, nullptr );
if ( rc != SQLITE_OK )
{
QgsDebugMsg( QString( "%1 failed" ).arg( sql ) );
ret = false;
}
}

// We need an index on the gmlid, since we will check for duplicates, particularly
// useful in the case we do overlapping BBOX requests
sql = QString( "CREATE INDEX idx_%2 ON %1(%2)" ).arg( mCacheTablename ).arg( QgsWFSConstants::FIELD_GMLID );
rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, nullptr );
if ( rc != SQLITE_OK )
{
QgsDebugMsg( QString( "%1 failed" ).arg( sql ) );
ret = false;
}

if ( mDistinctSelect )
{
sql = QString( "CREATE INDEX idx_%2 ON %1(%2)" ).arg( mCacheTablename ).arg( QgsWFSConstants::FIELD_MD5 );
rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, nullptr );
if ( rc != SQLITE_OK )
{
QgsDebugMsg( QString( "%1 failed" ).arg( sql ) );
ret = false;
}
}

( void )sqlite3_exec( db, "COMMIT", nullptr, nullptr, nullptr );
Expand All @@ -435,7 +457,7 @@ bool QgsWFSSharedData::createCache()
// regarding crashes, since this is a temporary DB
QgsDataSourceURI dsURI;
dsURI.setDatabase( mCacheDbname );
dsURI.setDataSource( "", mCacheTablename, geometryFieldname, "", "ogc_fid" );
dsURI.setDataSource( "", mCacheTablename, geometryFieldname, "", fidName );
QStringList pragmas;
pragmas << "synchronous=OFF";
pragmas << "journal_mode=WAL"; // WAL is needed to avoid reader to block writers
Expand Down
39 changes: 21 additions & 18 deletions tests/src/python/test_provider_wfs.py
Expand Up @@ -389,7 +389,8 @@ def testWFS10(self):
<xsd:element maxOccurs="1" minOccurs="0" name="longfield" nillable="true" type="xsd:long"/>
<xsd:element maxOccurs="1" minOccurs="0" name="stringfield" nillable="true" type="xsd:string"/>
<xsd:element maxOccurs="1" minOccurs="0" name="datetimefield" nillable="true" type="xsd:dateTime"/>
<xsd:element maxOccurs="1" minOccurs="0" name="geometryProperty" nillable="true" type="gml:PointPropertyType"/>
<!-- use geometry that is the default spatialite geometry name -->
<xsd:element maxOccurs="1" minOccurs="0" name="geometry" nillable="true" type="gml:PointPropertyType"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
Expand All @@ -416,8 +417,9 @@ def testWFS10(self):
<gml:boundedBy><gml:null>unknown</gml:null></gml:boundedBy>
<gml:featureMember>
<my:typename fid="typename.0">
<my:geometryProperty>
<gml:Point srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"><gml:coordinates decimal="." cs="," ts=" ">426858,5427937</gml:coordinates></gml:Point></my:geometryProperty>
<my:geometry>
<gml:Point srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"><gml:coordinates decimal="." cs="," ts=" ">426858,5427937</gml:coordinates></gml:Point>
</my:geometry>
<my:INTFIELD>1</my:INTFIELD>
<my:GEOMETRY>2</my:GEOMETRY>
<my:longfield>1234567890123</my:longfield>
Expand Down Expand Up @@ -878,7 +880,8 @@ def testWFSGetOnlyFeaturesInViewExtent(self):
<xsd:complexContent>
<xsd:extension base="gml:AbstractFeatureType">
<xsd:sequence>
<xsd:element maxOccurs="1" minOccurs="0" name="id" nillable="true" type="xsd:int"/>
<!-- use ogc_fid that is the default spatialite FID name -->
<xsd:element maxOccurs="1" minOccurs="0" name="ogc_fid" nillable="true" type="xsd:int"/>
<xsd:element maxOccurs="1" minOccurs="0" name="geometryProperty" nillable="true" type="gml:PointPropertyType"/>
</xsd:sequence>
</xsd:extension>
Expand All @@ -903,14 +906,14 @@ def testWFSGetOnlyFeaturesInViewExtent(self):
<gml:featureMembers>
<my:typename gml:id="typename.200">
<my:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326"><gml:pos>70 -65</gml:pos></gml:Point></my:geometryProperty>
<my:id>2</my:id>
<my:ogc_fid>2</my:ogc_fid>
</my:typename>
</gml:featureMembers>
</wfs:FeatureCollection>""".encode('UTF-8'))

extent = QgsRectangle(-70, 60, -60, 80)
request = QgsFeatureRequest().setFilterRect(extent)
values = [f['id'] for f in vl.getFeatures(request)]
values = [f['ogc_fid'] for f in vl.getFeatures(request)]
self.assertEqual(values, [2])

# To show that if we zoom-in, we won't issue a new request
Expand All @@ -923,14 +926,14 @@ def testWFSGetOnlyFeaturesInViewExtent(self):
<gml:featureMembers>
<my:typename gml:id="typename.20000">
<my:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326"><gml:pos>70 -65</gml:pos></gml:Point></my:geometryProperty>
<my:id>200</my:id>
<my:ogc_fid>200</my:ogc_fid>
</my:typename>
</gml:featureMembers>
</wfs:FeatureCollection>""".encode('UTF-8'))

extent = QgsRectangle(-66, 62, -62, 78)
request = QgsFeatureRequest().setFilterRect(extent)
values = [f['id'] for f in vl.getFeatures(request)]
values = [f['ogc_fid'] for f in vl.getFeatures(request)]
self.assertEqual(values, [2])

# Move to a neighbouring area, and reach the download limit
Expand All @@ -944,18 +947,18 @@ def testWFSGetOnlyFeaturesInViewExtent(self):
<gml:featureMembers>
<my:typename gml:id="typename.200">
<my:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326"><gml:pos>70 -65</gml:pos></gml:Point></my:geometryProperty>
<my:id>2</my:id>
<my:ogc_fid>2</my:ogc_fid>
</my:typename>
<my:typename gml:id="typename.300">
<my:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326"><gml:pos>85 -65</gml:pos></gml:Point></my:geometryProperty>
<my:id>3</my:id>
<my:ogc_fid>3</my:ogc_fid>
</my:typename>
</gml:featureMembers>
</wfs:FeatureCollection>""".encode('UTF-8'))

extent = QgsRectangle(-70, 65, -60, 90)
request = QgsFeatureRequest().setFilterRect(extent)
values = [f['id'] for f in vl.getFeatures(request)]
values = [f['ogc_fid'] for f in vl.getFeatures(request)]
self.assertEqual(values, [2, 3])

# Zoom-in again, and bring more features
Expand All @@ -969,18 +972,18 @@ def testWFSGetOnlyFeaturesInViewExtent(self):
<gml:featureMembers>
<my:typename gml:id="typename.200">
<my:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326"><gml:pos>70 -65</gml:pos></gml:Point></my:geometryProperty>
<my:id>2</my:id>
<my:ogc_fid>2</my:ogc_fid>
</my:typename>
<my:typename gml:id="typename.400">
<my:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326"><gml:pos>84 -64</gml:pos></gml:Point></my:geometryProperty>
<my:id>4</my:id>
<my:ogc_fid>4</my:ogc_fid>
</my:typename>
</gml:featureMembers>
</wfs:FeatureCollection>""".encode('UTF-8'))

extent = QgsRectangle(-69, 66, -61, 89)
request = QgsFeatureRequest().setFilterRect(extent)
values = [f['id'] for f in vl.getFeatures(request)]
values = [f['ogc_fid'] for f in vl.getFeatures(request)]
self.assertEqual(values, [2, 3, 4])

# Test RESULTTYPE=hits
Expand All @@ -1003,7 +1006,7 @@ def testWFSGetOnlyFeaturesInViewExtent(self):
</gml:Envelope>
</ogc:BBOX>
<ogc:PropertyIsEqualTo xmlns:ogc="http://www.opengis.net/ogc">
<ogc:PropertyName xmlns:ogc="http://www.opengis.net/ogc">id</ogc:PropertyName>
<ogc:PropertyName xmlns:ogc="http://www.opengis.net/ogc">ogc_fid</ogc:PropertyName>
<ogc:Literal xmlns:ogc="http://www.opengis.net/ogc">101</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:And>
Expand All @@ -1018,15 +1021,15 @@ def testWFSGetOnlyFeaturesInViewExtent(self):
<gml:featureMembers>
<my:typename gml:id="typename.101">
<my:geometryProperty><gml:Point srsName="urn:ogc:def:crs:EPSG::4326"><gml:pos>70 -65</gml:pos></gml:Point></my:geometryProperty>
<my:id>101</my:id>
<my:ogc_fid>101</my:ogc_fid>
</my:typename>
</gml:featureMembers>
</wfs:FeatureCollection>""".encode('UTF-8'))

vl.dataProvider().setSubsetString('id = 101')
vl.dataProvider().setSubsetString('ogc_fid = 101')
extent = QgsRectangle(-69, 66, -61, 89)
request = QgsFeatureRequest().setFilterRect(extent)
values = [f['id'] for f in vl.getFeatures(request)]
values = [f['ogc_fid'] for f in vl.getFeatures(request)]
self.assertEqual(values, [101])

def testWFS20TruncatedResponse(self):
Expand Down

0 comments on commit d7414d7

Please sign in to comment.