diff --git a/src/core/qgsfield.cpp b/src/core/qgsfield.cpp index d3beee41951..37f82071441 100644 --- a/src/core/qgsfield.cpp +++ b/src/core/qgsfield.cpp @@ -378,6 +378,34 @@ bool QgsField::convertCompatible( QVariant &v ) const return true; } + //String representations of doubles in QVariant will return false to convert( QVariant::LongLong ) + //work around this by first converting to double, and then checking whether the double is convertible to longlong + if ( d->type == QVariant::LongLong && v.canConvert( QVariant::Double ) ) + { + //firstly test the conversion to longlong because conversion to double will rounded the value + QVariant tmp( v ); + if ( !tmp.convert( d->type ) ) + { + bool ok = false; + double dbl = v.toDouble( &ok ); + if ( !ok ) + { + //couldn't convert to number + v = QVariant( d->type ); + return false; + } + + double round = std::round( dbl ); + if ( round > std::numeric_limits::max() || round < -std::numeric_limits::max() ) + { + //double too large to fit in longlong + v = QVariant( d->type ); + return false; + } + v = QVariant( static_cast< long long >( std::round( dbl ) ) ); + return true; + } + } if ( !v.convert( d->type ) ) { diff --git a/tests/src/core/testqgsfield.cpp b/tests/src/core/testqgsfield.cpp index 92773a2ec18..a4d09a45444 100644 --- a/tests/src/core/testqgsfield.cpp +++ b/tests/src/core/testqgsfield.cpp @@ -524,12 +524,6 @@ void TestQgsField::convertCompatible() QVERIFY( intField.convertCompatible( smallLonglong ) ); QCOMPARE( smallLonglong.type(), QVariant::Int ); QCOMPARE( smallLonglong, QVariant( 99 ) ); - //conversion of longlong to longlong field - QgsField longlongField( QStringLiteral( "long" ), QVariant::LongLong, QStringLiteral( "longlong" ) ); - longlong = QVariant( 99999999999999999LL ); - QVERIFY( longlongField.convertCompatible( longlong ) ); - QCOMPARE( longlong.type(), QVariant::LongLong ); - QCOMPARE( longlong, QVariant( 99999999999999999LL ) ); //string representation of an int QVariant stringInt( "123456" ); @@ -542,6 +536,13 @@ void TestQgsField::convertCompatible() QCOMPARE( stringInt.type(), QVariant::Int ); QCOMPARE( stringInt, QVariant( "123456" ) ); + //conversion of longlong to longlong field + QgsField longlongField( QStringLiteral( "long" ), QVariant::LongLong, QStringLiteral( "longlong" ) ); + longlong = QVariant( 99999999999999999LL ); + QVERIFY( longlongField.convertCompatible( longlong ) ); + QCOMPARE( longlong.type(), QVariant::LongLong ); + QCOMPARE( longlong, QVariant( 99999999999999999LL ) ); + //string representation of a longlong QVariant stringLong( "99999999999999999" ); QVERIFY( longlongField.convertCompatible( stringLong ) ); @@ -553,6 +554,30 @@ void TestQgsField::convertCompatible() QCOMPARE( stringLong.type(), QVariant::LongLong ); QCOMPARE( stringLong, QVariant( 99999999999999999LL ) ); + //conversion of string double value to longlong + notNumberString = QVariant( "notanumber" ); + QVERIFY( !longlongField.convertCompatible( notNumberString ) ); + QCOMPARE( notNumberString.type(), QVariant::LongLong ); + QVERIFY( notNumberString.isNull() ); + //small double, should be rounded + smallDoubleString = QVariant( "45.7" ); + QVERIFY( longlongField.convertCompatible( smallDoubleString ) ); + QCOMPARE( smallDoubleString.type(), QVariant::LongLong ); + QCOMPARE( smallDoubleString, QVariant( 46 ) ); + negativeSmallDoubleString = QVariant( "-9345.754534525235235" ); + QVERIFY( longlongField.convertCompatible( negativeSmallDoubleString ) ); + QCOMPARE( negativeSmallDoubleString.type(), QVariant::LongLong ); + QCOMPARE( negativeSmallDoubleString, QVariant( -9346 ) ); + //large double, can be converted + largeDoubleString = QVariant( "9999999999.99" ); + QVERIFY( longlongField.convertCompatible( largeDoubleString ) ); + QCOMPARE( largeDoubleString.type(), QVariant::LongLong ); + QCOMPARE( largeDoubleString, QVariant( 10000000000LL ) ); + //extra large double, cannot be converted + largeDoubleString = QVariant( "999999999999999999999.99" ); + QVERIFY( !longlongField.convertCompatible( largeDoubleString ) ); + QCOMPARE( largeDoubleString.type(), QVariant::LongLong ); + QVERIFY( largeDoubleString.isNull() ); //string representation of a double QVariant stringDouble( "123456.012345" ); diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 760fe0dc8b5..0efae4ba0f2 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -147,6 +147,7 @@ ADD_PYTHON_TEST(PyQgsNetworkContentFetcherTask test_qgsnetworkcontentfetchertask ADD_PYTHON_TEST(PyQgsNullSymbolRenderer test_qgsnullsymbolrenderer.py) ADD_PYTHON_TEST(PyQgsNewGeoPackageLayerDialog test_qgsnewgeopackagelayerdialog.py) ADD_PYTHON_TEST(PyQgsNoApplication test_qgsnoapplication.py) +ADD_PYTHON_TEST(PyQgsOgcUtils test_qgsogcutils.py) ADD_PYTHON_TEST(PyQgsOGRProviderGpkg test_provider_ogr_gpkg.py) ADD_PYTHON_TEST(PyQgsOGRProviderSqlite test_provider_ogr_sqlite.py) ADD_PYTHON_TEST(PyQgsOpacityWidget test_qgsopacitywidget.py) diff --git a/tests/src/python/test_qgsogcutils.py b/tests/src/python/test_qgsogcutils.py new file mode 100644 index 00000000000..bf3785a622c --- /dev/null +++ b/tests/src/python/test_qgsogcutils.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsOgcUtils. + +From build dir, run: ctest -R PyQgsOgcUtils -V + + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +""" +__author__ = 'René-Luc Dhont' +__date__ = '21/06/2019' +__copyright__ = 'Copyright 2019, The QGIS Project' + +import qgis # NOQA switch sip api + +from qgis.PyQt.QtCore import QVariant +from qgis.PyQt.QtXml import QDomDocument +from qgis.core import QgsOgcUtils, QgsVectorLayer, QgsField + +from qgis.testing import start_app, unittest + +start_app() + + +class TestQgsOgcUtils(unittest.TestCase): + + def test_expressionFromOgcFilterWithInt(self): + """ + Test expressionFromOgcFilter with Int type field + """ + vl = QgsVectorLayer('Point', 'vl', 'memory') + vl.dataProvider().addAttributes([QgsField('id', QVariant.Int)]) + vl.updateFields() + + # Literals are Integer 1 and 3 + f = ''' + + + + id + 1 + + + id + 3 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > 1 AND id < 3') + + # Literals are Double 1.0 and 3.0 + f = ''' + + + + id + 1.0 + + + id + 3.0 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > 1 AND id < 3') + + # Literals are Double 1.5 and 3.5 + f = ''' + + + + id + 1.5 + + + id + 3.5 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > 2 AND id < 4') + + def test_expressionFromOgcFilterWithLonglong(self): + """ + Test expressionFromOgcFilter with LongLong type field + """ + vl = QgsVectorLayer('Point', 'vl', 'memory') + vl.dataProvider().addAttributes([QgsField('id', QVariant.LongLong)]) + vl.updateFields() + + # Literals are Integer 1 and 3 + f = ''' + + + + id + 1 + + + id + 3 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > 1 AND id < 3') + # Literals are Double 1.0 and 3.0 + f = ''' + + + + id + 1.0 + + + id + 3.0 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > 1 AND id < 3') + + # Literals are Double 1.5 and 3.5 + f = ''' + + + + id + 1.5 + + + id + 3.5 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > 2 AND id < 4') + + def test_expressionFromOgcFilterWithDouble(self): + """ + Test expressionFromOgcFilter with Double type field + """ + vl = QgsVectorLayer('Point', 'vl', 'memory') + vl.dataProvider().addAttributes([QgsField('id', QVariant.Double)]) + vl.updateFields() + + # Literals are Integer 1 and 3 + f = ''' + + + + id + 1 + + + id + 3 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > 1 AND id < 3') + + # Literals are Double 1.0 and 3.0 + f = ''' + + + + id + 1.0 + + + id + 3.0 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > 1 AND id < 3') + + # Literals are Double 1.5 and 3.5 + f = ''' + + + + id + 1.5 + + + id + 3.5 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > 1.5 AND id < 3.5') + + def test_expressionFromOgcFilterWithString(self): + """ + Test expressionFromOgcFilter with String type field + """ + vl = QgsVectorLayer('Point', 'vl', 'memory') + vl.dataProvider().addAttributes([QgsField('id', QVariant.String)]) + vl.updateFields() + + # Literals are Integer 1 and 3 + f = ''' + + + + id + 1 + + + id + 3 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > \'1\' AND id < \'3\'') + + # Literals are Double 1.0 and 3.0 + f = ''' + + + + id + 1.0 + + + id + 3.0 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > \'1.0\' AND id < \'3.0\'') + + # Literals are Double 1.5 and 3.5 + f = ''' + + + + id + 1.5 + + + id + 3.5 + + + + ''' + d = QDomDocument('filter') + d.setContent(f, True) + + e = QgsOgcUtils.expressionFromOgcFilter(d.documentElement(), vl) + self.assertEqual(e.expression(), 'id > \'1.5\' AND id < \'3.5\'') + + +if __name__ == '__main__': + unittest.main()