Skip to content

Commit 50be6e8

Browse files
elpasonyalldawson
authored andcommitted
Merge pull request #8958 from elpaso/bugfix-20961-wfs-null-transactions
Fix NULL support in WFS server and client Cherry-picked from master 861a8b7
1 parent ac1b492 commit 50be6e8

48 files changed

Lines changed: 1285 additions & 880 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎src/app/qgsprojectproperties.cpp‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,10 +1074,7 @@ void QgsProjectProperties::apply()
10741074

10751075
QgsProject::instance()->writeEntry( QStringLiteral( "WMSServiceCapabilities" ), QStringLiteral( "/" ), grpOWSServiceCapabilities->isChecked() );
10761076
QgsProject::instance()->writeEntry( QStringLiteral( "WMSServiceTitle" ), QStringLiteral( "/" ), mWMSTitle->text() );
1077-
1078-
if ( !mWMSName->text().isEmpty() )
1079-
QgsProject::instance()->writeEntry( QStringLiteral( "WMSRootName" ), QStringLiteral( "/" ), mWMSName->text() );
1080-
1077+
QgsProject::instance()->writeEntry( QStringLiteral( "WMSRootName" ), QStringLiteral( "/" ), mWMSName->text() );
10811078
QgsProject::instance()->writeEntry( QStringLiteral( "WMSContactOrganization" ), QStringLiteral( "/" ), mWMSContactOrganization->text() );
10821079
QgsProject::instance()->writeEntry( QStringLiteral( "WMSContactPerson" ), QStringLiteral( "/" ), mWMSContactPerson->text() );
10831080
QgsProject::instance()->writeEntry( QStringLiteral( "WMSContactMail" ), QStringLiteral( "/" ), mWMSContactMail->text() );

