QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsofflineediting.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  offline_editing.cpp
3 
4  Offline Editing Plugin
5  a QGIS plugin
6  --------------------------------------
7  Date : 22-Jul-2010
8  Copyright : (C) 2010 by Sourcepole
9  Email : info at sourcepole.ch
10  ***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 
19 
20 #include "qgsapplication.h"
21 #include "qgsdatasourceuri.h"
22 #include "qgsgeometry.h"
23 #include "qgslayertreegroup.h"
24 #include "qgslayertreelayer.h"
25 #include "qgsmaplayer.h"
26 #include "qgsofflineediting.h"
27 #include "qgsproject.h"
28 #include "qgsvectordataprovider.h"
31 #include "qgsspatialiteutils.h"
32 #include "qgsfeatureiterator.h"
33 #include "qgslogger.h"
34 #include "qgsvectorlayerutils.h"
35 #include "qgsrelationmanager.h"
36 #include "qgsmapthemecollection.h"
37 #include "qgslayertree.h"
38 #include "qgsogrutils.h"
39 #include "qgsvectorfilewriter.h"
40 #include "qgsvectorlayer.h"
41 #include "qgsproviderregistry.h"
42 #include "qgsprovidermetadata.h"
44 #include "qgsjsonutils.h"
45 #include "qgstransactiongroup.h"
46 
47 #include <QDir>
48 #include <QDomDocument>
49 #include <QDomNode>
50 #include <QFile>
51 #include <QRegularExpression>
52 
53 #include <ogr_srs_api.h>
54 
55 extern "C"
56 {
57 #include <sqlite3.h>
58 }
59 
60 #ifdef HAVE_SPATIALITE
61 extern "C"
62 {
63 #include <spatialite.h>
64 }
65 #endif
66 
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"
75 
77 {
78  connect( QgsProject::instance(), &QgsProject::layerWasAdded, this, &QgsOfflineEditing::setupLayer );
79 }
80 
93 bool QgsOfflineEditing::convertToOfflineProject( const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected, ContainerType containerType, const QString &layerNameSuffix )
94 {
95  if ( layerIds.isEmpty() )
96  {
97  return false;
98  }
99 
100  const QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
101  if ( createOfflineDb( dbPath, containerType ) )
102  {
104  const int rc = database.open( dbPath );
105  if ( rc != SQLITE_OK )
106  {
107  showWarning( tr( "Could not open the SpatiaLite database" ) );
108  }
109  else
110  {
111  // create logging tables
112  createLoggingTables( database.get() );
113 
114  emit progressStarted();
115 
116  // copy selected vector layers to offline layer
117  for ( int i = 0; i < layerIds.count(); i++ )
118  {
119  emit layerProgressUpdated( i + 1, layerIds.count() );
120 
121  QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerIds.at( i ) );
122  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
123  if ( vl && vl->isValid() )
124  {
125  convertToOfflineLayer( vl, database.get(), dbPath, onlySelected, containerType, layerNameSuffix );
126  }
127  }
128 
129  emit progressStopped();
130 
131  // save offline project
132  QString projectTitle = QgsProject::instance()->title();
133  if ( projectTitle.isEmpty() )
134  {
135  projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName();
136  }
137  projectTitle += QLatin1String( " (offline)" );
138  QgsProject::instance()->setTitle( projectTitle );
140 
141  return true;
142  }
143  }
144 
145  return false;
146 }
147 
149 {
151 }
152 
153 void QgsOfflineEditing::synchronize( bool useTransaction )
154 {
155  // open logging db
156  const sqlite3_database_unique_ptr database = openLoggingDb();
157  if ( !database )
158  {
159  return;
160  }
161 
162  emit progressStarted();
163 
164  const QgsSnappingConfig snappingConfig = QgsProject::instance()->snappingConfig();
165 
166  // restore and sync remote layers
167  QMap<QString, QgsMapLayer *> mapLayers = QgsProject::instance()->mapLayers();
168  QMap<int, std::shared_ptr<QgsVectorLayer>> remoteLayersByOfflineId;
169  QMap<int, QgsVectorLayer *> offlineLayersByOfflineId;
170 
171  for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
172  {
173  QgsVectorLayer *offlineLayer( qobject_cast<QgsVectorLayer *>( layer_it.value() ) );
174 
175  if ( !offlineLayer || !offlineLayer->isValid() )
176  {
177  QgsDebugMsgLevel( QStringLiteral( "Skipping offline layer %1 because it is an invalid layer" ).arg( layer_it.key() ), 4 );
178  continue;
179  }
180 
181  if ( !offlineLayer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
182  continue;
183 
184  const QString remoteSource = offlineLayer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
185  const QString remoteProvider = offlineLayer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
186  QString remoteName = offlineLayer->name();
187  const QString remoteNameSuffix = offlineLayer->customProperty( CUSTOM_PROPERTY_LAYERNAME_SUFFIX, " (offline)" ).toString();
188  if ( remoteName.endsWith( remoteNameSuffix ) )
189  remoteName.chop( remoteNameSuffix.size() );
191 
192  std::shared_ptr<QgsVectorLayer> remoteLayer = std::make_shared<QgsVectorLayer>( remoteSource, remoteName, remoteProvider, options );
193 
194  if ( ! remoteLayer->isValid() )
195  {
196  QgsDebugMsgLevel( QStringLiteral( "Skipping offline layer %1 because it failed to recreate its corresponding remote layer" ).arg( offlineLayer->id() ), 4 );
197  continue;
198  }
199 
200  // Rebuild WFS cache to get feature id<->GML fid mapping
201  if ( remoteLayer->providerType().contains( QLatin1String( "WFS" ), Qt::CaseInsensitive ) )
202  {
203  QgsFeatureIterator fit = remoteLayer->getFeatures();
204  QgsFeature f;
205  while ( fit.nextFeature( f ) )
206  {
207  }
208  }
209 
210  // TODO: only add remote layer if there are log entries?
211  // apply layer edit log
212  const QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( offlineLayer->id() );
213  const int layerId = sqlQueryInt( database.get(), sql, -1 );
214 
215  if ( layerId == -1 )
216  {
217  QgsDebugMsgLevel( QStringLiteral( "Skipping offline layer %1 because it failed to determine the offline editing layer id" ).arg( offlineLayer->id() ), 4 );
218  continue;
219  }
220 
221  remoteLayersByOfflineId.insert( layerId, remoteLayer );
222  offlineLayersByOfflineId.insert( layerId, offlineLayer );
223  }
224 
225  QgsDebugMsgLevel( QStringLiteral( "Found %1 offline layers in total" ).arg( offlineLayersByOfflineId.count() ), 4 );
226 
227  QMap<QPair<QString, QString>, std::shared_ptr<QgsTransactionGroup>> transactionGroups;
228  if ( useTransaction )
229  {
230  for ( const std::shared_ptr<QgsVectorLayer> &remoteLayer : std::as_const( remoteLayersByOfflineId ) )
231  {
232  const QString connectionString = QgsTransaction::connectionString( remoteLayer->source() );
233  const QPair<QString, QString> pair( remoteLayer->providerType(), connectionString );
234  std::shared_ptr<QgsTransactionGroup> transactionGroup = transactionGroups.value( pair );
235 
236  if ( !transactionGroup.get() )
237  transactionGroup = std::make_shared<QgsTransactionGroup>();
238 
239  if ( !transactionGroup->addLayer( remoteLayer.get() ) )
240  {
241  QgsDebugMsgLevel( QStringLiteral( "Failed to add a layer %1 into transaction group, will be modified without transaction" ).arg( remoteLayer->name() ), 4 );
242  continue;
243  }
244 
245  transactionGroups.insert( pair, transactionGroup );
246  }
247 
248  QgsDebugMsgLevel( QStringLiteral( "Created %1 transaction groups" ).arg( transactionGroups.count() ), 4 );
249  }
250 
251  const QList<int> offlineIds = remoteLayersByOfflineId.keys();
252  for ( int offlineLayerId : offlineIds )
253  {
254  std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId.value( offlineLayerId );
255  QgsVectorLayer *offlineLayer = offlineLayersByOfflineId.value( offlineLayerId );
256 
257  // NOTE: if transaction is enabled, the layer might be already in editing mode
258  if ( !remoteLayer->startEditing() && !remoteLayer->isEditable() )
259  {
260  QgsDebugMsgLevel( QStringLiteral( "Failed to turn layer %1 into editing mode" ).arg( remoteLayer->name() ), 4 );
261  continue;
262  }
263 
264  // TODO: only get commitNos of this layer?
265  const int commitNo = getCommitNo( database.get() );
266  QgsDebugMsgLevel( QStringLiteral( "Found %1 commits" ).arg( commitNo ), 4 );
267 
268  for ( int i = 0; i < commitNo; i++ )
269  {
270  QgsDebugMsgLevel( QStringLiteral( "Apply commits chronologically from %1" ).arg( offlineLayer->name() ), 4 );
271  // apply commits chronologically
272  applyAttributesAdded( remoteLayer.get(), database.get(), offlineLayerId, i );
273  applyAttributeValueChanges( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId, i );
274  applyGeometryChanges( remoteLayer.get(), database.get(), offlineLayerId, i );
275  }
276 
277  applyFeaturesAdded( offlineLayer, remoteLayer.get(), database.get(), offlineLayerId );
278  applyFeaturesRemoved( remoteLayer.get(), database.get(), offlineLayerId );
279  }
280 
281 
282  for ( int offlineLayerId : offlineIds )
283  {
284  std::shared_ptr<QgsVectorLayer> remoteLayer = remoteLayersByOfflineId[offlineLayerId];
285  QgsVectorLayer *offlineLayer = offlineLayersByOfflineId[offlineLayerId];
286 
287  if ( !remoteLayer->isEditable() )
288  continue;
289 
290  if ( remoteLayer->commitChanges() )
291  {
292  // update fid lookup
293  updateFidLookup( remoteLayer.get(), database.get(), offlineLayerId );
294 
295  QString sql;
296  // clear edit log for this layer
297  sql = QStringLiteral( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
298  sqlExec( database.get(), sql );
299  sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
300  sqlExec( database.get(), sql );
301  sql = QStringLiteral( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
302  sqlExec( database.get(), sql );
303  sql = QStringLiteral( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
304  sqlExec( database.get(), sql );
305  sql = QStringLiteral( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( offlineLayerId );
306  sqlExec( database.get(), sql );
307  }
308  else
309  {
310  showWarning( remoteLayer->commitErrors().join( QLatin1Char( '\n' ) ) );
311  }
312 
313  // Invalidate the connection to force a reload if the project is put offline
314  // again with the same path
315  offlineLayer->dataProvider()->invalidateConnections( QgsDataSourceUri( offlineLayer->source() ).database() );
316 
317  remoteLayer->reload(); //update with other changes
318  offlineLayer->setDataSource( remoteLayer->source(), remoteLayer->name(), remoteLayer->dataProvider()->name() );
319 
320  // remove offline layer properties
322 
323  // remove original layer source and information
328 
329  // remove connected signals
330  disconnect( offlineLayer, &QgsVectorLayer::editingStarted, this, &QgsOfflineEditing::startListenFeatureChanges );
331  disconnect( offlineLayer, &QgsVectorLayer::editingStopped, this, &QgsOfflineEditing::stopListenFeatureChanges );
332 
333  //add constrainst of fields that use defaultValueClauses from provider on original
334  const QgsFields fields = remoteLayer->fields();
335  for ( const QgsField &field : fields )
336  {
337  if ( !remoteLayer->dataProvider()->defaultValueClause( remoteLayer->fields().fieldOriginIndex( remoteLayer->fields().indexOf( field.name() ) ) ).isEmpty() )
338  {
339  offlineLayer->setFieldConstraint( offlineLayer->fields().indexOf( field.name() ), QgsFieldConstraints::ConstraintNotNull );
340  }
341  }
342  }
343 
344  // disable offline project
345  const QString projectTitle = QgsProject::instance()->title().remove( QRegularExpression( " \\(offline\\)$" ) );
346  QgsProject::instance()->setTitle( projectTitle );
348  // reset commitNo
349  const QString sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
350  sqlExec( database.get(), sql );
351  emit progressStopped();
352 }
353 
354 void QgsOfflineEditing::initializeSpatialMetadata( sqlite3 *sqlite_handle )
355 {
356 #ifdef HAVE_SPATIALITE
357  // attempting to perform self-initialization for a newly created DB
358  if ( !sqlite_handle )
359  return;
360  // checking if this DB is really empty
361  char **results = nullptr;
362  int rows, columns;
363  int ret = sqlite3_get_table( sqlite_handle, "select count(*) from sqlite_master", &results, &rows, &columns, nullptr );
364  if ( ret != SQLITE_OK )
365  return;
366  int count = 0;
367  if ( rows >= 1 )
368  {
369  for ( int i = 1; i <= rows; i++ )
370  count = atoi( results[( i * columns ) + 0] );
371  }
372 
373  sqlite3_free_table( results );
374 
375  if ( count > 0 )
376  return;
377 
378  bool above41 = false;
379  ret = sqlite3_get_table( sqlite_handle, "select spatialite_version()", &results, &rows, &columns, nullptr );
380  if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
381  {
382  const QString version = QString::fromUtf8( results[1] );
383 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
384  QStringList parts = version.split( ' ', QString::SkipEmptyParts );
385 #else
386  const QStringList parts = version.split( ' ', Qt::SkipEmptyParts );
387 #endif
388  if ( !parts.empty() )
389  {
390 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
391  QStringList verparts = parts.at( 0 ).split( '.', QString::SkipEmptyParts );
392 #else
393  const QStringList verparts = parts.at( 0 ).split( '.', Qt::SkipEmptyParts );
394 #endif
395  above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
396  }
397  }
398 
399  sqlite3_free_table( results );
400 
401  // all right, it's empty: proceeding to initialize
402  char *errMsg = nullptr;
403  ret = sqlite3_exec( sqlite_handle, above41 ? "SELECT InitSpatialMetadata(1)" : "SELECT InitSpatialMetadata()", nullptr, nullptr, &errMsg );
404 
405  if ( ret != SQLITE_OK )
406  {
407  QString errCause = tr( "Unable to initialize SpatialMetadata:\n" );
408  errCause += QString::fromUtf8( errMsg );
409  showWarning( errCause );
410  sqlite3_free( errMsg );
411  return;
412  }
413  spatial_ref_sys_init( sqlite_handle, 0 );
414 #else
415  ( void )sqlite_handle;
416 #endif
417 }
418 
419 bool QgsOfflineEditing::createOfflineDb( const QString &offlineDbPath, ContainerType containerType )
420 {
421  int ret;
422  char *errMsg = nullptr;
423  const QFile newDb( offlineDbPath );
424  if ( newDb.exists() )
425  {
426  QFile::remove( offlineDbPath );
427  }
428 
429  // see also QgsNewSpatialiteLayerDialog::createDb()
430 
431  const QFileInfo fullPath = QFileInfo( offlineDbPath );
432  const QDir path = fullPath.dir();
433 
434  // Must be sure there is destination directory ~/.qgis
435  QDir().mkpath( path.absolutePath() );
436 
437  // creating/opening the new database
438  const QString dbPath = newDb.fileName();
439 
440  // creating geopackage
441  switch ( containerType )
442  {
443  case GPKG:
444  {
445  OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
446  if ( !hGpkgDriver )
447  {
448  showWarning( tr( "Creation of database failed. GeoPackage driver not found." ) );
449  return false;
450  }
451 
452  const gdal::ogr_datasource_unique_ptr hDS( OGR_Dr_CreateDataSource( hGpkgDriver, dbPath.toUtf8().constData(), nullptr ) );
453  if ( !hDS )
454  {
455  showWarning( tr( "Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
456  return false;
457  }
458  break;
459  }
460  case SpatiaLite:
461  {
462  break;
463  }
464  }
465 
467  ret = database.open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
468  if ( ret )
469  {
470  // an error occurred
471  QString errCause = tr( "Could not create a new database\n" );
472  errCause += database.errorMessage();
473  showWarning( errCause );
474  return false;
475  }
476  // activating Foreign Key constraints
477  ret = sqlite3_exec( database.get(), "PRAGMA foreign_keys = 1", nullptr, nullptr, &errMsg );
478  if ( ret != SQLITE_OK )
479  {
480  showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) );
481  sqlite3_free( errMsg );
482  return false;
483  }
484  initializeSpatialMetadata( database.get() );
485  return true;
486 }
487 
488 void QgsOfflineEditing::createLoggingTables( sqlite3 *db )
489 {
490  // indices
491  QString sql = QStringLiteral( "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
492  sqlExec( db, sql );
493 
494  sql = QStringLiteral( "INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
495  sqlExec( db, sql );
496 
497  sql = QStringLiteral( "INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
498  sqlExec( db, sql );
499 
500  // layername <-> layer id
501  sql = QStringLiteral( "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
502  sqlExec( db, sql );
503 
504  // offline fid <-> remote fid
505  sql = QStringLiteral( "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER, 'remote_pk' TEXT)" );
506  sqlExec( db, sql );
507 
508  // added attributes
509  sql = QStringLiteral( "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
510  sql += QLatin1String( "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
511  sqlExec( db, sql );
512 
513  // added features
514  sql = QStringLiteral( "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
515  sqlExec( db, sql );
516 
517  // removed features
518  sql = QStringLiteral( "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
519  sqlExec( db, sql );
520 
521  // feature updates
522  sql = QStringLiteral( "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
523  sqlExec( db, sql );
524 
525  // geometry updates
526  sql = QStringLiteral( "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
527  sqlExec( db, sql );
528 
529  /* TODO: other logging tables
530  - attr delete (not supported by SpatiaLite provider)
531  */
532 }
533 
534 void QgsOfflineEditing::convertToOfflineLayer( QgsVectorLayer *layer, sqlite3 *db, const QString &offlineDbPath, bool onlySelected, ContainerType containerType, const QString &layerNameSuffix )
535 {
536  if ( !layer || !layer->isValid() )
537  {
538  QgsDebugMsgLevel( QStringLiteral( "Layer %1 is invalid and cannot be copied" ).arg( layer ? layer->id() : QStringLiteral( "<UNKNOWN>" ) ), 4 );
539  return;
540  }
541 
542  const QString tableName = layer->id();
543  QgsDebugMsgLevel( QStringLiteral( "Creating offline table %1 ..." ).arg( tableName ), 4 );
544 
545  // new layer
546  std::unique_ptr<QgsVectorLayer> newLayer;
547 
548  switch ( containerType )
549  {
550  case SpatiaLite:
551  {
552 #ifdef HAVE_SPATIALITE
553  // create table
554  QString sql = QStringLiteral( "CREATE TABLE '%1' (" ).arg( tableName );
555  QString delim;
556  const QgsFields providerFields = layer->dataProvider()->fields();
557  for ( const auto &field : providerFields )
558  {
559  QString dataType;
560  const QVariant::Type type = field.type();
561  if ( type == QVariant::Int || type == QVariant::LongLong )
562  {
563  dataType = QStringLiteral( "INTEGER" );
564  }
565  else if ( type == QVariant::Double )
566  {
567  dataType = QStringLiteral( "REAL" );
568  }
569  else if ( type == QVariant::String )
570  {
571  dataType = QStringLiteral( "TEXT" );
572  }
573  else if ( type == QVariant::StringList || type == QVariant::List )
574  {
575  dataType = QStringLiteral( "TEXT" );
576  showWarning( tr( "Field '%1' from layer %2 has been converted from a list to a string of comma-separated values." ).arg( field.name(), layer->name() ) );
577  }
578  else
579  {
580  showWarning( tr( "%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
581  }
582 
583  sql += delim + QStringLiteral( "'%1' %2" ).arg( field.name(), dataType );
584  delim = ',';
585  }
586  sql += ')';
587 
588  int rc = sqlExec( db, sql );
589 
590  // add geometry column
591  if ( layer->isSpatial() )
592  {
593  const QgsWkbTypes::Type sourceWkbType = layer->wkbType();
594 
595  QString geomType;
596  switch ( QgsWkbTypes::flatType( sourceWkbType ) )
597  {
598  case QgsWkbTypes::Point:
599  geomType = QStringLiteral( "POINT" );
600  break;
602  geomType = QStringLiteral( "MULTIPOINT" );
603  break;
605  geomType = QStringLiteral( "LINESTRING" );
606  break;
608  geomType = QStringLiteral( "MULTILINESTRING" );
609  break;
611  geomType = QStringLiteral( "POLYGON" );
612  break;
614  geomType = QStringLiteral( "MULTIPOLYGON" );
615  break;
616  default:
617  showWarning( tr( "Layer %1 has unsupported geometry type %2." ).arg( layer->name(), QgsWkbTypes::displayString( layer->wkbType() ) ) );
618  break;
619  };
620 
621  QString zmInfo = QStringLiteral( "XY" );
622 
623  if ( QgsWkbTypes::hasZ( sourceWkbType ) )
624  zmInfo += 'Z';
625  if ( QgsWkbTypes::hasM( sourceWkbType ) )
626  zmInfo += 'M';
627 
628  QString epsgCode;
629 
630  if ( layer->crs().authid().startsWith( QLatin1String( "EPSG:" ), Qt::CaseInsensitive ) )
631  {
632  epsgCode = layer->crs().authid().mid( 5 );
633  }
634  else
635  {
636  epsgCode = '0';
637  showWarning( tr( "Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->name(), layer->crs().authid() ) );
638  }
639 
640  const QString sqlAddGeom = QStringLiteral( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
641  .arg( tableName, epsgCode, geomType, zmInfo );
642 
643  // create spatial index
644  const QString sqlCreateIndex = QStringLiteral( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
645 
646  if ( rc == SQLITE_OK )
647  {
648  rc = sqlExec( db, sqlAddGeom );
649  if ( rc == SQLITE_OK )
650  {
651  rc = sqlExec( db, sqlCreateIndex );
652  }
653  }
654  }
655 
656  if ( rc != SQLITE_OK )
657  {
658  showWarning( tr( "Filling SpatiaLite for layer %1 failed" ).arg( layer->name() ) );
659  return;
660  }
661 
662  // add new layer
663  const QString connectionString = QStringLiteral( "dbname='%1' table='%2'%3 sql=" )
664  .arg( offlineDbPath,
665  tableName, layer->isSpatial() ? "(Geometry)" : "" );
667  newLayer = std::make_unique<QgsVectorLayer>( connectionString,
668  layer->name() + layerNameSuffix, QStringLiteral( "spatialite" ), options );
669  break;
670 
671 #else
672  showWarning( tr( "No Spatialite support available" ) );
673  return;
674 #endif
675  }
676 
677  case GPKG:
678  {
679  // Set options
680  char **options = nullptr;
681 
682  options = CSLSetNameValue( options, "OVERWRITE", "YES" );
683  options = CSLSetNameValue( options, "IDENTIFIER", tr( "%1 (offline)" ).arg( layer->id() ).toUtf8().constData() );
684  options = CSLSetNameValue( options, "DESCRIPTION", layer->dataComment().toUtf8().constData() );
685 
686  //the FID-name should not exist in the original data
687  const QString fidBase( QStringLiteral( "fid" ) );
688  QString fid = fidBase;
689  int counter = 1;
690  while ( layer->dataProvider()->fields().lookupField( fid ) >= 0 && counter < 10000 )
691  {
692  fid = fidBase + '_' + QString::number( counter );
693  counter++;
694  }
695  if ( counter == 10000 )
696  {
697  showWarning( tr( "Cannot make FID-name for GPKG " ) );
698  return;
699  }
700 
701  options = CSLSetNameValue( options, "FID", fid.toUtf8().constData() );
702 
703  if ( layer->isSpatial() )
704  {
705  options = CSLSetNameValue( options, "GEOMETRY_COLUMN", "geom" );
706  options = CSLSetNameValue( options, "SPATIAL_INDEX", "YES" );
707  }
708 
709  OGRSFDriverH hDriver = nullptr;
711  gdal::ogr_datasource_unique_ptr hDS( OGROpen( offlineDbPath.toUtf8().constData(), true, &hDriver ) );
712  OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS, static_cast<OGRwkbGeometryType>( layer->wkbType() ), options );
713  CSLDestroy( options );
714  if ( hSRS )
715  OSRRelease( hSRS );
716  if ( !hLayer )
717  {
718  showWarning( tr( "Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
719  return;
720  }
721 
722  const QgsFields providerFields = layer->dataProvider()->fields();
723  for ( const auto &field : providerFields )
724  {
725  const QString fieldName( field.name() );
726  const QVariant::Type type = field.type();
727  OGRFieldType ogrType( OFTString );
728  OGRFieldSubType ogrSubType = OFSTNone;
729  if ( type == QVariant::Int )
730  ogrType = OFTInteger;
731  else if ( type == QVariant::LongLong )
732  ogrType = OFTInteger64;
733  else if ( type == QVariant::Double )
734  ogrType = OFTReal;
735  else if ( type == QVariant::Time )
736  ogrType = OFTTime;
737  else if ( type == QVariant::Date )
738  ogrType = OFTDate;
739  else if ( type == QVariant::DateTime )
740  ogrType = OFTDateTime;
741  else if ( type == QVariant::Bool )
742  {
743  ogrType = OFTInteger;
744  ogrSubType = OFSTBoolean;
745  }
746  else if ( type == QVariant::StringList || type == QVariant::List )
747  {
748  ogrType = OFTString;
749  ogrSubType = OFSTJSON;
750  showWarning( tr( "Field '%1' from layer %2 has been converted from a list to a JSON-formatted string value." ).arg( fieldName, layer->name() ) );
751  }
752  else
753  ogrType = OFTString;
754 
755  const int ogrWidth = field.length();
756 
757  const gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( fieldName.toUtf8().constData(), ogrType ) );
758  OGR_Fld_SetWidth( fld.get(), ogrWidth );
759  if ( ogrSubType != OFSTNone )
760  OGR_Fld_SetSubType( fld.get(), ogrSubType );
761 
762  if ( OGR_L_CreateField( hLayer, fld.get(), true ) != OGRERR_NONE )
763  {
764  showWarning( tr( "Creation of field %1 failed (OGR error: %2)" )
765  .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
766  return;
767  }
768  }
769 
770  // In GDAL >= 2.0, the driver implements a deferred creation strategy, so
771  // issue a command that will force table creation
772  CPLErrorReset();
773  OGR_L_ResetReading( hLayer );
774  if ( CPLGetLastErrorType() != CE_None )
775  {
776  const QString msg( tr( "Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
777  showWarning( msg );
778  return;
779  }
780  hDS.reset();
781 
782  const QString uri = QStringLiteral( "%1|layername=%2|option:QGIS_FORCE_WAL=ON" ).arg( offlineDbPath, tableName );
784  newLayer = std::make_unique<QgsVectorLayer>( uri, layer->name() + layerNameSuffix, QStringLiteral( "ogr" ), layerOptions );
785  break;
786  }
787  }
788 
789  if ( newLayer && newLayer->isValid() )
790  {
791 
792  // copy features
793  newLayer->startEditing();
794  QgsFeature f;
795 
796  QgsFeatureRequest req;
797 
798  if ( onlySelected )
799  {
800  const QgsFeatureIds selectedFids = layer->selectedFeatureIds();
801  if ( !selectedFids.isEmpty() )
802  req.setFilterFids( selectedFids );
803  }
804 
805  QgsFeatureIterator fit = layer->dataProvider()->getFeatures( req );
806 
808  {
810  }
811  else
812  {
814  }
815  long long featureCount = 1;
816  const int remotePkIdx = getLayerPkIdx( layer );
817 
818  QList<QgsFeatureId> remoteFeatureIds;
819  QStringList remoteFeaturePks;
820  while ( fit.nextFeature( f ) )
821  {
822  remoteFeatureIds << f.id();
823  remoteFeaturePks << ( remotePkIdx >= 0 ? f.attribute( remotePkIdx ).toString() : QString() );
824 
825  // NOTE: SpatiaLite provider ignores position of geometry column
826  // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
827  int column = 0;
828  const QgsAttributes attrs = f.attributes();
829  // on GPKG newAttrs has an addition FID attribute, so we have to add a dummy in the original set
830  QgsAttributes newAttrs( containerType == GPKG ? attrs.count() + 1 : attrs.count() );
831  for ( int it = 0; it < attrs.count(); ++it )
832  {
833  QVariant attr = attrs.at( it );
834  if ( layer->fields().at( it ).type() == QVariant::StringList || layer->fields().at( it ).type() == QVariant::List )
835  {
836  attr = QgsJsonUtils::encodeValue( attr );
837  }
838  newAttrs[column++] = attr;
839  }
840  f.setAttributes( newAttrs );
841 
842  newLayer->addFeature( f );
843 
844  emit progressUpdated( featureCount++ );
845  }
846  if ( newLayer->commitChanges() )
847  {
849  featureCount = 1;
850 
851  // update feature id lookup
852  const int layerId = getOrCreateLayerId( db, layer->id() );
853  QList<QgsFeatureId> offlineFeatureIds;
854 
855  QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setNoAttributes() );
856  while ( fit.nextFeature( f ) )
857  {
858  offlineFeatureIds << f.id();
859  }
860 
861  // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
862  sqlExec( db, QStringLiteral( "BEGIN" ) );
863  const int remoteCount = remoteFeatureIds.size();
864  for ( int i = 0; i < remoteCount; i++ )
865  {
866  // Check if the online feature has been fetched (WFS download aborted for some reason)
867  if ( i < offlineFeatureIds.count() )
868  {
869  addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ), remoteFeaturePks.at( i ) );
870  }
871  else
872  {
873  showWarning( tr( "Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->name() ) );
874  return;
875  }
876  emit progressUpdated( featureCount++ );
877  }
878  sqlExec( db, QStringLiteral( "COMMIT" ) );
879  }
880  else
881  {
882  showWarning( newLayer->commitErrors().join( QLatin1Char( '\n' ) ) );
883  }
884 
885  // mark as offline layer
887 
888  // store original layer source and information
892  layer->setCustomProperty( CUSTOM_PROPERTY_LAYERNAME_SUFFIX, layerNameSuffix );
893 
894  //remove constrainst of fields that use defaultValueClauses from provider on original
895  const QgsFields fields = layer->fields();
896  QStringList notNullFieldNames;
897  for ( const QgsField &field : fields )
898  {
899  if ( !layer->dataProvider()->defaultValueClause( layer->fields().fieldOriginIndex( layer->fields().indexOf( field.name() ) ) ).isEmpty() )
900  {
901  notNullFieldNames << field.name();
902  }
903  }
904 
905  layer->setDataSource( newLayer->source(), newLayer->name(), newLayer->dataProvider()->name() );
906 
907  for ( const QgsField &field : fields ) //QString &fieldName : fieldsToRemoveConstraint )
908  {
909  const int index = layer->fields().indexOf( field.name() );
910  if ( index > -1 )
911  {
912  // restore unique value constraints coming from original data provider
915 
916  // remove any undesired not null constraints coming from original data provider
917  if ( notNullFieldNames.contains( field.name() ) )
918  {
919  notNullFieldNames.removeAll( field.name() );
921  }
922  }
923  }
924 
925  setupLayer( layer );
926  }
927  return;
928 }
929 
930 void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
931 {
932  Q_ASSERT( remoteLayer );
933 
934  const QString sql = QStringLiteral( "SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
935  QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
936 
937  const QgsVectorDataProvider *provider = remoteLayer->dataProvider();
938  const QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
939 
940  // NOTE: uses last matching QVariant::Type of nativeTypes
941  QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
942  for ( int i = 0; i < nativeTypes.size(); i++ )
943  {
944  const QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
945  typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
946  }
947 
948  emit progressModeSet( QgsOfflineEditing::AddFields, fields.size() );
949 
950  for ( int i = 0; i < fields.size(); i++ )
951  {
952  // lookup typename from layer provider
953  QgsField field = fields[i];
954  if ( typeNameLookup.contains( field.type() ) )
955  {
956  const QString typeName = typeNameLookup[ field.type()];
958  remoteLayer->addAttribute( field );
959  }
960  else
961  {
962  showWarning( QStringLiteral( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
963  }
964 
965  emit progressUpdated( i + 1 );
966  }
967 }
968 
969 void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
970 {
971  Q_ASSERT( offlineLayer );
972  Q_ASSERT( remoteLayer );
973 
974  const QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
975  const QList<int> featureIdInts = sqlQueryInts( db, sql );
976  QgsFeatureIds newFeatureIds;
977  for ( const int id : featureIdInts )
978  {
979  newFeatureIds << id;
980  }
981 
982  QgsExpressionContext context = remoteLayer->createExpressionContext();
983 
984  // get new features from offline layer
985  QgsFeatureList features;
986  QgsFeatureIterator it = offlineLayer->getFeatures( QgsFeatureRequest().setFilterFids( newFeatureIds ) );
987  QgsFeature feature;
988  while ( it.nextFeature( feature ) )
989  {
990  features << feature;
991  }
992 
993  // copy features to remote layer
994  emit progressModeSet( QgsOfflineEditing::AddFeatures, features.size() );
995 
996  int i = 1;
997  const int newAttrsCount = remoteLayer->fields().count();
998  for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
999  {
1000  // NOTE: SpatiaLite provider ignores position of geometry column
1001  // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
1002  const QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1003  QgsAttributes newAttrs( newAttrsCount );
1004  const QgsAttributes attrs = it->attributes();
1005  for ( int it = 0; it < attrs.count(); ++it )
1006  {
1007  const int remoteAttributeIndex = attrLookup.value( it, -1 );
1008  // if virtual or non existing field
1009  if ( remoteAttributeIndex == -1 )
1010  continue;
1011  QVariant attr = attrs.at( it );
1012  if ( remoteLayer->fields().at( remoteAttributeIndex ).type() == QVariant::StringList )
1013  {
1014  if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1015  {
1016  attr = attr.toStringList();
1017  }
1018  else
1019  {
1020  attr = QgsJsonUtils::parseArray( attr.toString(), QVariant::String );
1021  }
1022  }
1023  else if ( remoteLayer->fields().at( remoteAttributeIndex ).type() == QVariant::List )
1024  {
1025  if ( attr.type() == QVariant::StringList || attr.type() == QVariant::List )
1026  {
1027  attr = attr.toList();
1028  }
1029  else
1030  {
1031  attr = QgsJsonUtils::parseArray( attr.toString(), remoteLayer->fields().at( remoteAttributeIndex ).subType() );
1032  }
1033  }
1034  newAttrs[ remoteAttributeIndex ] = attr;
1035  }
1036 
1037  // respect constraints and provider default values
1038  QgsFeature f = QgsVectorLayerUtils::createFeature( remoteLayer, it->geometry(), newAttrs.toMap(), &context );
1039  remoteLayer->addFeature( f );
1040 
1041  emit progressUpdated( i++ );
1042  }
1043 }
1044 
1045 void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
1046 {
1047  Q_ASSERT( remoteLayer );
1048 
1049  const QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1050  const QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1051 
1052  emit progressModeSet( QgsOfflineEditing::RemoveFeatures, values.size() );
1053 
1054  int i = 1;
1055  for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1056  {
1057  const QgsFeatureId fid = remoteFid( db, layerId, *it, remoteLayer );
1058  remoteLayer->deleteFeature( fid );
1059 
1060  emit progressUpdated( i++ );
1061  }
1062 }
1063 
1064 void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
1065 {
1066  Q_ASSERT( offlineLayer );
1067  Q_ASSERT( remoteLayer );
1068 
1069  const QString sql = QStringLiteral( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
1070  const AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1071 
1072  emit progressModeSet( QgsOfflineEditing::UpdateFeatures, values.size() );
1073 
1074  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1075 
1076  for ( int i = 0; i < values.size(); i++ )
1077  {
1078  const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1079  QgsDebugMsgLevel( QStringLiteral( "Offline changeAttributeValue %1 = %2" ).arg( attrLookup[ values.at( i ).attr ] ).arg( values.at( i ).value ), 4 );
1080 
1081  const int remoteAttributeIndex = attrLookup[ values.at( i ).attr ];
1082  QVariant attr = values.at( i ).value;
1083  if ( remoteLayer->fields().at( remoteAttributeIndex ).type() == QVariant::StringList )
1084  {
1085  attr = QgsJsonUtils::parseArray( attr.toString(), QVariant::String );
1086  }
1087  else if ( remoteLayer->fields().at( remoteAttributeIndex ).type() == QVariant::List )
1088  {
1089  attr = QgsJsonUtils::parseArray( attr.toString(), remoteLayer->fields().at( remoteAttributeIndex ).subType() );
1090  }
1091 
1092  remoteLayer->changeAttributeValue( fid, remoteAttributeIndex, attr );
1093 
1094  emit progressUpdated( i + 1 );
1095  }
1096 }
1097 
1098 void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
1099 {
1100  Q_ASSERT( remoteLayer );
1101 
1102  const QString sql = QStringLiteral( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
1103  const GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1104 
1106 
1107  for ( int i = 0; i < values.size(); i++ )
1108  {
1109  const QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid, remoteLayer );
1110  QgsGeometry newGeom = QgsGeometry::fromWkt( values.at( i ).geom_wkt );
1111  remoteLayer->changeGeometry( fid, newGeom );
1112 
1113  emit progressUpdated( i + 1 );
1114  }
1115 }
1116 
1117 void QgsOfflineEditing::updateFidLookup( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
1118 {
1119  Q_ASSERT( remoteLayer );
1120 
1121  // update fid lookup for added features
1122 
1123  // get remote added fids
1124  // NOTE: use QMap for sorted fids
1125  QMap < QgsFeatureId, QString > newRemoteFids;
1126  QgsFeature f;
1127 
1128  QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setNoAttributes() );
1129 
1131 
1132  const int remotePkIdx = getLayerPkIdx( remoteLayer );
1133 
1134  int i = 1;
1135  while ( fit.nextFeature( f ) )
1136  {
1137  if ( offlineFid( db, layerId, f.id() ) == -1 )
1138  {
1139  newRemoteFids[ f.id()] = remotePkIdx >= 0 ? f.attribute( remotePkIdx ).toString() : QString();
1140  }
1141 
1142  emit progressUpdated( i++ );
1143  }
1144 
1145  // get local added fids
1146  // NOTE: fids are sorted
1147  const QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1148  const QList<int> newOfflineFids = sqlQueryInts( db, sql );
1149 
1150  if ( newRemoteFids.size() != newOfflineFids.size() )
1151  {
1152  //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
1153  }
1154  else
1155  {
1156  // add new fid lookups
1157  i = 0;
1158  sqlExec( db, QStringLiteral( "BEGIN" ) );
1159  for ( QMap<QgsFeatureId, QString>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1160  {
1161  addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key(), it.value() );
1162  }
1163  sqlExec( db, QStringLiteral( "COMMIT" ) );
1164  }
1165 }
1166 
1167 // NOTE: use this to map column indices in case the remote geometry column is not last
1168 QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer )
1169 {
1170  Q_ASSERT( offlineLayer );
1171  Q_ASSERT( remoteLayer );
1172 
1173  const QgsAttributeList &offlineAttrs = offlineLayer->attributeList();
1174 
1175  QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
1176  // NOTE: though offlineAttrs can have new attributes not yet synced, we take the amount of offlineAttrs
1177  // because we anyway only add mapping for the fields existing in remoteLayer (this because it could contain fid on 0)
1178  for ( int i = 0; i < offlineAttrs.size(); i++ )
1179  {
1180  if ( remoteLayer->fields().lookupField( offlineLayer->fields().field( i ).name() ) >= 0 )
1181  attrLookup.insert( offlineAttrs.at( i ), remoteLayer->fields().indexOf( offlineLayer->fields().field( i ).name() ) );
1182  }
1183 
1184  return attrLookup;
1185 }
1186 
1187 void QgsOfflineEditing::showWarning( const QString &message )
1188 {
1189  emit warning( tr( "Offline Editing Plugin" ), message );
1190 }
1191 
1192 sqlite3_database_unique_ptr QgsOfflineEditing::openLoggingDb()
1193 {
1194  sqlite3_database_unique_ptr database;
1196  if ( !dbPath.isEmpty() )
1197  {
1198  const QString absoluteDbPath = QgsProject::instance()->readPath( dbPath );
1199  const int rc = database.open( absoluteDbPath );
1200  if ( rc != SQLITE_OK )
1201  {
1202  QgsDebugMsg( QStringLiteral( "Could not open the SpatiaLite logging database" ) );
1203  showWarning( tr( "Could not open the SpatiaLite logging database" ) );
1204  }
1205  }
1206  else
1207  {
1208  QgsDebugMsg( QStringLiteral( "dbPath is empty!" ) );
1209  }
1210  return database;
1211 }
1212 
1213 int QgsOfflineEditing::getOrCreateLayerId( sqlite3 *db, const QString &qgisLayerId )
1214 {
1215  QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1216  int layerId = sqlQueryInt( db, sql, -1 );
1217  if ( layerId == -1 )
1218  {
1219  // next layer id
1220  sql = QStringLiteral( "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1221  const int newLayerId = sqlQueryInt( db, sql, -1 );
1222 
1223  // insert layer
1224  sql = QStringLiteral( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1225  sqlExec( db, sql );
1226 
1227  // increase layer_id
1228  // TODO: use trigger for auto increment?
1229  sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1230  sqlExec( db, sql );
1231 
1232  layerId = newLayerId;
1233  }
1234 
1235  return layerId;
1236 }
1237 
1238 int QgsOfflineEditing::getCommitNo( sqlite3 *db )
1239 {
1240  const QString sql = QStringLiteral( "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1241  return sqlQueryInt( db, sql, -1 );
1242 }
1243 
1244 void QgsOfflineEditing::increaseCommitNo( sqlite3 *db )
1245 {
1246  const QString sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1247  sqlExec( db, sql );
1248 }
1249 
1250 void QgsOfflineEditing::addFidLookup( sqlite3 *db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid, const QString &remotePk )
1251 {
1252  const QString sql = QStringLiteral( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3, %4 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid ).arg( sqlEscape( remotePk ) );
1253  sqlExec( db, sql );
1254 }
1255 
1256 QgsFeatureId QgsOfflineEditing::remoteFid( sqlite3 *db, int layerId, QgsFeatureId offlineFid, QgsVectorLayer *remoteLayer )
1257 {
1258  const int pkIdx = getLayerPkIdx( remoteLayer );
1259 
1260  if ( pkIdx == -1 )
1261  {
1262  const QString sql = QStringLiteral( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1263  return sqlQueryInt( db, sql, -1 );
1264  }
1265 
1266  const QString sql = QStringLiteral( "SELECT \"remote_pk\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1267  QString defaultValue;
1268  const QString pkValue = sqlQueryStr( db, sql, defaultValue );
1269 
1270  if ( pkValue.isNull() )
1271  {
1272  return -1;
1273  }
1274 
1275  const QString pkFieldName = remoteLayer->fields().at( pkIdx ).name();
1276  QgsFeatureIterator fit = remoteLayer->getFeatures( QStringLiteral( " %1 = %2 " ).arg( pkFieldName ).arg( sqlEscape( pkValue ) ) );
1277  QgsFeature f;
1278  while ( fit.nextFeature( f ) )
1279  return f.id();
1280 
1281  return -1;
1282 }
1283 
1284 QgsFeatureId QgsOfflineEditing::offlineFid( sqlite3 *db, int layerId, QgsFeatureId remoteFid )
1285 {
1286  const QString sql = QStringLiteral( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1287  return sqlQueryInt( db, sql, -1 );
1288 }
1289 
1290 bool QgsOfflineEditing::isAddedFeature( sqlite3 *db, int layerId, QgsFeatureId fid )
1291 {
1292  const QString sql = QStringLiteral( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1293  return ( sqlQueryInt( db, sql, 0 ) > 0 );
1294 }
1295 
1296 int QgsOfflineEditing::sqlExec( sqlite3 *db, const QString &sql )
1297 {
1298  char *errmsg = nullptr;
1299  const int rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, &errmsg );
1300  if ( rc != SQLITE_OK )
1301  {
1302  showWarning( errmsg );
1303  }
1304  return rc;
1305 }
1306 
1307 QString QgsOfflineEditing::sqlQueryStr( sqlite3 *db, const QString &sql, QString &defaultValue )
1308 {
1309  sqlite3_stmt *stmt = nullptr;
1310  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1311  {
1312  showWarning( sqlite3_errmsg( db ) );
1313  return defaultValue;
1314  }
1315 
1316  QString value = defaultValue;
1317  const int ret = sqlite3_step( stmt );
1318  if ( ret == SQLITE_ROW )
1319  {
1320  value = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) ) );
1321  }
1322  sqlite3_finalize( stmt );
1323 
1324  return value;
1325 }
1326 
1327 int QgsOfflineEditing::sqlQueryInt( sqlite3 *db, const QString &sql, int defaultValue )
1328 {
1329  sqlite3_stmt *stmt = nullptr;
1330  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1331  {
1332  showWarning( sqlite3_errmsg( db ) );
1333  return defaultValue;
1334  }
1335 
1336  int value = defaultValue;
1337  const int ret = sqlite3_step( stmt );
1338  if ( ret == SQLITE_ROW )
1339  {
1340  value = sqlite3_column_int( stmt, 0 );
1341  }
1342  sqlite3_finalize( stmt );
1343 
1344  return value;
1345 }
1346 
1347 QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3 *db, const QString &sql )
1348 {
1349  QList<int> values;
1350 
1351  sqlite3_stmt *stmt = nullptr;
1352  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1353  {
1354  showWarning( sqlite3_errmsg( db ) );
1355  return values;
1356  }
1357 
1358  int ret = sqlite3_step( stmt );
1359  while ( ret == SQLITE_ROW )
1360  {
1361  values << sqlite3_column_int( stmt, 0 );
1362 
1363  ret = sqlite3_step( stmt );
1364  }
1365  sqlite3_finalize( stmt );
1366 
1367  return values;
1368 }
1369 
1370 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3 *db, const QString &sql )
1371 {
1372  QList<QgsField> values;
1373 
1374  sqlite3_stmt *stmt = nullptr;
1375  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1376  {
1377  showWarning( sqlite3_errmsg( db ) );
1378  return values;
1379  }
1380 
1381  int ret = sqlite3_step( stmt );
1382  while ( ret == SQLITE_ROW )
1383  {
1384  const QgsField field( QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) ) ),
1385  static_cast< QVariant::Type >( sqlite3_column_int( stmt, 1 ) ),
1386  QString(), // typeName
1387  sqlite3_column_int( stmt, 2 ),
1388  sqlite3_column_int( stmt, 3 ),
1389  QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 4 ) ) ) );
1390  values << field;
1391 
1392  ret = sqlite3_step( stmt );
1393  }
1394  sqlite3_finalize( stmt );
1395 
1396  return values;
1397 }
1398 
1399 QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3 *db, const QString &sql )
1400 {
1401  QgsFeatureIds values;
1402 
1403  sqlite3_stmt *stmt = nullptr;
1404  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1405  {
1406  showWarning( sqlite3_errmsg( db ) );
1407  return values;
1408  }
1409 
1410  int ret = sqlite3_step( stmt );
1411  while ( ret == SQLITE_ROW )
1412  {
1413  values << sqlite3_column_int( stmt, 0 );
1414 
1415  ret = sqlite3_step( stmt );
1416  }
1417  sqlite3_finalize( stmt );
1418 
1419  return values;
1420 }
1421 
1422 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3 *db, const QString &sql )
1423 {
1424  AttributeValueChanges values;
1425 
1426  sqlite3_stmt *stmt = nullptr;
1427  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1428  {
1429  showWarning( sqlite3_errmsg( db ) );
1430  return values;
1431  }
1432 
1433  int ret = sqlite3_step( stmt );
1434  while ( ret == SQLITE_ROW )
1435  {
1436  AttributeValueChange change;
1437  change.fid = sqlite3_column_int( stmt, 0 );
1438  change.attr = sqlite3_column_int( stmt, 1 );
1439  change.value = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 2 ) ) );
1440  values << change;
1441 
1442  ret = sqlite3_step( stmt );
1443  }
1444  sqlite3_finalize( stmt );
1445 
1446  return values;
1447 }
1448 
1449 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3 *db, const QString &sql )
1450 {
1451  GeometryChanges values;
1452 
1453  sqlite3_stmt *stmt = nullptr;
1454  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1455  {
1456  showWarning( sqlite3_errmsg( db ) );
1457  return values;
1458  }
1459 
1460  int ret = sqlite3_step( stmt );
1461  while ( ret == SQLITE_ROW )
1462  {
1463  GeometryChange change;
1464  change.fid = sqlite3_column_int( stmt, 0 );
1465  change.geom_wkt = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 1 ) ) );
1466  values << change;
1467 
1468  ret = sqlite3_step( stmt );
1469  }
1470  sqlite3_finalize( stmt );
1471 
1472  return values;
1473 }
1474 
1475 void QgsOfflineEditing::committedAttributesAdded( const QString &qgisLayerId, const QList<QgsField> &addedAttributes )
1476 {
1477  const sqlite3_database_unique_ptr database = openLoggingDb();
1478  if ( !database )
1479  return;
1480 
1481  // insert log
1482  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1483  const int commitNo = getCommitNo( database.get() );
1484 
1485  for ( const QgsField &field : addedAttributes )
1486  {
1487  const QString sql = QStringLiteral( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1488  .arg( layerId )
1489  .arg( commitNo )
1490  .arg( field.name() )
1491  .arg( field.type() )
1492  .arg( field.length() )
1493  .arg( field.precision() )
1494  .arg( field.comment() );
1495  sqlExec( database.get(), sql );
1496  }
1497 
1498  increaseCommitNo( database.get() );
1499 }
1500 
1501 void QgsOfflineEditing::committedFeaturesAdded( const QString &qgisLayerId, const QgsFeatureList &addedFeatures )
1502 {
1503  const sqlite3_database_unique_ptr database = openLoggingDb();
1504  if ( !database )
1505  return;
1506 
1507  // insert log
1508  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1509 
1510  // get new feature ids from db
1511  QgsMapLayer *layer = QgsProject::instance()->mapLayer( qgisLayerId );
1512  const QString dataSourceString = layer->source();
1513  const QgsDataSourceUri uri = QgsDataSourceUri( dataSourceString );
1514 
1516  QString tableName;
1517 
1518  if ( !offlinePath.contains( ".gpkg" ) )
1519  {
1520  tableName = uri.table();
1521  }
1522  else
1523  {
1524  QgsProviderMetadata *ogrProviderMetaData = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
1525  const QVariantMap decodedUri = ogrProviderMetaData->decodeUri( dataSourceString );
1526  tableName = decodedUri.value( QStringLiteral( "layerName" ) ).toString();
1527  if ( tableName.isEmpty() )
1528  {
1529  showWarning( tr( "Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1530  }
1531  }
1532 
1533  // only store feature ids
1534  const QString sql = QStringLiteral( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
1535  const QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1536  for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
1537  {
1538  const QString sql = QStringLiteral( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1539  .arg( layerId )
1540  .arg( newFeatureIds.at( i ) );
1541  sqlExec( database.get(), sql );
1542  }
1543 }
1544 
1545 void QgsOfflineEditing::committedFeaturesRemoved( const QString &qgisLayerId, const QgsFeatureIds &deletedFeatureIds )
1546 {
1547  const sqlite3_database_unique_ptr database = openLoggingDb();
1548  if ( !database )
1549  return;
1550 
1551  // insert log
1552  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1553 
1554  for ( const QgsFeatureId id : deletedFeatureIds )
1555  {
1556  if ( isAddedFeature( database.get(), layerId, id ) )
1557  {
1558  // remove from added features log
1559  const QString sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( id );
1560  sqlExec( database.get(), sql );
1561  }
1562  else
1563  {
1564  const QString sql = QStringLiteral( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1565  .arg( layerId )
1566  .arg( id );
1567  sqlExec( database.get(), sql );
1568  }
1569  }
1570 }
1571 
1572 void QgsOfflineEditing::committedAttributeValuesChanges( const QString &qgisLayerId, const QgsChangedAttributesMap &changedAttrsMap )
1573 {
1574  const sqlite3_database_unique_ptr database = openLoggingDb();
1575  if ( !database )
1576  return;
1577 
1578  // insert log
1579  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1580  const int commitNo = getCommitNo( database.get() );
1581 
1582  for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1583  {
1584  const QgsFeatureId fid = cit.key();
1585  if ( isAddedFeature( database.get(), layerId, fid ) )
1586  {
1587  // skip added features
1588  continue;
1589  }
1590  const QgsAttributeMap attrMap = cit.value();
1591  for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1592  {
1593  QString value = it.value().type() == QVariant::StringList || it.value().type() == QVariant::List ? QgsJsonUtils::encodeValue( it.value() ) : it.value().toString();
1594  value.replace( QLatin1String( "'" ), QLatin1String( "''" ) ); // escape quote
1595  const QString sql = QStringLiteral( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1596  .arg( layerId )
1597  .arg( commitNo )
1598  .arg( fid )
1599  .arg( it.key() ) // attribute
1600  .arg( value );
1601  sqlExec( database.get(), sql );
1602  }
1603  }
1604 
1605  increaseCommitNo( database.get() );
1606 }
1607 
1608 void QgsOfflineEditing::committedGeometriesChanges( const QString &qgisLayerId, const QgsGeometryMap &changedGeometries )
1609 {
1610  const sqlite3_database_unique_ptr database = openLoggingDb();
1611  if ( !database )
1612  return;
1613 
1614  // insert log
1615  const int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1616  const int commitNo = getCommitNo( database.get() );
1617 
1618  for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1619  {
1620  const QgsFeatureId fid = it.key();
1621  if ( isAddedFeature( database.get(), layerId, fid ) )
1622  {
1623  // skip added features
1624  continue;
1625  }
1626  const QgsGeometry geom = it.value();
1627  const QString sql = QStringLiteral( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1628  .arg( layerId )
1629  .arg( commitNo )
1630  .arg( fid )
1631  .arg( geom.asWkt() );
1632  sqlExec( database.get(), sql );
1633 
1634  // TODO: use WKB instead of WKT?
1635  }
1636 
1637  increaseCommitNo( database.get() );
1638 }
1639 
1640 void QgsOfflineEditing::startListenFeatureChanges()
1641 {
1642  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1643 
1644  Q_ASSERT( vLayer );
1645 
1646  // enable logging, check if editBuffer is not null
1647  if ( vLayer->editBuffer() )
1648  {
1649  QgsVectorLayerEditBuffer *editBuffer = vLayer->editBuffer();
1651  this, &QgsOfflineEditing::committedAttributesAdded );
1653  this, &QgsOfflineEditing::committedAttributeValuesChanges );
1655  this, &QgsOfflineEditing::committedGeometriesChanges );
1656  }
1657  connect( vLayer, &QgsVectorLayer::committedFeaturesAdded,
1658  this, &QgsOfflineEditing::committedFeaturesAdded );
1659  connect( vLayer, &QgsVectorLayer::committedFeaturesRemoved,
1660  this, &QgsOfflineEditing::committedFeaturesRemoved );
1661 }
1662 
1663 void QgsOfflineEditing::stopListenFeatureChanges()
1664 {
1665  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1666 
1667  Q_ASSERT( vLayer );
1668 
1669  // disable logging, check if editBuffer is not null
1670  if ( vLayer->editBuffer() )
1671  {
1672  QgsVectorLayerEditBuffer *editBuffer = vLayer->editBuffer();
1673  disconnect( editBuffer, &QgsVectorLayerEditBuffer::committedAttributesAdded,
1674  this, &QgsOfflineEditing::committedAttributesAdded );
1676  this, &QgsOfflineEditing::committedAttributeValuesChanges );
1678  this, &QgsOfflineEditing::committedGeometriesChanges );
1679  }
1680  disconnect( vLayer, &QgsVectorLayer::committedFeaturesAdded,
1681  this, &QgsOfflineEditing::committedFeaturesAdded );
1682  disconnect( vLayer, &QgsVectorLayer::committedFeaturesRemoved,
1683  this, &QgsOfflineEditing::committedFeaturesRemoved );
1684 }
1685 
1686 void QgsOfflineEditing::setupLayer( QgsMapLayer *layer )
1687 {
1688  Q_ASSERT( layer );
1689 
1690  if ( QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer ) )
1691  {
1692  // detect offline layer
1693  if ( vLayer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
1694  {
1695  connect( vLayer, &QgsVectorLayer::editingStarted, this, &QgsOfflineEditing::startListenFeatureChanges );
1696  connect( vLayer, &QgsVectorLayer::editingStopped, this, &QgsOfflineEditing::stopListenFeatureChanges );
1697  }
1698  }
1699 }
1700 
1701 int QgsOfflineEditing::getLayerPkIdx( const QgsVectorLayer *layer ) const
1702 {
1703  const QList<int> pkAttrs = layer->primaryKeyAttributes();
1704  if ( pkAttrs.length() == 1 )
1705  {
1706  const QgsField pkField = layer->fields().at( pkAttrs[0] );
1707  const QVariant::Type pkType = pkField.type();
1708 
1709  if ( pkType == QVariant::String )
1710  {
1711  return pkAttrs[0];
1712  }
1713  }
1714 
1715  return -1;
1716 }
1717 
1718 QString QgsOfflineEditing::sqlEscape( QString value ) const
1719 {
1720  if ( value.isNull() )
1721  return QStringLiteral( "NULL" );
1722 
1723  value.replace( "'", "''" );
1724 
1725  return QStringLiteral( "'%1'" ).arg( value );
1726 }
QgsFeatureRequest::NoGeometry
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition: qgsfeaturerequest.h:115
QgsVectorLayer::getFeatures
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
Definition: qgsvectorlayer.cpp:1052
QgsExpressionContext
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Definition: qgsexpressioncontext.h:406
QgsMapLayer::crs
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QgsVectorLayer::editBuffer
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
Definition: qgsvectorlayer.h:2014
QgsProject::writeEntry
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean value to the project file.
Definition: qgsproject.cpp:2861
QgsProject::title
QString title() const
Returns the project's title.
Definition: qgsproject.cpp:503
spatialite_database_unique_ptr::open
int open(const QString &path)
Opens the database at the specified file path.
Definition: qgsspatialiteutils.cpp:44
QgsDataSourceUri
Class for storing the component parts of a RDBMS data source URI (e.g. a Postgres data source).
Definition: qgsdatasourceuri.h:37
qgsmaplayerstylemanager.h
sqlite3_database_unique_ptr::open
int open(const QString &path)
Opens the database at the specified file path.
Definition: qgssqliteutils.cpp:78
QgsVectorLayer::wkbType
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
Definition: qgsvectorlayer.cpp:725
QgsWkbTypes::displayString
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...
Definition: qgswkbtypes.cpp:145
QgsMapLayer::editingStopped
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
QgsWkbTypes::Point
@ Point
Definition: qgswkbtypes.h:72
QgsVectorLayer::dataProvider
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
Definition: qgsvectorlayer.cpp:676
CUSTOM_PROPERTY_ORIGINAL_LAYERID
#define CUSTOM_PROPERTY_ORIGINAL_LAYERID
Definition: qgsofflineediting.cpp:71
QgsWkbTypes::MultiPolygon
@ MultiPolygon
Definition: qgswkbtypes.h:78
qgsmapthemecollection.h
QgsField::length
int length
Definition: qgsfield.h:56
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsVectorLayerEditBuffer::committedGeometriesChanges
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
sqlite3
struct sqlite3 sqlite3
Definition: qgscoordinatereferencesystem.h:61
QgsOfflineEditing::isOfflineProject
bool isOfflineProject() const
Returns true if current project is offline.
Definition: qgsofflineediting.cpp:148
QgsProject::mapLayers
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
Definition: qgsproject.cpp:3955
QgsMapLayer::setCustomProperty
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
Definition: qgsmaplayer.cpp:1976
QgsVectorDataProvider::fields
QgsFields fields() const override=0
Returns the fields associated with this data provider.
QgsVectorLayer::featureCount
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
Definition: qgsvectorlayer.cpp:812
QgsWkbTypes::flatType
static Type flatType(Type type) SIP_HOLDGIL
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:732
QgsFeatureRequest::filterType
FilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
Definition: qgsfeaturerequest.h:355
gdal::ogr_datasource_unique_ptr
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
Definition: qgsogrutils.h:119
QgsWkbTypes::LineString
@ LineString
Definition: qgswkbtypes.h:73
qgsfeatureiterator.h
QgsFields::count
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsDataProvider::invalidateConnections
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
Definition: qgsdataprovider.h:445
QgsFields
Container of fields for a vector layer.
Definition: qgsfields.h:44
QgsFeatureRequest::FilterFids
@ FilterFids
Filter using feature IDs.
Definition: qgsfeaturerequest.h:118
QgsGeometry::fromWkt
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
gdal::ogr_field_def_unique_ptr
std::unique_ptr< std::remove_pointer< OGRFieldDefnH >::type, OGRFldDeleter > ogr_field_def_unique_ptr
Scoped OGR field definition.
Definition: qgsogrutils.h:129
qgslayertreelayer.h
PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
Definition: qgsofflineediting.cpp:74
QgsProject::transformContext
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:110
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:480
CUSTOM_PROPERTY_REMOTE_SOURCE
#define CUSTOM_PROPERTY_REMOTE_SOURCE
Definition: qgsofflineediting.cpp:68
QgsVectorLayer::isSpatial
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
Definition: qgsvectorlayer.cpp:3733
qgsogrutils.h
CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
Definition: qgsofflineediting.cpp:67
QgsVectorDataProvider::featureCount
long long featureCount() const override=0
Number of features in the layer.
QgsWkbTypes::Type
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:69
QgsProject::layerWasAdded
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the registry.
QgsChangedAttributesMap
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap
Definition: qgsfeature.h:868
QgsMapLayer::isValid
bool isValid
Definition: qgsmaplayer.h:81
QgsOfflineEditing::GPKG
@ GPKG
Definition: qgsofflineediting.h:55
QgsProject::readEntry
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.
Definition: qgsproject.cpp:2946
QgsOfflineEditing::warning
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
QgsMapLayer::editingStarted
void editingStarted()
Emitted when editing on this layer has started.
field
const QgsField & field
Definition: qgsfield.h:463
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsVectorDataProvider::NativeType
Definition: qgsvectordataprovider.h:471
QgsProject::mapLayer
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
Definition: qgsproject.cpp:3680
QgsAttributeList
QList< int > QgsAttributeList
Definition: qgsfield.h:26
QgsField::name
QString name
Definition: qgsfield.h:60
QgsFieldConstraints::ConstraintNotNull
@ ConstraintNotNull
Field may not be null.
Definition: qgsfieldconstraints.h:45
QgsVectorLayer::committedFeaturesAdded
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
Emitted when features are added to the provider if not in transaction mode.
QgsVectorLayerEditBuffer::committedAttributesAdded
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
QgsVectorLayer::setFieldConstraint
void setFieldConstraint(int index, QgsFieldConstraints::Constraint constraint, QgsFieldConstraints::ConstraintStrength strength=QgsFieldConstraints::ConstraintStrengthHard)
Sets a constraint for a specified field index.
Definition: qgsvectorlayer.cpp:5674
QgsVectorLayer::changeAttributeValue
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).
Definition: qgsvectorlayer.cpp:3072
QgsVectorLayer::attributeList
QgsAttributeList attributeList() const
Returns list of attribute indexes.
Definition: qgsvectorlayer.h:1712
spatialite_database_unique_ptr::open_v2
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
Definition: qgsspatialiteutils.cpp:68
QgsMapLayer::providerType
QString providerType() const
Returns the provider type (provider key) for this layer.
Definition: qgsmaplayer.cpp:1864
QgsOfflineEditing::UpdateFeatures
@ UpdateFeatures
Definition: qgsofflineediting.h:47
qgsapplication.h
OGRSpatialReferenceH
void * OGRSpatialReferenceH
Definition: qgscoordinatereferencesystem.h:66
QgsOfflineEditing::SpatiaLite
@ SpatiaLite
Definition: qgsofflineediting.h:54
QgsOfflineEditing::progressStarted
void progressStarted()
Emitted when the process has started.
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3436
QgsField::precision
int precision
Definition: qgsfield.h:57
QgsVectorLayer::changeGeometry
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...
Definition: qgsvectorlayer.cpp:3048
QgsFeature::id
QgsFeatureId id
Definition: qgsfeature.h:68
QgsFeatureRequest
This class wraps a request for features to a vector layer (or directly its vector data provider).
Definition: qgsfeaturerequest.h:83
QgsWkbTypes::MultiLineString
@ MultiLineString
Definition: qgswkbtypes.h:77
QgsVectorDataProvider::nativeTypes
QList< QgsVectorDataProvider::NativeType > nativeTypes() const
Returns the names of the supported types.
Definition: qgsvectordataprovider.cpp:371
QgsOfflineEditing::UpdateGeometries
@ UpdateGeometries
Definition: qgsofflineediting.h:48
QgsVectorLayer::dataComment
QString dataComment() const
Returns a description for this layer as defined in the data provider.
Definition: qgsvectorlayer.cpp:398
qgsprovidermetadata.h
QgsField::setTypeName
void setTypeName(const QString &typeName)
Set the field type.
Definition: qgsfield.cpp:190
QgsVectorLayer::removeFieldConstraint
void removeFieldConstraint(int index, QgsFieldConstraints::Constraint constraint)
Removes a constraint for a specified field index.
Definition: qgsvectorlayer.cpp:5691
QgsOfflineEditing::QgsOfflineEditing
QgsOfflineEditing()
Definition: qgsofflineediting.cpp:76
qgsproviderregistry.h
QgsVectorLayerEditBuffer::committedAttributeValuesChanges
void committedAttributeValuesChanges(const QString &layerId, const QgsChangedAttributesMap &changedAttributesValues)
qgsvectorlayerjoinbuffer.h
QgsOfflineEditing::AddFields
@ AddFields
Definition: qgsofflineediting.h:44
qgsdatasourceuri.h
QgsFieldConstraints::constraints
Constraints constraints
Definition: qgsfieldconstraints.h:36
QgsOfflineEditing::synchronize
void synchronize(bool useTransaction=false)
Synchronize to remote layers.
Definition: qgsofflineediting.cpp:153
qgslayertreegroup.h
qgsmaplayer.h
QgsVectorLayer::selectedFeatureIds
const Q_INVOKABLE QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
Definition: qgsvectorlayer.cpp:3621
PROJECT_ENTRY_SCOPE_OFFLINE
#define PROJECT_ENTRY_SCOPE_OFFLINE
Definition: qgsofflineediting.cpp:73
qgstransactiongroup.h
QgsField::comment
QString comment
Definition: qgsfield.h:59
QgsOfflineEditing::progressUpdated
void progressUpdated(long long progress)
Emitted with the progress of the current mode.
QgsProject::readPath
QString readPath(const QString &filename) const
Transforms a filename read from the project file to an absolute path.
Definition: qgsproject.cpp:3122
qgsvectordataprovider.h
QgsOfflineEditing::AddFeatures
@ AddFeatures
Definition: qgsofflineediting.h:45
QgsFeatureList
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:882
QgsAttributeMap
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:38
QgsWkbTypes::hasM
static bool hasM(Type type) SIP_HOLDGIL
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1130
QgsFieldConstraints::ConstraintUnique
@ ConstraintUnique
Field must have a unique value.
Definition: qgsfieldconstraints.h:46
qgsvectorlayerutils.h
QgsVectorDataProvider::NativeType::mTypeName
QString mTypeName
Definition: qgsvectordataprovider.h:485
spatialite_database_unique_ptr
Unique pointer for spatialite databases, which automatically closes the database when the pointer goe...
Definition: qgsspatialiteutils.h:56
QgsFeature::attribute
QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
Definition: qgsfeature.cpp:327
QgsMapLayer::id
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Definition: qgsmaplayer.cpp:169
QgsFeatureRequest::setFilterFids
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
Definition: qgsfeaturerequest.cpp:148
QgsSnappingConfig
This is a container for configuration of the snapping of the project.
Definition: qgssnappingconfig.h:37
QgsOfflineEditing::progressStopped
void progressStopped()
Emitted when the processing of all layers has finished.
QgsProviderRegistry::providerMetadata
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
Definition: qgsproviderregistry.cpp:873
QgsJsonUtils::encodeValue
static Q_INVOKABLE QString encodeValue(const QVariant &value)
Encodes a value to a JSON string representation, adding appropriate quotations and escaping where req...
Definition: qgsjsonutils.cpp:271
QgsVectorLayer::primaryKeyAttributes
QgsAttributeList primaryKeyAttributes() const
Returns the list of attributes which make up the layer's primary keys.
Definition: qgsvectorlayer.cpp:3441
typeName
const QString & typeName
Definition: qgswfsgetfeature.cpp:109
QgsMapLayer::removeCustomProperty
void removeCustomProperty(const QString &key)
Remove a custom property from layer.
Definition: qgsmaplayer.cpp:2004
qgslayertree.h
qgsrelationmanager.h
QgsVectorDataProvider::getFeatures
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override=0
Query the provider for features specified in request.
QgsFeatureIds
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
QgsOfflineEditing::RemoveFeatures
@ RemoveFeatures
Definition: qgsofflineediting.h:46
QgsFeature::attributes
QgsAttributes attributes
Definition: qgsfeature.h:69
QgsProject::setTitle
void setTitle(const QString &title)
Sets the project's title.
Definition: qgsproject.cpp:491
QgsFields::field
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:168
QgsProviderMetadata
Holds data provider key, description, and associated shared library file or function pointer informat...
Definition: qgsprovidermetadata.h:177
QgsVectorLayerUtils::createFeature
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.
Definition: qgsvectorlayerutils.cpp:478
qgsvectorlayer.h
QgsOfflineEditing::convertToOfflineProject
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.
Definition: qgsofflineediting.cpp:93
QgsVectorLayer::LayerOptions
Setting options for loading vector layers.
Definition: qgsvectorlayer.h:408
QgsField::subType
QVariant::Type subType() const
If the field is a collection, gets its element's type.
Definition: qgsfield.cpp:134
QgsOfflineEditing::ContainerType
ContainerType
Type of offline database container file.
Definition: qgsofflineediting.h:52
QgsMapLayer::source
QString source() const
Returns the source for the layer.
Definition: qgsmaplayer.cpp:300
CUSTOM_PROPERTY_LAYERNAME_SUFFIX
#define CUSTOM_PROPERTY_LAYERNAME_SUFFIX
Definition: qgsofflineediting.cpp:72
QgsVectorDataProvider::NativeType::mType
QVariant::Type mType
Definition: qgsvectordataprovider.h:486
QgsMapLayer::setDataSource
void setDataSource(const QString &dataSource, const QString &baseName, const QString &provider, bool loadDefaultStyleFlag=false)
Updates the data source of the layer.
Definition: qgsmaplayer.cpp:1803
QgsGeometry::asWkt
QString asWkt(int precision=17) const
Exports the geometry to WKT.
Definition: qgsgeometry.cpp:1407
QgsTransaction::connectionString
QString connectionString() const
Returns the connection string of the transaction.
Definition: qgstransaction.cpp:69
QgsMapLayer::customProperty
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
Definition: qgsmaplayer.cpp:1999
qgsgeometry.h
spatialite_database_unique_ptr::errorMessage
QString errorMessage() const
Returns the most recent error message encountered by the database.
Definition: qgsspatialiteutils.cpp:97
QgsOfflineEditing::progressModeSet
void progressModeSet(QgsOfflineEditing::ProgressMode mode, long long maximum)
Emitted when the mode for the progress of the current operation is set.
QgsOfflineEditing::ProcessFeatures
@ ProcessFeatures
Definition: qgsofflineediting.h:43
QgsFeatureIterator::nextFeature
bool nextFeature(QgsFeature &f)
Definition: qgsfeatureiterator.h:399
QgsGeometry
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
QgsVectorLayerEditBuffer
Definition: qgsvectorlayereditbuffer.h:37
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsProviderMetadata::decodeUri
virtual QVariantMap decodeUri(const QString &uri) const
Breaks a provider data source URI into its component paths (e.g.
Definition: qgsprovidermetadata.cpp:175
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsWkbTypes::Polygon
@ Polygon
Definition: qgswkbtypes.h:74
qgsspatialiteutils.h
QgsProject::snappingConfig
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:113
QgsWkbTypes::MultiPoint
@ MultiPoint
Definition: qgswkbtypes.h:76
QgsOfflineEditing::layerProgressUpdated
void layerProgressUpdated(int layer, int numLayers)
Emitted whenever a new layer is being processed.
qgsofflineediting.h
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:76
QgsDataSourceUri::database
QString database() const
Returns the database name stored in the URI.
Definition: qgsdatasourceuri.cpp:280
QgsFields::fieldOriginIndex
int fieldOriginIndex(int fieldIdx) const
Returns the field's origin index (its meaning is specific to each type of origin).
Definition: qgsfields.cpp:197
QgsField::constraints
QgsFieldConstraints constraints
Definition: qgsfield.h:63
QgsCoordinateReferenceSystem::authid
QString authid
Definition: qgscoordinatereferencesystem.h:217
QgsVectorDataProvider::defaultValueClause
virtual QString defaultValueClause(int fieldIndex) const
Returns any default value clauses which are present at the provider for a specified field index.
Definition: qgsvectordataprovider.cpp:159
QgsWkbTypes::hasZ
static bool hasZ(Type type) SIP_HOLDGIL
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1080
QgsAttributes
A vector of attributes. Mostly equal to QVector<QVariant>.
Definition: qgsattributes.h:57
QgsGeometryMap
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
Definition: qgsfeature.h:877
QgsVectorDataProvider
This is the base class for vector data providers.
Definition: qgsvectordataprovider.h:58
QgsFeature
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:55
QgsVectorLayer::deleteFeature
bool deleteFeature(QgsFeatureId fid, DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
Definition: qgsvectorlayer.cpp:3405
qgsjsonutils.h
qgslogger.h
QgsFeature::setAttributes
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:160
qgsvectorfilewriter.h
QgsFields::lookupField
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
QgsVectorLayer::createExpressionContext
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Definition: qgsvectorlayer.cpp:5203
QgsFields::at
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
QgsOfflineEditing::CopyFeatures
@ CopyFeatures
Definition: qgsofflineediting.h:42
QgsDataSourceUri::table
QString table() const
Returns the table name stored in the URI.
Definition: qgsdatasourceuri.cpp:315
sqlite3_database_unique_ptr
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
Definition: qgssqliteutils.h:118
QgsFeatureIterator
Wrapper for iterator of features from vector data provider or vector layer.
Definition: qgsfeatureiterator.h:289
QgsVectorLayer::addFeature
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
Definition: qgsvectorlayer.cpp:1070
QgsProviderRegistry::instance
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
Definition: qgsproviderregistry.cpp:73
qgsproject.h
QgsOgrUtils::crsToOGRSpatialReference
static OGRSpatialReferenceH crsToOGRSpatialReference(const QgsCoordinateReferenceSystem &crs)
Returns a OGRSpatialReferenceH corresponding to the specified crs object.
Definition: qgsogrutils.cpp:1058
CUSTOM_PROPERTY_REMOTE_PROVIDER
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
Definition: qgsofflineediting.cpp:69
QgsJsonUtils::parseArray
static Q_INVOKABLE QVariantList parseArray(const QString &json, QVariant::Type type=QVariant::Invalid)
Parse a simple array (depth=1)
Definition: qgsjsonutils.cpp:332
QgsField::type
QVariant::Type type
Definition: qgsfield.h:58
qgsvectorlayereditbuffer.h
QgsProject::removeEntry
bool removeEntry(const QString &scope, const QString &key)
Remove the given key from the specified scope.
Definition: qgsproject.cpp:3043
QgsVectorLayer::committedFeaturesRemoved
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
Emitted when features are deleted from the provider if not in transaction mode.
QgsFields::indexOf
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
QgsFeatureId
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
QgsVectorLayer::addAttribute
bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
Definition: qgsvectorlayer.cpp:3161
QgsField
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:50