Skip to content

Commit a87db9c

Browse files
committedMar 24, 2023
Add capacity for storing additional metadata/properties in QgsField
This adds the API framework for storing additional, semi-structured properties inside QgsField objects. The intention is that strong field-type specific properties can be stored. Eg for a geometry field type coming from the postgres provider the metadata can be used to store the associated CRS and WKB types so that clients can be aware of the correct format required for geometries stored in that field. Instead of cluttering the QgsField API with specific getters/setters for properties like crs(), wkbType(), etc which only apply for a certain field type, the metadata map approach helps us keep the API nice and slim. The API has been designed to follow the approach used by various Qt objects (such as QTextFormat) where a preset set of keys are exposed as an enum, but additional ones can be used for custom property storage. This allows for a more structured use of properties with conventions which apply across different providers (as opposed to a free-form string key approach). Refs #49380
1 parent 462b4be commit a87db9c

File tree

8 files changed

+160
-3
lines changed

8 files changed

+160
-3
lines changed
 

‎python/core/auto_additions/qgis.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,13 @@
10721072
Qgis.SublayerPromptMode.__doc__ = 'Specifies how to handle layer sources with multiple sublayers.\n\n.. versionadded:: 3.22\n\n' + '* ``AlwaysAsk``: ' + Qgis.SublayerPromptMode.AlwaysAsk.__doc__ + '\n' + '* ``AskExcludingRasterBands``: ' + Qgis.SublayerPromptMode.AskExcludingRasterBands.__doc__ + '\n' + '* ``NeverAskSkip``: ' + Qgis.SublayerPromptMode.NeverAskSkip.__doc__ + '\n' + '* ``NeverAskLoadAll``: ' + Qgis.SublayerPromptMode.NeverAskLoadAll.__doc__
10731073
# --
10741074
Qgis.SublayerPromptMode.baseClass = Qgis
1075+
# monkey patching scoped based enum
1076+
Qgis.FieldMetadataProperty.GeometryCrs.__doc__ = "Available for geometry field types with a specific associated coordinate reference system (as a QgsCoordinateReferenceSystem value)"
1077+
Qgis.FieldMetadataProperty.GeometryWkbType.__doc__ = "Available for geometry field types which accept geometries of a specific WKB type only (as a QgsWkbTypes.Type value)"
1078+
Qgis.FieldMetadataProperty.CustomProperty.__doc__ = "Starting point for custom user set properties"
1079+
Qgis.FieldMetadataProperty.__doc__ = 'Standard field metadata values.\n\n.. versionadded:: 3.30\n\n' + '* ``GeometryCrs``: ' + Qgis.FieldMetadataProperty.GeometryCrs.__doc__ + '\n' + '* ``GeometryWkbType``: ' + Qgis.FieldMetadataProperty.GeometryWkbType.__doc__ + '\n' + '* ``CustomProperty``: ' + Qgis.FieldMetadataProperty.CustomProperty.__doc__
1080+
# --
1081+
Qgis.FieldMetadataProperty.baseClass = Qgis
10751082
QgsVectorLayer.SelectBehavior = Qgis.SelectBehavior
10761083
# monkey patching scoped based enum
10771084
QgsVectorLayer.SetSelection = Qgis.SelectBehavior.SetSelection

‎python/core/auto_generated/qgis.sip.in

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,13 @@ The development version
623623
NeverAskLoadAll,
624624
};
625625