‎src/core/qgsgml.cpp‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,19 +1166,20 @@ void QgsGmlStreamingParser::setAttribute( const QString &name, const QString &va
11661166
{
11671167
//find index with attribute name
11681168
QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.constFind( name );
1169+
bool conversionOk = true;
11691170
if ( att_it != mThematicAttributes.constEnd() )
11701171
{
11711172
QVariant var;
11721173
switch ( att_it.value().second.type() )
11731174
{
11741175
case QVariant::Double:
1175-
var = QVariant( value.toDouble() );
1176+
var = QVariant( value.toDouble( &conversionOk ) );
11761177
break;
11771178
case QVariant::Int:
1178-
var = QVariant( value.toInt() );
1179+
var = QVariant( value.toInt( &conversionOk ) );
11791180
break;
11801181
case QVariant::LongLong:
1181-
var = QVariant( value.toLongLong() );
1182+
var = QVariant( value.toLongLong( &conversionOk ) );
11821183
break;
11831184
case QVariant::DateTime:
11841185
var = QVariant( QDateTime::fromString( value, Qt::ISODate ) );
@@ -1187,6 +1188,10 @@ void QgsGmlStreamingParser::setAttribute( const QString &name, const QString &va
11871188
var = QVariant( value );
11881189
break;
11891190
}
1191+
if ( ! conversionOk ) // Assume is NULL
1192+
{
1193+
var = QVariant();
1194+
}
11901195
Q_ASSERT( mCurrentFeature );
11911196
mCurrentFeature->setAttribute( att_it.value().first, var );
11921197
}

‎src/providers/wfs/qgswfsfeatureiterator.cpp‎

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,38 +1447,35 @@ void QgsWFSFeatureIterator::copyFeature( const QgsFeature &srcFeature, QgsFeatur
14471447
QgsFields &fields = mShared->mFields;
14481448
dstFeature.initAttributes( fields.size() );
14491449

1450+
auto setAttr = [ & ]( const int i )
1451+
{
1452+
int idx = srcFeature.fields().indexFromName( fields.at( i ).name() );
1453+
if ( idx >= 0 )
1454+
{
1455+
const QVariant &v = srcFeature.attributes().value( idx );
1456+
if ( v.isNull() )
1457+
dstFeature.setAttribute( i, QVariant( fields.at( i ).type() ) );
1458+
else if ( v.type() == fields.at( i ).type() )
1459+
dstFeature.setAttribute( i, v );
1460+
else if ( fields.at( i ).type() == QVariant::DateTime && !v.isNull() )
1461+
dstFeature.setAttribute( i, QVariant( QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ) );
1462+
else
1463+
dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fields.at( i ).type(), v.toString() ) );
1464+
}
1465+
};
1466+
14501467
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
14511468
{
1452-
Q_FOREACH ( int i, mSubSetAttributes )
1469+
for ( auto i : qgis::as_const( mSubSetAttributes ) )
14531470
{
1454-
int idx = srcFeature.fields().indexFromName( fields.at( i ).name() );
1455-
if ( idx >= 0 )
1456-
{
1457-
const QVariant &v = srcFeature.attributes().value( idx );
1458-
if ( v.type() == fields.at( i ).type() )
1459-
dstFeature.setAttribute( i, v );
1460-
else if ( fields.at( i ).type() == QVariant::DateTime && !v.isNull() )
1461-
dstFeature.setAttribute( i, QVariant( QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ) );
1462-
else
1463-
dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fields.at( i ).type(), v.toString() ) );
1464-
}
1471+
setAttr( i );
14651472
}
14661473
}
14671474
else
14681475
{
14691476
for ( int i = 0; i < fields.size(); i++ )
14701477
{
1471-
int idx = srcFeature.fields().indexFromName( fields.at( i ).name() );
1472-
if ( idx >= 0 )
1473-
{
1474-
const QVariant &v = srcFeature.attributes().value( idx );
1475-
if ( v.type() == fields.at( i ).type() )
1476-
dstFeature.setAttribute( i, v );
1477-
else if ( fields.at( i ).type() == QVariant::DateTime && !v.isNull() )
1478-
dstFeature.setAttribute( i, QVariant( QDateTime::fromMSecsSinceEpoch( v.toLongLong() ) ) );
1479-
else
1480-
dstFeature.setAttribute( i, QgsVectorDataProvider::convertValue( fields.at( i ).type(), v.toString() ) );
1481-
}
1478+
setAttr( i );
14821479
}
14831480
}
14841481

‎src/server/services/wfs/qgswfsdescribefeaturetype.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ namespace QgsWfs
8787
// test oFormat
8888
if ( oFormat == QgsWfsParameters::Format::NONE )
8989
throw QgsBadRequestException( QStringLiteral( "Invalid WFS Parameter" ),
90-
"OUTPUTFORMAT " + wfsParameters.outputFormatAsString() + "is not supported" );
90+
QStringLiteral( "OUTPUTFORMAT %1 is not supported" ).arg( wfsParameters.outputFormatAsString() ) );
9191

9292
QgsAccessControl *accessControl = serverIface->accessControls();
9393

@@ -354,6 +354,11 @@ namespace QgsWfs
354354
}
355355
}
356356

357+
if ( !( field.constraints().constraints() & QgsFieldConstraints::Constraint::ConstraintNotNull ) )
358+
{
359+
attElem.setAttribute( QStringLiteral( "nillable" ), QStringLiteral( "true" ) );
360+
}
361+
357362
sequenceElem.appendChild( attElem );
358363

359364
QString alias = field.alias();

‎src/server/services/wfs/qgswfsgetfeature.cpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,10 @@ namespace QgsWfs
13451345

13461346
QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() ) );
13471347
QDomText fieldText = doc.createTextNode( encodeValueToText( featureAttributes[idx], setup ) );
1348+
if ( featureAttributes[idx].isNull() )
1349+
{
1350+
fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
1351+
}
13481352
fieldElem.appendChild( fieldText );
13491353
typeNameElement.appendChild( fieldElem );
13501354
}
@@ -1436,12 +1440,18 @@ namespace QgsWfs
14361440
{
14371441
continue;
14381442
}
1443+
14391444
const QgsField field = fields.at( idx );
14401445
const QgsEditorWidgetSetup setup = field.editorWidgetSetup();
1446+
14411447
QString attributeName = field.name();
14421448

