Skip to content

Commit 8e7691a

Browse files
committed
[OGR provider] Expose OGR FID as first attribute when it is meaningful
Useful for GPKG and other database based drivers - For OGR drivers that have GetFIDColumn() != '', expose it as a QGIS field. - When creating features, use the value potentially provided in this first field, as the feature id to force to OGR. Disallow changing it in changeAttributeValues()
1 parent 0c62441 commit 8e7691a

File tree

6 files changed

+175
-23
lines changed

6 files changed

+175
-23
lines changed

src/providers/ogr/qgsogrexpressioncompiler.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ QgsSqlExpressionCompiler::Result QgsOgrExpressionCompiler::compile( const QgsExp
4343
return Fail;
4444
else if ( mSource->mDriverName == "MSSQLSpatial" )
4545
return Fail;
46+
else if ( mSource->mDriverName == "GPKG" )
47+
return Fail;
4648

4749
return QgsSqlExpressionCompiler::compile( exp );
4850
}

src/providers/ogr/qgsogrfeatureiterator.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource* source, bool
8282
// filter if we choose to ignore them (fixes #11223)
8383
if (( mSource->mDriverName != "VRT" && mSource->mDriverName != "OGR_VRT" ) || mRequest.filterRect().isNull() )
8484
{
85-
QgsOgrProviderUtils::setRelevantFields( ogrLayer, mSource->mFields.count(), mFetchGeometry, attrs );
85+
QgsOgrProviderUtils::setRelevantFields( ogrLayer, mSource->mFields.count(), mFetchGeometry, attrs, mSource->mFirstFieldIsFid );
8686
}
8787

8888
// spatial query to select features
@@ -279,8 +279,15 @@ bool QgsOgrFeatureIterator::close()
279279

280280
void QgsOgrFeatureIterator::getFeatureAttribute( OGRFeatureH ogrFet, QgsFeature & f, int attindex )
281281
{
282+
if ( mSource->mFirstFieldIsFid && attindex == 0 )
283+
{
284+
f.setAttribute( 0, static_cast<qint64>( OGR_F_GetFID( ogrFet ) ) );
285+
return;
286+
}
287+
288+
int attindexWithoutFid = ( mSource->mFirstFieldIsFid ) ? attindex - 1 : attindex;
282289
bool ok = false;
283-
QVariant value = QgsOgrUtils::getOgrFeatureAttribute( ogrFet, mSource->mFields, attindex, mSource->mEncoding, &ok );
290+
QVariant value = QgsOgrUtils::getOgrFeatureAttribute( ogrFet, mSource->mFieldsWithoutFid, attindexWithoutFid, mSource->mEncoding, &ok );
284291
if ( !ok )
285292
return;
286293

@@ -354,7 +361,10 @@ QgsOgrFeatureSource::QgsOgrFeatureSource( const QgsOgrProvider* p )
354361
mSubsetString = p->mSubsetString;
355362
mEncoding = p->mEncoding; // no copying - this is a borrowed pointer from Qt
356363
mFields = p->mAttributeFields;
364+
for ( int i = ( p->mFirstFieldIsFid ) ? 1 : 0; i < mFields.size(); i++ )
365+
mFieldsWithoutFid.append( mFields.at( i ) );
357366
mDriverName = p->ogrDriverName;
367+
mFirstFieldIsFid = p->mFirstFieldIsFid;
358368
mOgrGeometryTypeFilter = wkbFlatten( p->mOgrGeometryTypeFilter );
359369
QgsOgrConnPool::instance()->ref( mFilePath );
360370
}

src/providers/ogr/qgsogrfeatureiterator.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class QgsOgrFeatureSource : public QgsAbstractFeatureSource
4040
QString mSubsetString;
4141
QTextCodec* mEncoding;
4242
QgsFields mFields;
43+
bool mFirstFieldIsFid;
44+
QgsFields mFieldsWithoutFid;
4345
OGRwkbGeometryType mOgrGeometryTypeFilter;
4446
QString mDriverName;
4547

