23#include "moc_qgsofflineediting.cpp" 
   39#include <QDomDocument> 
   42#include <QRegularExpression> 
   44#include <ogr_srs_api.h> 
   54#include <spatialite.h> 
   58#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable" 
   59#define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource" 
   60#define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider" 
   61#define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount" 
   62#define CUSTOM_PROPERTY_ORIGINAL_LAYERID "remoteLayerId" 
   63#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX "layerNameSuffix" 
   64#define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin" 
   65#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath" 
   86  if ( layerIds.isEmpty() )
 
   91  const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
 
   92  if ( createOfflineDb( dbPath, containerType ) )
 
   95    const int rc = database.
open( dbPath );
 
   96    if ( rc != SQLITE_OK )
 
   98      showWarning( tr( 
"Could not open the SpatiaLite database" ) );
 
  103      createLoggingTables( database.get() );
 
  108      for ( 
int i = 0; i < layerIds.count(); i++ )
 
  116          convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
 
  124      if ( projectTitle.isEmpty() )
 
  128      projectTitle += QLatin1String( 
" (offline)" ); 
 
 
  159  QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
 
  160  QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
 
  162  for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
 
  164    QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
 
  166    if ( !offlineLayer || !offlineLayer->
isValid() )
 
  168      QgsDebugMsgLevel( QStringLiteral( 
"Skipping offline layer %1 because it is an invalid layer" ).arg( layer_it.key() ), 4 );
 
  177    QString remoteName = offlineLayer->