14431449
QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( ' ', '_' ).replace( cleanTagNameRegExp, QString() ) );
14441450
QDomText fieldText = doc.createTextNode( encodeValueToText( featureAttributes[idx], setup ) );
1451+
if ( featureAttributes[idx].isNull() )
1452+
{
1453+
fieldElem.setAttribute( QStringLiteral( "xsi:nil" ), QStringLiteral( "true" ) );
1454+
}
14451455
fieldElem.appendChild( fieldText );
14461456
typeNameElement.appendChild( fieldElem );
14471457
}

‎src/server/services/wfs/qgswfstransaction.cpp‎

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -407,26 +407,50 @@ namespace QgsWfs
407407
}
408408
QgsField field = fields.at( fieldMapIt.value() );
409409
QVariant value = it.value();
410-
if ( field.type() == 2 )
410+
if ( value.isNull() )
411411
{
412-
value = it.value().toInt( &conversionSuccess );
413-
if ( !conversionSuccess )
412+
if ( field.constraints().constraints() & QgsFieldConstraints::Constraint::ConstraintNotNull )
414413
{
415414
action.error = true;
416-
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
415+
action.errorMsg = QStringLiteral( "NOT NULL constraint error on layer '%1', field '%2'" ).arg( typeName, field.name() );
417416
vlayer->rollBack();
418417
break;
419418
}
420419
}
421-
else if ( field.type() == 6 )
420+
else // Not NULL
422421
{
423-
value = it.value().toDouble( &conversionSuccess );
424-
if ( !conversionSuccess )
422+
if ( field.type() == QVariant::Type::Int )
425423
{
426-
action.error = true;
427-
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
428-
vlayer->rollBack();
429-
break;
424+
value = it.value().toInt( &conversionSuccess );
425+
if ( !conversionSuccess )
426+
{
427+
action.error = true;
428+
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
429+
vlayer->rollBack();
430+
break;
431+
}
432+
}
433+
else if ( field.type() == QVariant::Type::Double )
434+
{
435+
value = it.value().toDouble( &conversionSuccess );
436+
if ( !conversionSuccess )
437+
{
438+
action.error = true;
439+
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
440+
vlayer->rollBack();
441+
break;
442+
}
443+
}
444+
else if ( field.type() == QVariant::Type::LongLong )
445+
{
446+
value = it.value().toLongLong( &conversionSuccess );
447+
if ( !conversionSuccess )
448+
{
449+
action.error = true;
450+
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
451+
vlayer->rollBack();
452+
break;
453+
}
430454
}
431455
}
432456
vlayer->changeAttributeValue( feature.id(), fieldMapIt.value(), value );

