Skip to content
Permalink
Browse files

Merge pull request #8958 from elpaso/bugfix-20961-wfs-null-transactions

Fix NULL support in WFS server and client
  • Loading branch information
elpaso committed Jan 24, 2019
2 parents 25f2aba + 15a81bd commit 861a8b7105de2f89dec4187273593dca5778be57
Showing with 1,285 additions and 880 deletions.
  1. +1 −4 src/app/qgsprojectproperties.cpp
  2. +8 −3 src/core/qgsgml.cpp
  3. +20 −23 src/providers/wfs/qgswfsfeatureiterator.cpp
  4. +6 −1 src/server/services/wfs/qgswfsdescribefeaturetype.cpp
  5. +10 −0 src/server/services/wfs/qgswfsgetfeature.cpp
  6. +35 −11 src/server/services/wfs/qgswfstransaction.cpp
  7. +35 −11 src/server/services/wfs/qgswfstransaction_1_0_0.cpp
  8. +16 −6 tests/src/core/testqgsgml.cpp
  9. +156 −0 tests/src/python/test_provider_wfs.py
  10. +7 −1 tests/src/python/test_qgsserver.py
  11. +165 −44 tests/src/python/test_qgsserver_wfs.py
  12. BIN tests/testdata/qgis_server/test_project_wms_grouped_layers.gpkg
  13. +603 −555 tests/testdata/qgis_server/test_project_wms_grouped_layers.qgs
  14. +22 −22 tests/testdata/qgis_server/wfs_describeFeatureType_1_0_0_typename_as_areas_1_0_0.txt
  15. +62 −61 tests/testdata/qgis_server/wfs_describeFeatureType_1_0_0_typename_empty_1_0_0.txt
  16. +22 −22 tests/testdata/qgis_server/wfs_describeFeatureType_1_1_0_typename_as_areas.txt
  17. +62 −61 tests/testdata/qgis_server/wfs_describeFeatureType_1_1_0_typename_empty.txt
  18. +4 −4 tests/testdata/qgis_server/wfs_describefeaturetype.txt
  19. +4 −4 tests/testdata/qgis_server/wfs_describefeaturetype_1_0_0.txt
  20. +1 −1 tests/testdata/qgis_server/wfs_getFeature_1_0_0_EXP_FILTER_FID_one_1_0_0.txt
  21. +1 −1 tests/testdata/qgis_server/wfs_getFeature_1_0_0_EXP_FILTER_gml_bbox_one_1_0_0.txt
  22. +1 −1 tests/testdata/qgis_server/wfs_getFeature_1_0_0_EXP_FILTER_gml_bbox_three_1_0_0.txt
  23. +1 −1 tests/testdata/qgis_server/wfs_getFeature_1_0_0_EXP_FILTER_two_1_0_0.txt
  24. +1 −1 tests/testdata/qgis_server/wfs_getFeature_1_0_0_epsgbbox_1_feature_1_0_0.txt
  25. +2 −2 tests/testdata/qgis_server/wfs_getFeature_1_0_0_epsgbbox_1_feature_3857_1_0_0.txt
  26. +1 −1 tests/testdata/qgis_server/wfs_getFeature_1_0_0_epsgbbox_3_feature_1_0_0.txt
  27. +2 −2 tests/testdata/qgis_server/wfs_getFeature_1_0_0_epsgbbox_3_feature_3857_1_0_0.txt
  28. +1 −1 tests/testdata/qgis_server/wfs_getFeature_1_1_0_epsgbbox_1_feature.txt
  29. +2 −2 tests/testdata/qgis_server/wfs_getFeature_1_1_0_epsgbbox_1_feature_3857.txt
  30. +1 −1 tests/testdata/qgis_server/wfs_getFeature_1_1_0_epsgbbox_3_feature.txt
  31. +2 −2 tests/testdata/qgis_server/wfs_getFeature_1_1_0_epsgbbox_3_feature_3857.txt
  32. +9 −9 tests/testdata/qgis_server/wfs_getcapabilities.txt
  33. +7 −7 tests/testdata/qgis_server/wfs_getcapabilities_1_0_0.txt
  34. +1 −1 tests/testdata/qgis_server/wfs_getfeature_bbox_inside_and_post.txt
  35. +1 −1 tests/testdata/qgis_server/wfs_getfeature_hits.txt
  36. +1 −1 tests/testdata/qgis_server/wfs_getfeature_limit2.txt
  37. +1 −1 tests/testdata/qgis_server/wfs_getfeature_limit2_post.txt
  38. +1 −1 tests/testdata/qgis_server/wfs_getfeature_nobbox.txt
  39. +1 −1 tests/testdata/qgis_server/wfs_getfeature_nobbox_post.txt
  40. +1 −1 tests/testdata/qgis_server/wfs_getfeature_sortby.txt
  41. +1 −1 tests/testdata/qgis_server/wfs_getfeature_sortby_post.txt
  42. +1 −1 tests/testdata/qgis_server/wfs_getfeature_srs_two_layers_post.txt
  43. +1 −1 tests/testdata/qgis_server/wfs_getfeature_srsname.txt
  44. +1 −1 tests/testdata/qgis_server/wfs_getfeature_srsname_post.txt
  45. +1 −1 tests/testdata/qgis_server/wfs_getfeature_start1_limit1.txt
  46. +1 −1 tests/testdata/qgis_server/wfs_getfeature_start1_limit1_post.txt
  47. +1 −1 tests/testdata/qgis_server/wfs_getfeature_startindex2.txt
  48. +1 −1 tests/testdata/qgis_server/wfs_getfeature_startindex2_post.txt