626+
enum class FieldMetadataProperty
627+
{
628+
GeometryCrs,
629+
GeometryWkbType,
630+
CustomProperty,
631+
};
632+
626633
enum class SelectBehavior
627634
{
628635
SetSelection,

‎python/core/auto_generated/qgsfield.sip.in

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,46 @@ Gets the precision of the field. Not all field types have a related precision.
173173
QString comment() const;
174174
%Docstring
175175
Returns the field comment
176+
%End
177+
178+
QMap< int, QVariant > metadata() const;
179+
%Docstring
180+
Returns the map of field metadata.
181+
182+
Map keys should match values from the Qgis.FieldMetadataProperty enum.
183+
184+
.. seealso:: :py:func:`setMetadata`
185+
186+
.. versionadded:: 3.30
187+
%End
188+
189+
QVariant metadata( Qgis::FieldMetadataProperty property ) const;
190+
%Docstring
191+
Returns a specific metadata ``property``.
192+
193+
.. seealso:: :py:func:`setMetadata`
194+
195+
.. versionadded:: 3.30
196+
%End
197+
198+
void setMetadata( const QMap< int, QVariant > metadata );
199+
%Docstring
200+
Sets the map of field ``metadata``.
201+
202+
Map keys should match values from the Qgis.FieldMetadataProperty enum.
203+
204+
.. seealso:: :py:func:`metadata`
205+
206+
.. versionadded:: 3.30
207+
%End
208+
209+
void setMetadata( Qgis::FieldMetadataProperty property, const QVariant &value );
210+
%Docstring
211+
Sets a metadata ``property`` to ``value``.
212+
213+
.. seealso:: :py:func:`metadata`
214+
215+
.. versionadded:: 3.30
176216
%End
177217

178218
bool isNumeric() const;

‎src/core/qgis.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,19 @@ class CORE_EXPORT Qgis
10001000
};
10011001
Q_ENUM( SublayerPromptMode )
10021002

1003+
/**
1004+
* Standard field metadata values.
1005+
*
1006+
* \since QGIS 3.30
1007+
*/
1008+
enum class FieldMetadataProperty : int
1009+
{
1010+
GeometryCrs = 0x1000, //!< Available for geometry field types with a specific associated coordinate reference system (as a QgsCoordinateReferenceSystem value)
1011+
GeometryWkbType = 0x1001, //!< Available for geometry field types which accept geometries of a specific WKB type only (as a QgsWkbTypes::Type value)
1012+
CustomProperty = 0x100000, //!< Starting point for custom user set properties
1013+
};
1014+
Q_ENUM( FieldMetadataProperty )
1015+
10031016
/**
10041017
* Specifies how a selection should be applied.
10051018
*

‎src/core/qgsfield.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,26 @@ QString QgsField::comment() const
167167
return d->comment;
168168
}
169169

170+
QMap<int, QVariant> QgsField::metadata() const
171+
{
172+
return d->metadata;
173+
}
174+
175+
QVariant QgsField::metadata( Qgis::FieldMetadataProperty property ) const
176+
{
177+
return d->metadata.value( static_cast< int >( property ) );
178+
}
179+
180+
void QgsField::setMetadata( const QMap<int, QVariant> metadata )
181+
{
182+
d->metadata = metadata;
183+
}
184+
185+
void QgsField::setMetadata( Qgis::FieldMetadataProperty property, const QVariant &value )
186+
{
187+
d->metadata[ static_cast< int >( property )] = value;
188+
}
189+
170190
bool QgsField::isNumeric() const
171191
{
172192
return d->type == QVariant::Double || d->type == QVariant::Int || d->type == QVariant::UInt || d->type == QVariant::LongLong || d->type == QVariant::ULongLong;
@@ -374,7 +394,7 @@ QString QgsField::displayString( const QVariant &v ) const
374394
else if ( d->typeName.compare( QLatin1String( "json" ), Qt::CaseInsensitive ) == 0 || d->typeName == QLatin1String( "jsonb" ) )
375395
{
376396
const QJsonDocument doc = QJsonDocument::fromVariant( v );
377-
return QString::fromUtf8( doc.toJson().data() );
397+
return QString::fromUtf8( doc.toJson().constData() );
378398
}
379399
else if ( d->type == QVariant::ByteArray )
380400
{
@@ -722,6 +742,7 @@ QDataStream &operator<<( QDataStream &out, const QgsField &field )
722742
out << field.constraints().constraintDescription();
723743
out << static_cast< quint32 >( field.subType() );
724744
out << static_cast< int >( field.splitPolicy() );
745+
out << field.metadata();
725746
return out;
726747
}
727748

@@ -749,10 +770,11 @@ QDataStream &operator>>( QDataStream &in, QgsField &field )
749770
QString defaultValueExpression;
750771
QString constraintExpression;
751772
QString constraintDescription;
773+
QMap< int, QVariant > metadata;
752774

753775
in >> name >> type >> typeName >> length >> precision >> comment >> alias
754776
>> defaultValueExpression >> applyOnUpdate >> constraints >> originNotNull >> originUnique >> originExpression >> strengthNotNull >> strengthUnique >> strengthExpression >>
755-
constraintExpression >> constraintDescription >> subType >> splitPolicy;
777+
constraintExpression >> constraintDescription >> subType >> splitPolicy >> metadata;
756778
field.setName( name );
757779
field.setType( static_cast< QVariant::Type >( type ) );
758780
field.setTypeName( typeName );
@@ -787,5 +809,6 @@ QDataStream &operator>>( QDataStream &in, QgsField &field )
787809
fieldConstraints.setConstraintExpression( constraintExpression, constraintDescription );
788810
field.setConstraints( fieldConstraints );
789811
field.setSubType( static_cast< QVariant::Type >( subType ) );
812+
field.setMetadata( metadata );
790813
return in;
791814
}

‎src/core/qgsfield.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <QSharedDataPointer>
2323
#include "qgis_core.h"
2424
#include "qgis_sip.h"
25+
#include "qgis.h"
2526

2627
typedef QList<int> QgsAttributeList SIP_SKIP;
2728

@@ -216,6 +217,42 @@ class CORE_EXPORT QgsField
216217
*/
217218
QString comment() const;
218219

