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           const QString origLayerId = vl->
id();
 
  126           convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
 
  134       if ( projectTitle.isEmpty() )
 
  138       projectTitle += QLatin1String( 
" (offline)" );
 
  169   QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
 
  170   QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
 
  172   for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
 
  174     QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
 
  176     if ( !offlineLayer || !offlineLayer->
isValid() )
 
  178       QgsDebugMsgLevel( QStringLiteral( 
"Skipping offline layer %1 because it is an invalid layer" ).arg( layer_it.key() ), 4 );
 
  187     QString remoteName = offlineLayer->
name();
 
  189     if ( remoteName.endsWith( remoteNameSuffix ) )
 
  190       remoteName.chop( remoteNameSuffix.size() );
 
  193     std::shared_ptr<QgsVectorLayer> remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
 
  195     if ( ! remoteLayer->isValid() )
 
  197       QgsDebugMsgLevel( QStringLiteral( 
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer" ).arg( offlineLayer->
id() ), 4 );
 
  202     if ( remoteLayer->providerType().contains( QLatin1String( 
"WFS" ), Qt::CaseInsensitive ) )
 
  213     const QString sql = QStringLiteral( 
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( offlineLayer->
id() );
 
  214     const int layerId = sqlQueryInt( database.get(), sql, -1 );
 
  218       QgsDebugMsgLevel( QStringLiteral( 
"Skipping offline layer %1 because it failed to determine the offline editing layer id" ).arg( offlineLayer->
id() ), 4 );
 
  222     remoteLayersByOfflineId.insert( layerId, remoteLayer );
 
  223     offlineLayersByOfflineId.insert( layerId, offlineLayer );
 
  226   QgsDebugMsgLevel( QStringLiteral( 
"Found %1 offline layers in total" ).arg( offlineLayersByOfflineId.count() ), 4 );
 
  228   QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
 
  229   if ( useTransaction )
 
  231     for ( 
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
 
  233       const QString connectionString = QgsTransaction::connectionString( remoteLayer->source() );
 
  234       const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
 
  235       std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
 
  237       if ( !transactionGroup.get() )
 
  238         transactionGroup = std::make_shared<QgsTransactionGroup>();
 
  240       if ( !transactionGroup->addLayer( remoteLayer.get() ) )
 
  242         QgsDebugMsgLevel( QStringLiteral( 
"Failed to add a layer %1 into transaction group, will be modified without transaction" ).arg( remoteLayer->name() ), 4 );
 
  246       transactionGroups.insert( pair, transactionGroup );
 
  249     QgsDebugMsgLevel( QStringLiteral( 
"Created %1 transaction groups" ).arg( transactionGroups.count() ), 4 );
 
  252   const QList<int> offlineIds = remoteLayersByOfflineId.keys();
 
  253   for ( 
int offlineLayerId : offlineIds )
 
  255     std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
 
  256     QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
 
  259     if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
 
  261       QgsDebugMsgLevel( QStringLiteral( 
"Failed to turn layer %1 into editing mode" ).arg( remoteLayer->name() ), 4 );
 
  266     const int commitNo = getCommitNo( database.get() );
 
  267     QgsDebugMsgLevel( QStringLiteral( 
"Found %1 commits" ).arg( commitNo ), 4 );
 
  269     for ( 
int i = 0; i < commitNo; i++ )
 
  271       QgsDebugMsgLevel( QStringLiteral( 
"Apply commits chronologically from %1" ).arg( offlineLayer->
name() ), 4 );
 
  273       applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
 
  274       applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
 
  275       applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
 
  278     applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
 
  279     applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
 
  283   for ( 
int offlineLayerId : offlineIds )
 
  285     std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
 
  286     QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
 
  288     if ( !remoteLayer->isEditable() )
 
  291     if ( remoteLayer->commitChanges() )
 
  294       updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
 
  298       sql = QStringLiteral( 
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  299       sqlExec( database.get(), sql );
 
  300       sql = QStringLiteral( 
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  301       sqlExec( database.get(), sql );
 
  302       sql = QStringLiteral( 
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  303       sqlExec( database.get(), sql );
 
  304       sql = QStringLiteral( 
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  305       sqlExec( database.get(), sql );
 
  306       sql = QStringLiteral( 
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  307       sqlExec( database.get(), sql );
 
  311       showWarning( remoteLayer->commitErrors().join( QLatin1Char( 
'\n' ) ) );
 
  318     remoteLayer->reload(); 
 
  319     offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
 
  335     const QgsFields fields = remoteLayer->fields();
 
  338       if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf( 
field.
name() ) ) ).isEmpty() )
 
  350   const QString sql = QStringLiteral( 
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
 
  351   sqlExec( database.get(), sql );
 
  355 void QgsOfflineEditing::initializeSpatialMetadata( 
sqlite3 *sqlite_handle )
 
  357 #ifdef HAVE_SPATIALITE 
  359   if ( !sqlite_handle )
 
  362   char **results = 
nullptr;
 
  364   int ret = sqlite3_get_table( sqlite_handle, 
"select count(*) from sqlite_master", &results, &rows, &columns, 
nullptr );
 
  365   if ( ret != SQLITE_OK )
 
  370     for ( 
int i = 1; i <= rows; i++ )
 
  371       count = atoi( results[( i * columns ) + 0] );
 
  374   sqlite3_free_table( results );
 
  379   bool above41 = 
false;
 
  380   ret = sqlite3_get_table( sqlite_handle, 
"select spatialite_version()", &results, &rows, &columns, 
nullptr );
 
  381   if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
 
  383     const QString version = QString::fromUtf8( results[1] );
 
  384 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 
  385     QStringList parts = version.split( 
' ', QString::SkipEmptyParts );
 
  387     const QStringList parts = version.split( 
' ', Qt::SkipEmptyParts );
 
  389     if ( !parts.empty() )
 
  391 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 
  392       QStringList verparts = parts.at( 0 ).split( 
'.', QString::SkipEmptyParts );
 
  394       const QStringList verparts = parts.at( 0 ).split( 
'.', Qt::SkipEmptyParts );
 
  396       above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
 
  400   sqlite3_free_table( results );
 
  403   char *errMsg = 
nullptr;
 
  404   ret = sqlite3_exec( sqlite_handle, above41 ? 
"SELECT InitSpatialMetadata(1)" : 
"SELECT InitSpatialMetadata()", 
nullptr, 
nullptr, &errMsg );
 
  406   if ( ret != SQLITE_OK )
 
  408     QString errCause = tr( 
"Unable to initialize SpatialMetadata:\n" );
 
  409     errCause += QString::fromUtf8( errMsg );
 
  410     showWarning( errCause );
 
  411     sqlite3_free( errMsg );
 
  414   spatial_ref_sys_init( sqlite_handle, 0 );
 
  416   ( void )sqlite_handle;
 
  420 bool QgsOfflineEditing::createOfflineDb( 
const QString &offlineDbPath, ContainerType containerType )
 
  423   char *errMsg = 
nullptr;
 
  424   const QFile newDb( offlineDbPath );
 
  425   if ( newDb.exists() )
 
  427     QFile::remove( offlineDbPath );
 
  432   const QFileInfo fullPath = QFileInfo( offlineDbPath );
 
  433   const QDir path = fullPath.dir();
 
  436   QDir().mkpath( path.absolutePath() );
 
  439   const QString dbPath = newDb.fileName();
 
  442   switch ( containerType )
 
  446       OGRSFDriverH hGpkgDriver = OGRGetDriverByName( 
"GPKG" );
 
  449         showWarning( tr( 
"Creation of database failed. GeoPackage driver not found." ) );
 
  456         showWarning( tr( 
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  468   ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 
nullptr );
 
  472     QString errCause = tr( 
"Could not create a new database\n" );
 
  474     showWarning( errCause );
 
  478   ret = sqlite3_exec( database.get(), 
"PRAGMA foreign_keys = 1", 
nullptr, 
nullptr, &errMsg );
 
  479   if ( ret != SQLITE_OK )
 
  481     showWarning( tr( 
"Unable to activate FOREIGN_KEY constraints" ) );
 
  482     sqlite3_free( errMsg );
 
  485   initializeSpatialMetadata( database.get() );
 
  489 void QgsOfflineEditing::createLoggingTables( 
sqlite3 *db )
 
  492   QString sql = QStringLiteral( 
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
 
  495   sql = QStringLiteral( 
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
 
  498   sql = QStringLiteral( 
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
 
  502   sql = QStringLiteral( 
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
 
  506   sql = QStringLiteral( 
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)" );
 
  510   sql = QStringLiteral( 
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
 
  511   sql += QLatin1String( 
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
 
  515   sql = QStringLiteral( 
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
 
  519   sql = QStringLiteral( 
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
 
  523   sql = QStringLiteral( 
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
 
  527   sql = QStringLiteral( 
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
 
  535 void QgsOfflineEditing::convertToOfflineLayer( 
QgsVectorLayer *layer, 
sqlite3 *db, 
const QString &offlineDbPath, 
bool onlySelected, ContainerType containerType, 
const QString &layerNameSuffix )
 
  537   if ( !layer || !layer->
isValid() )
 
  539     QgsDebugMsgLevel( QStringLiteral( 
"Layer %1 is invalid and cannot be copied" ).arg( layer ? layer->
id() : QStringLiteral( 
"<UNKNOWN>" ) ), 4 );
 
  543   const QString tableName = layer->
id();
 
  544   QgsDebugMsgLevel( QStringLiteral( 
"Creating offline table %1 ..." ).arg( tableName ), 4 );
 
  547   std::unique_ptr<QgsVectorLayer> newLayer;
 
  549   switch ( containerType )
 
  553 #ifdef HAVE_SPATIALITE 
  555       QString sql = QStringLiteral( 
"CREATE TABLE '%1' (" ).arg( tableName );
 
  558       for ( 
const auto &
field : providerFields )
 
  561         const QVariant::Type type = 
field.
type();
 
  562         if ( type == QVariant::Int || type == QVariant::LongLong )
 
  564           dataType = QStringLiteral( 
"INTEGER" );
 
  566         else if ( type == QVariant::Double )
 
  568           dataType = QStringLiteral( 
"REAL" );
 
  570         else if ( type == QVariant::String )
 
  572           dataType = QStringLiteral( 
"TEXT" );
 
  574         else if ( type == QVariant::StringList  || type == QVariant::List )
 
  576           dataType = QStringLiteral( 
"TEXT" );
 
  577           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() ) );
 
  581           showWarning( tr( 
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( 
field.
name(), QVariant::typeToName( type ) ) );
 
  584         sql += delim + QStringLiteral( 
"'%1' %2" ).arg( 
field.
name(), dataType );
 
  589       int rc = sqlExec( db, sql );
 
  600             geomType = QStringLiteral( 
"POINT" );
 
  603             geomType = QStringLiteral( 
"MULTIPOINT" );
 
  606             geomType = QStringLiteral( 
"LINESTRING" );
 
  609             geomType = QStringLiteral( 
"MULTILINESTRING" );
 
  612             geomType = QStringLiteral( 
"POLYGON" );
 
  615             geomType = QStringLiteral( 
"MULTIPOLYGON" );
 
  622         QString zmInfo = QStringLiteral( 
"XY" );
 
  631         if ( layer->
crs().
authid().startsWith( QLatin1String( 
"EPSG:" ), Qt::CaseInsensitive ) )
 
  633           epsgCode = layer->
crs().
authid().mid( 5 );
 
  638           showWarning( tr( 
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
 
  641         const QString sqlAddGeom = QStringLiteral( 
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
 
  642                                    .arg( tableName, epsgCode, geomType, zmInfo );
 
  645         const QString sqlCreateIndex = QStringLiteral( 
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
 
  647         if ( rc == SQLITE_OK )
 
  649           rc = sqlExec( db, sqlAddGeom );
 
  650           if ( rc == SQLITE_OK )
 
  652             rc = sqlExec( db, sqlCreateIndex );
 
  657       if ( rc != SQLITE_OK )
 
  659         showWarning( tr( 
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
 
  664       const QString connectionString = QStringLiteral( 
"dbname='%1' table='%2'%3 sql=" )
 
  666                                              tableName, layer->
isSpatial() ? 
"(Geometry)" : 
"" );
 
  668       newLayer = std::make_unique<QgsVectorLayer>( connectionString,
 
  669                  layer->
name() + layerNameSuffix, QStringLiteral( 
"spatialite" ), options );
 
  673       showWarning( tr( 
"No Spatialite support available" ) );
 
  681       char **options = 
nullptr;
 
  683       options = CSLSetNameValue( options, 
"OVERWRITE", 
"YES" );
 
  684       options = CSLSetNameValue( options, 
"IDENTIFIER", tr( 
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
 
  685       options = CSLSetNameValue( options, 
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
 
  688       const QString fidBase( QStringLiteral( 
"fid" ) );
 
  689       QString fid = fidBase;
 
  693         fid = fidBase + 
'_' + QString::number( counter );
 
  696       if ( counter == 10000 )
 
  698         showWarning( tr( 
"Cannot make FID-name for GPKG " ) );
 
  702       options = CSLSetNameValue( options, 
"FID", fid.toUtf8().constData() );
 
  706         options = CSLSetNameValue( options, 
"GEOMETRY_COLUMN", 
"geom" );
 
  707         options = CSLSetNameValue( options, 
"SPATIAL_INDEX", 
"YES" );
 
  710       OGRSFDriverH hDriver = 
nullptr;
 
  713       OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS, 
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
 
  714       CSLDestroy( options );
 
  719         showWarning( tr( 
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  724       for ( 
const auto &
field : providerFields )
 
  727         const QVariant::Type type = 
field.
type();
 
  728         OGRFieldType ogrType( OFTString );
 
  729         OGRFieldSubType ogrSubType = OFSTNone;
 
  730         if ( type == QVariant::Int )
 
  731           ogrType = OFTInteger;
 
  732         else if ( type == QVariant::LongLong )
 
  733           ogrType = OFTInteger64;
 
  734         else if ( type == QVariant::Double )
 
  736         else if ( type == QVariant::Time )
 
  738         else if ( type == QVariant::Date )
 
  740         else if ( type == QVariant::DateTime )
 
  741           ogrType = OFTDateTime;
 
  742         else if ( type == QVariant::Bool )
 
  744           ogrType = OFTInteger;
 
  745           ogrSubType = OFSTBoolean;
 
  747         else if ( type == QVariant::StringList || type == QVariant::List )
 
  750           ogrSubType = OFSTJSON;
 
  751           showWarning( tr( 
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
 
  759         OGR_Fld_SetWidth( fld.get(), ogrWidth );
 
  760         if ( ogrSubType != OFSTNone )
 
  761           OGR_Fld_SetSubType( fld.get(), ogrSubType );
 
  763         if ( OGR_L_CreateField( hLayer, fld.get(), 
true ) != OGRERR_NONE )
 
  765           showWarning( tr( 
"Creation of field %1 failed (OGR error: %2)" )
 
  766                        .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  774       OGR_L_ResetReading( hLayer );
 
  775       if ( CPLGetLastErrorType() != CE_None )
 
  777         const QString msg( tr( 
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  783       const QString uri = QStringLiteral( 
"%1|layername=%2" ).arg( offlineDbPath,  tableName );
 
  785       newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, QStringLiteral( 
"ogr" ), layerOptions );
 
  790   if ( newLayer && newLayer->isValid() )
 
  794     newLayer->startEditing();
 
  802       if ( !selectedFids.isEmpty() )
 
  816     long long featureCount = 1;
 
  817     const int remotePkIdx = getLayerPkIdx( layer );
 
  819     QList<QgsFeatureId> remoteFeatureIds;
 
  820     QStringList remoteFeaturePks;
 
  823       remoteFeatureIds << f.
id();
 
  824       remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
 
  831       QgsAttributes newAttrs( containerType == 
GPKG ? attrs.count() + 1 : attrs.count() );
 
  832       for ( 
int it = 0; it < attrs.count(); ++it )
 
  834         QVariant attr = attrs.at( it );
 
  839         newAttrs[column++] = attr;
 
  843       newLayer->addFeature( f );
 
  847     if ( newLayer->commitChanges() )
 
  853       const int layerId = getOrCreateLayerId( db, layer->
id() );
 
  854       QList<QgsFeatureId> offlineFeatureIds;
 
  859         offlineFeatureIds << f.
id();
 
  863       sqlExec( db, QStringLiteral( 
"BEGIN" ) );
 
  864       const int remoteCount = remoteFeatureIds.size();
 
  865       for ( 
int i = 0; i < remoteCount; i++ )
 
  868         if ( i < offlineFeatureIds.count() )
 
  870           addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
 
  874           showWarning( tr( 
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
 
  879       sqlExec( db, QStringLiteral( 
"COMMIT" ) );
 
  883       showWarning( newLayer->commitErrors().join( QLatin1Char( 
'\n' ) ) );
 
  897     QStringList notNullFieldNames;
 
  906     layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
 
  918         if ( notNullFieldNames.contains( 
field.
name() ) )
 
  920           notNullFieldNames.removeAll( 
field.
name() );
 
  931 void QgsOfflineEditing::applyAttributesAdded( 
QgsVectorLayer *remoteLayer, 
sqlite3 *db, 
int layerId, 
int commitNo )
 
  933   Q_ASSERT( remoteLayer );
 
  935   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 );
 
  936   QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
 
  939   const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
 
  942   QMap < QVariant::Type, QString  > typeNameLookup;
 
  943   for ( 
int i = 0; i < nativeTypes.size(); i++ )
 
  951   for ( 
int i = 0; i < fields.size(); i++ )
 
  955     if ( typeNameLookup.contains( 
field.
type() ) )
 
  963       showWarning( QStringLiteral( 
"Could not add attribute '%1' of type %2" ).arg( 
field.
name() ).arg( 
field.
type() ) );
 
  972   Q_ASSERT( offlineLayer );
 
  973   Q_ASSERT( remoteLayer );
 
  975   const QString sql = QStringLiteral( 
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
 
  976   const QList<int> featureIdInts = sqlQueryInts( db, sql );
 
  978   for ( 
const int id : featureIdInts )
 
  998   const int newAttrsCount = remoteLayer->
fields().
count();
 
  999   for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
 
 1003     const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
 
 1006     for ( 
int it = 0; it < attrs.count(); ++it )
 
 1008       const int remoteAttributeIndex = attrLookup.value( it, -1 );
 
 1010       if ( remoteAttributeIndex == -1 )
 
 1012       QVariant attr = attrs.at( it );
 
 1013       if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
 
 1015         if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
 
 1017           attr = attr.toStringList();
 
 1024       else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
 
 1026         if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
 
 1028           attr = attr.toList();
 
 1035       newAttrs[ remoteAttributeIndex ] = attr;
 
 1046 void QgsOfflineEditing::applyFeaturesRemoved( 
QgsVectorLayer *remoteLayer, 
sqlite3 *db, 
int layerId )
 
 1048   Q_ASSERT( remoteLayer );
 
 1050   const QString sql = QStringLiteral( 
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
 
 1051   const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
 
 1056   for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
 
 1058     const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
 
 1067   Q_ASSERT( offlineLayer );
 
 1068   Q_ASSERT( remoteLayer );
 
 1070   const QString sql = QStringLiteral( 
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
 
 1071   const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
 
 1075   QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
 
 1077   for ( 
int i = 0; i < values.size(); i++ )
 
 1079     const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
 
 1080     QgsDebugMsgLevel( QStringLiteral( 
"Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
 
 1082     const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
 
 1083     QVariant attr = values.at( i ).value;
 
 1084     if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::StringList )
 
 1088     else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QVariant::List )
 
 1099 void QgsOfflineEditing::applyGeometryChanges( 
QgsVectorLayer *remoteLayer, 
sqlite3 *db, 
int layerId, 
int commitNo )
 
 1101   Q_ASSERT( remoteLayer );
 
 1103   const QString sql = QStringLiteral( 
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
 
 1104   const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
 
 1108   for ( 
int i = 0; i < values.size(); i++ )
 
 1110     const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
 
 1120   Q_ASSERT( remoteLayer );
 
 1126   QMap < QgsFeatureId, QString > newRemoteFids;
 
 1133   const int remotePkIdx = getLayerPkIdx( remoteLayer );
 
 1138     if ( offlineFid( db, layerId, f.
id() ) == -1 )
 
 1140       newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
 
 1148   const QString sql = QStringLiteral( 
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
 
 1149   const QList<int> newOfflineFids = sqlQueryInts( db, sql );
 
 1151   if ( newRemoteFids.size() != newOfflineFids.size() )
 
 1159     sqlExec( db, QStringLiteral( 
"BEGIN" ) );
 
 1160     for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
 
 1162       addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
 
 1164     sqlExec( db, QStringLiteral( 
"COMMIT" ) );
 
 1171   Q_ASSERT( offlineLayer );
 
 1172   Q_ASSERT( remoteLayer );
 
 1176   QMap < 
int , 
int  > attrLookup;
 
 1179   for ( 
int i = 0; i < offlineAttrs.size(); i++ )
 
 1188 void QgsOfflineEditing::showWarning( 
const QString &message )
 
 1190   emit 
warning( tr( 
"Offline Editing Plugin" ), message );
 
 1197   if ( !dbPath.isEmpty() )
 
 1200     const int rc = database.
open( absoluteDbPath );
 
 1201     if ( rc != SQLITE_OK )
 
 1203       QgsDebugMsg( QStringLiteral( 
"Could not open the SpatiaLite logging database" ) );
 
 1204       showWarning( tr( 
"Could not open the SpatiaLite logging database" ) );
 
 1209     QgsDebugMsg( QStringLiteral( 
"dbPath is empty!" ) );
 
 1214 int QgsOfflineEditing::getOrCreateLayerId( 
sqlite3 *db, 
const QString &qgisLayerId )
 
 1216   QString sql = QStringLiteral( 
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
 
 1217   int layerId = sqlQueryInt( db, sql, -1 );
 
 1218   if ( layerId == -1 )
 
 1221     sql = QStringLiteral( 
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
 
 1222     const int newLayerId = sqlQueryInt( db, sql, -1 );
 
 1225     sql = QStringLiteral( 
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
 
 1230     sql = QStringLiteral( 
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
 
 1233     layerId = newLayerId;
 
 1239 int QgsOfflineEditing::getCommitNo( 
sqlite3 *db )
 
 1241   const QString sql = QStringLiteral( 
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
 
 1242   return sqlQueryInt( db, sql, -1 );
 
 1245 void QgsOfflineEditing::increaseCommitNo( 
sqlite3 *db )
 
 1247   const QString sql = QStringLiteral( 
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
 
 1253   const QString sql = QStringLiteral( 
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
 
 1259   const int pkIdx = getLayerPkIdx( remoteLayer );
 
 1263     const QString sql = QStringLiteral( 
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
 
 1264     return sqlQueryInt( db, sql, -1 );
 
 1267   const QString sql = QStringLiteral( 
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
 
 1268   QString defaultValue;
 
 1269   const QString pkValue = sqlQueryStr( db, sql, defaultValue );
 
 1271   if ( pkValue.isNull() )
 
 1276   const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
 
 1287   const QString sql = QStringLiteral( 
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
 
 1288   return sqlQueryInt( db, sql, -1 );
 
 1293   const QString sql = QStringLiteral( 
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
 
 1294   return ( sqlQueryInt( db, sql, 0 ) > 0 );
 
 1297 int QgsOfflineEditing::sqlExec( 
sqlite3 *db, 
const QString &sql )
 
 1299   char *errmsg = 
nullptr;
 
 1300   const int rc = sqlite3_exec( db, sql.toUtf8(), 
nullptr, 
nullptr, &errmsg );
 
 1301   if ( rc != SQLITE_OK )
 
 1303     showWarning( errmsg );
 
 1308 QString QgsOfflineEditing::sqlQueryStr( 
sqlite3 *db, 
const QString &sql, QString &defaultValue )
 
 1310   sqlite3_stmt *stmt = 
nullptr;
 
 1311   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1313     showWarning( sqlite3_errmsg( db ) );
 
 1314     return defaultValue;
 
 1317   QString value = defaultValue;
 
 1318   const int ret = sqlite3_step( stmt );
 
 1319   if ( ret == SQLITE_ROW )
 
 1321     value = QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 0 ) ) );
 
 1323   sqlite3_finalize( stmt );
 
 1328 int QgsOfflineEditing::sqlQueryInt( 
sqlite3 *db, 
const QString &sql, 
int defaultValue )
 
 1330   sqlite3_stmt *stmt = 
nullptr;
 
 1331   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1333     showWarning( sqlite3_errmsg( db ) );
 
 1334     return defaultValue;
 
 1337   int value = defaultValue;
 
 1338   const int ret = sqlite3_step( stmt );
 
 1339   if ( ret == SQLITE_ROW )
 
 1341     value = sqlite3_column_int( stmt, 0 );
 
 1343   sqlite3_finalize( stmt );
 
 1348 QList<int> QgsOfflineEditing::sqlQueryInts( 
sqlite3 *db, 
const QString &sql )
 
 1352   sqlite3_stmt *stmt = 
nullptr;
 
 1353   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1355     showWarning( sqlite3_errmsg( db ) );
 
 1359   int ret = sqlite3_step( stmt );
 
 1360   while ( ret == SQLITE_ROW )
 
 1362     values << sqlite3_column_int( stmt, 0 );
 
 1364     ret = sqlite3_step( stmt );
 
 1366   sqlite3_finalize( stmt );
 
 1371 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( 
sqlite3 *db, 
const QString &sql )
 
 1373   QList<QgsField> values;
 
 1375   sqlite3_stmt *stmt = 
nullptr;
 
 1376   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1378     showWarning( sqlite3_errmsg( db ) );
 
 1382   int ret = sqlite3_step( stmt );
 
 1383   while ( ret == SQLITE_ROW )
 
 1385     const QgsField field( QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 0 ) ) ),
 
 1386                           static_cast< QVariant::Type 
>( sqlite3_column_int( stmt, 1 ) ),
 
 1388                           sqlite3_column_int( stmt, 2 ),
 
 1389                           sqlite3_column_int( stmt, 3 ),
 
 1390                           QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 4 ) ) ) );
 
 1393     ret = sqlite3_step( stmt );
 
 1395   sqlite3_finalize( stmt );
 
 1404   sqlite3_stmt *stmt = 
nullptr;
 
 1405   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1407     showWarning( sqlite3_errmsg( db ) );
 
 1411   int ret = sqlite3_step( stmt );
 
 1412   while ( ret == SQLITE_ROW )
 
 1414     values << sqlite3_column_int( stmt, 0 );
 
 1416     ret = sqlite3_step( stmt );
 
 1418   sqlite3_finalize( stmt );
 
 1423 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( 
sqlite3 *db, 
const QString &sql )
 
 1425   AttributeValueChanges values;
 
 1427   sqlite3_stmt *stmt = 
nullptr;
 
 1428   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1430     showWarning( sqlite3_errmsg( db ) );
 
 1434   int ret = sqlite3_step( stmt );
 
 1435   while ( ret == SQLITE_ROW )
 
 1437     AttributeValueChange change;
 
 1438     change.fid = sqlite3_column_int( stmt, 0 );
 
 1439     change.attr = sqlite3_column_int( stmt, 1 );
 
 1440     change.value = QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 2 ) ) );
 
 1443     ret = sqlite3_step( stmt );
 
 1445   sqlite3_finalize( stmt );
 
 1450 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( 
sqlite3 *db, 
const QString &sql )
 
 1452   GeometryChanges values;
 
 1454   sqlite3_stmt *stmt = 
nullptr;
 
 1455   if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1457     showWarning( sqlite3_errmsg( db ) );
 
 1461   int ret = sqlite3_step( stmt );
 
 1462   while ( ret == SQLITE_ROW )
 
 1464     GeometryChange change;
 
 1465     change.fid = sqlite3_column_int( stmt, 0 );
 
 1466     change.geom_wkt = QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 1 ) ) );
 
 1469     ret = sqlite3_step( stmt );
 
 1471   sqlite3_finalize( stmt );
 
 1476 void QgsOfflineEditing::committedAttributesAdded( 
const QString &qgisLayerId, 
const QList<QgsField> &addedAttributes )
 
 1483   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1484   const int commitNo = getCommitNo( database.get() );
 
 1488     const QString sql = QStringLiteral( 
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
 
 1496     sqlExec( database.get(), sql );
 
 1499   increaseCommitNo( database.get() );
 
 1502 void QgsOfflineEditing::committedFeaturesAdded( 
const QString &qgisLayerId, 
const QgsFeatureList &addedFeatures )
 
 1509   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1513   const QString dataSourceString = layer->
source();
 
 1519   if ( !offlinePath.contains( 
".gpkg" ) )
 
 1521     tableName = uri.
table();
 
 1526     const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
 
 1527     tableName = decodedUri.value( QStringLiteral( 
"layerName" ) ).toString();
 
 1528     if ( tableName.isEmpty() )
 
 1530       showWarning( tr( 
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
 
 1535   const QString sql = QStringLiteral( 
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
 
 1536   const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
 
 1537   for ( 
int i = newFeatureIds.size() - 1; i >= 0; i-- )
 
 1539     const QString sql = QStringLiteral( 
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
 
 1541                         .arg( newFeatureIds.at( i ) );
 
 1542     sqlExec( database.get(), sql );
 
 1546 void QgsOfflineEditing::committedFeaturesRemoved( 
const QString &qgisLayerId, 
const QgsFeatureIds &deletedFeatureIds )
 
 1553   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1557     if ( isAddedFeature( database.get(), layerId, 
id ) )
 
 1560       const QString sql = QStringLiteral( 
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( 
id );
 
 1561       sqlExec( database.get(), sql );
 
 1565       const QString sql = QStringLiteral( 
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
 
 1568       sqlExec( database.get(), sql );
 
 1573 void QgsOfflineEditing::committedAttributeValuesChanges( 
const QString &qgisLayerId, 
const QgsChangedAttributesMap &changedAttrsMap )
 
 1580   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1581   const int commitNo = getCommitNo( database.get() );
 
 1583   for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
 
 1586     if ( isAddedFeature( database.get(), layerId, fid ) )
 
 1592     for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
 
 1594       QString value = it.value().type() == QVariant::StringList || it.value().type() == QVariant::List ? 
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
 
 1595       value.replace( QLatin1String( 
"'" ), QLatin1String( 
"''" ) ); 
 
 1596       const QString sql = QStringLiteral( 
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
 
 1602       sqlExec( database.get(), sql );
 
 1606   increaseCommitNo( database.get() );
 
 1609 void QgsOfflineEditing::committedGeometriesChanges( 
const QString &qgisLayerId, 
const QgsGeometryMap &changedGeometries )
 
 1616   const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1617   const int commitNo = getCommitNo( database.get() );
 
 1619   for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
 
 1622     if ( isAddedFeature( database.get(), layerId, fid ) )
 
 1628     const QString sql = QStringLiteral( 
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
 
 1632                         .arg( geom.
asWkt() );
 
 1633     sqlExec( database.get(), sql );
 
 1638   increaseCommitNo( database.get() );
 
 1641 void QgsOfflineEditing::startListenFeatureChanges()
 
 1643   QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
 
 1652              this, &QgsOfflineEditing::committedAttributesAdded );
 
 1654              this, &QgsOfflineEditing::committedAttributeValuesChanges );
 
 1656              this, &QgsOfflineEditing::committedGeometriesChanges );
 
 1659            this, &QgsOfflineEditing::committedFeaturesAdded );
 
 1661            this, &QgsOfflineEditing::committedFeaturesRemoved );
 
 1664 void QgsOfflineEditing::stopListenFeatureChanges()
 
 1666   QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
 
 1675                 this, &QgsOfflineEditing::committedAttributesAdded );
 
 1677                 this, &QgsOfflineEditing::committedAttributeValuesChanges );
 
 1679                 this, &QgsOfflineEditing::committedGeometriesChanges );
 
 1682               this, &QgsOfflineEditing::committedFeaturesAdded );
 
 1684               this, &QgsOfflineEditing::committedFeaturesRemoved );
 
 1687 void QgsOfflineEditing::setupLayer( 
QgsMapLayer *layer )
 
 1691   if ( 
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
 
 1702 int QgsOfflineEditing::getLayerPkIdx( 
const QgsVectorLayer *layer )
 const 
 1705   if ( pkAttrs.length() == 1 )
 
 1708     const QVariant::Type pkType = pkField.
type();
 
 1710     if ( pkType == QVariant::String )
 
 1719 QString QgsOfflineEditing::sqlEscape( QString value )
 const 
 1721   if ( value.isNull() )
 
 1722     return QStringLiteral( 
"NULL" );
 
 1724   value.replace( 
"'", 
"''" );
 
 1726   return QStringLiteral( 
"'%1'" ).arg( value );
 
QString authid() const
Returns the authority identifier for the CRS.
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.
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.
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.