@@ -1115,10 +1115,7 @@ void QgsProjectProperties::apply()

QgsProject::instance()->writeEntry( QStringLiteral( "WMSServiceCapabilities" ), QStringLiteral( "/" ), grpOWSServiceCapabilities->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSServiceTitle" ), QStringLiteral( "/" ), mWMSTitle->text() );

if ( !mWMSName->text().isEmpty() )
QgsProject::instance()->writeEntry( QStringLiteral( "WMSRootName" ), QStringLiteral( "/" ), mWMSName->text() );

QgsProject::instance()->writeEntry( QStringLiteral( "WMSRootName" ), QStringLiteral( "/" ), mWMSName->text() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSContactOrganization" ), QStringLiteral( "/" ), mWMSContactOrganization->text() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSContactPerson" ), QStringLiteral( "/" ), mWMSContactPerson->text() );
QgsProject::instance()->writeEntry( QStringLiteral( "WMSContactMail" ), QStringLiteral( "/" ), mWMSContactMail->text() );
@@ -1167,19 +1167,20 @@ void QgsGmlStreamingParser::setAttribute( const QString &name, const QString &va
{
//find index with attribute name
QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.constFind( name );
bool conversionOk = true;
if ( att_it != mThematicAttributes.constEnd() )
{
QVariant var;
switch ( att_it.value().second.type() )
{
case QVariant::Double:
var = QVariant( value.toDouble() );
var = QVariant( value.toDouble( &conversionOk ) );
break;
case QVariant::Int:
var = QVariant( value.toInt() );
var = QVariant( value.toInt( &conversionOk ) );
break;
case QVariant::LongLong:
var = QVariant( value.toLongLong() );
var = QVariant( value.toLongLong( &conversionOk ) );
break;
case QVariant::DateTime:
var = QVariant( QDateTime::fromString( value, Qt::ISODate ) );
@@ -1188,6 +1189,10 @@ void QgsGmlStreamingParser::setAttribute( const QString &name, const QString &va
var = QVariant( value );
break;
}
if ( ! conversionOk ) // Assume is NULL
{
var = QVariant();
}
Q_ASSERT( mCurrentFeature );
mCurrentFeature->setAttribute( att_it.value().first, var );
}
@@ -1423,38 +1423,35 @@ void QgsWFSFeatureIterator::copyFeature( const QgsFeature &srcFeature, QgsFeatur
QgsFields &fields = mShared->mFields;
dstFeature.initAttributes( fields.size() );

auto setAttr = [ & ]( const int i )
{
int idx = srcFeature.fields().indexFromName( fields.at( i ).name() );
if ( idx >= 0 )
{
const QVariant &v = srcFeature.attributes().value( idx );
if ( v.isNull() )
dstFeature.setAttribute( i, QVariant( fields.at( i ).type() ) );
else if ( v.type() == fields.at( i ).type() )
dstFeature.setAttribute( i, v );
else if ( fields.at( i ).type() == QVariant::DateTime && !v.isNull() )
dstFeature.setAttribute( i, QVariant( QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ) );
else
dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fields.at( i ).type(), v.toString() ) );
}
};

if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
{
Q_FOREACH ( int i, mSubSetAttributes )
for ( auto i : qgis::as_const( mSubSetAttributes ) )
{
int idx = srcFeature.fields().indexFromName( fields.at( i ).name() );
if ( idx >= 0 )
{
const QVariant &v = srcFeature.attributes().value( idx );
if ( v.type() == fields.at( i ).type() )
dstFeature.setAttribute( i, v );
else if ( fields.at( i ).type() == QVariant::DateTime && !v.isNull() )
dstFeature.setAttribute( i, QVariant( QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ) );
else
dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fields.at( i ).type(), v.toString() ) );
}
setAttr( i );
}
}
else
{
for ( int i = 0; i < fields.size(); i++ )
{
int idx = srcFeature.fields().indexFromName( fields.at( i ).name() );
if ( idx >= 0 )
{
const QVariant &v = srcFeature.attributes().value( idx );
if ( v.type() == fields.at( i ).type() )
dstFeature.setAttribute( i, v );
else if ( fields.at( i ).type() == QVariant::DateTime && !v.isNull() )
dstFeature.setAttribute( i, QVariant( QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ) );
else
dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fields.at( i ).type(), v.toString() ) );
}
setAttr( i );
}
}

