48 #include <QDomDocument> 
   51 #include <QRegularExpression> 
   53 #include <ogr_srs_api.h> 
   60 #ifdef HAVE_SPATIALITE 
   63 #include <spatialite.h> 
   67 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable" 
   68 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource" 
   69 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider" 
   70 #define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount" 
   71 #define CUSTOM_PROPERTY_ORIGINAL_LAYERID "remoteLayerId" 
   72 #define CUSTOM_PROPERTY_LAYERNAME_SUFFIX "layerNameSuffix" 
   73 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin" 
   74 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath" 
   95   if ( layerIds.isEmpty() )
 
  100   const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
 
  101   if ( createOfflineDb( dbPath, containerType ) )
 
  104     const int rc = database.
open( dbPath );
 
  105     if ( rc != SQLITE_OK )
 
  107       showWarning( tr( 
"Could not open the SpatiaLite database" ) );
 
  112       createLoggingTables( database.get() );
 
  117       for ( 
int i = 0; i < layerIds.count(); i++ )
 
  125           convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
 
  133       if ( projectTitle.isEmpty() )
 
  137       projectTitle += QLatin1String( 
" (offline)" );
 
  168   QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
 
  169   QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
 
  171   for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
 
  173     QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
 
  175     if ( !offlineLayer || !offlineLayer->
isValid() )
 
  177       QgsDebugMsgLevel( QStringLiteral( 
"Skipping offline layer %1 because it is an invalid layer" ).arg( layer_it.key() ), 4 );
 
  186     QString remoteName = offlineLayer->
