@@ -62,6 +62,16 @@ QgsWFSSharedData::~QgsWFSSharedData()
62
62
QgsDebugMsgLevel ( QStringLiteral ( " ~QgsWFSSharedData()" ), 4 );
63
63
64
64
invalidateCache ();
65
+
66
+ mCacheIdDb .reset ();
67
+ if ( !mCacheIdDbname .isEmpty () )
68
+ {
69
+ QFile::remove ( mCacheIdDbname );
70
+ QFile::remove ( mCacheIdDbname + " -wal" );
71
+ QFile::remove ( mCacheIdDbname + " -shm" );
72
+ QgsWFSUtils::releaseCacheDirectory ();
73
+ mCacheIdDbname .clear ();
74
+ }
65
75
}
66
76
67
77
QString QgsWFSSharedData::srsName () const
@@ -215,7 +225,8 @@ bool QgsWFSSharedData::createCache()
215
225
216
226
static QAtomicInt sTmpCounter = 0 ;
217
227
int tmpCounter = ++sTmpCounter ;
218
- mCacheDbname = QDir ( QgsWFSUtils::acquireCacheDirectory () ).filePath ( QStringLiteral ( " wfs_cache_%1.sqlite" ).arg ( tmpCounter ) );
228
+ QString cacheDirectory ( QgsWFSUtils::acquireCacheDirectory () );
229
+ mCacheDbname = QDir ( cacheDirectory ).filePath ( QStringLiteral ( " wfs_cache_%1.sqlite" ).arg ( tmpCounter ) );
219
230
Q_ASSERT ( !QFile::exists ( mCacheDbname ) );
220
231
221
232
QgsFields cacheFields;
@@ -466,6 +477,35 @@ bool QgsWFSSharedData::createCache()
466
477
return false ;
467
478
}
468
479
480
+ // The id_cache should be generated once for the lifetime of QgsWFSConstants
481
+ // to ensure consistency of the ids returned to the user.
482
+ if ( mCacheIdDbname .isEmpty () )
483
+ {
484
+ mCacheIdDbname = QDir ( cacheDirectory ).filePath ( QStringLiteral ( " wfs_id_cache_%1.sqlite" ).arg ( tmpCounter ) );
485
+ Q_ASSERT ( !QFile::exists ( mCacheIdDbname ) );
486
+ if ( mCacheIdDb .open ( mCacheIdDbname ) != SQLITE_OK )
487
+ {
488
+ QgsMessageLog::logMessage ( tr ( " Cannot create temporary id cache" ), tr ( " WFS" ) );
489
+ return false ;
490
+ }
491
+ QString errorMsg;
492
+ bool ok = mCacheIdDb .exec ( QStringLiteral ( " PRAGMA synchronous=OFF" ), errorMsg ) == SQLITE_OK;
493
+ // WAL is needed to avoid reader to block writers
494
+ ok &= mCacheIdDb .exec ( QStringLiteral ( " PRAGMA journal_mode=WAL" ), errorMsg ) == SQLITE_OK;
495
+ // gmlid is the gmlid or fid attribute coming from the GML GetFeature response
496
+ // qgisId is the feature id of the features returned to QGIS. That one should remain the same for a given gmlid even after a layer reload
497
+ // dbId is the feature id of the Spatialite feature in mCacheDataProvider. It might change for a given gmlid after a layer reload
498
+ ok &= mCacheIdDb .exec ( QStringLiteral ( " CREATE TABLE id_cache(gmlid TEXT, dbId INTEGER, qgisId INTEGER)" ), errorMsg ) == SQLITE_OK;
499
+ ok &= mCacheIdDb .exec ( QStringLiteral ( " CREATE INDEX idx_gmlid ON id_cache(gmlid)" ), errorMsg ) == SQLITE_OK;
500
+ ok &= mCacheIdDb .exec ( QStringLiteral ( " CREATE INDEX idx_dbId ON id_cache(dbId)" ), errorMsg ) == SQLITE_OK;
501
+ ok &= mCacheIdDb .exec ( QStringLiteral ( " CREATE INDEX idx_qgisId ON id_cache(qgisId)" ), errorMsg ) == SQLITE_OK;
502
+ if ( !ok )
503
+ {
504
+ QgsDebugMsg ( errorMsg );
505
+ return false ;
506
+ }
507
+ }
508
+
469
509
return true ;
470
510
}
471
511
@@ -572,6 +612,10 @@ int QgsWFSSharedData::getUpdatedCounter()
572
612
573
613
QSet<QString> QgsWFSSharedData::getExistingCachedGmlIds ( const QVector<QgsWFSFeatureGmlIdPair> &featureList )
574
614
{
615
+ // We query the Spatialite cache here, not the persistent id_cache,
616
+ // since we want to know which features in this session we have already
617
+ // downloaded.
618
+
575
619
QString expr;
576
620
bool first = true ;
577
621
QSet<QString> setExistingGmlIds;
@@ -675,46 +719,79 @@ QSet<QString> QgsWFSSharedData::getExistingCachedMD5( const QVector<QgsWFSFeatur
675
719
// Used by WFS-T
676
720
QString QgsWFSSharedData::findGmlId ( QgsFeatureId fid )
677
721
{
678
- if ( !mCacheDataProvider )
722
+ if ( !mCacheIdDb )
679
723
return QString ();
680
- QgsFeatureRequest request;
681
- request.setFilterFid ( fid );
682
724
683
- QgsFields dataProviderFields = mCacheDataProvider ->fields ();
684
- int gmlidIdx = dataProviderFields.indexFromName ( QgsWFSConstants::FIELD_GMLID );
685
-
686
- QgsAttributeList attList;
687
- attList.append ( gmlidIdx );
688
- request.setSubsetOfAttributes ( attList );
689
-
690
- QgsFeatureIterator iterGmlIds ( mCacheDataProvider ->getFeatures ( request ) );
691
- QgsFeature gmlidFeature;
692
- while ( iterGmlIds.nextFeature ( gmlidFeature ) )
725
+ auto sql = QgsSqlite3Mprintf ( " SELECT gmlid FROM id_cache WHERE qgisId = %lld" , fid );
726
+ int resultCode;
727
+ auto stmt = mCacheIdDb .prepare ( sql, resultCode );
728
+ Q_ASSERT ( resultCode == SQLITE_OK );
729
+ if ( stmt.step () == SQLITE_ROW )
693
730
{
694
- const QVariant &v = gmlidFeature.attributes ().value ( gmlidIdx );
695
- return v.toString ();
731
+ return stmt.columnAsText ( 0 );
696
732
}
697
733
return QString ();
698
734
}
699
735
736
+ QgsFeatureIds QgsWFSSharedData::dbIdsFromQgisIds ( const QgsFeatureIds &qgisIds )
737
+ {
738
+ QgsFeatureIds dbIds;
739
+ if ( !mCacheIdDb )
740
+ return dbIds;
741
+ // To avoid excessive memory consumption in expression building, do not
742
+ // query more than 1000 ids at a time.
743
+ bool first = true ;
744
+ QString expr;
745
+ int i = 0 ;
746
+ for ( const auto &qgisId : qgisIds )
747
+ {
748
+ if ( !first )
749
+ expr += ' ,' ;
750
+ else
751
+ {
752
+ expr = QStringLiteral ( " SELECT dbId FROM id_cache WHERE qgisId IN (" );
753
+ first = false ;
754
+ }
755
+ expr += FID_TO_STRING ( qgisId );
756
+
757
+ if ( ( i > 0 && ( i % 1000 ) == 0 ) || i + 1 == qgisIds.size () )
758
+ {
759
+ expr += ' )' ;
760
+
761
+ int resultCode;
762
+ auto stmt = mCacheIdDb .prepare ( expr.toUtf8 ().constData (), resultCode );
763
+ Q_ASSERT ( resultCode == SQLITE_OK );
764
+ while ( stmt.step () == SQLITE_ROW )
765
+ {
766
+ dbIds.insert ( stmt.columnAsInt64 ( 0 ) );
767
+ }
768
+ // Should we check that we got a dbId from every qgisId... ?
769
+
770
+ first = true ;
771
+ }
772
+ i++;
773
+ }
774
+ return dbIds;
775
+ }
776
+
700
777
// Used by WFS-T
701
778
bool QgsWFSSharedData::deleteFeatures ( const QgsFeatureIds &fidlist )
702
779
{
703
- if ( !mCacheDataProvider )
780
+ if ( !mCacheIdDb || ! mCacheDataProvider )
704
781
return false ;
705
782
706
783
{
707
784
QMutexLocker locker ( &mMutex );
708
785
mFeatureCount -= fidlist.size ();
709
786
}
710
787
711
- return mCacheDataProvider ->deleteFeatures ( fidlist );
788
+ return mCacheDataProvider ->deleteFeatures ( dbIdsFromQgisIds ( fidlist ) );
712
789
}
713
790
714
791
// Used by WFS-T
715
792
bool QgsWFSSharedData::changeGeometryValues ( const QgsGeometryMap &geometry_map )
716
793
{
717
- if ( !mCacheDataProvider )
794
+ if ( !mCacheIdDb || ! mCacheDataProvider )
718
795
return false ;
719
796
720
797
// We need to replace the geometry by its bounding box and issue a attribute
@@ -727,22 +804,34 @@ bool QgsWFSSharedData::changeGeometryValues( const QgsGeometryMap &geometry_map
727
804
QgsChangedAttributesMap newChangedAttrMap;
728
805
for ( QgsGeometryMap::const_iterator iter = geometry_map.constBegin (); iter != geometry_map.constEnd (); ++iter )
729
806
{
807
+ auto sql = QgsSqlite3Mprintf ( " SELECT dbId FROM id_cache WHERE qgisId = %lld" , iter.key () );
808
+ int resultCode;
809
+ auto stmt = mCacheIdDb .prepare ( sql, resultCode );
810
+ Q_ASSERT ( resultCode == SQLITE_OK );
811
+ if ( stmt.step () != SQLITE_ROW )
812
+ {
813
+ // shouldn't happen normally
814
+ QgsDebugMsg ( QStringLiteral ( " cannot find dbId corresponding to qgisId = %1" ).arg ( iter.key () ) );
815
+ continue ;
816
+ }
817
+ QgsFeatureId dbId = stmt.columnAsInt64 ( 0 );
818
+
730
819
QByteArray wkb = iter->asWkb ();
731
820
if ( !wkb.isEmpty () )
732
821
{
733
822
QgsAttributeMap newAttrMap;
734
823
newAttrMap[idx] = QString ( wkb.toHex ().data () );
735
- newChangedAttrMap[ iter. key () ] = newAttrMap;
824
+ newChangedAttrMap[ dbId ] = newAttrMap;
736
825
737
826
QgsGeometry polyBoundingBox = QgsGeometry::fromRect ( iter.value ().boundingBox () );
738
- newGeometryMap[ iter. key () ] = polyBoundingBox;
827
+ newGeometryMap[ dbId ] = polyBoundingBox;
739
828
}
740
829
else
741
830
{
742
831
QgsAttributeMap newAttrMap;
743
832
newAttrMap[idx] = QString ();
744
- newChangedAttrMap[ iter. key () ] = newAttrMap;
745
- newGeometryMap[ iter. key () ] = QgsGeometry ();
833
+ newChangedAttrMap[ dbId ] = newAttrMap;
834
+ newGeometryMap[ dbId ] = QgsGeometry ();
746
835
}
747
836
}
748
837
@@ -753,14 +842,25 @@ bool QgsWFSSharedData::changeGeometryValues( const QgsGeometryMap &geometry_map
753
842
// Used by WFS-T
754
843
bool QgsWFSSharedData::changeAttributeValues ( const QgsChangedAttributesMap &attr_map )
755
844
{
756
- if ( !mCacheDataProvider )
845
+ if ( !mCacheIdDb || ! mCacheDataProvider )
757
846
return false ;
758
847
759
848
QgsFields dataProviderFields = mCacheDataProvider ->fields ();
760
849
QgsChangedAttributesMap newMap;
761
850
for ( QgsChangedAttributesMap::const_iterator iter = attr_map.begin (); iter != attr_map.end (); ++iter )
762
851
{
763
- QgsFeatureId fid = iter.key ();
852
+ auto sql = QgsSqlite3Mprintf ( " SELECT dbId FROM id_cache WHERE qgisId = %lld" , iter.key () );
853
+ int resultCode;
854
+ auto stmt = mCacheIdDb .prepare ( sql, resultCode );
855
+ Q_ASSERT ( resultCode == SQLITE_OK );
856
+ if ( stmt.step () != SQLITE_ROW )
857
+ {
858
+ // shouldn't happen normally
859
+ QgsDebugMsg ( QStringLiteral ( " cannot find dbId corresponding to qgisId = %1" ).arg ( iter.key () ) );
860
+ continue ;
861
+ }
862
+ QgsFeatureId dbId = stmt.columnAsInt64 ( 0 );
863
+
764
864
const QgsAttributeMap &attrs = iter.value ();
765
865
if ( attrs.isEmpty () )
766
866
continue ;
@@ -774,7 +874,7 @@ bool QgsWFSSharedData::changeAttributeValues( const QgsChangedAttributesMap &att
774
874
else
775
875
newAttrMap[idx] = siter.value ();
776
876
}
777
- newMap[fid ] = newAttrMap;
877
+ newMap[dbId ] = newAttrMap;
778
878
}
779
879
780
880
return mCacheDataProvider ->changeAttributeValues ( newMap );
@@ -923,10 +1023,57 @@ void QgsWFSSharedData::serializeFeatures( QVector<QgsWFSFeatureGmlIdPair> &featu
923
1023
Q_ASSERT ( featureListToCache.size () == updatedFeatureList.size () );
924
1024
for ( int i = 0 ; i < updatedFeatureList.size (); i++ )
925
1025
{
926
- if ( cacheOk )
927
- updatedFeatureList[i].first .setId ( featureListToCache[i].id () );
1026
+ int resultCode;
1027
+ QgsFeatureId dbId ( cacheOk ? featureListToCache[i].id () : mTotalFeaturesAttemptedToBeCached + i + 1 );
1028
+ QgsFeatureId qgisId;
1029
+ const auto &gmlId ( updatedFeatureList[i].second );
1030
+ if ( gmlId.isEmpty () )
1031
+ {
1032
+ // Degraded case. Won't work properly in reload situations, but we
1033
+ // can't do better.
1034
+ qgisId = dbId;
1035
+ }
928
1036
else
929
- updatedFeatureList[i].first .setId ( mTotalFeaturesAttemptedToBeCached + i + 1 );
1037
+ {
1038
+ auto sql = QgsSqlite3Mprintf ( " SELECT qgisId, dbId FROM id_cache WHERE gmlid = '%q'" ,
1039
+ gmlId.toUtf8 ().constData () );
1040
+ auto stmt = mCacheIdDb .prepare ( sql, resultCode );
1041
+ Q_ASSERT ( resultCode == SQLITE_OK );
1042
+ if ( stmt.step () == SQLITE_ROW )
1043
+ {
1044
+ qgisId = stmt.columnAsInt64 ( 0 );
1045
+ QgsFeatureId oldDbId = stmt.columnAsInt64 ( 1 );
1046
+ if ( dbId != oldDbId )
1047
+ {
1048
+ sql = QgsSqlite3Mprintf ( " UPDATE id_cache SET dbId = %lld WHERE gmlid = '%q'" ,
1049
+ dbId,
1050
+ gmlId.toUtf8 ().constData () );
1051
+ // QgsDebugMsg( QStringLiteral( "%1" ).arg( sql ) );
1052
+ QString errorMsg;
1053
+ if ( mCacheIdDb .exec ( sql, errorMsg ) != SQLITE_OK )
1054
+ {
1055
+ QgsMessageLog::logMessage ( tr ( " Problem when updating WFS id cache: %1 -> %2" ).arg ( sql ).arg ( errorMsg ), tr ( " WFS" ) );
1056
+ }
1057
+ }
1058
+ }
1059
+ else
1060
+ {
1061
+ qgisId = mNextCachedIdQgisId ;
1062
+ mNextCachedIdQgisId ++;
1063
+ sql = QgsSqlite3Mprintf ( " INSERT INTO id_cache (gmlid, dbId, qgisId) VALUES ('%q', %lld, %lld)" ,
1064
+ gmlId.toUtf8 ().constData (),
1065
+ dbId,
1066
+ qgisId );
1067
+ // QgsDebugMsg( QStringLiteral( "%1" ).arg( sql ) );
1068
+ QString errorMsg;
1069
+ if ( mCacheIdDb .exec ( sql, errorMsg ) != SQLITE_OK )
1070
+ {
1071
+ QgsMessageLog::logMessage ( tr ( " Problem when updating WFS id cache: %1 -> %2" ).arg ( sql ).arg ( errorMsg ), tr ( " WFS" ) );
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ updatedFeatureList[i].first .setId ( qgisId );
930
1077
}
931
1078
932
1079
{
@@ -1104,7 +1251,6 @@ void QgsWFSSharedData::invalidateCache()
1104
1251
QFile::remove ( mCacheDbname );
1105
1252
QFile::remove ( mCacheDbname + " -wal" );
1106
1253
QFile::remove ( mCacheDbname + " -shm" );
1107
- QgsWFSUtils::releaseCacheDirectory ();
1108
1254
mCacheDbname .clear ();
1109
1255
}
1110
1256
}
0 commit comments