@@ -79,7 +79,7 @@ namespace QgsWfs
// test oFormat
if ( oFormat == QgsWfsParameters::Format::NONE )
throw QgsBadRequestException( QStringLiteral( "Invalid WFS Parameter" ),
"OUTPUTFORMAT " + wfsParameters.outputFormatAsString() + "is not supported" );
QStringLiteral( "OUTPUTFORMAT %1 is not supported" ).arg( wfsParameters.outputFormatAsString() ) );

QgsAccessControl *accessControl = serverIface->accessControls();

@@ -346,6 +346,11 @@ namespace QgsWfs
}
}

if ( !( field.constraints().constraints() & QgsFieldConstraints::Constraint::ConstraintNotNull ) )
{
attElem.setAttribute( QStringLiteral( "nillable" ), QStringLiteral( "true" ) );
}

sequenceElem.appendChild( attElem );

QString alias = field.alias();
@@ -1340,6 +1340,10 @@ namespace QgsWfs

QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() ) );
QDomText fieldText = doc.createTextNode( encodeValueToText( featureAttributes[idx], setup ) );
if ( featureAttributes[idx].isNull() )
{
fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
}
fieldElem.appendChild( fieldText );
typeNameElement.appendChild( fieldElem );
}
@@ -1431,12 +1435,18 @@ namespace QgsWfs
{
continue;
}

const QgsField field = fields.at( idx );
const QgsEditorWidgetSetup setup = field.editorWidgetSetup();

QString attributeName = field.name();

QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() ) );
QDomText fieldText = doc.createTextNode( encodeValueToText( featureAttributes[idx], setup ) );
if ( featureAttributes[idx].isNull() )
{
fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
}
fieldElem.appendChild( fieldText );
typeNameElement.appendChild( fieldElem );
}
@@ -407,26 +407,50 @@ namespace QgsWfs
}
QgsField field = fields.at( fieldMapIt.value() );
QVariant value = it.value();
if ( field.type() == 2 )
if ( value.isNull() )
{
value = it.value().toInt( &conversionSuccess );
if ( !conversionSuccess )
if ( field.constraints().constraints() & QgsFieldConstraints::Constraint::ConstraintNotNull )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
action.errorMsg = QStringLiteral( "NOT NULL constraint error on layer '%1', field '%2'" ).arg( typeName, field.name() );
vlayer->rollBack();
break;
}
}
else if ( field.type() == 6 )
else // Not NULL
{
value = it.value().toDouble( &conversionSuccess );
if ( !conversionSuccess )
if ( field.type() == QVariant::Type::Int )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
vlayer->rollBack();
break;
value = it.value().toInt( &conversionSuccess );
if ( !conversionSuccess )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
vlayer->rollBack();
break;
}
}
else if ( field.type() == QVariant::Type::Double )
{
value = it.value().toDouble( &conversionSuccess );
if ( !conversionSuccess )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
vlayer->rollBack();
break;
}
}
else if ( field.type() == QVariant::Type::LongLong )
{
value = it.value().toLongLong( &conversionSuccess );
if ( !conversionSuccess )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
vlayer->rollBack();
break;
}
}
}
vlayer->changeAttributeValue( feature.id(), fieldMapIt.value(), value );
@@ -388,26 +388,50 @@ namespace QgsWfs
}
QgsField field = fields.at( fieldMapIt.value() );
QVariant value = it.value();
if ( field.type() == 2 )
if ( value.isNull() )
{
value = it.value().toInt( &conversionSuccess );
if ( !conversionSuccess )
if ( field.constraints().constraints() & QgsFieldConstraints::Constraint::ConstraintNotNull )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
action.errorMsg = QStringLiteral( "NOT NULL constraint error on layer '%1', field '%2'" ).arg( typeName, field.name() );
vlayer->rollBack();
break;
}
}
else if ( field.type() == 6 )
else // Not NULL
{
value = it.value().toDouble( &conversionSuccess );
if ( !conversionSuccess )
if ( field.type() == QVariant::Type::Int )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
vlayer->rollBack();
break;
value = it.value().toInt( &conversionSuccess );
if ( !conversionSuccess )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
vlayer->rollBack();
break;
}
}
else if ( field.type() == QVariant::Type::Double )
{
value = it.value().toDouble( &conversionSuccess );
if ( !conversionSuccess )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
vlayer->rollBack();
break;
}
}
else if ( field.type() == QVariant::Type::LongLong )
{
value = it.value().toLongLong( &conversionSuccess );
if ( !conversionSuccess )
{
action.error = true;
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
vlayer->rollBack();
break;
}
}
}
vlayer->changeAttributeValue( feature.id(), fieldMapIt.value(), value );
@@ -83,11 +83,13 @@ class TestQgsGML : public QObject