‎src/server/services/wfs/qgswfstransaction_1_0_0.cpp‎

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -388,26 +388,50 @@ namespace QgsWfs
388388
}
389389
QgsField field = fields.at( fieldMapIt.value() );
390390
QVariant value = it.value();
391-
if ( field.type() == 2 )
391+
if ( value.isNull() )
392392
{
393-
value = it.value().toInt( &conversionSuccess );
394-
if ( !conversionSuccess )
393+
if ( field.constraints().constraints() & QgsFieldConstraints::Constraint::ConstraintNotNull )
395394
{
396395
action.error = true;
397-
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
396+
action.errorMsg = QStringLiteral( "NOT NULL constraint error on layer '%1', field '%2'" ).arg( typeName, field.name() );
398397
vlayer->rollBack();
399398
break;
400399
}
401400
}
402-
else if ( field.type() == 6 )
401+
else // Not NULL
403402
{
404-
value = it.value().toDouble( &conversionSuccess );
405-
if ( !conversionSuccess )
403+
if ( field.type() == QVariant::Type::Int )
406404
{
407-
action.error = true;
408-
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
409-
vlayer->rollBack();
410-
break;
405+
value = it.value().toInt( &conversionSuccess );
406+
if ( !conversionSuccess )
407+
{
408+
action.error = true;
409+
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
410+
vlayer->rollBack();
411+
break;
412+
}
413+
}
414+
else if ( field.type() == QVariant::Type::Double )
415+
{
416+
value = it.value().toDouble( &conversionSuccess );
417+
if ( !conversionSuccess )
418+
{
419+
action.error = true;
420+
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
421+
vlayer->rollBack();
422+
break;
423+
}
424+
}
425+
else if ( field.type() == QVariant::Type::LongLong )
426+
{
427+
value = it.value().toLongLong( &conversionSuccess );
428+
if ( !conversionSuccess )
429+
{
430+
action.error = true;
431+
action.errorMsg = QStringLiteral( "Property conversion error on layer '%1'" ).arg( typeName );
432+
vlayer->rollBack();
433+
break;
434+
}
411435
}
412436
}
413437
vlayer->changeAttributeValue( feature.id(), fieldMapIt.value(), value );

‎tests/src/core/testqgsgml.cpp‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,13 @@ class TestQgsGML : public QObject
8383

