From 71e574db6b719b2136e7da00a7ad9da4b5605f40 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 2 Jul 2024 15:00:10 +1000 Subject: [PATCH] Add some helpful api to QgsFields - Construct QgsFields from a list of QgsField - Append a list of QgsField - Append another QgsFields --- .../core/auto_generated/qgsfields.sip.in | 25 +++ python/core/auto_generated/qgsfields.sip.in | 25 +++ src/core/qgsfields.cpp | 39 ++++ src/core/qgsfields.h | 25 +++ tests/src/core/testqgsfields.cpp | 173 ++++++++++++++++++ 5 files changed, 287 insertions(+) diff --git a/python/PyQt6/core/auto_generated/qgsfields.sip.in b/python/PyQt6/core/auto_generated/qgsfields.sip.in index c77b683113f4..1299581852ab 100644 --- a/python/PyQt6/core/auto_generated/qgsfields.sip.in +++ b/python/PyQt6/core/auto_generated/qgsfields.sip.in @@ -45,6 +45,13 @@ Copy constructor %End + QgsFields( const QList< QgsField > &fields ) /HoldGIL/; +%Docstring +Construct QgsFields from a list of ``fields``. + +.. versionadded:: 3.40 +%End + virtual ~QgsFields(); void clear() /HoldGIL/; @@ -63,6 +70,24 @@ The ``originIndex`` argument must be set to a value corresponding to the ``origi - :py:class:`Qgis`.FieldOrigin.Provider: The field's originIndex is the index in provider's fields. - :py:class:`Qgis`.FieldOrigin.Join: The field's originIndex / 1000 = index of the join, originIndex % 1000 = index within the join - :py:class:`Qgis`.FieldOrigin.Edit: The originIndex is the index in the list of added attributes +%End + + bool append( const QList< QgsField > &fields, Qgis::FieldOrigin origin = Qgis::FieldOrigin::Provider ) /HoldGIL/; +%Docstring +Appends a list of ``fields``. + +The fields must have unique names, otherwise it is rejected (returns ``False``). + +.. versionadded:: 3.40 +%End + + bool append( const QgsFields &fields ) /HoldGIL/; +%Docstring +Appends another set of ``fields`` to these fields. + +The fields must have unique names, otherwise it is rejected (returns ``False``). + +.. versionadded:: 3.40 %End bool rename( int fieldIdx, const QString &name ) /HoldGIL/; diff --git a/python/core/auto_generated/qgsfields.sip.in b/python/core/auto_generated/qgsfields.sip.in index 5fcec3f4fc4c..f756c2dec698 100644 --- a/python/core/auto_generated/qgsfields.sip.in +++ b/python/core/auto_generated/qgsfields.sip.in @@ -45,6 +45,13 @@ Copy constructor %End + QgsFields( const QList< QgsField > &fields ) /HoldGIL/; +%Docstring +Construct QgsFields from a list of ``fields``. + +.. versionadded:: 3.40 +%End + virtual ~QgsFields(); void clear() /HoldGIL/; @@ -63,6 +70,24 @@ The ``originIndex`` argument must be set to a value corresponding to the ``origi - :py:class:`Qgis`.FieldOrigin.Provider: The field's originIndex is the index in provider's fields. - :py:class:`Qgis`.FieldOrigin.Join: The field's originIndex / 1000 = index of the join, originIndex % 1000 = index within the join - :py:class:`Qgis`.FieldOrigin.Edit: The originIndex is the index in the list of added attributes +%End + + bool append( const QList< QgsField > &fields, Qgis::FieldOrigin origin = Qgis::FieldOrigin::Provider ) /HoldGIL/; +%Docstring +Appends a list of ``fields``. + +The fields must have unique names, otherwise it is rejected (returns ``False``). + +.. versionadded:: 3.40 +%End + + bool append( const QgsFields &fields ) /HoldGIL/; +%Docstring +Appends another set of ``fields`` to these fields. + +The fields must have unique names, otherwise it is rejected (returns ``False``). + +.. versionadded:: 3.40 %End bool rename( int fieldIdx, const QString &name ) /HoldGIL/; diff --git a/src/core/qgsfields.cpp b/src/core/qgsfields.cpp index 40d3e2c81e0a..fe92f669e598 100644 --- a/src/core/qgsfields.cpp +++ b/src/core/qgsfields.cpp @@ -42,6 +42,15 @@ QgsFields &QgsFields::operator =( const QgsFields &other ) //NOLINT return *this; } +QgsFields::QgsFields( const QList &fields ) +{ + d = new QgsFieldsPrivate(); + for ( const QgsField &field : fields ) + { + append( field ); + } +} + QgsFields::~QgsFields() //NOLINT {} @@ -70,6 +79,36 @@ bool QgsFields::append( const QgsField &field, Qgis::FieldOrigin origin, int ori return true; } +bool QgsFields::append( const QList &fields, Qgis::FieldOrigin origin ) +{ + for ( const QgsField &field : fields ) + { + if ( d->nameToIndex.contains( field.name() ) ) + return false; + } + + for ( const QgsField &field : fields ) + { + append( field, origin ); + } + return true; +} + +bool QgsFields::append( const QgsFields &fields ) +{ + for ( const QgsField &field : fields ) + { + if ( d->nameToIndex.contains( field.name() ) ) + return false; + } + + for ( int i = 0; i < fields.size(); ++ i ) + { + append( fields.at( i ), fields.fieldOrigin( i ), fields.fieldOriginIndex( i ) ); + } + return true; +} + bool QgsFields::rename( int fieldIdx, const QString &name ) { if ( !exists( fieldIdx ) ) diff --git a/src/core/qgsfields.h b/src/core/qgsfields.h index ad0b1e645621..136784e35774 100644 --- a/src/core/qgsfields.h +++ b/src/core/qgsfields.h @@ -92,6 +92,13 @@ class CORE_EXPORT QgsFields */ QgsFields &operator =( const QgsFields &other ) SIP_SKIP; + /** + * Construct QgsFields from a list of \a fields. + * + * \since QGIS 3.40 + */ + QgsFields( const QList< QgsField > &fields ) SIP_HOLDGIL; + virtual ~QgsFields(); //! Removes all fields @@ -110,6 +117,24 @@ class CORE_EXPORT QgsFields */ bool append( const QgsField &field, Qgis::FieldOrigin origin = Qgis::FieldOrigin::Provider, int originIndex = -1 ) SIP_HOLDGIL; + /** + * Appends a list of \a fields. + * + * The fields must have unique names, otherwise it is rejected (returns FALSE). + * + * \since QGIS 3.40 + */ + bool append( const QList< QgsField > &fields, Qgis::FieldOrigin origin = Qgis::FieldOrigin::Provider ) SIP_HOLDGIL; + + /** + * Appends another set of \a fields to these fields. + * + * The fields must have unique names, otherwise it is rejected (returns FALSE). + * + * \since QGIS 3.40 + */ + bool append( const QgsFields &fields ) SIP_HOLDGIL; + /** * Renames a name of field. The field must have unique name, otherwise change is rejected (returns FALSE) * \since QGIS 3.6 diff --git a/tests/src/core/testqgsfields.cpp b/tests/src/core/testqgsfields.cpp index 208e33bfe557..0eeb7d789b37 100644 --- a/tests/src/core/testqgsfields.cpp +++ b/tests/src/core/testqgsfields.cpp @@ -34,6 +34,7 @@ class TestQgsFields: public QObject void assignment(); void equality(); //test equality operators void asVariant(); //test conversion to and from a QVariant + void construct(); void clear(); void exists(); void count(); @@ -54,6 +55,8 @@ class TestQgsFields: public QObject void qforeach(); void iterator(); void constIterator(); + void appendList(); + void appendQgsFields(); private: }; @@ -164,6 +167,30 @@ void TestQgsFields::asVariant() QCOMPARE( fromVar, original ); } +void TestQgsFields::construct() +{ + // construct using a list of fields + QgsFields fields( + QList< QgsField > + { + QgsField( QStringLiteral( "field1" ), QMetaType::Type::QString ), + QgsField( QStringLiteral( "field2" ), QMetaType::Type::Int ), + QgsField( QStringLiteral( "field3" ), QMetaType::Type::Double ), + } + ); + + QCOMPARE( fields.size(), 3 ); + QCOMPARE( fields.at( 0 ).name(), QStringLiteral( "field1" ) ); + QCOMPARE( fields.at( 0 ).type(), QMetaType::Type::QString ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field1" ) ), 0 ); + QCOMPARE( fields.at( 1 ).name(), QStringLiteral( "field2" ) ); + QCOMPARE( fields.at( 1 ).type(), QMetaType::Type::Int ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field2" ) ), 1 ); + QCOMPARE( fields.at( 2 ).name(), QStringLiteral( "field3" ) ); + QCOMPARE( fields.at( 2 ).type(), QMetaType::Type::Double ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field3" ) ), 2 ); +} + void TestQgsFields::clear() { QgsFields original; @@ -621,5 +648,151 @@ void TestQgsFields::constIterator() QCOMPARE( it3 - it, 1 ); } +void TestQgsFields::appendList() +{ + // test appending a list of fields + QgsFields fields; + + QVERIFY( fields.append( + QList< QgsField > + { + QgsField( QStringLiteral( "field1" ), QMetaType::Type::QString ), + QgsField( QStringLiteral( "field2" ), QMetaType::Type::Int ), + QgsField( QStringLiteral( "field3" ), QMetaType::Type::Double ), + } + ) ); + + QCOMPARE( fields.size(), 3 ); + QCOMPARE( fields.at( 0 ).name(), QStringLiteral( "field1" ) ); + QCOMPARE( fields.at( 0 ).type(), QMetaType::Type::QString ); + QCOMPARE( fields.fieldOrigin( 0 ), Qgis::FieldOrigin::Provider ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field1" ) ), 0 ); + QCOMPARE( fields.at( 1 ).name(), QStringLiteral( "field2" ) ); + QCOMPARE( fields.at( 1 ).type(), QMetaType::Type::Int ); + QCOMPARE( fields.fieldOrigin( 1 ), Qgis::FieldOrigin::Provider ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field2" ) ), 1 ); + QCOMPARE( fields.at( 2 ).name(), QStringLiteral( "field3" ) ); + QCOMPARE( fields.at( 2 ).type(), QMetaType::Type::Double ); + QCOMPARE( fields.fieldOrigin( 2 ), Qgis::FieldOrigin::Provider ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field3" ) ), 2 ); + + // should be rejected, duplicate field name + QVERIFY( !fields.append( + QList< QgsField > + { + QgsField( QStringLiteral( "field1" ), QMetaType::Type::QString ) + } + ) ); + + QCOMPARE( fields.size(), 3 ); + + QVERIFY( fields.append( + QList< QgsField > + { + QgsField( QStringLiteral( "field4" ), QMetaType::Type::QString ), + QgsField( QStringLiteral( "field5" ), QMetaType::Type::Int ), + QgsField( QStringLiteral( "field6" ), QMetaType::Type::Double ), + }, Qgis::FieldOrigin::Join + ) ); + + QCOMPARE( fields.size(), 6 ); + QCOMPARE( fields.at( 0 ).name(), QStringLiteral( "field1" ) ); + QCOMPARE( fields.at( 0 ).type(), QMetaType::Type::QString ); + QCOMPARE( fields.fieldOrigin( 0 ), Qgis::FieldOrigin::Provider ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field1" ) ), 0 ); + QCOMPARE( fields.at( 1 ).name(), QStringLiteral( "field2" ) ); + QCOMPARE( fields.at( 1 ).type(), QMetaType::Type::Int ); + QCOMPARE( fields.fieldOrigin( 1 ), Qgis::FieldOrigin::Provider ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field2" ) ), 1 ); + QCOMPARE( fields.at( 2 ).name(), QStringLiteral( "field3" ) ); + QCOMPARE( fields.at( 2 ).type(), QMetaType::Type::Double ); + QCOMPARE( fields.fieldOrigin( 2 ), Qgis::FieldOrigin::Provider ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field3" ) ), 2 ); + QCOMPARE( fields.at( 3 ).name(), QStringLiteral( "field4" ) ); + QCOMPARE( fields.at( 3 ).type(), QMetaType::Type::QString ); + QCOMPARE( fields.fieldOrigin( 3 ), Qgis::FieldOrigin::Join ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field4" ) ), 3 ); + QCOMPARE( fields.at( 4 ).name(), QStringLiteral( "field5" ) ); + QCOMPARE( fields.at( 4 ).type(), QMetaType::Type::Int ); + QCOMPARE( fields.fieldOrigin( 4 ), Qgis::FieldOrigin::Join ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field5" ) ), 4 ); + QCOMPARE( fields.at( 5 ).name(), QStringLiteral( "field6" ) ); + QCOMPARE( fields.at( 5 ).type(), QMetaType::Type::Double ); + QCOMPARE( fields.fieldOrigin( 5 ), Qgis::FieldOrigin::Join ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field6" ) ), 5 ); +} + +void TestQgsFields::appendQgsFields() +{ + // test appending fields from QgsFields + QgsFields fields; + + QgsFields fields2; + fields2.append( QgsField( QStringLiteral( "field1" ), QMetaType::Type::QString ), Qgis::FieldOrigin::Edit, 2 ); + fields2.append( QgsField( QStringLiteral( "field2" ), QMetaType::Type::Int ), Qgis::FieldOrigin::Join, 4 ); + fields2.append( QgsField( QStringLiteral( "field3" ), QMetaType::Type::Double ), Qgis::FieldOrigin::Provider, 6 ); + + QVERIFY( fields.append( fields2 ) ); + QCOMPARE( fields.size(), 3 ); + QCOMPARE( fields.at( 0 ).name(), QStringLiteral( "field1" ) ); + QCOMPARE( fields.at( 0 ).type(), QMetaType::Type::QString ); + QCOMPARE( fields.fieldOrigin( 0 ), Qgis::FieldOrigin::Edit ); + QCOMPARE( fields.fieldOriginIndex( 0 ), 2 ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field1" ) ), 0 ); + QCOMPARE( fields.at( 1 ).name(), QStringLiteral( "field2" ) ); + QCOMPARE( fields.at( 1 ).type(), QMetaType::Type::Int ); + QCOMPARE( fields.fieldOrigin( 1 ), Qgis::FieldOrigin::Join ); + QCOMPARE( fields.fieldOriginIndex( 1 ), 4 ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field2" ) ), 1 ); + QCOMPARE( fields.at( 2 ).name(), QStringLiteral( "field3" ) ); + QCOMPARE( fields.at( 2 ).type(), QMetaType::Type::Double ); + QCOMPARE( fields.fieldOrigin( 2 ), Qgis::FieldOrigin::Provider ); + QCOMPARE( fields.fieldOriginIndex( 2 ), 6 ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field3" ) ), 2 ); + + // should be rejected, duplicate field name + QVERIFY( !fields.append( fields2 ) ); + QCOMPARE( fields.size(), 3 ); + + QgsFields fields3; + fields3.append( QgsField( QStringLiteral( "field4" ), QMetaType::Type::QString ), Qgis::FieldOrigin::Expression, 3 ); + fields3.append( QgsField( QStringLiteral( "field5" ), QMetaType::Type::Int ), Qgis::FieldOrigin::Join, 5 ); + fields3.append( QgsField( QStringLiteral( "field6" ), QMetaType::Type::Double ), Qgis::FieldOrigin::Provider, 7 ); + + QVERIFY( fields.append( fields3 ) ); + + QCOMPARE( fields.size(), 6 ); + QCOMPARE( fields.at( 0 ).name(), QStringLiteral( "field1" ) ); + QCOMPARE( fields.at( 0 ).type(), QMetaType::Type::QString ); + QCOMPARE( fields.fieldOrigin( 0 ), Qgis::FieldOrigin::Edit ); + QCOMPARE( fields.fieldOriginIndex( 0 ), 2 ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field1" ) ), 0 ); + QCOMPARE( fields.at( 1 ).name(), QStringLiteral( "field2" ) ); + QCOMPARE( fields.at( 1 ).type(), QMetaType::Type::Int ); + QCOMPARE( fields.fieldOrigin( 1 ), Qgis::FieldOrigin::Join ); + QCOMPARE( fields.fieldOriginIndex( 1 ), 4 ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field2" ) ), 1 ); + QCOMPARE( fields.at( 2 ).name(), QStringLiteral( "field3" ) ); + QCOMPARE( fields.at( 2 ).type(), QMetaType::Type::Double ); + QCOMPARE( fields.fieldOrigin( 2 ), Qgis::FieldOrigin::Provider ); + QCOMPARE( fields.fieldOriginIndex( 2 ), 6 ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field3" ) ), 2 ); + QCOMPARE( fields.at( 3 ).name(), QStringLiteral( "field4" ) ); + QCOMPARE( fields.at( 3 ).type(), QMetaType::Type::QString ); + QCOMPARE( fields.fieldOrigin( 3 ), Qgis::FieldOrigin::Expression ); + QCOMPARE( fields.fieldOriginIndex( 3 ), 3 ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field4" ) ), 3 ); + QCOMPARE( fields.at( 4 ).name(), QStringLiteral( "field5" ) ); + QCOMPARE( fields.at( 4 ).type(), QMetaType::Type::Int ); + QCOMPARE( fields.fieldOrigin( 4 ), Qgis::FieldOrigin::Join ); + QCOMPARE( fields.fieldOriginIndex( 4 ), 5 ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field5" ) ), 4 ); + QCOMPARE( fields.at( 5 ).name(), QStringLiteral( "field6" ) ); + QCOMPARE( fields.at( 5 ).type(), QMetaType::Type::Double ); + QCOMPARE( fields.fieldOrigin( 5 ), Qgis::FieldOrigin::Provider ); + QCOMPARE( fields.fieldOriginIndex( 5 ), 7 ); + QCOMPARE( fields.indexFromName( QStringLiteral( "field6" ) ), 5 ); +} + QGSTEST_MAIN( TestQgsFields ) #include "testqgsfields.moc"