name();
 
  179    if ( remoteName.endsWith( remoteNameSuffix ) )
 
  180      remoteName.chop( remoteNameSuffix.size() );
 
  183    auto remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
 
  185    if ( ! remoteLayer->isValid() )
 
  187      QgsDebugMsgLevel( QStringLiteral( 
"Skipping offline layer %1 because it failed to recreate its corresponding remote layer" ).arg( offlineLayer->
id() ), 4 );
 
  192    if ( remoteLayer->providerType().contains( QLatin1String( 
"WFS" ), Qt::CaseInsensitive ) )
 
  203    const QString sql = QStringLiteral( 
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( offlineLayer->
id() );
 
  204    const int layerId = sqlQueryInt( database.get(), sql, -1 );
 
  208      QgsDebugMsgLevel( QStringLiteral( 
"Skipping offline layer %1 because it failed to determine the offline editing layer id" ).arg( offlineLayer->
id() ), 4 );
 
  212    remoteLayersByOfflineId.insert( layerId, remoteLayer );
 
  213    offlineLayersByOfflineId.insert( layerId, offlineLayer );
 
  216  QgsDebugMsgLevel( QStringLiteral( 
"Found %1 offline layers in total" ).arg( offlineLayersByOfflineId.count() ), 4 );
 
  218  QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
 
  219  if ( useTransaction )
 
  221    for ( 
const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
 
  224      const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
 
  225      std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
 
  227      if ( !transactionGroup )
 
  228        transactionGroup = std::make_shared<QgsTransactionGroup>();
 
  230      if ( !transactionGroup->addLayer( remoteLayer.get() ) )
 
  232        QgsDebugMsgLevel( QStringLiteral( 
"Failed to add a layer %1 into transaction group, will be modified without transaction" ).arg( remoteLayer->name() ), 4 );
 
  236      transactionGroups.insert( pair, transactionGroup );
 
  239    QgsDebugMsgLevel( QStringLiteral( 
"Created %1 transaction groups" ).arg( transactionGroups.count() ), 4 );
 
  242  const QList<int> offlineIds = remoteLayersByOfflineId.keys();
 
  243  for ( 
int offlineLayerId : offlineIds )
 
  245    std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
 
  246    QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
 
  249    if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
 
  251      QgsDebugMsgLevel( QStringLiteral( 
"Failed to turn layer %1 into editing mode" ).arg( remoteLayer->name() ), 4 );
 
  256    const int commitNo = getCommitNo( database.get() );
 
  257    QgsDebugMsgLevel( QStringLiteral( 
"Found %1 commits" ).arg( commitNo ), 4 );
 
  259    for ( 
int i = 0; i < commitNo; i++ )
 
  261      QgsDebugMsgLevel( QStringLiteral( 
"Apply commits chronologically from %1" ).arg( offlineLayer->
name() ), 4 );
 
  263      applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
 
  264      applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
 
  265      applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
 
  268    applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
 
  269    applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
 
  273  for ( 
int offlineLayerId : offlineIds )
 
  275    std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
 
  276    QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
 
  278    if ( !remoteLayer->isEditable() )
 
  281    if ( remoteLayer->commitChanges() )
 
  284      updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
 
  288      sql = QStringLiteral( 
"DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  289      sqlExec( database.get(), sql );
 
  290      sql = QStringLiteral( 
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  291      sqlExec( database.get(), sql );
 
  292      sql = QStringLiteral( 
"DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  293      sqlExec( database.get(), sql );
 
  294      sql = QStringLiteral( 
"DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  295      sqlExec( database.get(), sql );
 
  296      sql = QStringLiteral( 
"DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
 
  297      sqlExec( database.get(), sql );
 
  301      showWarning( remoteLayer->commitErrors().join( QLatin1Char( 
'\n' ) ) );
 
  308    remoteLayer->reload(); 
 
  309    offlineLayer->
setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
 
  325    const QgsFields fields = remoteLayer->fields();
 
  326    for ( 
const QgsField &field : fields )
 
  328      if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf( field.name() ) ) ).isEmpty() )
 
  340  const QString sql = QStringLiteral( 
"UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
 
  341  sqlExec( database.get(), sql );
 
 
  345void QgsOfflineEditing::initializeSpatialMetadata( 
sqlite3 *sqlite_handle )
 
  347#ifdef HAVE_SPATIALITE 
  349  if ( !sqlite_handle )
 
  352  char **results = 
nullptr;
 
  354  int ret = sqlite3_get_table( sqlite_handle, 
"select count(*) from sqlite_master", &results, &rows, &columns, 
nullptr );
 
  355  if ( ret != SQLITE_OK )
 
  360    for ( 
int i = 1; i <= rows; i++ )
 
  361      count = atoi( results[( i * columns ) + 0] );
 
  364  sqlite3_free_table( results );
 
  369  bool above41 = 
false;
 
  370  ret = sqlite3_get_table( sqlite_handle, 
"select spatialite_version()", &results, &rows, &columns, 
nullptr );
 
  371  if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
 
  373    const QString version = QString::fromUtf8( results[1] );
 
  374    const QStringList parts = version.split( 
' ', Qt::SkipEmptyParts );
 
  375    if ( !parts.empty() )
 
  377      const QStringList verparts = parts.at( 0 ).split( 
'.', Qt::SkipEmptyParts );
 
  378      above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
 
  382  sqlite3_free_table( results );
 
  385  char *errMsg = 
nullptr;
 
  386  ret = sqlite3_exec( sqlite_handle, above41 ? 
"SELECT InitSpatialMetadata(1)" : 
"SELECT InitSpatialMetadata()", nullptr, nullptr, &errMsg );
 
  388  if ( ret != SQLITE_OK )
 
  390    QString errCause = tr( 
"Unable to initialize SpatialMetadata:\n" );
 
  391    errCause += QString::fromUtf8( errMsg );
 
  392    showWarning( errCause );
 
  393    sqlite3_free( errMsg );
 
  396  spatial_ref_sys_init( sqlite_handle, 0 );
 
  398  ( void )sqlite_handle;
 
  402bool QgsOfflineEditing::createOfflineDb( 
const QString &offlineDbPath, ContainerType containerType )
 
  405  char *errMsg = 
nullptr;
 
  406  const QFile newDb( offlineDbPath );
 
  407  if ( newDb.exists() )
 
  409    QFile::remove( offlineDbPath );
 
  414  const QFileInfo fullPath = QFileInfo( offlineDbPath );
 
  415  const QDir path = fullPath.dir();
 
  418  QDir().mkpath( path.absolutePath() );
 
  421  const QString dbPath = newDb.fileName();
 
  424  switch ( containerType )
 
  428      OGRSFDriverH hGpkgDriver = OGRGetDriverByName( 
"GPKG" );
 
  431        showWarning( tr( 
"Creation of database failed. GeoPackage driver not found." ) );
 
  438        showWarning( tr( 
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  450  ret = database.
open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 
nullptr );
 
  454    QString errCause = tr( 
"Could not create a new database\n" );
 
  456    showWarning( errCause );
 
  460  ret = sqlite3_exec( database.get(), 
"PRAGMA foreign_keys = 1", 
nullptr, 
nullptr, &errMsg );
 
  461  if ( ret != SQLITE_OK )
 
  463    showWarning( tr( 
"Unable to activate FOREIGN_KEY constraints" ) );
 
  464    sqlite3_free( errMsg );
 
  467  initializeSpatialMetadata( database.get() );
 
  471void QgsOfflineEditing::createLoggingTables( 
sqlite3 *db )
 
  474  QString sql = QStringLiteral( 
"CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
 
  477  sql = QStringLiteral( 
"INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
 
  480  sql = QStringLiteral( 
"INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
 
  484  sql = QStringLiteral( 
"CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
 
  488  sql = QStringLiteral( 
"CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)" );
 
  492  sql = QStringLiteral( 
"CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
 
  493  sql += QLatin1String( 
"'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
 
  497  sql = QStringLiteral( 
"CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
 
  501  sql = QStringLiteral( 
"CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
 
  505  sql = QStringLiteral( 
"CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
 
  509  sql = QStringLiteral( 
"CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
 
  517void QgsOfflineEditing::convertToOfflineLayer( 
QgsVectorLayer *layer, 
sqlite3 *db, 
const QString &offlineDbPath, 
bool onlySelected, ContainerType containerType, 
const QString &layerNameSuffix )
 
  519  if ( !layer || !layer->
isValid() )
 
  521    QgsDebugMsgLevel( QStringLiteral( 
"Layer %1 is invalid and cannot be copied" ).arg( layer ? layer->
id() : QStringLiteral( 
"<UNKNOWN>" ) ), 4 );
 
  525  const QString tableName = layer->
id();
 
  526  QgsDebugMsgLevel( QStringLiteral( 
"Creating offline table %1 ..." ).arg( tableName ), 4 );
 
  529  std::unique_ptr<QgsVectorLayer> newLayer;
 
  531  switch ( containerType )
 
  535#ifdef HAVE_SPATIALITE 
  537      QString sql = QStringLiteral( 
"CREATE TABLE '%1' (" ).arg( tableName );
 
  540      for ( 
const auto &field : providerFields )
 
  543        const QMetaType::Type type = field.type();
 
  544        if ( type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
 
  546          dataType = QStringLiteral( 
"INTEGER" );
 
  548        else if ( type == QMetaType::Type::Double )
 
  550          dataType = QStringLiteral( 
"REAL" );
 
  552        else if ( type == QMetaType::Type::QString )
 
  554          dataType = QStringLiteral( 
"TEXT" );
 
  556        else if ( type == QMetaType::Type::QStringList  || type == QMetaType::Type::QVariantList )
 
  558          dataType = QStringLiteral( 
"TEXT" );
 
  559          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() ) );
 
  563          showWarning( tr( 
"%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
 
  566        sql += delim + QStringLiteral( 
"'%1' %2" ).arg( field.name(), dataType );
 
  571      int rc = sqlExec( db, sql );
 
  582            geomType = QStringLiteral( 
"POINT" );
 
  585            geomType = QStringLiteral( 
"MULTIPOINT" );
 
  588            geomType = QStringLiteral( 
"LINESTRING" );
 
  591            geomType = QStringLiteral( 
"MULTILINESTRING" );
 
  594            geomType = QStringLiteral( 
"POLYGON" );
 
  597            geomType = QStringLiteral( 
"MULTIPOLYGON" );
 
  604        QString zmInfo = QStringLiteral( 
"XY" );
 
  613        if ( layer->
crs().
authid().startsWith( QLatin1String( 
"EPSG:" ), Qt::CaseInsensitive ) )
 
  615          epsgCode = layer->
crs().
authid().mid( 5 );
 
  620          showWarning( tr( 
"Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->
name(), layer->
crs().
authid() ) );
 
  623        const QString sqlAddGeom = QStringLiteral( 
"SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
 
  624                                   .arg( tableName, epsgCode, geomType, zmInfo );
 
  627        const QString sqlCreateIndex = QStringLiteral( 
"SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
 
  629        if ( rc == SQLITE_OK )
 
  631          rc = sqlExec( db, sqlAddGeom );
 
  632          if ( rc == SQLITE_OK )
 
  634            rc = sqlExec( db, sqlCreateIndex );
 
  639      if ( rc != SQLITE_OK )
 
  641        showWarning( tr( 
"Filling SpatiaLite for layer %1 failed" ).arg( layer->
name() ) );
 
  646      const QString connectionString = QStringLiteral( 
"dbname='%1' table='%2'%3 sql=" )
 
  648                                             tableName, layer->
isSpatial() ? 
"(Geometry)" : 
"" );
 
  650      newLayer = std::make_unique<QgsVectorLayer>( connectionString,
 
  651                 layer->
name() + layerNameSuffix, QStringLiteral( 
"spatialite" ), options );
 
  655      showWarning( tr( 
"No Spatialite support available" ) );
 
  663      char **options = 
nullptr;
 
  665      options = CSLSetNameValue( options, 
"OVERWRITE", 
"YES" );
 
  666      options = CSLSetNameValue( options, 
"IDENTIFIER", tr( 
"%1 (offline)" ).arg( layer->
id() ).toUtf8().constData() );
 
  667      options = CSLSetNameValue( options, 
"DESCRIPTION", layer->
dataComment().toUtf8().constData() );
 
  670      const QString fidBase( QStringLiteral( 
"fid" ) );
 
  671      QString fid = fidBase;
 
  675        fid = fidBase + 
'_' + QString::number( counter );
 
  678      if ( counter == 10000 )
 
  680        showWarning( tr( 
"Cannot make FID-name for GPKG " ) );
 
  684      options = CSLSetNameValue( options, 
"FID", fid.toUtf8().constData() );
 
  688        options = CSLSetNameValue( options, 
"GEOMETRY_COLUMN", 
"geom" );
 
  689        options = CSLSetNameValue( options, 
"SPATIAL_INDEX", 
"YES" );
 
  692      OGRSFDriverH hDriver = 
nullptr;
 
  695      OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS, 
static_cast<OGRwkbGeometryType
>( layer->
wkbType() ), options );
 
  696      CSLDestroy( options );
 
  701        showWarning( tr( 
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  706      for ( 
const auto &field : providerFields )
 
  708        const QString fieldName( field.name() );
 
  709        const QMetaType::Type type = field.type();
 
  710        OGRFieldType ogrType( OFTString );
 
  711        OGRFieldSubType ogrSubType = OFSTNone;
 
  712        if ( type == QMetaType::Type::Int )
 
  713          ogrType = OFTInteger;
 
  714        else if ( type == QMetaType::Type::LongLong )
 
  715          ogrType = OFTInteger64;
 
  716        else if ( type == QMetaType::Type::Double )
 
  718        else if ( type == QMetaType::Type::QTime )
 
  720        else if ( type == QMetaType::Type::QDate )
 
  722        else if ( type == QMetaType::Type::QDateTime )
 
  723          ogrType = OFTDateTime;
 
  724        else if ( type == QMetaType::Type::Bool )
 
  726          ogrType = OFTInteger;
 
  727          ogrSubType = OFSTBoolean;
 
  729        else if ( type == QMetaType::Type::QStringList || type == QMetaType::Type::QVariantList )
 
  732          ogrSubType = OFSTJSON;
 
  733          showWarning( tr( 
"Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->
name() ) );
 
  738        const int ogrWidth = field.length();
 
  741        OGR_Fld_SetWidth( fld.get(), ogrWidth );
 
  742        if ( ogrSubType != OFSTNone )
 
  743          OGR_Fld_SetSubType( fld.get(), ogrSubType );
 
  745        if ( OGR_L_CreateField( hLayer, fld.get(), 
true ) != OGRERR_NONE )
 
  747          showWarning( tr( 
"Creation of field %1 failed (OGR error: %2)" )
 
  748                       .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  756      OGR_L_ResetReading( hLayer );
 
  757      if ( CPLGetLastErrorType() != CE_None )
 
  759        const QString msg( tr( 
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
 
  765      const QString uri = QStringLiteral( 
"%1|layername=%2|option:QGIS_FORCE_WAL=ON" ).arg( offlineDbPath,  tableName );
 
  767      newLayer = std::make_unique<QgsVectorLayer>( uri, layer->
name() + layerNameSuffix, QStringLiteral( 
"ogr" ), layerOptions );
 
  772  if ( newLayer && newLayer->isValid() )
 
  776    newLayer->startEditing();
 
  784      if ( !selectedFids.isEmpty() )
 
  798    long long featureCount = 1;
 
  799    const int remotePkIdx = getLayerPkIdx( layer );
 
  801    QList<QgsFeatureId> remoteFeatureIds;
 
  802    QStringList remoteFeaturePks;
 
  805      remoteFeatureIds << f.
id();
 
  806      remoteFeaturePks << ( remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString() );
 
  813      QgsAttributes newAttrs( containerType == 
GPKG ? attrs.count() + 1 : attrs.count() );
 
  814      for ( 
int it = 0; it < attrs.count(); ++it )
 
  816        const QVariant attr = attrs.at( it );
 
  817        newAttrs[column++] = attr;
 
  821      newLayer->addFeature( f );
 
  825    if ( newLayer->commitChanges() )
 
  831      const int layerId = getOrCreateLayerId( db, layer->
id() );
 
  832      QList<QgsFeatureId> offlineFeatureIds;
 
  837        offlineFeatureIds << f.
id();
 
  841      sqlExec( db, QStringLiteral( 
"BEGIN" ) );
 
  842      const int remoteCount = remoteFeatureIds.size();
 
  843      for ( 
int i = 0; i < remoteCount; i++ )
 
  846        if ( i < offlineFeatureIds.count() )
 
  848          addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
 
  852          showWarning( tr( 
"Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->
name() ) );
 
  857      sqlExec( db, QStringLiteral( 
"COMMIT" ) );
 
  861      showWarning( newLayer->commitErrors().join( QLatin1Char( 
'\n' ) ) );
 
  875    QStringList notNullFieldNames;
 
  876    for ( 
const QgsField &field : fields )
 
  880        notNullFieldNames << field.name();
 
  884    layer->
setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
 
  886    for ( 
const QgsField &field : fields ) 
 
  896        if ( notNullFieldNames.contains( field.name() ) )
 
  898          notNullFieldNames.removeAll( field.name() );
 
  909void QgsOfflineEditing::applyAttributesAdded( 
QgsVectorLayer *remoteLayer, 
sqlite3 *db, 
int layerId, 
int commitNo )
 
  911  Q_ASSERT( remoteLayer );
 
  913  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 );
 
  914  QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
 
  917  const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->
nativeTypes();
 
  920  QMap < QMetaType::Type, QString  > typeNameLookup;
 
  921  for ( 
int i = 0; i < nativeTypes.size(); i++ )
 
  929  for ( 
int i = 0; i < fields.size(); i++ )
 
  933    if ( typeNameLookup.contains( field.
type() ) )
 
  941      showWarning( QStringLiteral( 
"Could not add attribute '%1' of type %2" ).arg( field.
name() ).arg( field.
type() ) );
 
  950  Q_ASSERT( offlineLayer );
 
  951  Q_ASSERT( remoteLayer );
 
  953  const QString sql = QStringLiteral( 
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
 
  954  const QList<int> featureIdInts = sqlQueryInts( db, sql );
 
  956  for ( 
const int id : featureIdInts )
 
  976  const int newAttrsCount = remoteLayer->
fields().
count();
 
  977  for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
 
  981    const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
 
  984    for ( 
int it = 0; it < attrs.count(); ++it )
 
  986      const int remoteAttributeIndex = attrLookup.value( it, -1 );
 
  988      if ( remoteAttributeIndex == -1 )
 
  990      QVariant attr = attrs.at( it );
 
  991      if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
 
  993        if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
 
  995          attr = attr.toStringList();
 
 1002      else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
 
 1004        if ( attr.userType() == QMetaType::Type::QStringList || attr.userType() == QMetaType::Type::QVariantList )
 
 1006          attr = attr.toList();
 
 1013      newAttrs[ remoteAttributeIndex ] = attr;
 
 1024void QgsOfflineEditing::applyFeaturesRemoved( 
QgsVectorLayer *remoteLayer, 
sqlite3 *db, 
int layerId )
 
 1026  Q_ASSERT( remoteLayer );
 
 1028  const QString sql = QStringLiteral( 
"SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
 
 1029  const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
 
 1034  for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
 
 1036    const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
 
 1045  Q_ASSERT( offlineLayer );
 
 1046  Q_ASSERT( remoteLayer );
 
 1048  const QString sql = QStringLiteral( 
"SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
 
 1049  const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
 
 1053  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
 
 1055  for ( 
int i = 0; i < values.size(); i++ )
 
 1057    const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
 
 1058    QgsDebugMsgLevel( QStringLiteral( 
"Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
 
 1060    const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
 
 1061    QVariant attr = values.at( i ).value;
 
 1062    if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QStringList )
 
 1066    else if ( remoteLayer->
fields().
at( remoteAttributeIndex ).
type() == QMetaType::Type::QVariantList )
 
 1077void QgsOfflineEditing::applyGeometryChanges( 
QgsVectorLayer *remoteLayer, 
sqlite3 *db, 
int layerId, 
int commitNo )
 
 1079  Q_ASSERT( remoteLayer );
 
 1081  const QString sql = QStringLiteral( 
"SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
 
 1082  const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
 
 1086  for ( 
int i = 0; i < values.size(); i++ )
 
 1088    const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
 
 1098  Q_ASSERT( remoteLayer );
 
 1104  QMap < QgsFeatureId, QString > newRemoteFids;
 
 1111  const int remotePkIdx = getLayerPkIdx( remoteLayer );
 
 1116    if ( offlineFid( db, layerId, f.
id() ) == -1 )
 
 1118      newRemoteFids[ f.
id()] = remotePkIdx >= 0 ? f.
attribute( remotePkIdx ).toString() : QString();
 
 1126  const QString sql = QStringLiteral( 
"SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
 
 1127  const QList<int> newOfflineFids = sqlQueryInts( db, sql );
 
 1129  if ( newRemoteFids.size() != newOfflineFids.size() )
 
 1137    sqlExec( db, QStringLiteral( 
"BEGIN" ) );
 
 1138    for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
 
 1140      addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
 
 1142    sqlExec( db, QStringLiteral( 
"COMMIT" ) );
 
 1149  Q_ASSERT( offlineLayer );
 
 1150  Q_ASSERT( remoteLayer );
 
 1154  QMap < 
int , 
int  > attrLookup;
 
 1157  for ( 
int i = 0; i < offlineAttrs.size(); i++ )
 
 1166void QgsOfflineEditing::showWarning( 
const QString &message )
 
 1168  emit 
warning( tr( 
"Offline Editing Plugin" ), message );
 
 1175  if ( !dbPath.isEmpty() )
 
 1178    const int rc = database.
open( absoluteDbPath );
 
 1179    if ( rc != SQLITE_OK )
 
 1181      QgsDebugError( QStringLiteral( 
"Could not open the SpatiaLite logging database" ) );
 
 1182      showWarning( tr( 
"Could not open the SpatiaLite logging database" ) );
 
 1192int QgsOfflineEditing::getOrCreateLayerId( 
sqlite3 *db, 
const QString &qgisLayerId )
 
 1194  QString sql = QStringLiteral( 
"SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
 
 1195  int layerId = sqlQueryInt( db, sql, -1 );
 
 1196  if ( layerId == -1 )
 
 1199    sql = QStringLiteral( 
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
 
 1200    const int newLayerId = sqlQueryInt( db, sql, -1 );
 
 1203    sql = QStringLiteral( 
"INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
 
 1208    sql = QStringLiteral( 
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
 
 1211    layerId = newLayerId;
 
 1217int QgsOfflineEditing::getCommitNo( 
sqlite3 *db )
 
 1219  const QString sql = QStringLiteral( 
"SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
 
 1220  return sqlQueryInt( db, sql, -1 );
 
 1223void QgsOfflineEditing::increaseCommitNo( 
sqlite3 *db )
 
 1225  const QString sql = QStringLiteral( 
"UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
 
 1231  const QString sql = QStringLiteral( 
"INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
 
 1237  const int pkIdx = getLayerPkIdx( remoteLayer );
 
 1241    const QString sql = QStringLiteral( 
"SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
 
 1242    return sqlQueryInt( db, sql, -1 );
 
 1245  const QString sql = QStringLiteral( 
"SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
 
 1246  QString defaultValue;
 
 1247  const QString pkValue = sqlQueryStr( db, sql, defaultValue );
 
 1249  if ( pkValue.isNull() )
 
 1254  const QString pkFieldName = remoteLayer->
fields().
at( pkIdx ).
name();
 
 1265  const QString sql = QStringLiteral( 
"SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
 
 1266  return sqlQueryInt( db, sql, -1 );
 
 1271  const QString sql = QStringLiteral( 
"SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
 
 1272  return ( sqlQueryInt( db, sql, 0 ) > 0 );
 
 1275int QgsOfflineEditing::sqlExec( 
sqlite3 *db, 
const QString &sql )
 
 1277  char *errmsg = 
nullptr;
 
 1278  const int rc = sqlite3_exec( db, sql.toUtf8(), 
nullptr, 
nullptr, &errmsg );
 
 1279  if ( rc != SQLITE_OK )
 
 1281    showWarning( errmsg );
 
 1286QString QgsOfflineEditing::sqlQueryStr( 
sqlite3 *db, 
const QString &sql, QString &defaultValue )
 
 1288  sqlite3_stmt *stmt = 
nullptr;
 
 1289  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1291    showWarning( sqlite3_errmsg( db ) );
 
 1292    return defaultValue;
 
 1295  QString value = defaultValue;
 
 1296  const int ret = sqlite3_step( stmt );
 
 1297  if ( ret == SQLITE_ROW )
 
 1299    value = QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 0 ) ) );
 
 1301  sqlite3_finalize( stmt );
 
 1306int QgsOfflineEditing::sqlQueryInt( 
sqlite3 *db, 
const QString &sql, 
int defaultValue )
 
 1308  sqlite3_stmt *stmt = 
nullptr;
 
 1309  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1311    showWarning( sqlite3_errmsg( db ) );
 
 1312    return defaultValue;
 
 1315  int value = defaultValue;
 
 1316  const int ret = sqlite3_step( stmt );
 
 1317  if ( ret == SQLITE_ROW )
 
 1319    value = sqlite3_column_int( stmt, 0 );
 
 1321  sqlite3_finalize( stmt );
 
 1326QList<int> QgsOfflineEditing::sqlQueryInts( 
sqlite3 *db, 
const QString &sql )
 
 1330  sqlite3_stmt *stmt = 
nullptr;
 
 1331  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1333    showWarning( sqlite3_errmsg( db ) );
 
 1337  int ret = sqlite3_step( stmt );
 
 1338  while ( ret == SQLITE_ROW )
 
 1340    values << sqlite3_column_int( stmt, 0 );
 
 1342    ret = sqlite3_step( stmt );
 
 1344  sqlite3_finalize( stmt );
 
 1349QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( 
sqlite3 *db, 
const QString &sql )
 
 1351  QList<QgsField> values;
 
 1353  sqlite3_stmt *stmt = 
nullptr;
 
 1354  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1356    showWarning( sqlite3_errmsg( db ) );
 
 1360  int ret = sqlite3_step( stmt );
 
 1361  while ( ret == SQLITE_ROW )
 
 1363    const QgsField field( QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 0 ) ) ),
 
 1364                          static_cast< QMetaType::Type 
>( sqlite3_column_int( stmt, 1 ) ),
 
 1366                          sqlite3_column_int( stmt, 2 ),
 
 1367                          sqlite3_column_int( stmt, 3 ),
 
 1368                          QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 4 ) ) ) );
 
 1371    ret = sqlite3_step( stmt );
 
 1373  sqlite3_finalize( stmt );
 
 1382  sqlite3_stmt *stmt = 
nullptr;
 
 1383  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1385    showWarning( sqlite3_errmsg( db ) );
 
 1389  int ret = sqlite3_step( stmt );
 
 1390  while ( ret == SQLITE_ROW )
 
 1392    values << sqlite3_column_int( stmt, 0 );
 
 1394    ret = sqlite3_step( stmt );
 
 1396  sqlite3_finalize( stmt );
 
 1401QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( 
sqlite3 *db, 
const QString &sql )
 
 1403  AttributeValueChanges values;
 
 1405  sqlite3_stmt *stmt = 
nullptr;
 
 1406  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1408    showWarning( sqlite3_errmsg( db ) );
 
 1412  int ret = sqlite3_step( stmt );
 
 1413  while ( ret == SQLITE_ROW )
 
 1415    AttributeValueChange change;
 
 1416    change.fid = sqlite3_column_int( stmt, 0 );
 
 1417    change.attr = sqlite3_column_int( stmt, 1 );
 
 1418    change.value = QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 2 ) ) );
 
 1421    ret = sqlite3_step( stmt );
 
 1423  sqlite3_finalize( stmt );
 
 1428QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( 
sqlite3 *db, 
const QString &sql )
 
 1430  GeometryChanges values;
 
 1432  sqlite3_stmt *stmt = 
nullptr;
 
 1433  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, 
nullptr ) != SQLITE_OK )
 
 1435    showWarning( sqlite3_errmsg( db ) );
 
 1439  int ret = sqlite3_step( stmt );
 
 1440  while ( ret == SQLITE_ROW )
 
 1442    GeometryChange change;
 
 1443    change.fid = sqlite3_column_int( stmt, 0 );
 
 1444    change.geom_wkt = QString( 
reinterpret_cast< const char * 
>( sqlite3_column_text( stmt, 1 ) ) );
 
 1447    ret = sqlite3_step( stmt );
 
 1449  sqlite3_finalize( stmt );
 
 1454void QgsOfflineEditing::committedAttributesAdded( 
const QString &qgisLayerId, 
const QList<QgsField> &addedAttributes )
 
 1461  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1462  const int commitNo = getCommitNo( database.get() );
 
 1464  for ( 
const QgsField &field : addedAttributes )
 
 1466    const QString sql = QStringLiteral( 
"INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
 
 1469                        .arg( field.
name() )
 
 1470                        .arg( field.
type() )
 
 1474    sqlExec( database.get(), sql );
 
 1477  increaseCommitNo( database.get() );
 
 1480void QgsOfflineEditing::committedFeaturesAdded( 
const QString &qgisLayerId, 
const QgsFeatureList &addedFeatures )
 
 1487  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1491  const QString dataSourceString = layer->
source();
 
 1497  if ( !offlinePath.contains( 
".gpkg" ) )
 
 1499    tableName = uri.
table();
 
 1504    const QVariantMap decodedUri = ogrProviderMetaData->
decodeUri( dataSourceString );
 
 1505    tableName = decodedUri.value( QStringLiteral( 
"layerName" ) ).toString();
 
 1506    if ( tableName.isEmpty() )
 
 1508      showWarning( tr( 
"Could not deduce table name from data source %1." ).arg( dataSourceString ) );
 
 1513  const QString sql = QStringLiteral( 
"SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
 
 1514  const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
 
 1515  for ( 
int i = newFeatureIds.size() - 1; i >= 0; i-- )
 
 1517    const QString sql = QStringLiteral( 
"INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
 
 1519                        .arg( newFeatureIds.at( i ) );
 
 1520    sqlExec( database.get(), sql );
 
 1524void QgsOfflineEditing::committedFeaturesRemoved( 
const QString &qgisLayerId, 
const QgsFeatureIds &deletedFeatureIds )
 
 1531  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1535    if ( isAddedFeature( database.get(), layerId, 
id ) )
 
 1538      const QString sql = QStringLiteral( 
"DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( 
id );
 
 1539      sqlExec( database.get(), sql );
 
 1543      const QString sql = QStringLiteral( 
"INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
 
 1546      sqlExec( database.get(), sql );
 
 1551void QgsOfflineEditing::committedAttributeValuesChanges( 
const QString &qgisLayerId, 
const QgsChangedAttributesMap &changedAttrsMap )
 
 1558  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1559  const int commitNo = getCommitNo( database.get() );
 
 1561  for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
 
 1564    if ( isAddedFeature( database.get(), layerId, fid ) )
 
 1570    for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
 
 1572      QString value = it.value().userType() == QMetaType::Type::QStringList || it.value().userType() == QMetaType::Type::QVariantList ? 
QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
 
 1573      value.replace( QLatin1String( 
"'" ), QLatin1String( 
"''" ) ); 
 
 1574      const QString sql = QStringLiteral( 
"INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
 
 1580      sqlExec( database.get(), sql );
 
 1584  increaseCommitNo( database.get() );
 
 1587void QgsOfflineEditing::committedGeometriesChanges( 
const QString &qgisLayerId, 
const QgsGeometryMap &changedGeometries )
 
 1594  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
 
 1595  const int commitNo = getCommitNo( database.get() );
 
 1597  for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
 
 1600    if ( isAddedFeature( database.get(), layerId, fid ) )
 
 1606    const QString sql = QStringLiteral( 
"INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
 
 1610                        .arg( geom.
asWkt() );
 
 1611    sqlExec( database.get(), sql );
 
 1616  increaseCommitNo( database.get() );
 
 1619void QgsOfflineEditing::startListenFeatureChanges()
 
 1621  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
 
 1630             this, &QgsOfflineEditing::committedAttributesAdded );
 
 1632             this, &QgsOfflineEditing::committedAttributeValuesChanges );
 
 1634             this, &QgsOfflineEditing::committedGeometriesChanges );
 
 1637           this, &QgsOfflineEditing::committedFeaturesAdded );
 
 1639           this, &QgsOfflineEditing::committedFeaturesRemoved );
 
 1642void QgsOfflineEditing::stopListenFeatureChanges()
 
 1644  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
 
 1653                this, &QgsOfflineEditing::committedAttributesAdded );
 
 1655                this, &QgsOfflineEditing::committedAttributeValuesChanges );
 
 1657                this, &QgsOfflineEditing::committedGeometriesChanges );
 
 1660              this, &QgsOfflineEditing::committedFeaturesAdded );
 
 1662              this, &QgsOfflineEditing::committedFeaturesRemoved );
 
 1665void QgsOfflineEditing::setupLayer( 
QgsMapLayer *layer )
 
 1669  if ( 
QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
 
 1680int QgsOfflineEditing::getLayerPkIdx( 
const QgsVectorLayer *layer )
 const 
 1683  if ( pkAttrs.length() == 1 )
 
 1686    const QMetaType::Type pkType = pkField.
type();
 
 1688    if ( pkType == QMetaType::Type::QString )
 
 1697QString QgsOfflineEditing::sqlEscape( QString value )
 const 
 1699  if ( value.isNull() )
 
 1700    return QStringLiteral( 
"NULL" );
 
 1702  value.replace( 
"'", 
"''" );
 
 1704  return QStringLiteral( 
"'%1'" ).arg( value );
 
@ Fids
Filter using feature IDs.
 
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
 
WkbType
The WKB type describes the number of dimensions a geometry has.
 
@ MultiPolygon
MultiPolygon.
 
@ MultiLineString
MultiLineString.
 
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
 
Stores the component parts of a 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)
Fetch next feature and stores in f, returns true on success.
 
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.
 
Qgis::FeatureRequestFilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
 
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.
 
Q_INVOKABLE 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.
 
Encapsulate a field in an attribute table or data source.
 
QMetaType::Type subType() const
If the field is a collection, gets its element's type.
 
void setTypeName(const QString &typeName)
Set the field type.
 
Container of fields for a vector layer.
 
Q_INVOKABLE int indexOf(const QString &fieldName) const
Gets the field index from the field name.
 
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).
 
Q_INVOKABLE 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 Q_INVOKABLE QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
 
Q_INVOKABLE 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, QMetaType::Type type=QMetaType::Type::UnknownType)
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
 
void setDataSource(const QString &dataSource, const QString &baseName=QString(), const QString &provider=QString(), bool loadDefaultStyleFlag=false)
Updates the data source of the layer.
 
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for 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.
 
Stores configuration of snapping settings for the project.
 
QString connectionString() const
Returns the connection string of the transaction.
 
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.
 
Stores queued vector layer edit operations prior to committing changes to the layer's data provider.
 
void committedAttributeValuesChanges(const QString &layerId, const QgsChangedAttributesMap &changedAttributesValues)
Emitted after feature attribute value changes have been committed to the layer.
 
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
Emitted after attribute addition has been committed to the layer.
 
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
Emitted after feature geometry changes have been committed to the layer.
 
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 dataset.
 
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
Emitted when features are added to the provider if not in transaction mode.
 
Q_INVOKABLE QgsAttributeList attributeList() const
Returns list of attribute indexes.
 
Q_INVOKABLE bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false, QgsVectorLayerToolsContext *context=nullptr)
Changes an attribute value for a feature (but does not immediately commit the changes).
 
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.
 
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
 
Q_INVOKABLE bool deleteFeature(QgsFeatureId fid, QgsVectorLayer::DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
 
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.
 
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
 
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
 
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
 
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.
 
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 Q_INVOKABLE QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...
 
static Q_INVOKABLE bool hasZ(Qgis::WkbType type)
Tests whether a WKB type contains the z-dimension.
 
static Q_INVOKABLE bool hasM(Qgis::WkbType type)
Tests whether a WKB type contains m values.
 
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
 
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
 
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 QgsDebugError(str)
 
#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.