src/providers/ogr/qgsogrprovider.cpp

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ QgsVectorLayerImport::ImportError QgsOgrProvider::createEmptyLayer(
271271
272272
QgsOgrProvider::QgsOgrProvider( QString const & uri )
273273
: QgsVectorDataProvider( uri )
274+
, mFirstFieldIsFid( false )
274275
, ogrDataSource( nullptr )
275276
, mExtent( nullptr )
276277
, ogrLayer( nullptr )
@@ -733,6 +734,21 @@ void QgsOgrProvider::loadFields()
733734
OGRFeatureDefnH fdef = OGR_L_GetLayerDefn( ogrLayer );
734735
if ( fdef )
735736
{
737+
// Expose the OGR FID if it comes from a "real" column (typically GPKG)
738+
// and make sure that this FID column is not exposed as a regular OGR field (shouldn't happen normally)
739+
mFirstFieldIsFid = !( EQUAL( OGR_L_GetFIDColumn( ogrLayer ), "" ) ) &&
740+
OGR_FD_GetFieldIndex( fdef, OGR_L_GetFIDColumn( ogrLayer ) ) < 0;
741+
if ( mFirstFieldIsFid )
742+
{
743+
mAttributeFields.append(
744+
QgsField(
745+
OGR_L_GetFIDColumn( ogrLayer ),
746+
QVariant::LongLong,
747+
"Integer64"
748+
)
749+
);
750+
}
751+
736752
for ( int i = 0; i < OGR_FD_GetFieldCount( fdef ); ++i )
737753
{
738754
OGRFieldDefnH fldDef = OGR_FD_GetFieldDefn( fdef, i );
@@ -817,23 +833,23 @@ QString QgsOgrProvider::storageType() const
817833

818834
void QgsOgrProvider::setRelevantFields( OGRLayerH ogrLayer, bool fetchGeometry, const QgsAttributeList &fetchAttributes )
819835
{
820-
QgsOgrProviderUtils::setRelevantFields( ogrLayer, mAttributeFields.count(), fetchGeometry, fetchAttributes );
836+
QgsOgrProviderUtils::setRelevantFields( ogrLayer, mAttributeFields.count(), fetchGeometry, fetchAttributes, mFirstFieldIsFid );
821837
}
822838

823839

824-
void QgsOgrProviderUtils::setRelevantFields( OGRLayerH ogrLayer, int fieldCount, bool fetchGeometry, const QgsAttributeList &fetchAttributes )
840+
void QgsOgrProviderUtils::setRelevantFields( OGRLayerH ogrLayer, int fieldCount, bool fetchGeometry, const QgsAttributeList &fetchAttributes, bool firstAttrIsFid )
825841
{
826842
#if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 1800
827843
if ( OGR_L_TestCapability( ogrLayer, OLCIgnoreFields ) )
828844
{
829845
QVector<const char*> ignoredFields;
830846
OGRFeatureDefnH featDefn = OGR_L_GetLayerDefn( ogrLayer );
831-
for ( int i = 0; i < fieldCount; i++ )
847+
for ( int i = ( firstAttrIsFid ? 1 : 0 ); i < fieldCount; i++ )
832848
{
833849
if ( !fetchAttributes.contains( i ) )
834850
{
835851
// add to ignored fields
836-
ignoredFields.append( OGR_Fld_GetNameRef( OGR_FD_GetFieldDefn( featDefn, i ) ) );
852+
ignoredFields.append( OGR_Fld_GetNameRef( OGR_FD_GetFieldDefn( featDefn, firstAttrIsFid ? i - 1 : i ) ) );
837853
}
838854
}
839855

@@ -1025,45 +1041,65 @@ bool QgsOgrProvider::addFeature( QgsFeature& f )
10251041

10261042
QgsLocaleNumC l;
10271043

1044+
int qgisAttId = ( mFirstFieldIsFid ) ? 1 : 0;
1045+
// If the first attribute is the FID and the user has set it, then use it
1046+
if ( mFirstFieldIsFid && attrs.count() > 0 )
1047+
{
1048+
QVariant attrFid = attrs.at( 0 );
1049+
if ( !attrFid.isNull() )
1050+
{
1051+
bool ok = false;
1052+
qlonglong id = attrFid.toLongLong( &ok );
1053+
if ( ok )
1054+
{
1055+
#if GDAL_VERSION_MAJOR >= 2
1056+
OGR_F_SetFID( feature, static_cast<GIntBig>( id ) );
1057+
#else
1058+
OGR_F_SetFID( feature, static_cast<long>( id ) );
1059+
#endif
1060+
}
1061+
}
1062+
}
1063+
10281064
//add possible attribute information
1029-
for ( int targetAttributeId = 0; targetAttributeId < attrs.count(); ++targetAttributeId )
1065+
for ( int ogrAttId = 0; qgisAttId < attrs.count(); ++qgisAttId, ++ogrAttId )
10301066
{
10311067
// don't try to set field from attribute map if it's not present in layer
1032-
if ( targetAttributeId < 0 || targetAttributeId >= OGR_FD_GetFieldCount( fdef ) )
1068+
if ( ogrAttId >= OGR_FD_GetFieldCount( fdef ) )
10331069
continue;
10341070

10351071
//if(!s.isEmpty())
10361072
// continue;
10371073
//
1038-
OGRFieldDefnH fldDef = OGR_FD_GetFieldDefn( fdef, targetAttributeId );
1074+
OGRFieldDefnH fldDef = OGR_FD_GetFieldDefn( fdef, ogrAttId );
10391075
OGRFieldType type = OGR_Fld_GetType( fldDef );
10401076

1041-
QVariant attrVal = attrs.at( targetAttributeId );
1077+
QVariant attrVal = attrs.at( qgisAttId );
10421078
if ( attrVal.isNull() || ( type != OFTString && attrVal.toString().isEmpty() ) )
10431079
{
1044-
OGR_F_UnsetField( feature, targetAttributeId );
1080+
OGR_F_UnsetField( feature, ogrAttId );
10451081
}
10461082
else
10471083
{
10481084
switch ( type )
10491085
{
10501086
case OFTInteger:
1051-
OGR_F_SetFieldInteger( feature, targetAttributeId, attrVal.toInt() );
1087+
OGR_F_SetFieldInteger( feature, ogrAttId, attrVal.toInt() );
10521088
break;
10531089

10541090

10551091
#if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 2000000
10561092
case OFTInteger64:
1057-
OGR_F_SetFieldInteger64( feature, targetAttributeId, attrVal.toLongLong() );
1093+
OGR_F_SetFieldInteger64( feature, ogrAttId, attrVal.toLongLong() );
10581094
break;
10591095
#endif
10601096

10611097
case OFTReal:
1062-
OGR_F_SetFieldDouble( feature, targetAttributeId, attrVal.toDouble() );
1098+
OGR_F_SetFieldDouble( feature, ogrAttId, attrVal.toDouble() );
10631099
break;
10641100

10651101
case OFTDate:
1066-
OGR_F_SetFieldDateTime( feature, targetAttributeId,
1102+
OGR_F_SetFieldDateTime( feature, ogrAttId,
10671103
attrVal.toDate().year(),
10681104
attrVal.toDate().month(),
10691105
attrVal.toDate().day(),
@@ -1072,7 +1108,7 @@ bool QgsOgrProvider::addFeature( QgsFeature& f )
10721108
break;
10731109

10741110
case OFTTime:
1075-
OGR_F_SetFieldDateTime( feature, targetAttributeId,
1111+
OGR_F_SetFieldDateTime( feature, ogrAttId,
10761112
0, 0, 0,
10771113
attrVal.toTime().hour(),
10781114
attrVal.toTime().minute(),
@@ -1081,7 +1117,7 @@ bool QgsOgrProvider::addFeature( QgsFeature& f )
10811117
break;
10821118

10831119
case OFTDateTime:
1084-
OGR_F_SetFieldDateTime( feature, targetAttributeId,
1120+
OGR_F_SetFieldDateTime( feature, ogrAttId,
10851121
attrVal.toDateTime().date().year(),
10861122
attrVal.toDateTime().date().month(),
10871123
attrVal.toDateTime().date().day(),
@@ -1093,14 +1129,14 @@ bool QgsOgrProvider::addFeature( QgsFeature& f )
10931129

10941130
case OFTString:
10951131
QgsDebugMsg( QString( "Writing string attribute %1 with %2, encoding %3" )
1096-
.arg( targetAttributeId )
1132+
.arg( qgisAttId )
10971133
.arg( attrVal.toString(),
10981134
mEncoding->name().data() ) );
1099-
OGR_F_SetFieldString( feature, targetAttributeId, mEncoding->fromUnicode( attrVal.toString() ).constData() );
1135+
OGR_F_SetFieldString( feature, ogrAttId, mEncoding->fromUnicode( attrVal.toString() ).constData() );
11001136
break;
11011137

11021138
default:
1103-
QgsMessageLog::logMessage( tr( "type %1 for attribute %2 not found" ).arg( type ).arg( targetAttributeId ), tr( "OGR" ) );
1139+
QgsMessageLog::logMessage( tr( "type %1 for attribute %2 not found" ).arg( type ).arg( qgisAttId ), tr( "OGR" ) );
11041140
break;
11051141
}
11061142
}
@@ -1113,9 +1149,16 @@ bool QgsOgrProvider::addFeature( QgsFeature& f )
11131149
}
11141150
else
11151151
{
1116-
long id = OGR_F_GetFID( feature );
1152+
QgsFeatureId id = static_cast<QgsFeatureId>( OGR_F_GetFID( feature ) );
11171153
if ( id >= 0 )
1154+
{
11181155
f.setFeatureId( id );
1156+
1157+
if ( mFirstFieldIsFid && attrs.count() > 0 )
1158+
{
1159+
f.setAttribute( 0, id );
1160+
}
1161+
}
11191162
}
11201163
OGR_F_Destroy( feature );
11211164

@@ -1215,6 +1258,12 @@ bool QgsOgrProvider::deleteAttributes( const QgsAttributeIds &attributes )
12151258
qSort( attrsLst.begin(), attrsLst.end(), qGreater<int>() );
12161259
Q_FOREACH ( int attr, attrsLst )
12171260
{
1261+
if ( attr == 0 && mFirstFieldIsFid )
1262+
{
1263+
pushError( tr( "Cannot delete feature id column" ) );
1264+
res = false;
1265+
break;
1266+
}
12181267
if ( OGR_L_DeleteField( ogrLayer, attr ) != OGRERR_NONE )
12191268
{
12201269
pushError( tr( "OGR error deleting field %1: %2" ).arg( attr ).arg( CPLGetLastErrorMsg() ) );
@@ -1266,6 +1315,21 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
12661315
for ( QgsAttributeMap::const_iterator it2 = attr.begin(); it2 != attr.end(); ++it2 )
12671316
{
12681317
int f = it2.key();
1318+
if ( mFirstFieldIsFid )
1319+
{
1320+
if ( f == 0 )
1321+
{
1322+
if ( it2->toLongLong() != fid )
1323+
{
1324+
pushError( tr( "Changing feature id of feature %1 is not allowed." ).arg( fid ) );
1325+
continue;
1326+
}
1327+
}
1328+
else
1329+
{
1330+
--f;
1331+
}
1332+
}
12691333

12701334
OGRFieldDefnH fd = OGR_F_GetFieldDefnRef( of, f );
12711335
if ( !fd )

src/providers/ogr/qgsogrprovider.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ class QgsOgrProvider : public QgsVectorDataProvider
295295
QString ogrWkbGeometryTypeName( OGRwkbGeometryType type ) const;
296296
OGRwkbGeometryType ogrWkbGeometryTypeFromName( const QString& typeName ) const;
297297
QgsFields mAttributeFields;
298+
bool mFirstFieldIsFid;
298299
OGRDataSourceH ogrDataSource;
299300
OGREnvelope* mExtent;
300301

@@ -365,7 +366,7 @@ class QgsOgrProvider : public QgsVectorDataProvider
365366
class QgsOgrProviderUtils
366367
{
367368
public:
368-
static void setRelevantFields( OGRLayerH ogrLayer, int fieldCount, bool fetchGeometry, const QgsAttributeList &fetchAttributes );
369+
static void setRelevantFields( OGRLayerH ogrLayer, int fieldCount, bool fetchGeometry, const QgsAttributeList &fetchAttributes, bool firstAttrIsFid );
369370
static OGRLayerH setSubsetString( OGRLayerH layer, OGRDataSourceH ds, QTextCodec* encoding, const QString& subsetString );
370371
static QByteArray quotedIdentifier( QByteArray field, const QString& ogrDriverName );
371372

tests/src/python/test_provider_ogr_gpkg.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import glob
2121
from osgeo import gdal, ogr
2222

23-
from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry
23+
from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsFeatureRequest
2424
from qgis.testing import start_app, unittest
2525
from utilities import unitTestDataPath
2626

@@ -81,6 +81,79 @@ def testCurveGeometryType(self):
8181
got = [f.geometry().exportToWkt(0) for f in vl.getFeatures()][0]
8282
self.assertEqual(got, 'CurvePolygon ((0 0, 0 1, 1 1, 0 0))')
8383

84+
def testFidSupport(self):
85+
86+
version_num = int(gdal.VersionInfo('VERSION_NUM'))
87+
if version_num < GDAL_COMPUTE_VERSION(2, 0, 0):
88+
return
89+
90+
tmpfile = os.path.join(self.basetestpath, 'testFidSupport.gpkg')
91+
ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
92+
lyr = ds.CreateLayer('test', geom_type=ogr.wkbPoint)
93+
lyr.CreateField(ogr.FieldDefn('strfield', ogr.OFTString))
94+
f = ogr.Feature(lyr.GetLayerDefn())
95+
f.SetFID(12)
96+
f.SetField(0, 'foo')
97+
lyr.CreateFeature(f)
98+
f = None
99+
ds = None
100+
101+
vl = QgsVectorLayer(u'{}'.format(tmpfile), u'test', u'ogr')
102+
self.assertEqual(len(vl.fields()), 2)
103+
got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures()]
104+
self.assertEqual(got, [(12, 'foo')])
105+
106+
got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("strfield = 'foo'"))]
107+
self.assertEqual(got, [(12, 'foo')])
108+
109+
got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 12"))]
110+
self.assertEqual(got, [(12, 'foo')])
111+
112+
result = [f['strfield'] for f in vl.dataProvider().getFeatures(QgsFeatureRequest().setSubsetOfAttributes(['strfield'], vl.dataProvider().fields()))]
113+
self.assertEqual(result, ['foo'])
114+
115+
result = [f['fid'] for f in vl.dataProvider().getFeatures(QgsFeatureRequest().setSubsetOfAttributes(['fid'], vl.dataProvider().fields()))]
116+
self.assertEqual(result, [12])
117+
118+
# Test that when the 'fid' field is not set, regular insertion is done
119+
f = QgsFeature()
120+
f.setFields(vl.fields())
121+
f.setAttributes([None, 'automatic_id'])
122+
(res, out_f) = vl.dataProvider().addFeatures([f])
123+
self.assertEqual(out_f[0].id(), 13)
124+
self.assertEqual(out_f[0].attribute('fid'), 13)
125+
self.assertEqual(out_f[0].attribute('strfield'), 'automatic_id')
126+
127+
# Test that when the 'fid' field is set, it is really used to set the id
128+
f = QgsFeature()
129+
f.setFields(vl.fields())
130+
f.setAttributes([9876543210, 'bar'])
131+
(res, out_f) = vl.dataProvider().addFeatures([f])
132+
self.assertEqual(out_f[0].id(), 9876543210)
133+
self.assertEqual(out_f[0].attribute('fid'), 9876543210)
134+
self.assertEqual(out_f[0].attribute('strfield'), 'bar')
135+
136+
got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 9876543210"))]
137+
self.assertEqual(got, [(9876543210, 'bar')])
138+
139+
self.assertTrue(vl.dataProvider().changeAttributeValues({9876543210: {1: 'baz'}}))
140+
141+
got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 9876543210"))]
142+
self.assertEqual(got, [(9876543210, 'baz')])
143+
144+
self.assertTrue(vl.dataProvider().changeAttributeValues({9876543210: {0: 9876543210, 1: 'baw'}}))
145+
146+
got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 9876543210"))]
147+
self.assertEqual(got, [(9876543210, 'baw')])
148+
149+
# Not allowed: changing the fid regular field
150+
self.assertTrue(vl.dataProvider().changeAttributeValues({9876543210: {0: 12, 1: 'baw'}}))
151+
152+
got = [(f.attribute('fid'), f.attribute('strfield')) for f in vl.getFeatures(QgsFeatureRequest().setFilterExpression("fid = 9876543210"))]
153+
self.assertEqual(got, [(9876543210, 'baw')])
154+
155+
# Cannot delete fid
156+
self.assertFalse(vl.dataProvider().deleteAttributes([0]))
84157

85158
if __name__ == '__main__':
86159
unittest.main()

0 commit comments

Comments
 (0)