8484
const QString data1( "<myns:FeatureCollection "
8585
"xmlns:myns='http://myns' "
86+
"xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "
8687
"xmlns:gml='http://www.opengis.net/gml'>"
8788
"<gml:boundedBy><gml:null>unknown</gml:null></gml:boundedBy>"
8889
"<gml:featureMember>"
8990
"<myns:mytypename fid='mytypename.1'>"
9091
"<myns:intfield>1</myns:intfield>"
92+
"<myns:nillablefield xsi:nil='true'/>"
9193
"<myns:longfield>1234567890123</myns:longfield>"
9294
"<myns:doublefield>1.23</myns:doublefield>"
9395
"<myns:strfield>foo</myns:strfield>"
@@ -105,6 +107,7 @@ void TestQgsGML::testFromURL()
105107
{
106108
QgsFields fields;
107109
fields.append( QgsField( QStringLiteral( "intfield" ), QVariant::Int, QStringLiteral( "int" ) ) );
110+
fields.append( QgsField( QStringLiteral( "nillablefield" ), QVariant::Int, QStringLiteral( "nillablefield" ) ) );
108111
QgsGml gmlParser( QStringLiteral( "mytypename" ), QStringLiteral( "mygeom" ), fields );
109112
QgsWkbTypes::Type wkbType;
110113
QTemporaryFile tmpFile;
@@ -117,30 +120,36 @@ void TestQgsGML::testFromURL()
117120
QCOMPARE( featureMaps.size(), 1 );
118121
QCOMPARE( gmlParser.idsMap().size(), 1 );
119122
QCOMPARE( gmlParser.crs().authid(), QString( "EPSG:27700" ) );
123+
QCOMPARE( featureMaps[0]->attribute( QStringLiteral( "intfield" ) ).toInt(), 1 );
124+
QVERIFY( featureMaps[0]->attribute( QStringLiteral( "nillablefield" ) ).isNull( ) );
120125
delete featureMaps[ 0 ];
121126
}
122127

123128
void TestQgsGML::testFromByteArray()
124129
{
125130
QgsFields fields;
126131
fields.append( QgsField( QStringLiteral( "intfield" ), QVariant::Int, QStringLiteral( "int" ) ) );
132+
fields.append( QgsField( QStringLiteral( "nillablefield" ), QVariant::Int, QStringLiteral( "nillablefield" ) ) );
127133
QgsGml gmlParser( QStringLiteral( "mytypename" ), QStringLiteral( "mygeom" ), fields );
128134
QgsWkbTypes::Type wkbType;
129135
QCOMPARE( gmlParser.getFeatures( data1.toAscii(), &wkbType ), 0 );
130136
QMap<QgsFeatureId, QgsFeature * > featureMaps = gmlParser.featuresMap();
131137
QCOMPARE( featureMaps.size(), 1 );
132138
QVERIFY( featureMaps.constFind( 0 ) != featureMaps.constEnd() );
133-
QCOMPARE( featureMaps[ 0 ]->attributes().size(), 1 );
139+
QCOMPARE( featureMaps[ 0 ]->attributes().size(), 2 );
134140
QMap<QgsFeatureId, QString > idsMap = gmlParser.idsMap();
135141
QVERIFY( idsMap.constFind( 0 ) != idsMap.constEnd() );
136142
QCOMPARE( idsMap[ 0 ], QString( "mytypename.1" ) );
143+
QCOMPARE( featureMaps[0]->attribute( QStringLiteral( "intfield" ) ).toInt(), 1 );
144+
QVERIFY( featureMaps[0]->attribute( QStringLiteral( "nillablefield" ) ).isNull( ) );
137145
delete featureMaps[ 0 ];
138146
}
139147

140148
void TestQgsGML::testStreamingParser()
141149
{
142150
QgsFields fields;
143151
fields.append( QgsField( QStringLiteral( "intfield" ), QVariant::Int, QStringLiteral( "int" ) ) );
152+
fields.append( QgsField( QStringLiteral( "nillablefield" ), QVariant::Int, QStringLiteral( "nillablefield" ) ) );
144153
fields.append( QgsField( QStringLiteral( "longfield" ), QVariant::LongLong, QStringLiteral( "longlong" ) ) );
145154
fields.append( QgsField( QStringLiteral( "doublefield" ), QVariant::Double, QStringLiteral( "double" ) ) );
146155
fields.append( QgsField( QStringLiteral( "strfield" ), QVariant::String, QStringLiteral( "string" ) ) );
@@ -152,12 +161,13 @@ void TestQgsGML::testStreamingParser()
152161
QCOMPARE( gmlParser.isException(), false );
153162
QVector<QgsGmlStreamingParser::QgsGmlFeaturePtrGmlIdPair> features = gmlParser.getAndStealReadyFeatures();
154163
QCOMPARE( features.size(), 1 );
155-
QCOMPARE( features[0].first->attributes().size(), 5 );
164+
QCOMPARE( features[0].first->attributes().size(), 6 );
156165
QCOMPARE( features[0].first->attributes().at( 0 ), QVariant( 1 ) );
157-
QCOMPARE( features[0].first->attributes().at( 1 ), QVariant( Q_INT64_C( 1234567890123 ) ) );
158-
QCOMPARE( features[0].first->attributes().at( 2 ), QVariant( 1.23 ) );
159-
QCOMPARE( features[0].first->attributes().at( 3 ), QVariant( "foo" ) );
160-
QCOMPARE( features[0].first->attributes().at( 4 ), QVariant( QDateTime( QDate( 2016, 4, 10 ), QTime( 12, 34, 56, 789 ), Qt::UTC ) ) );
166+
QCOMPARE( features[0].first->attributes().at( 1 ), QVariant( ) );
167+
QCOMPARE( features[0].first->attributes().at( 2 ), QVariant( Q_INT64_C( 1234567890123 ) ) );
168+
QCOMPARE( features[0].first->attributes().at( 3 ), QVariant( 1.23 ) );
169+
QCOMPARE( features[0].first->attributes().at( 4 ), QVariant( "foo" ) );
170+
QCOMPARE( features[0].first->attributes().at( 5 ), QVariant( QDateTime( QDate( 2016, 4, 10 ), QTime( 12, 34, 56, 789 ), Qt::UTC ) ) );
161171
QVERIFY( features[0].first->hasGeometry() );
162172
QCOMPARE( features[0].first->geometry().wkbType(), QgsWkbTypes::Point );
163173
QCOMPARE( features[0].first->geometry().asPoint(), QgsPointXY( 10, 20 ) );

0 commit comments

Comments
 (0)