220+
/**
221+
* Returns the map of field metadata.
222+
*
223+
* Map keys should match values from the Qgis::FieldMetadataProperty enum.
224+
*
225+
* \see setMetadata()
226+
* \since QGIS 3.30
227+
*/
228+
QMap< int, QVariant > metadata() const;
229+
230+
/**
231+
* Returns a specific metadata \a property.
232+
*
233+
* \see setMetadata()
234+
* \since QGIS 3.30
235+
*/
236+
QVariant metadata( Qgis::FieldMetadataProperty property ) const;
237+
238+
/**
239+
* Sets the map of field \a metadata.
240+
*
241+
* Map keys should match values from the Qgis::FieldMetadataProperty enum.
242+
*
243+
* \see metadata()
244+
* \since QGIS 3.30
245+
*/
246+
void setMetadata( const QMap< int, QVariant > metadata );
247+
248+
/**
249+
* Sets a metadata \a property to \a value.
250+
*
251+
* \see metadata()
252+
* \since QGIS 3.30
253+
*/
254+
void setMetadata( Qgis::FieldMetadataProperty property, const QVariant &value );
255+
219256
/**
220257
* Returns if this field is numeric. Any integer or floating point type
221258
* will return TRUE for this.

‎src/core/qgsfield_p.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,16 @@ class QgsFieldPrivate : public QSharedData
5555
const QString &typeName = QString(),
5656
int len = 0,
5757
int prec = 0,
58-
const QString &comment = QString() )
58+
const QString &comment = QString(),
59+
const QMap< int, QVariant > &metadata = QMap< int, QVariant >() )
5960
: name( name )
6061
, type( type )
6162
, subType( subType )
6263
, typeName( typeName )
6364
, length( len )
6465
, precision( prec )
6566
, comment( comment )
67+
, metadata( metadata )
6668
{
6769
}
6870

@@ -75,6 +77,7 @@ class QgsFieldPrivate : public QSharedData
7577
, length( other.length )
7678
, precision( other.precision )
7779
, comment( other.comment )
80+
, metadata( other.metadata )
7881
, alias( other.alias )
7982
, flags( other.flags )
8083
, defaultValueDefinition( other.defaultValueDefinition )
@@ -91,6 +94,7 @@ class QgsFieldPrivate : public QSharedData
9194
{
9295
return ( ( name == other.name ) && ( type == other.type ) && ( subType == other.subType )
9396
&& ( length == other.length ) && ( precision == other.precision )
97+
&& ( metadata == other.metadata )
9498
&& ( alias == other.alias ) && ( defaultValueDefinition == other.defaultValueDefinition )
9599
&& ( constraints == other.constraints ) && ( flags == other.flags )
96100
&& ( splitPolicy == other.splitPolicy )
@@ -118,6 +122,9 @@ class QgsFieldPrivate : public QSharedData
118122
//! Comment
119123
QString comment;
120124

125+
//! Field metadata. Keys should match Qgis::FieldMetadataProperty values.
126+
QMap< int, QVariant > metadata;
127+
121128
//! Alias for field name (friendly name shown to users)
122129
QString alias;
123130

‎tests/src/core/testqgsfield.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ void TestQgsField::copy()
103103
original.setConstraints( constraints );
104104
original.setReadOnly( true );
105105
original.setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio );
106+
original.setMetadata( {{ 1, QStringLiteral( "abc" )}, {2, 5 }} );
106107
QgsField copy( original );
107108
QVERIFY( copy == original );
108109

@@ -121,6 +122,7 @@ void TestQgsField::assignment()
121122
original.setConstraints( constraints );
122123
original.setReadOnly( true );
123124
original.setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio );
125+
original.setMetadata( {{ 1, QStringLiteral( "abc" )}, {2, 5 }} );
124126
QgsField copy;
125127
copy = original;
126128
QVERIFY( copy == original );
@@ -193,6 +195,19 @@ void TestQgsField::gettersSetters()
193195

194196
field.setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio );
195197
QCOMPARE( field.splitPolicy(), Qgis::FieldDomainSplitPolicy::GeometryRatio );
198+
199+
field.setMetadata( {{ static_cast< int >( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" )}, {2, 5 }} );
200+
QMap< int, QVariant> expected {{ static_cast< int >( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" )}, {2, 5 }};
201+
QCOMPARE( field.metadata(), expected );
202+
QVERIFY( !field.metadata( Qgis::FieldMetadataProperty::GeometryWkbType ).isValid() );
203+
QCOMPARE( field.metadata( Qgis::FieldMetadataProperty::GeometryCrs ).toString(), QStringLiteral( "abc" ) );
204+
field.setMetadata( Qgis::FieldMetadataProperty::GeometryWkbType, QStringLiteral( "def" ) );
205+
QCOMPARE( field.metadata( Qgis::FieldMetadataProperty::GeometryWkbType ).toString(), QStringLiteral( "def" ) );
206+
207+
expected = QMap< int, QVariant> {{ static_cast< int >( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" )}, {2, 5 }
208+
, {static_cast<int>( Qgis::FieldMetadataProperty::GeometryWkbType ), QStringLiteral( "def" ) }
209+
};
210+
QCOMPARE( field.metadata(), expected );
196211
}
197212

198213
void TestQgsField::isNumeric()
@@ -336,6 +351,13 @@ void TestQgsField::equality()
336351
field2.setSplitPolicy( Qgis::FieldDomainSplitPolicy::GeometryRatio );
337352
QVERIFY( field1 == field2 );
338353

354+
field1.setMetadata( {{ static_cast< int >( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" )}, {2, 5 }} );
355+
QVERIFY( !( field1 == field2 ) );
356+
QVERIFY( field1 != field2 );
357+
field2.setMetadata( {{ static_cast< int >( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" )}, {2, 5 }} );
358+
QVERIFY( field1 == field2 );
359+
QVERIFY( !( field1 != field2 ) );
360+
339361
QgsFieldConstraints constraints1;
340362
QgsFieldConstraints constraints2;
341363
constraints1.setDomainName( QStringLiteral( "d" ) );
@@ -856,6 +878,7 @@ void TestQgsField::dataStream()
856878
constraints.setConstraintExpression( QStringLiteral( "constraint expression" ), QStringLiteral( "description" ) );
857879
constraints.setConstraintStrength( QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintStrengthSoft );
858880
original.setConstraints( constraints );
881+
original.setMetadata( {{ static_cast< int >( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" )}, {2, 5 }} );
859882

860883
QByteArray ba;
861884
QDataStream ds( &ba, QIODevice::ReadWrite );

0 commit comments

Comments
 (0)
Please sign in to comment.