const QString data1( "<myns:FeatureCollection "
"xmlns:myns='http://myns' "
"xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "
"xmlns:gml='http://www.opengis.net/gml'>"
"<gml:boundedBy><gml:null>unknown</gml:null></gml:boundedBy>"
"<gml:featureMember>"
"<myns:mytypename fid='mytypename.1'>"
"<myns:intfield>1</myns:intfield>"
"<myns:nillablefield xsi:nil='true'/>"
"<myns:longfield>1234567890123</myns:longfield>"
"<myns:doublefield>1.23</myns:doublefield>"
"<myns:strfield>foo</myns:strfield>"
@@ -105,6 +107,7 @@ void TestQgsGML::testFromURL()
{
QgsFields fields;
fields.append( QgsField( QStringLiteral( "intfield" ), QVariant::Int, QStringLiteral( "int" ) ) );
fields.append( QgsField( QStringLiteral( "nillablefield" ), QVariant::Int, QStringLiteral( "nillablefield" ) ) );
QgsGml gmlParser( QStringLiteral( "mytypename" ), QStringLiteral( "mygeom" ), fields );
QgsWkbTypes::Type wkbType;
QTemporaryFile tmpFile;
@@ -117,30 +120,36 @@ void TestQgsGML::testFromURL()
QCOMPARE( featureMaps.size(), 1 );
QCOMPARE( gmlParser.idsMap().size(), 1 );
QCOMPARE( gmlParser.crs().authid(), QString( "EPSG:27700" ) );
QCOMPARE( featureMaps[0]->attribute( QStringLiteral( "intfield" ) ).toInt(), 1 );
QVERIFY( featureMaps[0]->attribute( QStringLiteral( "nillablefield" ) ).isNull( ) );
delete featureMaps[ 0 ];
}

void TestQgsGML::testFromByteArray()
{
QgsFields fields;
fields.append( QgsField( QStringLiteral( "intfield" ), QVariant::Int, QStringLiteral( "int" ) ) );
fields.append( QgsField( QStringLiteral( "nillablefield" ), QVariant::Int, QStringLiteral( "nillablefield" ) ) );
QgsGml gmlParser( QStringLiteral( "mytypename" ), QStringLiteral( "mygeom" ), fields );
QgsWkbTypes::Type wkbType;
QCOMPARE( gmlParser.getFeatures( data1.toAscii(), &wkbType ), 0 );
QMap<QgsFeatureId, QgsFeature * > featureMaps = gmlParser.featuresMap();
QCOMPARE( featureMaps.size(), 1 );
QVERIFY( featureMaps.constFind( 0 ) != featureMaps.constEnd() );
QCOMPARE( featureMaps[ 0 ]->attributes().size(), 1 );
QCOMPARE( featureMaps[ 0 ]->attributes().size(), 2 );
QMap<QgsFeatureId, QString > idsMap = gmlParser.idsMap();
QVERIFY( idsMap.constFind( 0 ) != idsMap.constEnd() );
QCOMPARE( idsMap[ 0 ], QString( "mytypename.1" ) );
QCOMPARE( featureMaps[0]->attribute( QStringLiteral( "intfield" ) ).toInt(), 1 );
QVERIFY( featureMaps[0]->attribute( QStringLiteral( "nillablefield" ) ).isNull( ) );
delete featureMaps[ 0 ];
}

void TestQgsGML::testStreamingParser()
{
QgsFields fields;
fields.append( QgsField( QStringLiteral( "intfield" ), QVariant::Int, QStringLiteral( "int" ) ) );
fields.append( QgsField( QStringLiteral( "nillablefield" ), QVariant::Int, QStringLiteral( "nillablefield" ) ) );
fields.append( QgsField( QStringLiteral( "longfield" ), QVariant::LongLong, QStringLiteral( "longlong" ) ) );
fields.append( QgsField( QStringLiteral( "doublefield" ), QVariant::Double, QStringLiteral( "double" ) ) );
fields.append( QgsField( QStringLiteral( "strfield" ), QVariant::String, QStringLiteral( "string" ) ) );
@@ -152,12 +161,13 @@ void TestQgsGML::testStreamingParser()
QCOMPARE( gmlParser.isException(), false );
QVector<QgsGmlStreamingParser::QgsGmlFeaturePtrGmlIdPair> features = gmlParser.getAndStealReadyFeatures();
QCOMPARE( features.size(), 1 );
QCOMPARE( features[0].first->attributes().size(), 5 );
QCOMPARE( features[0].first->attributes().size(), 6 );
QCOMPARE( features[0].first->attributes().at( 0 ), QVariant( 1 ) );
QCOMPARE( features[0].first->attributes().at( 1 ), QVariant( Q_INT64_C( 1234567890123 ) ) );
QCOMPARE( features[0].first->attributes().at( 2 ), QVariant( 1.23 ) );
QCOMPARE( features[0].first->attributes().at( 3 ), QVariant( "foo" ) );
QCOMPARE( features[0].first->attributes().at( 4 ), QVariant( QDateTime( QDate( 2016, 4, 10 ), QTime( 12, 34, 56, 789 ), Qt::UTC ) ) );
QCOMPARE( features[0].first->attributes().at( 1 ), QVariant( ) );
QCOMPARE( features[0].first->attributes().at( 2 ), QVariant( Q_INT64_C( 1234567890123 ) ) );
QCOMPARE( features[0].first->attributes().at( 3 ), QVariant( 1.23 ) );
QCOMPARE( features[0].first->attributes().at( 4 ), QVariant( "foo" ) );
QCOMPARE( features[0].first->attributes().at( 5 ), QVariant( QDateTime( QDate( 2016, 4, 10 ), QTime( 12, 34, 56, 789 ), Qt::UTC ) ) );
QVERIFY( features[0].first->hasGeometry() );
QCOMPARE( features[0].first->geometry().wkbType(), QgsWkbTypes::Point );
QCOMPARE( features[0].first->geometry().asPoint(), QgsPointXY( 10, 20 ) );

0 comments on commit 861a8b7

Please sign in to comment.
You can’t perform that action at this time.