name();
 
  188     if ( remoteName.endsWith( remoteNameSuffix ) )
 
  189       remoteName.chop( remoteNameSuffix.size() );
 
  192     std::shared_ptr<QgsVectorLayer> remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
 
  194     if ( ! remoteLayer->isValid() )
 
  196       QgsDebugMsgLevel( QStringLiteral( 
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer" ).arg( offlineLayer->
id() ), 4 );
 
  201     if ( remoteLayer->providerType().contains( QLatin1String( 
"WFS" ), Qt::CaseInsensitive ) )
 
  212     const QString sql = QStringLiteral( 
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( offlineLayer->
id() );
 
  213     const int layerId = sqlQueryInt( database.get(), sql, -1 );
 
  217       QgsDebugMsgLevel( QStringLiteral( 
"Skipping offline layer %1 because it failed to determine the offline editing layer id" ).arg( offlineLayer->
id() ), 4 );
 
  221     remoteLayersByOfflineId.insert( layerId, remoteLayer );
 
  222     offlineLayersByOfflineId.insert( layerId, offlineLayer );
 
  225   QgsDebugMsgLevel( QStringLiteral( 
"Found %1 offline layers in total" ).arg( offlineLayersByOfflineId.count() ), 4 );
 
  227   QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
 
  228   if ( useTransaction )
 
  230     for ( 
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
 
  232       const QString connectionString = QgsTransaction::connectionString( remoteLayer->source() );
 
  233       const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
 
  234       std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
 
  236       if ( !transactionGroup.get() )
 
  237         transactionGroup = std::make_shared<QgsTransactionGroup>();
 
  239       if ( !transactionGroup->addLayer( remoteLayer.get() ) )
 
  241         QgsDebugMsgLevel( QStringLiteral( 
"Failed to add a layer %1 into transaction group, will be modified without transaction" ).arg( remoteLayer->name() ), 4 );
 
  245       transactionGroups.insert( pair, transactionGroup );
 
  248     QgsDebugMsgLevel( QStringLiteral( 
"Created %1 transaction groups" ).arg( transactionGroups.count() ), 4 );
 
  251   const QList<int> offlineIds = remoteLayersByOfflineId.keys();
 
  252   for ( 
int offlineLayerId : offlineIds )
 
  254     std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
 
  255     QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
 
  258     if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
 
  260       QgsDebugMsgLevel( QStringLiteral( 
"Failed to turn layer %1 into editing mode" ).arg( remoteLayer->name() ), 4 );
 
  265     const int commitNo = getCommitNo( database.get() );
 
  266     QgsDebugMsgLevel( QStringLiteral( 
"Found %1 commits" ).arg( commitNo ), 4 );
 
  268     for ( 
int i = 0; i < commitNo; i++ )
 
  270       QgsDebugMsgLevel( QStringLiteral( 
"Apply commits chronologically from %1" ).arg( offlineLayer->
name() ), 4 );
 
  272       applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
 
  273       applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
 
  274       applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
 
  277     applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
 
  278     applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
 
  282   for ( 
int offlineLayerId : offlineIds )
 
  284     std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
 
  285     QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
 
  287     if ( !remoteLayer->isEditable() )
 
  290     if ( remoteLayer->commitChanges() )
 
  293       updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
 
  297       sql = QStringLiteral( 
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  298       sqlExec( database.get(), sql );
 
  299       sql = QStringLiteral( 
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  300       sqlExec( database.get(), sql );
 
  301       sql = QStringLiteral( 
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  302       sqlExec( database.get(), sql );
 
  303       sql = QStringLiteral( 
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  304       sqlExec( database.get(), sql );
 
  305       sql = QStringLiteral( 
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  306       sqlExec( database.get(), sql );
 
  310       showWarning( remoteLayer->commitErrors().join( QLatin1Char( 
'\n' ) ) );
 
  317     remoteLayer->reload(); 
 
  318     offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
 
  334     const QgsFields fields = remoteLayer->fields();
 
  337       if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf( 
field.
name() ) ) ).isEmpty() )
 
  349   const QString sql = QStringLiteral( 
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
 
  350   sqlExec( database.get(), sql );
 
  354 void QgsOfflineEditing::initializeSpatialMetadata( 
sqlite3 *sqlite_handle )
 
  356 #ifdef HAVE_SPATIALITE 
  358   if ( !sqlite_handle )
 
  361   char **results = 
nullptr;
 
  363   int ret = sqlite3_get_table( sqlite_handle, 
"select count(*) from sqlite_master", &results, &rows, &columns, 
nullptr );
 
  364   if ( ret != SQLITE_OK )
 
  369     for ( 
int i = 1; i <= rows; i++ )
 
  370       count = atoi( results[( i * columns ) + 0] );
 
  373   sqlite3_free_table( results );
 
  378   bool above41 = 
false;
 
  379   ret = sqlite3_get_table( sqlite_handle, 
"select spatialite_version()", &results, &rows, &columns, 
nullptr );
 
  380   if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
 
  382     const QString version = QString::fromUtf8( results[1] );
 
  383 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 
  384     QStringList parts = version.split( 
' ', QString::SkipEmptyParts );
 
  386     const QStringList parts = version.split( 
' ', Qt::SkipEmptyParts );
 
  388     if ( !parts.empty() )
 
  390 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 
  391       QStringList verparts = parts.at( 0 ).split( 
'.', QString::SkipEmptyParts );
 
  393       const QStringList verparts = parts.at( 0 ).split( 
'.', Qt::SkipEmptyParts );
 
  395       above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
 
  399   sqlite3_free_table( results );
 
  402   char *errMsg = 
nullptr;
 
  403   ret = sqlite3_exec( sqlite_handle, above41 ? 
"SELECT InitSpatialMetadata(1)" : 
"SELECT InitSpatialMetadata()", 
nullptr, 
nullptr, &errMsg );
 
  405   if ( ret != SQLITE_OK )
 
  407     QString errCause = tr( 
"Unable to initialize SpatialMetadata:\n" );
 
  408     errCause += QString::fromUtf8( errMsg );
 
  409     showWarning( errCause );
 
  410     sqlite3_free( errMsg );
 
  413   spatial_ref_sys_init( sqlite_handle, 0 );
 
  415   ( void )sqlite_handle;
 
  419 bool QgsOfflineEditing::createOfflineDb( 
const QString &offlineDbPath, ContainerType containerType )
 
  422   char *errMsg = 
nullptr;
 
  423   const QFile newDb( offlineDbPath );
 
  424   if ( newDb.exists() )
 
  426     QFile::remove( offlineDbPath );
 
  431   const QFileInfo fullPath = QFileInfo( offlineDbPath );
 
  432   const QDir path = fullPath.dir();
 
  435   QDir().mkpath( path.absolutePath() );
 
  438   const QString dbPath = newDb.fileName();
 
  441   switch ( containerType )
 
  445       OGRSFDriverH hGpkgDriver = OGRGetDriverByName( 
"GPKG" );
 
  448         showWarning( tr( 
"Creation of database failed. GeoPackage driver not found." ) );
 
  455         showWarning( tr( 
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  467   ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 
nullptr );
 
  471     QString errCause = tr( 
"Could not create a new database\n" );
 
  473     showWarning( errCause );
 
  477   ret = sqlite3_exec( database.get(), 
"PRAGMA foreign_keys = 1", 
nullptr, 
nullptr, &errMsg );
 
  478   if ( ret != SQLITE_OK )
 
  480     showWarning( tr( 
"Unable to activate FOREIGN_KEY constraints" ) );
 
  481     sqlite3_free( errMsg );
 
  484   initializeSpatialMetadata( database.get() );
 
  488 void QgsOfflineEditing::createLoggingTables( 
sqlite3 *db )
 
  491   QString sql = QStringLiteral( 
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
 
  494   sql = QStringLiteral( 
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
 
  497   sql = QStringLiteral( 
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
 
  501   sql = QStringLiteral( 
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
 
  505   sql = QStringLiteral( 
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)" );
 
  509   sql = QStringLiteral( 
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
 
  510   sql += QLatin1String( 
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
 
  514   sql = QStringLiteral( 
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
 
  518   sql = QStringLiteral( 
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
 
  522   sql = QStringLiteral( 
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
 
  526   sql = QStringLiteral( 
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
 
  534 void QgsOfflineEditing::convertToOfflineLayer( 
QgsVectorLayer *layer, 
sqlite3 *db, 
const QString &offlineDbPath, 
bool onlySelected, ContainerType containerType, 
const QString &layerNameSuffix )
 
  536   if ( !layer || !layer->
isValid() )
 
  538     QgsDebugMsgLevel( QStringLiteral( 
"Layer %1 is invalid and cannot be copied" ).arg( layer ? layer->
id() : QStringLiteral( 
"<UNKNOWN>" ) ), 4 );
 
  542   const QString tableName = layer->
id();
 
  543   QgsDebugMsgLevel( QStringLiteral( 
"Creating offline table %1 ..." ).arg( tableName ), 4 );
 
  546   std::unique_ptr<QgsVectorLayer> newLayer;
 
  548   switch ( containerType )
 
  552 #ifdef HAVE_SPATIALITE 
  554       QString sql = QStringLiteral( 
"CREATE TABLE '%1' (" ).arg( tableName );
 
  557       for ( 
const auto &
field : providerFields )
 
  560         const QVariant::Type type = 
field.
type();
 
  561         if ( type == QVariant::Int || type == QVariant::LongLong )
 
  563           dataType = QStringLiteral( 
"INTEGER" );
 
  565         else if ( type == QVariant::Double )
 
  567           dataType = QStringLiteral( 
"REAL" );
 
  569         else if ( type == QVariant::String )
 
  571           dataType = QStringLiteral( 
"TEXT" );
 
  573         else if ( type == QVariant::StringList  || type == QVariant::List )
 
  575           dataType = QStringLiteral( 
"TEXT" );
 
  576           showWarning( tr( 
"Field '%1' from layer %2 has been converted from a list to a string of comma-separated values." ).arg( 
field.
name(), layer->
name() ) );
 
  580           showWarning( tr( 
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( 
field.
name(), QVariant::typeToName( type ) ) );
 
  583         sql += delim + QStringLiteral( 
"'%1' %2" ).arg( 
field.
name(), dataType );
 
  588       int rc = sqlExec( db, sql );
 
  599             geomType = QStringLiteral( 
"POINT" );
 
  602             geomType = QStringLiteral( 
"MULTIPOINT" );
 
  605             geomType = QStringLiteral( 
"LINESTRING" );
 
  608             geomType = QStringLiteral( 
"MULTILINESTRING" );
 
  611             geomType = QStringLiteral( 
"POLYGON" );
 
  614             geomType = QStringLiteral( 
"MULTIPOLYGON" );
 
  621         QString zmInfo = QStringLiteral( 
"XY" );
 
  630         if ( layer->
crs().
authid().startsWith( QLatin1String( 
"EPSG:" ), Qt::CaseInsensitive ) )
 
  632           epsgCode = layer->
crs().
authid().mid( 5 );
 
  637           showWarning( tr( 
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
 
  640         const QString sqlAddGeom = QStringLiteral( 
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
 
  641                                    .arg( tableName, epsgCode, geomType, zmInfo );
 
  644         const QString sqlCreateIndex = QStringLiteral( 
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
 
  646         if ( rc == SQLITE_OK )
 
  648           rc = sqlExec( db, sqlAddGeom );
 
  649           if ( rc == SQLITE_OK )
 
  651             rc = sqlExec( db, sqlCreateIndex );
 
  656       if ( rc != SQLITE_OK )
 
  658         showWarning( tr( 
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
 
  663       const QString connectionString = QStringLiteral( 
"dbname='%1' table='%2'%3 sql=" )
 
  665                                              tableName, layer->
isSpatial() ? 
"(Geometry)" : 
"" );
 
  667       newLayer = std::make_unique<QgsVectorLayer>( connectionString,
 
  668                  layer->
name() + layerNameSuffix, QStringLiteral( 
"spatialite" ), options );
 
  672       showWarning( tr( 
"No Spatialite support available" ) );
 
  680       char **options = 
nullptr;
 
  682       options = CSLSetNameValue( options, 
"OVERWRITE", 
"YES" );
 
  683       options = CSLSetNameValue( options, 
"IDENTIFIER", tr( 
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
 
  684       options = CSLSetNameValue( options, 
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
 
  687       const QString fidBase( QStringLiteral( 
"fid" ) );
 
  688       QString fid = fidBase;
 
  692         fid = fidBase + 
'_' + QString::number( counter );
 
  695       if ( counter == 10000 )
 
  697         showWarning( tr( 
"Cannot make FID-name for GPKG " ) );
 
  701       options = CSLSetNameValue( options, 
"FID", fid.toUtf8().constData() );
 
  705         options = CSLSetNameValue( options, 
"GEOMETRY_COLUMN", 
"geom" );
 
  706         options = CSLSetNameValue( options, 
"SPATIAL_INDEX", 
"YES" );
 
  709       OGRSFDriverH hDriver = 
nullptr;
 
  712       OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS, 
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
 
  713       CSLDestroy( options );
 
  718         showWarning( tr( 
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  723       for ( 
const auto &
field : providerFields )
 
  726         const QVariant::Type type = 
field.
type();
 
  727         OGRFieldType ogrType( OFTString );
 
  728         OGRFieldSubType ogrSubType = OFSTNone;
 
  729         if ( type == QVariant::Int )
 
  730           ogrType = OFTInteger;
 
  731         else if ( type == QVariant::LongLong )
 
  732           ogrType = OFTInteger64;
 
  733         else if ( type == QVariant::Double )
 
  735         else if ( type == QVariant::Time )
 
  737         else if ( type == QVariant::Date )
 
  739         else if ( type == QVariant::DateTime )
 
  740           ogrType = OFTDateTime;
 
  741         else if ( type == QVariant::Bool )
 
  743           ogrType = OFTInteger;
 
  744           ogrSubType = OFSTBoolean;
 
  746         else if ( type == QVariant::StringList || type == QVariant::List )
 
  749           ogrSubType = OFSTJSON;
 
  750           showWarning( tr( 
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
 
  758         OGR_Fld_SetWidth( fld.get(), ogrWidth );
 
  759         if ( ogrSubType != OFSTNone )
 
  760           OGR_Fld_SetSubType( fld.get(), ogrSubType );
 
  762         if ( OGR_L_CreateField( hLayer, fld.get(), 
true ) != OGRERR_NONE )
 
  764           showWarning( tr( 
"Creation of field %1 failed (OGR error: %2)" )
 
  765                        .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  773       OGR_L_ResetReading( hLayer );
 
  774       if ( CPLGetLastErrorType() != CE_None )
 
  776         const QString msg( tr( 
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  782       const QString uri = QStringLiteral( 
"%1|layername=%2|option:QGIS_FORCE_WAL=ON" ).arg( offlineDbPath,  tableName );
 
  784       newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, QStringLiteral( 
"ogr" ), layerOptions );
 
  789   if ( newLayer && newLayer->isValid() )
 
  793     newLayer->startEditing();
 
  801       if ( !selectedFids.isEmpty() )
 
  815     long long featureCount = 1;
 
  816     const int remotePkIdx = getLayerPkIdx( layer );
 
  818     QList<QgsFeatureId> remoteFeatureIds;
 
  819     QStringList remoteFeaturePks;
 
  822       remoteFeatureIds << f.
id();
 
  823       remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
 
  830       QgsAttributes newAttrs( containerType == 
GPKG ? attrs.count() + 1 : attrs.count() );
 
  831       for ( 
int it = 0; it < attrs.count(); ++it )
 
  833         QVariant attr = attrs.at( it );
 
  838         newAttrs[column++] = attr;
 
  842       newLayer->addFeature( f );
 
  846     if ( newLayer->commitChanges() )
 
  852       const int layerId = getOrCreateLayerId( db, layer->
id() );
 
  853       QList<QgsFeatureId> offlineFeatureIds;
 
  858         offlineFeatureIds << f.
id();
 
  862       sqlExec( db, QStringLiteral( 
"BEGIN" ) );
 
  863       const int remoteCount = remoteFeatureIds.size();
 
  864       for ( 
int i = 0; i < remoteCount; i++ )
 
  867         if ( i < offlineFeatureIds.count() )
 
  869           addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
 
  873           showWarning( tr( 
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
 
  878       sqlExec( db, QStringLiteral( 
"COMMIT" ) );
 
  882       showWarning( newLayer->commitErrors().join( QLatin1Char( 
'\n' ) ) );
 
  896     QStringList notNullFieldNames;
 
  905     layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
 
  917         if ( notNullFieldNames.contains( 
field.
name() ) )
 
  919           notNullFieldNames.removeAll( 
field.
name() );
 
  930 void QgsOfflineEditing::applyAttributesAdded( 
QgsVectorLayer *remoteLayer, 
sqlite3 *db, 
int layerId, 
int commitNo )
 
  932   Q_ASSERT( remoteLayer );
 
  934   const QString sql = QStringLiteral( 
"SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
 
  935   QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
 
  938   const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
 
  941   QMap < QVariant::Type, QString  > typeNameLookup;
 
  942   for ( 
int i = 0; i < nativeTypes.size(); i++ )
 
  950   for ( 
int i = 0; i < fields.size(); i++ )
 
  954     if ( typeNameLookup.contains( 
field.
type() ) )
 
  962       showWarning( QStringLiteral( 
"Could not add attribute '%1' of type %2" ).arg( 
field.
name() ).arg( 
field.
type() ) );
 
  971   Q_ASSERT( offlineLayer );
 
  972   Q_ASSERT( remoteLayer );
 
  974   const QString sql = QStringLiteral( 
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
 
  975   const QList<int> featureIdInts = sqlQueryInts( db, sql );
 
  977   for ( 
const int id : featureIdInts )
 
  997   const int newAttrsCount = remoteLayer->
fields().
count();
 
  998   for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
 
 1002     const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
 
 1005     for ( 
int it = 0; it < attrs.count(); ++it )
 
 1007       const int remoteAttributeIndex = attrLookup.value( it, -1 );
 
 1009       if ( remoteAttributeIndex == -1 )
 
 1011       QVariant attr = attrs.at( it );
 
 1012       if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
 
 1014         if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
 
 1016           attr = attr.toStringList();
 
 1023       else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
 
 1025         if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
 
 1027           attr = attr.toList();
 
 1034       newAttrs[ remoteAttributeIndex ] = attr;
 
 1045 void QgsOfflineEditing::applyFeaturesRemoved( 
QgsVectorLayer *remoteLayer, 
sqlite3 *db, 
int layerId )
 
 1047   Q_ASSERT( remoteLayer );
 
 1049   const QString sql = QStringLiteral( 
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
 
 1050   const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
 
 1055   for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
 
 1057     const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
 
 1066   Q_ASSERT( offlineLayer );
 
 1067   Q_ASSERT( remoteLayer );
 
 1069   const QString sql = QStringLiteral( 
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
 
 1070   const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
 
 1074   QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
 
 1076   for ( 
int i = 0; i < values.size(); i++ )
 
 1078     const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
 
 1079     QgsDebugMsgLevel( QStringLiteral( 
"Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
 
 1081     const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
 
 1082     QVariant attr = values.at( i ).value;
 
 1083     if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
 
 1087     else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
 
 1098 void QgsOfflineEditing::applyGeometryChanges( 
QgsVectorLayer *remoteLayer, 
sqlite3 *db, 
int layerId, 
int commitNo )
 
 1100   Q_ASSERT( remoteLayer );
 
 1102   const QString sql = QStringLiteral( 
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
 
 1103   const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
 
 1107   for ( 
int i = 0; i < values.size(); i++ )
 
 1109     const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
 
 1119   Q_ASSERT( remoteLayer );
 
 1125   QMap < QgsFeatureId, QString > newRemoteFids;
 
 1132   const int remotePkIdx = getLayerPkIdx( remoteLayer );
 
 1137     if ( offlineFid( db, layerId, f.
id() ) == -1 )
 
 1139       newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
 
 1147   const QString sql = QStringLiteral( 
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
 
 1148   const QList<int> newOfflineFids = sqlQueryInts( db, sql );
 
 1150   if ( newRemoteFids.size() != newOfflineFids.size() )
 
 1158     sqlExec( db, QStringLiteral( 
"BEGIN" ) );
 
 1159     for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
 
 1161       addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
 
 1163     sqlExec( db, QStringLiteral( 
"COMMIT" ) );
 
 1170   Q_ASSERT( offlineLayer );
 
 1171   Q_ASSERT( remoteLayer );
 
 1175   QMap < 
int , 
int  > attrLookup;
 
 1178   for ( 
int i = 0; i < offlineAttrs.size(); i++ )
 
 1187 void QgsOfflineEditing::showWarning( 
const QString &message )
 
 1189   emit 
warning( tr( 
"Offline Editing Plugin" ), message );
 
 1196   if ( !dbPath.isEmpty() )
 
 1199     const int rc = database.
open( absoluteDbPath );
 
 1200     if ( rc != SQLITE_OK )
 
 1202       QgsDebugMsg( QStringLiteral( 
"Could not open the SpatiaLite logging database" ) );
 
 1203       showWarning( tr( 
"Could not open the SpatiaLite logging database" ) );
 
 1208     QgsDebugMsg( QStringLiteral( 
"dbPath is empty!" ) );
 
 1213 int QgsOfflineEditing::getOrCreateLayerId( 
sqlite3 *db, 
const QString &qgisLayerId )
 
 1215   QString sql = QStringLiteral( 
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
 
 1216   int layerId = sqlQueryInt( db, sql, -1 );
 
 1217   if ( layerId == -1 )
 
 1220     sql = QStringLiteral( 
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
 
 1221     const int newLayerId = sqlQueryInt( db, sql, -1 );
 
 1224     sql = QStringLiteral( 
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
 
 1229     sql = QStringLiteral( 
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
 
 1232     layerId = newLayerId;
 
 1238 int QgsOfflineEditing::getCommitNo( 
sqlite3 *db )
 
 1240   const QString sql = QStringLiteral( 
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
 
 1241   return sqlQueryInt( db, sql, -1 );
 
 1244 void QgsOfflineEditing::increaseCommitNo( 
sqlite3 *db )
 
 1246   const QString sql = QStringLiteral( 
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
 
 1252   const QString sql = QStringLiteral( 
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
 
 1258   const int pkIdx = getLayerPkIdx( remoteLayer );
 
 1262     const QString sql = QStringLiteral( 
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
 
 1263     return sqlQueryInt( db, sql, -1 );
 
 1266   const QString sql = QStringLiteral( 
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
 
 1267   QString defaultValue;
 
 1268   const QString pkValue = sqlQueryStr( db, sql, defaultValue );
 
 1270   if ( pkValue.isNull() )
 
 1275   const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
 
 1286   const QString sql = QStringLiteral( 
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
 
 1287   return sqlQueryInt( db, sql, -1 );
 
 1292   const QString sql = QStringLiteral( 
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
 
 1293   return ( sqlQueryInt( db, sql, 0 ) > 0 );
 
 1296 int QgsOfflineEditing::sqlExec( 
sqlite3 *db, 
const QString &sql )
 
 1298   char *errmsg = 
nullptr;
 
 1299   const int rc = sqlite3_exec( db, sql.toUtf8(), 
nullptr, 
nullptr, &errmsg );
 
 1300   if ( rc != SQLITE_OK )
 
 1302     showWarning( errmsg );
 
 1307 QString QgsOfflineEditing::sqlQueryStr( 
sqlite3 *db, 
const QString &sql, QString &defaultValue )
 
 1309   sqlite3_stmt *stmt = 
nullptr;
 
 1310   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1312     showWarning( sqlite3_errmsg( db ) );
 
 1313     return defaultValue;
 
 1316   QString value = defaultValue;
 
 1317   const int ret = sqlite3_step( stmt );
 
 1318   if ( ret == SQLITE_ROW )
 
 1320     value = QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 0 ) ) );
 
 1322   sqlite3_finalize( stmt );
 
 1327 int QgsOfflineEditing::sqlQueryInt( 
sqlite3 *db, 
const QString &sql, 
int defaultValue )
 
 1329   sqlite3_stmt *stmt = 
nullptr;
 
 1330   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1332     showWarning( sqlite3_errmsg( db ) );
 
 1333     return defaultValue;
 
 1336   int value = defaultValue;
 
 1337   const int ret = sqlite3_step( stmt );
 
 1338   if ( ret == SQLITE_ROW )
 
 1340     value = sqlite3_column_int( stmt, 0 );
 
 1342   sqlite3_finalize( stmt );
 
 1347 QList<int> QgsOfflineEditing::sqlQueryInts( 
sqlite3 *db, 
const QString &sql )
 
 1351   sqlite3_stmt *stmt = 
nullptr;
 
 1352   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1354     showWarning( sqlite3_errmsg( db ) );
 
 1358   int ret = sqlite3_step( stmt );
 
 1359   while ( ret == SQLITE_ROW )
 
 1361     values << sqlite3_column_int( stmt, 0 );
 
 1363     ret = sqlite3_step( stmt );
 
 1365   sqlite3_finalize( stmt );
 
 1370 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( 
sqlite3 *db, 
const QString &sql )
 
 1372   QList<QgsField> values;
 
 1374   sqlite3_stmt *stmt = 
nullptr;
 
 1375   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1377     showWarning( sqlite3_errmsg( db ) );
 
 1381   int ret = sqlite3_step( stmt );
 
 1382   while ( ret == SQLITE_ROW )
 
 1384     const QgsField field( QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 0 ) ) ),
 
 1385                           static_cast< QVariant::Type 
>( sqlite3_column_int( stmt, 1 ) ),
 
 1387                           sqlite3_column_int( stmt, 2 ),
 
 1388                           sqlite3_column_int( stmt, 3 ),
 
 1389                           QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 4 ) ) ) );
 
 1392     ret = sqlite3_step( stmt );
 
 1394   sqlite3_finalize( stmt );
 
 1403   sqlite3_stmt *stmt = 
nullptr;
 
 1404   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1406     showWarning( sqlite3_errmsg( db ) );
 
 1410   int ret = sqlite3_step( stmt );
 
 1411   while ( ret == SQLITE_ROW )
 
 1413     values << sqlite3_column_int( stmt, 0 );
 
 1415     ret = sqlite3_step( stmt );
 
 1417   sqlite3_finalize( stmt );
 
 1422 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( 
sqlite3 *db, 
const QString &sql )
 
 1424   AttributeValueChanges values;
 
 1426   sqlite3_stmt *stmt = 
nullptr;
 
 1427   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1429     showWarning( sqlite3_errmsg( db ) );
 
 1433   int ret = sqlite3_step( stmt );
 
 1434   while ( ret == SQLITE_ROW )
 
 1436     AttributeValueChange change;
 
 1437     change.fid = sqlite3_column_int( stmt, 0 );
 
 1438     change.attr = sqlite3_column_int( stmt, 1 );
 
 1439     change.value = QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 2 ) ) );
 
 1442     ret = sqlite3_step( stmt );
 
 1444   sqlite3_finalize( stmt );
 
 1449 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( 
sqlite3 *db, 
const QString &sql )
 
 1451   GeometryChanges values;
 
 1453   sqlite3_stmt *stmt = 
nullptr;
 
 1454   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1456     showWarning( sqlite3_errmsg( db ) );
 
 1460   int ret = sqlite3_step( stmt );
 
 1461   while ( ret == SQLITE_ROW )
 
 1463     GeometryChange change;
 
 1464     change.fid = sqlite3_column_int( stmt, 0 );
 
 1465     change.geom_wkt = QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 1 ) ) );
 
 1468     ret = sqlite3_step( stmt );
 
 1470   sqlite3_finalize( stmt );
 
 1475 void QgsOfflineEditing::committedAttributesAdded( 
const QString &qgisLayerId, 
const QList<QgsField> &addedAttributes )
 
 1482   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1483   const int commitNo = getCommitNo( database.get() );
 
 1487     const QString sql = QStringLiteral( 
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
 
 1495     sqlExec( database.get(), sql );
 
 1498   increaseCommitNo( database.get() );
 
 1501 void QgsOfflineEditing::committedFeaturesAdded( 
const QString &qgisLayerId, 
const QgsFeatureList &addedFeatures )
 
 1508   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1512   const QString dataSourceString = layer->
source();
 
 1518   if ( !offlinePath.contains( 
".gpkg" ) )
 
 1520     tableName = uri.
table();
 
 1525     const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
 
 1526     tableName = decodedUri.value( QStringLiteral( 
"layerName" ) ).toString();
 
 1527     if ( tableName.isEmpty() )
 
 1529       showWarning( tr( 
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
 
 1534   const QString sql = QStringLiteral( 
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
 
 1535   const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
 
 1536   for ( 
int i = newFeatureIds.size() - 1; i >= 0; i-- )
 
 1538     const QString sql = QStringLiteral( 
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
 
 1540                         .arg( newFeatureIds.at( i ) );
 
 1541     sqlExec( database.get(), sql );
 
 1545 void QgsOfflineEditing::committedFeaturesRemoved( 
const QString &qgisLayerId, 
const QgsFeatureIds &deletedFeatureIds )
 
 1552   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1556     if ( isAddedFeature( database.get(), layerId, 
id ) )
 
 1559       const QString sql = QStringLiteral( 
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( 
id );
 
 1560       sqlExec( database.get(), sql );
 
 1564       const QString sql = QStringLiteral( 
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
 
 1567       sqlExec( database.get(), sql );
 
 1572 void QgsOfflineEditing::committedAttributeValuesChanges( 
const QString &qgisLayerId, 
const QgsChangedAttributesMap &changedAttrsMap )
 
 1579   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1580   const int commitNo = getCommitNo( database.get() );
 
 1582   for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
 
 1585     if ( isAddedFeature( database.get(), layerId, fid ) )
 
 1591     for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
 
 1593       QString value = it.value().type() == QVariant::StringList || it.value().type() == QVariant::List ? 
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
 
 1594       value.replace( QLatin1String( 
"'" ), QLatin1String( 
"''" ) ); 
 
 1595       const QString sql = QStringLiteral( 
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
 
 1601       sqlExec( database.get(), sql );
 
 1605   increaseCommitNo( database.get() );
 
 1608 void QgsOfflineEditing::committedGeometriesChanges( 
const QString &qgisLayerId, 
const QgsGeometryMap &changedGeometries )
 
 1615   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1616   const int commitNo = getCommitNo( database.get() );
 
 1618   for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
 
 1621     if ( isAddedFeature( database.get(), layerId, fid ) )
 
 1627     const QString sql = QStringLiteral( 
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
 
 1631                         .arg( geom.
asWkt() );
 
 1632     sqlExec( database.get(), sql );
 
 1637   increaseCommitNo( database.get() );
 
 1640 void QgsOfflineEditing::startListenFeatureChanges()
 
 1642   QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
 
 1651              this, &QgsOfflineEditing::committedAttributesAdded );
 
 1653              this, &QgsOfflineEditing::committedAttributeValuesChanges );
 
 1655              this, &QgsOfflineEditing::committedGeometriesChanges );
 
 1658            this, &QgsOfflineEditing::committedFeaturesAdded );
 
 1660            this, &QgsOfflineEditing::committedFeaturesRemoved );
 
 1663 void QgsOfflineEditing::stopListenFeatureChanges()
 
 1665   QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
 
 1674                 this, &QgsOfflineEditing::committedAttributesAdded );
 
 1676                 this, &QgsOfflineEditing::committedAttributeValuesChanges );
 
 1678                 this, &QgsOfflineEditing::committedGeometriesChanges );
 
 1681               this, &QgsOfflineEditing::committedFeaturesAdded );
 
 1683               this, &QgsOfflineEditing::committedFeaturesRemoved );
 
 1686 void QgsOfflineEditing::setupLayer( 
QgsMapLayer *layer )
 
 1690   if ( 
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
 
 1701 int QgsOfflineEditing::getLayerPkIdx( 
const QgsVectorLayer *layer )
 const 
 1704   if ( pkAttrs.length() == 1 )
 
 1707     const QVariant::Type pkType = pkField.
type();
 
 1709     if ( pkType == QVariant::String )
 
 1718 QString QgsOfflineEditing::sqlEscape( QString value )
 const 
 1720   if ( value.isNull() )
 
 1721     return QStringLiteral( 
"NULL" );
 
 1723   value.replace( 
"'", 
"''" );
 
 1725   return QStringLiteral( 
"'%1'" ).arg( value );
 
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
 
Class for storing the component parts of a RDBMS data source URI (e.g.
 
QString table() const
Returns the table name stored in the URI.
 
QString database() const
Returns the database name stored in the URI.
 
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
 
Wrapper for iterator of features from vector data provider or vector layer.
 
bool nextFeature(QgsFeature &f)
 
This class wraps a request for features to a vector layer (or directly its vector data provider).
 
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
 
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
 
FilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
 
@ FilterFids
Filter using feature IDs.
 
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
 
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
 
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
 
@ ConstraintNotNull
Field may not be null.
 
@ ConstraintUnique
Field must have a unique value.
 
Q_GADGET Constraints constraints
 
Encapsulate a field in an attribute table or data source.
 
QVariant::Type subType() const
If the field is a collection, gets its element's type.
 
QgsFieldConstraints constraints
 
void setTypeName(const QString &typeName)
Set the field type.
 
Container of fields for a vector layer.
 
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
 
int count() const
Returns number of items.
 
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
 
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
 
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
 
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
 
A geometry is the spatial representation of a feature.
 
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
 
QString asWkt(int precision=17) const
Exports the geometry to WKT.
 
static Q_INVOKABLE QString encodeValue(const QVariant &value)
Encodes a value to a JSON string representation, adding appropriate quotations and escaping where req...
 
static Q_INVOKABLE QVariantList parseArray(const QString &json, QVariant::Type type=QVariant::Invalid)
Parse a simple array (depth=1)
 
Base class for all map layer types.
 
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
 
QString source() const
Returns the source for the layer.
 
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
 
QString providerType() const
Returns the provider type (provider key) for this layer.
 
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
 
void editingStarted()
Emitted when editing on this layer has started.
 
QgsCoordinateReferenceSystem crs
 
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
 
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
 
void setDataSource(const QString &dataSource, const QString &baseName, const QString &provider, bool loadDefaultStyleFlag=false)
Updates the data source of the layer.
 
void progressModeSet(QgsOfflineEditing::ProgressMode mode, long long maximum)
Emitted when the mode for the progress of the current operation is set.
 
void progressUpdated(long long progress)
Emitted with the progress of the current mode.
 
void layerProgressUpdated(int layer, int numLayers)
Emitted whenever a new layer is being processed.
 
bool isOfflineProject() const
Returns true if current project is offline.
 
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected=false, ContainerType containerType=SpatiaLite, const QString &layerNameSuffix=QStringLiteral(" (offline)"))
Convert current project for offline editing.
 
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
 
void progressStopped()
Emitted when the processing of all layers has finished.
 
void synchronize(bool useTransaction=false)
Synchronize to remote layers.
 
ContainerType
Type of offline database container file.
 
void progressStarted()
Emitted when the process has started.
 
static OGRSpatialReferenceH crsToOGRSpatialReference(const QgsCoordinateReferenceSystem &crs)
Returns a OGRSpatialReferenceH corresponding to the specified crs object.
 
QString title() const
Returns the project's title.
 
static QgsProject * instance()
Returns the QgsProject singleton instance.
 
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
 
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the registry.
 
QgsSnappingConfig snappingConfig
 
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
 
QgsCoordinateTransformContext transformContext
 
void setTitle(const QString &title)
Sets the project's title.
 
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean value to the project file.
 
QString readPath(const QString &filename) const
Transforms a filename read from the project file to an absolute path.
 
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
 
bool removeEntry(const QString &scope, const QString &key)
Remove the given key from the specified scope.
 
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
 
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
 
This is a container for configuration of the snapping of the project.
 
This is the base class for vector data providers.
 
long long featureCount() const override=0
Number of features in the layer.
 
QList< QgsVectorDataProvider::NativeType > nativeTypes() const
Returns the names of the supported types.
 
virtual QString defaultValueClause(int fieldIndex) const
Returns any default value clauses which are present at the provider for a specified field index.
 
QgsFields fields() const override=0
Returns the fields associated with this data provider.
 
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override=0
Query the provider for features specified in request.
 
void committedAttributeValuesChanges(const QString &layerId, const QgsChangedAttributesMap &changedAttributesValues)
 
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
 
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
 
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
 
Represents a vector layer which manages a vector based data sets.
 
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
 
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
Emitted when features are added to the provider if not in transaction mode.
 
bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
 
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
 
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
 
void setFieldConstraint(int index, QgsFieldConstraints::Constraint constraint, QgsFieldConstraints::ConstraintStrength strength=QgsFieldConstraints::ConstraintStrengthHard)
Sets a constraint for a specified field index.
 
bool deleteFeature(QgsFeatureId fid, DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
 
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
 
QgsFields fields() const FINAL
Returns the list of fields of this layer.
 
QgsAttributeList attributeList() const
Returns list of attribute indexes.
 
void removeFieldConstraint(int index, QgsFieldConstraints::Constraint constraint)
Removes a constraint for a specified field index.
 
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
Emitted when features are deleted from the provider if not in transaction mode.
 
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
 
Q_INVOKABLE const QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
 
QString dataComment() const
Returns a description for this layer as defined in the data provider.
 
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
 
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
 
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
 
QgsAttributeList primaryKeyAttributes() const
Returns the list of attributes which make up the layer's primary keys.
 
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
 
bool changeGeometry(QgsFeatureId fid, QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature's geometry within the layer's edit buffer (but does not immediately commit the chan...
 
static bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
 
Type
The WKB type describes the number of dimensions a geometry has.
 
static QString displayString(Type type) SIP_HOLDGIL
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
 
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
 
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
 
Unique pointer for spatialite databases, which automatically closes the database when the pointer goe...
 
int open(const QString &path)
Opens the database at the specified file path.
 
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
 
QString errorMessage() const
Returns the most recent error message encountered by the database.
 
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
 
int open(const QString &path)
Opens the database at the specified file path.
 
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
 
std::unique_ptr< std::remove_pointer< OGRFieldDefnH >::type, OGRFldDeleter > ogr_field_def_unique_ptr
Scoped OGR field definition.
 
QMap< int, QVariant > QgsAttributeMap
 
void * OGRSpatialReferenceH
 
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
 
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap
 
QList< QgsFeature > QgsFeatureList
 
QSet< QgsFeatureId > QgsFeatureIds
 
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
 
QList< int > QgsAttributeList
 
#define QgsDebugMsgLevel(str, level)
 
#define CUSTOM_PROPERTY_ORIGINAL_LAYERID
 
#define PROJECT_ENTRY_SCOPE_OFFLINE
 
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
 
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
 
#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX
 
#define CUSTOM_PROPERTY_REMOTE_SOURCE
 
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
 
Setting options for loading vector layers.