diff --git a/python/core/qgsauxiliarystorage.sip b/python/core/qgsauxiliarystorage.sip index f93c9caec34..d1ae76183b1 100644 --- a/python/core/qgsauxiliarystorage.sip +++ b/python/core/qgsauxiliarystorage.sip @@ -12,6 +12,55 @@ +class QgsAuxiliaryLayer : QgsVectorLayer +{ +%Docstring + + + Class allowing to manage the auxiliary storage for a vector layer + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsauxiliarystorage.h" +%End + public: + + QgsAuxiliaryLayer( const QString &pkField, const QString &filename, const QString &table, const QgsVectorLayer *vlayer ); +%Docstring + Constructor + + \param pkField The primary key to use for joining + \param filename The database path + \param table The table name + \param vlayer The target vector layer in join definition +%End + + virtual ~QgsAuxiliaryLayer(); +%Docstring + Destructor +%End + + + + QgsVectorLayerJoinInfo joinInfo() const; +%Docstring + Returns information to use for joining with primary key and so on. + :rtype: QgsVectorLayerJoinInfo +%End + + bool save(); +%Docstring + Commit changes and starts editing then. + + :return: true if commit step passed, false otherwise + :rtype: bool +%End + +}; + + class QgsAuxiliaryStorage { %Docstring @@ -95,6 +144,19 @@ class QgsAuxiliaryStorage :rtype: bool %End + QgsAuxiliaryLayer *createAuxiliaryLayer( const QgsField &field, const QgsVectorLayer *layer ) const; +%Docstring + Creates an auxiliary layer for a vector layer. A new table is created if + necessary. The primary key to use to construct the auxiliary layer is + given in parameter. + + \param field The primary key to join + \param layer The vector layer for which the auxiliary layer has to be created + + :return: A new auxiliary layer or a None if an error happened. + :rtype: QgsAuxiliaryLayer +%End + static QString extension(); %Docstring Returns the extension used for auxiliary databases. diff --git a/python/core/qgsproject.sip b/python/core/qgsproject.sip index d9b03e6db1e..06e6b796e7a 100644 --- a/python/core/qgsproject.sip +++ b/python/core/qgsproject.sip @@ -811,6 +811,14 @@ Returns the number of registered layers. :rtype: bool %End + QgsAuxiliaryStorage *auxiliaryStorage(); +%Docstring + Returns the current auxiliary storage. + +.. versionadded:: 3.0 + :rtype: QgsAuxiliaryStorage +%End + signals: void readProject( const QDomDocument & ); %Docstring diff --git a/python/core/qgsvectorlayer.sip b/python/core/qgsvectorlayer.sip index a1827cd1d29..07e7a9dee2c 100644 --- a/python/core/qgsvectorlayer.sip +++ b/python/core/qgsvectorlayer.sip @@ -774,6 +774,39 @@ Return the provider type for this layer :rtype: str %End + bool loadAuxiliaryLayer( const QgsAuxiliaryStorage &storage ); +%Docstring + Loads the auxiliary layer for this vector layer. If there's no + corresponding table in the database, then nothing happens and false is + returned. + + \param storage The auxiliary storage where to look for the table + + :return: true if the auxiliary layer is well loaded, false otherwise + +.. versionadded:: 3.0 + :rtype: bool +%End + + void setAuxiliaryLayer( QgsAuxiliaryLayer *layer = 0 ); +%Docstring + Sets the current auxiliary layer. The auxiliary layer is automatically + put in editable mode and fields are updated. Moreover, a join is created + between the current layer and the auxiliary layer. Ownership is + transferred. + +.. versionadded:: 3.0 +%End + + QgsAuxiliaryLayer *auxiliaryLayer(); +%Docstring + Returns the current auxiliary layer. + +.. versionadded:: 3.0 + :rtype: QgsAuxiliaryLayer +%End + + virtual bool readSymbology( const QDomNode &layerNode, QString &errorMessage, const QgsReadWriteContext &context ); %Docstring diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 692222ff398..a52111947fd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -573,6 +573,7 @@ SET(QGIS_CORE_MOC_HDRS qgsactionmanager.h qgsactionscoperegistry.h qgsanimatedicon.h + qgsauxiliarystorage.h qgsbrowsermodel.h qgscoordinatereferencesystem.h qgscredentials.h diff --git a/src/core/qgsauxiliarystorage.cpp b/src/core/qgsauxiliarystorage.cpp index 3926c0738f4..63457fded38 100644 --- a/src/core/qgsauxiliarystorage.cpp +++ b/src/core/qgsauxiliarystorage.cpp @@ -24,6 +24,44 @@ const QString AS_JOINFIELD = "ASPK"; const QString AS_EXTENSION = "qgd"; +const QString AS_JOINPREFIX = "auxiliary_storage_"; + +QgsAuxiliaryLayer::QgsAuxiliaryLayer( const QString &pkField, const QString &filename, const QString &table, const QgsVectorLayer *vlayer ) + : QgsVectorLayer( QString( "%1|layername=%2" ).arg( filename, table ), QString( "%1_auxiliarystorage" ).arg( table ), "ogr" ) + , mLayer( vlayer ) +{ + // init join info + mJoinInfo.setPrefix( AS_JOINPREFIX ); + mJoinInfo.setJoinLayer( this ); + mJoinInfo.setJoinFieldName( AS_JOINFIELD ); + mJoinInfo.setTargetFieldName( pkField ); + mJoinInfo.setEditable( true ); + mJoinInfo.setUpsertOnEdit( true ); + mJoinInfo.setCascadedDelete( true ); +} + +QgsVectorLayerJoinInfo QgsAuxiliaryLayer::joinInfo() const +{ + return mJoinInfo; +} + +bool QgsAuxiliaryLayer::save() +{ + bool rc = false; + + if ( isEditable() ) + { + rc = commitChanges(); + } + + startEditing(); + + return rc; +} + +// +// QgsAuxiliaryStorage +// QgsAuxiliaryStorage::QgsAuxiliaryStorage( const QgsProject &project, bool copy ) : mValid( false ) @@ -92,6 +130,31 @@ bool QgsAuxiliaryStorage::save() const } } +QgsAuxiliaryLayer *QgsAuxiliaryStorage::createAuxiliaryLayer( const QgsField &field, const QgsVectorLayer *layer ) const +{ + QgsAuxiliaryLayer *alayer = nullptr; + + if ( mValid && layer && layer->isSpatial() ) + { + const QString table( layer->id() ); + sqlite3 *handler = openDB( currentFileName() ); + + if ( !tableExists( table, handler ) ) + { + if ( !createTable( field.typeName(), table, handler ) ) + { + close( handler ); + return alayer; + } + } + + alayer = new QgsAuxiliaryLayer( field.name(), currentFileName(), table, layer ); + close( handler ); + } + + return alayer; +} + bool QgsAuxiliaryStorage::saveAs( const QString &filename ) const { if ( QFile::exists( filename ) ) @@ -149,6 +212,16 @@ sqlite3 *QgsAuxiliaryStorage::openDB( const QString &filename ) return handler; } +bool QgsAuxiliaryStorage::createTable( const QString &type, const QString &table, sqlite3 *handler ) +{ + const QString sql = QString( "CREATE TABLE IF NOT EXISTS '%1' ( '%2' %3 )" ).arg( table ).arg( AS_JOINFIELD ).arg( type ); + + if ( !exec( sql, handler ) ) + return false; + + return true; +} + sqlite3 *QgsAuxiliaryStorage::createDB( const QString &filename ) { sqlite3 *handler = nullptr; @@ -169,6 +242,26 @@ sqlite3 *QgsAuxiliaryStorage::createDB( const QString &filename ) return handler; } +bool QgsAuxiliaryStorage::tableExists( const QString &table, sqlite3 *handler ) +{ + const QString sql = QString( "SELECT 1 FROM sqlite_master WHERE type='table' AND name='%1'" ).arg( table ); + int rows = 0; + int columns = 0; + char **results = nullptr; + const int rc = sqlite3_get_table( handler, sql.toStdString().c_str(), &results, &rows, &columns, nullptr ); + if ( rc != SQLITE_OK ) + { + debugMsg( sql, handler ); + return false; + } + + sqlite3_free_table( results ); + if ( rows >= 1 ) + return true; + + return false; +} + sqlite3 *QgsAuxiliaryStorage::open( const QString &filename ) { sqlite3 *handler = nullptr; diff --git a/src/core/qgsauxiliarystorage.h b/src/core/qgsauxiliarystorage.h index be5238d5e4f..6ce535459cf 100644 --- a/src/core/qgsauxiliarystorage.h +++ b/src/core/qgsauxiliarystorage.h @@ -20,6 +20,7 @@ #include "qgis_core.h" #include "qgsdatasourceuri.h" +#include "qgsvectorlayerjoininfo.h" #include @@ -27,6 +28,61 @@ class QgsProject; +/** + * \class QgsAuxiliaryLayer + * + * \ingroup core + * + * \brief Class allowing to manage the auxiliary storage for a vector layer + * + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsAuxiliaryLayer : public QgsVectorLayer +{ + Q_OBJECT + + public: + + /** + * Constructor + * + * \param pkField The primary key to use for joining + * \param filename The database path + * \param table The table name + * \param vlayer The target vector layer in join definition + */ + QgsAuxiliaryLayer( const QString &pkField, const QString &filename, const QString &table, const QgsVectorLayer *vlayer ); + + /** + * Destructor + */ + virtual ~QgsAuxiliaryLayer() = default; + + /** + * Copy constructor deactivated + */ + QgsAuxiliaryLayer( const QgsAuxiliaryLayer &rhs ) = delete; + + QgsAuxiliaryLayer &operator=( QgsAuxiliaryLayer const &rhs ) = delete; + + /** + * Returns information to use for joining with primary key and so on. + */ + QgsVectorLayerJoinInfo joinInfo() const; + + /** + * Commit changes and starts editing then. + * + * \returns true if commit step passed, false otherwise + */ + bool save(); + + private: + QgsVectorLayerJoinInfo mJoinInfo; + const QgsVectorLayer *mLayer; +}; + + /** * \class QgsAuxiliaryStorage * @@ -102,6 +158,18 @@ class CORE_EXPORT QgsAuxiliaryStorage */ bool save() const; + /** + * Creates an auxiliary layer for a vector layer. A new table is created if + * necessary. The primary key to use to construct the auxiliary layer is + * given in parameter. + * + * \param field The primary key to join + * \param layer The vector layer for which the auxiliary layer has to be created + * + * \returns A new auxiliary layer or a nullptr if an error happened. + */ + QgsAuxiliaryLayer *createAuxiliaryLayer( const QgsField &field, const QgsVectorLayer *layer ) const; + /** * Returns the extension used for auxiliary databases. */ @@ -117,6 +185,8 @@ class CORE_EXPORT QgsAuxiliaryStorage static sqlite3 *createDB( const QString &filename ); static sqlite3 *openDB( const QString &filename ); static void close( sqlite3 *handler ); + static bool tableExists( const QString &table, sqlite3 *handler ); + static bool createTable( const QString &type, const QString &table, sqlite3 *handler ); static bool exec( const QString &sql, sqlite3 *handler ); static void debugMsg( const QString &sql, sqlite3 *handler ); diff --git a/src/core/qgsproject.cpp b/src/core/qgsproject.cpp index 553e2adcba6..c1f7836d8c5 100644 --- a/src/core/qgsproject.cpp +++ b/src/core/qgsproject.cpp @@ -2246,6 +2246,22 @@ QList QgsProject::addMapLayers( if ( addToLegend ) emit legendLayersAdded( myResultList ); } + + if ( mAuxiliaryStorage ) + { + Q_FOREACH ( QgsMapLayer *mlayer, myResultList ) + { + if ( mlayer->type() != QgsMapLayer::VectorLayer ) + continue; + + QgsVectorLayer *vl = qobject_cast( mlayer ); + if ( vl && vl->isSpatial() ) + { + vl->loadAuxiliaryLayer( *mAuxiliaryStorage.get() ); + } + } + } + return myResultList; } @@ -2343,6 +2359,18 @@ void QgsProject::setTrustLayerMetadata( bool trust ) bool QgsProject::saveAuxiliaryStorage( const QString &filename ) { + Q_FOREACH ( QgsMapLayer *l, mapLayers().values() ) + { + if ( l->type() != QgsMapLayer::VectorLayer ) + continue; + + QgsVectorLayer *vl = qobject_cast( l ); + if ( vl && vl->auxiliaryLayer() ) + { + vl->auxiliaryLayer()->save(); + } + } + if ( !filename.isEmpty() ) { return mAuxiliaryStorage->saveAs( filename ); @@ -2352,3 +2380,13 @@ bool QgsProject::saveAuxiliaryStorage( const QString &filename ) return mAuxiliaryStorage->saveAs( *this ); } } + +const QgsAuxiliaryStorage *QgsProject::auxiliaryStorage() const +{ + return mAuxiliaryStorage.get(); +} + +QgsAuxiliaryStorage *QgsProject::auxiliaryStorage() +{ + return mAuxiliaryStorage.get(); +} diff --git a/src/core/qgsproject.h b/src/core/qgsproject.h index 7564fe2c118..1f1a732ecf6 100644 --- a/src/core/qgsproject.h +++ b/src/core/qgsproject.h @@ -810,6 +810,20 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera */ bool trustLayerMetadata() const { return mTrustLayerMetadata; } + /** + * Returns the current const auxiliary storage. + * + * \since QGIS 3.0 + */ + const QgsAuxiliaryStorage *auxiliaryStorage() const SIP_SKIP; + + /** + * Returns the current auxiliary storage. + * + * \since QGIS 3.0 + */ + QgsAuxiliaryStorage *auxiliaryStorage(); + signals: //! emitted when project is being read void readProject( const QDomDocument & ); diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index efce0ca51e7..3726e267820 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -88,6 +88,7 @@ #include "qgsunittypes.h" #include "qgstaskmanager.h" #include "qgstransaction.h" +#include "qgsauxiliarystorage.h" #include "diagram/qgsdiagram.h" @@ -139,6 +140,8 @@ QgsVectorLayer::QgsVectorLayer( const QString &vectorLayerPath, bool readExtentFromXml ) : QgsMapLayer( VectorLayer, baseName, vectorLayerPath ) , mProviderKey( providerKey ) + , mAuxiliaryLayer( nullptr ) + , mAuxiliaryLayerKey( QString() ) , mReadExtentFromXml( readExtentFromXml ) { mActions = new QgsActionManager( this ); @@ -1447,6 +1450,14 @@ bool QgsVectorLayer::readXml( const QDomNode &layer_node, const QgsReadWriteCont } } + // auxiliary layer + const QDomNode asNode = layer_node.namedItem( QStringLiteral( "auxiliaryLayer" ) ); + const QDomElement asElem = asNode.toElement(); + if ( !asElem.isNull() ) + { + mAuxiliaryLayerKey = asElem.attribute( QStringLiteral( "key" ) ); + } + return mValid; // should be true if read successfully } // void QgsVectorLayer::readXml @@ -1667,6 +1678,15 @@ bool QgsVectorLayer::writeXml( QDomNode &layer_node, writeStyleManager( layer_node, document ); + // auxiliary layer + QDomElement asElem = document.createElement( QStringLiteral( "auxiliaryLayer" ) ); + if ( mAuxiliaryLayer ) + { + const QString pkField = mAuxiliaryLayer->joinInfo().targetFieldName(); + asElem.setAttribute( QStringLiteral( "key" ), pkField ); + } + layer_node.appendChild( asElem ); + // renderer specific settings QString errorMsg; return writeSymbology( layer_node, document, errorMsg, context ); @@ -4223,6 +4243,58 @@ QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &resultFlag return loadNamedStyle( theURI, resultFlag, false ); } +bool QgsVectorLayer::loadAuxiliaryLayer( const QgsAuxiliaryStorage &storage ) +{ + bool rc = false; + + if ( isSpatial() && storage.isValid() && !mAuxiliaryLayerKey.isEmpty() ) + { + QgsAuxiliaryLayer *alayer = nullptr; + + int idx = fields().lookupField( mAuxiliaryLayerKey ); + + if ( idx >= 0 ) + { + alayer = storage.createAuxiliaryLayer( fields().field( idx ), this ); + + if ( alayer ) + { + setAuxiliaryLayer( alayer ); + rc = true; + } + } + } + + return rc; +} + +void QgsVectorLayer::setAuxiliaryLayer( QgsAuxiliaryLayer *alayer ) +{ + if ( alayer ) + { + if ( mAuxiliaryLayer ) + removeJoin( mAuxiliaryLayer->id() ); + + addJoin( alayer->joinInfo() ); + + if ( !alayer->isEditable() ) + alayer->startEditing(); + } + + mAuxiliaryLayer.reset( alayer ); + updateFields(); +} + +const QgsAuxiliaryLayer *QgsVectorLayer::auxiliaryLayer() const +{ + return mAuxiliaryLayer.get(); +} + +QgsAuxiliaryLayer *QgsVectorLayer::auxiliaryLayer() +{ + return mAuxiliaryLayer.get(); +} + QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &resultFlag, bool loadFromLocalDB ) { QgsDataSourceUri dsUri( theURI ); diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index ea1676f6508..6c7bb45c7d4 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -70,6 +70,8 @@ class QgsVectorLayerFeatureCounter; class QgsAbstractVectorLayerLabeling; class QgsPoint; class QgsFeedback; +class QgsAuxiliaryStorage; +class QgsAuxiliaryLayer; typedef QList QgsAttributeList; typedef QSet QgsAttributeIds; @@ -784,6 +786,44 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte virtual QString loadNamedStyle( const QString &theURI, bool &resultFlag SIP_OUT ) override; /** + * Loads the auxiliary layer for this vector layer. If there's no + * corresponding table in the database, then nothing happens and false is + * returned. + * + * \param storage The auxiliary storage where to look for the table + * + * \returns true if the auxiliary layer is well loaded, false otherwise + * + * \since QGIS 3.0 + */ + bool loadAuxiliaryLayer( const QgsAuxiliaryStorage &storage ); + + /** + * Sets the current auxiliary layer. The auxiliary layer is automatically + * put in editable mode and fields are updated. Moreover, a join is created + * between the current layer and the auxiliary layer. Ownership is + * transferred. + * + * \since QGIS 3.0 + * + */ + void setAuxiliaryLayer( QgsAuxiliaryLayer *layer = nullptr ); + + /** + * Returns the current auxiliary layer. + * + * \since QGIS 3.0 + */ + QgsAuxiliaryLayer *auxiliaryLayer(); + + /** + * Returns the current const auxiliary layer. + * + * \since QGIS 3.0 + */ + const QgsAuxiliaryLayer *auxiliaryLayer() const SIP_SKIP; + + /** * Read the symbology for the current layer from the Dom node supplied. * \param layerNode node that will contain the symbology definition for this layer. * \param errorMessage reference to string that will be updated with any error messages @@ -2145,6 +2185,12 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte mutable bool mValidExtent = false; mutable bool mLazyExtent = true; + //! Auxiliary layer + std::unique_ptr mAuxiliaryLayer; + + //! Key to use to join auxiliary layer + QString mAuxiliaryLayerKey; + // Features in renderer classes counted bool mSymbolFeatureCounted = false;