Merge pull request #50015 from elpaso/layer-metadata-provider

Layer metadata provider API (part 1)
This commit is contained in:
Alessandro Pasotti 2022-09-01 13:58:22 +02:00 committed by GitHub
commit 0c577f07ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 2331 additions and 44 deletions

View File

@ -0,0 +1,201 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/metadata/qgsabstractlayermetadataprovider.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
struct QgsMetadataSearchContext
{
QgsCoordinateTransformContext transformContext;
};
class QgsLayerMetadataProviderResult: QgsLayerMetadata
{
%Docstring(signature="appended")
Result record of layer metadata provider search.
The result contains QGIS metadata information and all information
that is required by QGIS to load the layer and to filter
the results.
The class extends :py:class:`QgsLayerMetadata` by adding information
taken directly from the provider which is required for
filtering (geographic extent) or because the actual
values may be different by those stored in the metadata
(CRS authid) or totally missing from the metadata
(data provider name and layer type).
.. versionadded:: 3.28
%End
%TypeHeaderCode
#include "qgsabstractlayermetadataprovider.h"
%End
public:
QgsLayerMetadataProviderResult( const QgsLayerMetadata &metadata );
%Docstring
Constructor for QgsLayerMetadataProviderResult.
:param metadata: layer metadata.
%End
const QgsPolygon &geographicExtent() const;
%Docstring
Returns the layer extent in EPSG:4326
%End
void setGeographicExtent( const QgsPolygon &geographicExtent );
%Docstring
Sets the layer extent in EPSG:4326 to ``geographicExtent``
%End
const QgsWkbTypes::GeometryType &geometryType() const;
%Docstring
Returns the layer geometry type.
%End
void setGeometryType( const QgsWkbTypes::GeometryType &geometryType );
%Docstring
Sets the layer geometry type to ``geometryType``.
%End
const QString &authid() const;
%Docstring
Returns the layer CRS authid.
%End
void setAuthid( const QString &authid );
%Docstring
Sets the layer ``authid``.
%End
const QString &uri() const;
%Docstring
Returns the layer data source URI.
%End
void setUri( const QString &Uri );
%Docstring
Sets the layer data source URI to ``Uri``.
%End
const QString &dataProviderName() const;
%Docstring
Returns the data provider name.
%End
void setDataProviderName( const QString &dataProviderName );
%Docstring
Sets the data provider name to ``dataProviderName``.
%End
QgsMapLayerType layerType() const;
%Docstring
Returns the layer type.
%End
void setLayerType( QgsMapLayerType layerType );
%Docstring
Sets the layer type to ``layerType``.
%End
const QString &standardUri() const;
%Docstring
Returns the metadata standard URI (usually "http://mrcc.com/qgis.dtd")
%End
void setStandardUri( const QString &standardUri );
%Docstring
Sets the metadata standard URI to ``standardUri``.
%End
};
class QgsLayerMetadataSearchResults
{
%Docstring(signature="appended")
Container of result records from a layer metadata search.
Contains the records of the layer metadata provider that matched the
search criteria and the list of the errors that occurred while
searching for metadata.
.. versionadded:: 3.28
%End
%TypeHeaderCode
#include "qgsabstractlayermetadataprovider.h"
%End
public:
QList<QgsLayerMetadataProviderResult> metadata() const;
%Docstring
Returns the list of metadata results.
%End
void addMetadata( const QgsLayerMetadataProviderResult &metadata );
%Docstring
Adds a ``Metadata`` record to the list of results.
%End
QStringList errors() const;
%Docstring
Returns the list of errors occurred during a metadata search.
%End
void addError( const QString &error );
%Docstring
Adds a ``error`` to the list of errors.
%End
};
class QgsAbstractLayerMetadataProvider
{
%Docstring(signature="appended")
Layer metadata provider backend interface.
.. versionadded:: 3.28
%End
%TypeHeaderCode
#include "qgsabstractlayermetadataprovider.h"
%End
public:
virtual QString id() const = 0;
%Docstring
Returns the id of the layer metadata provider implementation, usually the name of the data provider
but it may be another unique identifier.
%End
virtual QgsLayerMetadataSearchResults search( const QgsMetadataSearchContext &searchContext, const QString &searchString = QString(), const QgsRectangle &geographicExtent = QgsRectangle(), QgsFeedback *feedback = 0 ) const = 0;
%Docstring
Searches for metadata optionally filtering by search string and geographic extent.
:param searchContext: context for the metadata search.
:param searchString: defines a filter to limit the results to the records where the search string appears in the "identifier", "title" or "abstract" metadata fields, a case-insensitive comparison is used for the match.
:param geographicExtent: defines a filter where the spatial extent matches the given extent in EPSG:4326
:param feedback: can be used to monitor and control the search process.
:return: a :py:class:`QgsLayerMetadataSearchResult` object with a list of metadata and errors
%End
virtual ~QgsAbstractLayerMetadataProvider();
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/metadata/qgsabstractlayermetadataprovider.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,73 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/metadata/qgslayermetadataproviderregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
%ModuleHeaderCode
#include "qgsabstractlayermetadataprovider.h"
%End
class QgsLayerMetadataProviderRegistry : QObject
{
%Docstring(signature="appended")
Registry of layer metadata provider backends.
This is a singleton that should be accessed through :py:func:`QgsApplication.layerMetadataProviderRegistry()`.
.. seealso:: :py:class:`QgsAbstractLayerMetadataProvider`
.. versionadded:: 3.28
%End
%TypeHeaderCode
#include "qgslayermetadataproviderregistry.h"
%End
public:
explicit QgsLayerMetadataProviderRegistry( QObject *parent = 0 );
%Docstring
Creates the layer metadata provider registry, with an optional ``parent``
%End
void registerLayerMetadataProvider( QgsAbstractLayerMetadataProvider *metadataProvider /Transfer/ );
%Docstring
Registers a layer metadata provider ``metadataProvider`` and takes ownership of it
%End
void unregisterLayerMetadataProvider( QgsAbstractLayerMetadataProvider *metadataProvider );
%Docstring
Unregisters a layer metadata provider ``metadataProvider`` and destroys its instance
%End
QList<QgsAbstractLayerMetadataProvider *> layerMetadataProviders() const;
%Docstring
Returns the list of all registered layer metadata providers.
%End
QgsAbstractLayerMetadataProvider *layerMetadataProviderFromId( const QString &id );
%Docstring
Returns metadata provider implementation if the ``id`` matches one. Returns ``None`` otherwise.
%End
const QgsLayerMetadataSearchResults search( const QgsMetadataSearchContext &searchContext, const QString &searchString = QString(), const QgsRectangle &geographicExtent = QgsRectangle(), QgsFeedback *feedback = 0 );
%Docstring
Search for layers in all the registered layer metadata providers, optionally filtering by ``searchString``
and ``geographicExtent``, an optional ``feedback`` can be used to monitor and control the search process.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/metadata/qgslayermetadataproviderregistry.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -803,6 +803,31 @@ Returns a SQL query builder for the connection, which provides an interface for
The caller takes ownership of the returned object.
.. versionadded:: 3.28
%End
virtual QList<QgsLayerMetadataProviderResult> searchLayerMetadata( const QgsMetadataSearchContext &searchContext, const QString &searchString = QString(), const QgsRectangle &geographicExtent = QgsRectangle(), QgsFeedback *feedback = 0 ) const throw( QgsProviderConnectionException, QgsNotSupportedException );
%Docstring
Search the stored layer metadata in the connection,
optionally limiting the search to the metadata identifier, title,
abstract, keywords and categories.
``searchContext`` context for the search
``searchString`` limit the search to metadata having an extent intersecting ``geographicExtent``,
an optional ``feedback`` can be used to monitor and control the search process.
The default implementation raises a :py:class:`QgsNotSupportedException`, data providers may implement
the search functionality.
A :py:class:`QgsProviderConnectionException` is raised in case of errors happening during the search for
providers that implement the search functionality.
:return: a (possibly empty) list of :py:class:`QgsLayerMetadataProviderResult`, throws a :py:class:`QgsProviderConnectionException`
if any error occurred during the search.
:raises QgsProviderConnectionException:
:raises QgsNotSupportedException:
.. versionadded:: 3.28
%End

View File

@ -974,6 +974,13 @@ Gets the registry of available scalebar renderers.
Returns registry of available project storage implementations.
.. versionadded:: 3.2
%End
static QgsLayerMetadataProviderRegistry *layerMetadataProviderRegistry() /KeepReference/;
%Docstring
Returns registry of available layer metadata provider implementations.
.. versionadded:: 3.28
%End
static QgsExternalStorageRegistry *externalStorageRegistry() /KeepReference/;

View File

@ -499,6 +499,8 @@
%Include auto_generated/metadata/qgslayermetadatavalidator.sip
%Include auto_generated/metadata/qgsmetadatautils.sip
%Include auto_generated/metadata/qgsprojectmetadata.sip
%Include auto_generated/metadata/qgsabstractlayermetadataprovider.sip
%Include auto_generated/metadata/qgslayermetadataproviderregistry.sip
%Include auto_generated/network/qgsblockingnetworkrequest.sip
%Include auto_generated/network/qgsfiledownloader.sip
%Include auto_generated/network/qgsnetworkaccessmanager.sip

View File

@ -513,7 +513,7 @@ sub fix_annotations {
$line =~ s/SIP_PYNAME\(\s*(\w+)\s*\)/\/PyName=$1\//;
$line =~ s/SIP_TYPEHINT\(\s*([\w\.\s,\[\]]+?)\s*\)/\/TypeHint="$1"\//g;
$line =~ s/SIP_VIRTUALERRORHANDLER\(\s*(\w+)\s*\)/\/VirtualErrorHandler=$1\//;
$line =~ s/SIP_THROW\(\s*(\w+)\s*\)/throw\( $1 \)/;
$line =~ s/SIP_THROW\(\s*([\w\s,]+?)\s*\)/throw\( $1 \)/;
# combine multiple annotations
# https://regex101.com/r/uvCt4M/5

View File

@ -166,6 +166,8 @@ set(QGIS_CORE_SRCS
metadata/qgslayermetadataformatter.cpp
metadata/qgsmetadatautils.cpp
metadata/qgsprojectmetadata.cpp
metadata/qgsabstractlayermetadataprovider.cpp
metadata/qgslayermetadataproviderregistry.cpp
numericformats/qgsbasicnumericformat.cpp
numericformats/qgsbearingnumericformat.cpp
@ -279,6 +281,7 @@ set(QGIS_CORE_SRCS
providers/meshmemory/qgsmeshmemorydataprovider.cpp
providers/ogr/qgsogrlayermetadataprovider.cpp
providers/ogr/qgsogrprovider.cpp
providers/ogr/qgsogrprovidermetadata.cpp
providers/ogr/qgsogrproviderutils.cpp
@ -1597,6 +1600,8 @@ set(QGIS_CORE_HDRS
metadata/qgslayermetadatavalidator.h
metadata/qgsmetadatautils.h
metadata/qgsprojectmetadata.h
metadata/qgsabstractlayermetadataprovider.h
metadata/qgslayermetadataproviderregistry.h
network/qgsblockingnetworkrequest.h
network/qgsfiledownloader.h
@ -1693,6 +1698,7 @@ set(QGIS_CORE_HDRS
providers/meshmemory/qgsmeshmemorydataprovider.h
providers/ogr/qgsogrlayermetadataprovider.h
providers/ogr/qgsgeopackagedataitems.h
providers/ogr/qgsgeopackageprojectstorage.h
providers/ogr/qgsgeopackageproviderconnection.h

View File

@ -0,0 +1,118 @@
/***************************************************************************
qgsabstractlayermetadataprovider.cpp - QgsAbstractLayerMetadataProvider
---------------------
begin : 17.8.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsabstractlayermetadataprovider.h"
#include "qgsprovidermetadata.h"
#include "qgsproviderregistry.h"
#include "qgsfeedback.h"
QList<QgsLayerMetadataProviderResult> QgsLayerMetadataSearchResults::metadata() const
{
return mMetadata;
}
void QgsLayerMetadataSearchResults::addMetadata( const QgsLayerMetadataProviderResult &metadata )
{
mMetadata.push_back( metadata );
}
QStringList QgsLayerMetadataSearchResults::errors() const
{
return mErrors;
}
void QgsLayerMetadataSearchResults::addError( const QString &error )
{
mErrors.push_back( error );
}
QgsLayerMetadataProviderResult::QgsLayerMetadataProviderResult( const QgsLayerMetadata &metadata )
: QgsLayerMetadata( metadata )
{
}
const QgsPolygon &QgsLayerMetadataProviderResult::geographicExtent() const
{
return mGeographicExtent;
}
void QgsLayerMetadataProviderResult::setGeographicExtent( const QgsPolygon &geographicExtent )
{
mGeographicExtent = geographicExtent;
}
const QgsWkbTypes::GeometryType &QgsLayerMetadataProviderResult::geometryType() const
{
return mGeometryType;
}
void QgsLayerMetadataProviderResult::setGeometryType( const QgsWkbTypes::GeometryType &geometryType )
{
mGeometryType = geometryType;
}
const QString &QgsLayerMetadataProviderResult::authid() const
{
return mAuthid;
}
void QgsLayerMetadataProviderResult::setAuthid( const QString &authid )
{
mAuthid = authid;
}
const QString &QgsLayerMetadataProviderResult::uri() const
{
return mUri;
}
void QgsLayerMetadataProviderResult::setUri( const QString &newUri )
{
mUri = newUri;
}
const QString &QgsLayerMetadataProviderResult::dataProviderName() const
{
return mDataProviderName;
}
void QgsLayerMetadataProviderResult::setDataProviderName( const QString &dataProviderName )
{
mDataProviderName = dataProviderName;
}
QgsMapLayerType QgsLayerMetadataProviderResult::layerType() const
{
return mLayerType;
}
void QgsLayerMetadataProviderResult::setLayerType( QgsMapLayerType layerType )
{
mLayerType = layerType;
}
const QString &QgsLayerMetadataProviderResult::standardUri() const
{
return mStandardUri;
}
void QgsLayerMetadataProviderResult::setStandardUri( const QString &standardUri )
{
mStandardUri = standardUri;
}

View File

@ -0,0 +1,232 @@
/***************************************************************************
qgslayermetadataprovider.h - QgsLayerMetadataProvider
---------------------
begin : 17.8.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSABSTRACTLAYERMETADATAPROVIDER_H
#define QGSABSTRACTLAYERMETADATAPROVIDER_H
#include <QObject>
#include "qgis_core.h"
#include "qgis.h"
#include "qgslayermetadata.h"
#include "qgsrectangle.h"
#include "qgspolygon.h"
#include "qgscoordinatetransformcontext.h"
class QgsFeedback;
/**
* \ingroup core
* \brief Metadata search context
* \since QGIS 3.28
*/
struct CORE_EXPORT QgsMetadataSearchContext
{
//! Coordinate transform context
QgsCoordinateTransformContext transformContext;
};
/**
* \ingroup core
* \brief Result record of layer metadata provider search.
* The result contains QGIS metadata information and all information
* that is required by QGIS to load the layer and to filter
* the results.
*
* The class extends QgsLayerMetadata by adding information
* taken directly from the provider which is required for
* filtering (geographic extent) or because the actual
* values may be different by those stored in the metadata
* (CRS authid) or totally missing from the metadata
* (data provider name and layer type).
*
* \since QGIS 3.28
*/
class CORE_EXPORT QgsLayerMetadataProviderResult: public QgsLayerMetadata
{
public:
/**
* Constructor for QgsLayerMetadataProviderResult.
* \param metadata layer metadata.
*/
QgsLayerMetadataProviderResult( const QgsLayerMetadata &metadata );
/**
* Returns the layer extent in EPSG:4326
*/
const QgsPolygon &geographicExtent() const;
/**
* Sets the layer extent in EPSG:4326 to \a geographicExtent
*/
void setGeographicExtent( const QgsPolygon &geographicExtent );
/**
* Returns the layer geometry type.
*/
const QgsWkbTypes::GeometryType &geometryType() const;
/**
* Sets the layer geometry type to \a geometryType.
*/
void setGeometryType( const QgsWkbTypes::GeometryType &geometryType );
/**
* Returns the layer CRS authid.
*/
const QString &authid() const;
/**
* Sets the layer \a authid.
*/
void setAuthid( const QString &authid );
/**
* Returns the layer data source URI.
*/
const QString &uri() const;
/**
* Sets the layer data source URI to \a Uri.
*/
void setUri( const QString &Uri );
/**
* Returns the data provider name.
*/
const QString &dataProviderName() const;
/**
* Sets the data provider name to \a dataProviderName.
*/
void setDataProviderName( const QString &dataProviderName );
/**
* Returns the layer type.
*/
QgsMapLayerType layerType() const;
/**
* Sets the layer type to \a layerType.
*/
void setLayerType( QgsMapLayerType layerType );
/**
* Returns the metadata standard URI (usually "http://mrcc.com/qgis.dtd")
*/
const QString &standardUri() const;
/**
* Sets the metadata standard URI to \a standardUri.
*/
void setStandardUri( const QString &standardUri );
private:
//! Layer spatial extent of the layer in EPSG:4326
QgsPolygon mGeographicExtent;
//! Layer geometry type (Point, Polygon, Linestring)
QgsWkbTypes::GeometryType mGeometryType;
//! Layer CRS authid
QString mAuthid;
//! Layer QgsDataSourceUri string
QString mUri;
//! Layer data provider name
QString mDataProviderName;
//! Layer type (vector, raster etc.)
QgsMapLayerType mLayerType;
//! Metadata standard uri, QGIS QMD metadata format uses "http://mrcc.com/qgis.dtd"
QString mStandardUri;
};
/**
* \ingroup core
* \brief Container of result records from a layer metadata search.
*
* Contains the records of the layer metadata provider that matched the
* search criteria and the list of the errors that occurred while
* searching for metadata.
*
* \since QGIS 3.28
*/
class CORE_EXPORT QgsLayerMetadataSearchResults
{
public:
/**
* Returns the list of metadata results.
*/
QList<QgsLayerMetadataProviderResult> metadata() const;
/**
* Adds a \a Metadata record to the list of results.
*/
void addMetadata( const QgsLayerMetadataProviderResult &metadata );
/**
* Returns the list of errors occurred during a metadata search.
*/
QStringList errors() const;
/**
* Adds a \a error to the list of errors.
*/
void addError( const QString &error );
private:
//! List of metadata that matched the search criteria
QList<QgsLayerMetadataProviderResult> mMetadata;
//! List of errors occurred while searching
QStringList mErrors;
};
/**
* \ingroup core
* \brief Layer metadata provider backend interface.
*
* \since QGIS 3.28
*/
class CORE_EXPORT QgsAbstractLayerMetadataProvider
{
public:
/**
* Returns the id of the layer metadata provider implementation, usually the name of the data provider
* but it may be another unique identifier.
*/
virtual QString id() const = 0;
/**
* Searches for metadata optionally filtering by search string and geographic extent.
* \param searchContext context for the metadata search.
* \param searchString defines a filter to limit the results to the records where the search string appears in the "identifier", "title" or "abstract" metadata fields, a case-insensitive comparison is used for the match.
* \param geographicExtent defines a filter where the spatial extent matches the given extent in EPSG:4326
* \param feedback can be used to monitor and control the search process.
* \returns a QgsLayerMetadataSearchResult object with a list of metadata and errors
*/
virtual QgsLayerMetadataSearchResults search( const QgsMetadataSearchContext &searchContext, const QString &searchString = QString(), const QgsRectangle &geographicExtent = QgsRectangle(), QgsFeedback *feedback = nullptr ) const = 0;
virtual ~QgsAbstractLayerMetadataProvider() = default;
};
#endif // QGSABSTRACTLAYERMETADATAPROVIDER_H

View File

@ -0,0 +1,70 @@
/***************************************************************************
qgslayermetadataproviderregistry.cpp - QgsLayerMetadataProviderRegistry
---------------------
begin : 17.8.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslayermetadataproviderregistry.h"
#include "qgsabstractlayermetadataprovider.h"
#include "qgsfeedback.h"
QgsLayerMetadataProviderRegistry::QgsLayerMetadataProviderRegistry( QObject *parent )
: QObject( parent )
{
}
void QgsLayerMetadataProviderRegistry::registerLayerMetadataProvider( QgsAbstractLayerMetadataProvider *metadataProvider )
{
mMetadataProviders.insert( metadataProvider->id(), metadataProvider );
}
void QgsLayerMetadataProviderRegistry::unregisterLayerMetadataProvider( QgsAbstractLayerMetadataProvider *metadataProvider )
{
delete mMetadataProviders.take( metadataProvider->id() );
}
QList<QgsAbstractLayerMetadataProvider *> QgsLayerMetadataProviderRegistry::layerMetadataProviders() const
{
return mMetadataProviders.values();
}
QgsAbstractLayerMetadataProvider *QgsLayerMetadataProviderRegistry::layerMetadataProviderFromId( const QString &type )
{
return mMetadataProviders.value( type, nullptr );
}
const QgsLayerMetadataSearchResults QgsLayerMetadataProviderRegistry::search( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback )
{
QgsLayerMetadataSearchResults results;
for ( auto it = mMetadataProviders.cbegin(); it != mMetadataProviders.cend(); ++it )
{
if ( feedback && feedback->isCanceled() )
{
break;
}
const QgsLayerMetadataSearchResults providerResults { it.value()->search( searchContext, searchString, geographicExtent ) };
const QList<QgsLayerMetadataProviderResult> constMetadata { providerResults.metadata() };
for ( const QgsLayerMetadataProviderResult &metadata : std::as_const( constMetadata ) )
{
results.addMetadata( metadata );
}
const QList<QString> constErrors { providerResults.errors() };
for ( const QString &error : std::as_const( constErrors ) )
{
results.addError( error );
}
}
return results;
}

View File

@ -0,0 +1,77 @@
/***************************************************************************
qgslayermetadataproviderregistry.h - QgsLayerMetadataProviderRegistry
---------------------
begin : 17.8.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSLAYERMETADATAPROVIDERREGISTRY_H
#define QGSLAYERMETADATAPROVIDERREGISTRY_H
#include <QObject>
#include "qgis_core.h"
#include "qgis.h"
#include "qgslayermetadata.h"
#include "qgsabstractlayermetadataprovider.h"
class QgsFeedback;
#ifdef SIP_RUN
% ModuleHeaderCode
#include "qgsabstractlayermetadataprovider.h"
% End
#endif
/**
* \ingroup core
* \brief Registry of layer metadata provider backends.
*
* This is a singleton that should be accessed through QgsApplication::layerMetadataProviderRegistry().
*
* \see QgsAbstractLayerMetadataProvider
* \since QGIS 3.28
*/
class CORE_EXPORT QgsLayerMetadataProviderRegistry : public QObject
{
Q_OBJECT
public:
//! Creates the layer metadata provider registry, with an optional \a parent
explicit QgsLayerMetadataProviderRegistry( QObject *parent = nullptr );
//! Registers a layer metadata provider \a metadataProvider and takes ownership of it
void registerLayerMetadataProvider( QgsAbstractLayerMetadataProvider *metadataProvider SIP_TRANSFER );
//! Unregisters a layer metadata provider \a metadataProvider and destroys its instance
void unregisterLayerMetadataProvider( QgsAbstractLayerMetadataProvider *metadataProvider );
//! Returns the list of all registered layer metadata providers.
QList<QgsAbstractLayerMetadataProvider *> layerMetadataProviders() const;
//! Returns metadata provider implementation if the \a id matches one. Returns NULLPTR otherwise.
QgsAbstractLayerMetadataProvider *layerMetadataProviderFromId( const QString &id );
/**
* Search for layers in all the registered layer metadata providers, optionally filtering by \a searchString
* and \a geographicExtent, an optional \a feedback can be used to monitor and control the search process.
*/
const QgsLayerMetadataSearchResults search( const QgsMetadataSearchContext &searchContext, const QString &searchString = QString(), const QgsRectangle &geographicExtent = QgsRectangle(), QgsFeedback *feedback = nullptr );
private:
QHash<QString, QgsAbstractLayerMetadataProvider *> mMetadataProviders;
};
#endif // QGSLAYERMETADATAPROVIDERREGISTRY_H

View File

@ -28,6 +28,7 @@
#include "qgsfeedback.h"
#include "qgsogrutils.h"
#include "qgsfielddomain.h"
#include "qgscoordinatetransform.h"
#include <QTextCodec>
#include <QRegularExpression>
@ -396,6 +397,138 @@ QString QgsGeoPackageProviderConnection::primaryKeyColumnName( const QString &ta
return pkName;
}
QList<QgsLayerMetadataProviderResult> QgsGeoPackageProviderConnection::searchLayerMetadata( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback ) const
{
QList<QgsLayerMetadataProviderResult> results;
if ( ! feedback || ! feedback->isCanceled() )
{
try
{
const QString searchQuery { QStringLiteral( R"SQL(
SELECT
ref.table_name, md.metadata, gc.geometry_type_name
FROM
gpkg_metadata_reference AS ref
JOIN
gpkg_metadata AS md ON md.id = ref.md_file_id
JOIN
gpkg_geometry_columns AS gc ON gc.table_name = ref.table_name
WHERE
md.md_standard_uri = 'http://mrcc.com/qgis.dtd'
AND ref.reference_scope = 'table'
AND md.md_scope = 'dataset'
)SQL" ) };
const QList<QVariantList> constMetadataResults { executeSql( searchQuery, feedback ) };
for ( const QVariantList &mdRow : std::as_const( constMetadataResults ) )
{
if ( feedback && feedback->isCanceled() )
{
break;
}
// Read MD from the XML
QDomDocument doc;
doc.setContent( mdRow[1].toString() );
QgsLayerMetadata layerMetadata;
if ( layerMetadata.readMetadataXml( doc.documentElement() ) )
{
QgsLayerMetadataProviderResult result{ layerMetadata };
QgsRectangle extents;
const auto cExtents { layerMetadata.extent().spatialExtents() };
for ( const auto &ext : std::as_const( cExtents ) )
{
QgsRectangle bbox { ext.bounds.toRectangle() };
QgsCoordinateTransform ct { ext.extentCrs, QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), searchContext.transformContext };
ct.transform( bbox );
extents.combineExtentWith( bbox );
}
QgsPolygon poly;
poly.fromWkt( extents.asWktPolygon() );
// Filters
if ( ! geographicExtent.isEmpty() && ( poly.isEmpty() || ! geographicExtent.intersects( extents ) ) )
{
continue;
}
if ( ! searchString.isEmpty() && (
! result.title().contains( searchString, Qt::CaseInsensitive ) &&
! result.identifier().contains( searchString, Qt::CaseInsensitive ) &&
! result.abstract().contains( searchString, Qt::CaseInsensitive ) ) )
{
bool found { false };
const QList<QStringList> keyVals { result.keywords().values() };
for ( const QStringList &kws : std::as_const( keyVals ) )
{
const QStringList constKws { kws };
for ( const QString &kw : std::as_const( kws ) )
{
if ( kw.contains( searchString, Qt::CaseSensitivity::CaseInsensitive ) )
{
found = true;
break;
}
}
if ( found )
break;
}
if ( ! found )
{
found = result.categories().contains( searchString, Qt::CaseSensitivity::CaseInsensitive ) ;
}
if ( ! found )
continue;
}
result.setGeographicExtent( poly );
result.setStandardUri( QStringLiteral( "http://mrcc.com/qgis.dtd" ) );
result.setDataProviderName( QStringLiteral( "ogr" ) );
result.setAuthid( layerMetadata.crs().authid() );
result.setUri( tableUri( QString(), mdRow[0].toString() ) );
const QString geomType { mdRow[2].toString().toUpper() };
if ( geomType == QStringLiteral( "POINT" ) )
{
result.setGeometryType( QgsWkbTypes::GeometryType::PointGeometry );
}
else if ( geomType == QStringLiteral( "POLYGON" ) )
{
result.setGeometryType( QgsWkbTypes::GeometryType::PolygonGeometry );
}
else if ( geomType == QStringLiteral( "LINESTRING" ) )
{
result.setGeometryType( QgsWkbTypes::GeometryType::LineGeometry );
}
else
{
result.setGeometryType( QgsWkbTypes::GeometryType::UnknownGeometry );
}
result.setLayerType( QgsMapLayerType::VectorLayer );
results.push_back( result );
}
else
{
throw QgsProviderConnectionException( QStringLiteral( "Error reading XML metdadata from connection %1" ).arg( uri() ) );
}
}
}
catch ( const QgsProviderConnectionException &ex )
{
throw QgsProviderConnectionException( QStringLiteral( "Error fetching metdadata from connection %1: %2" ).arg( uri(), ex.what() ) );
}
}
return results;
}
QgsFields QgsGeoPackageProviderConnection::fields( const QString &schema, const QString &table ) const
{
Q_UNUSED( schema )

View File

@ -50,6 +50,7 @@ class QgsGeoPackageProviderConnection : public QgsOgrProviderConnection
QgsFields fields( const QString &schema, const QString &table ) const override;
QMultiMap<Qgis::SqlKeywordCategory, QStringList> sqlDictionary() override;
QList< Qgis::FieldDomainType > supportedFieldDomainTypes() const override;
QList<QgsLayerMetadataProviderResult> searchLayerMetadata( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback ) const override;
protected:
QString databaseQueryLogIdentifier() const override;

View File

@ -0,0 +1,63 @@
/***************************************************************************
qgsogrlayermetadataprovider.cpp - QgsOgrLayerMetadataProvider
---------------------
begin : 24.8.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsogrlayermetadataprovider.h"
#include "qgsprovidermetadata.h"
#include "qgsproviderregistry.h"
#include "qgsfeedback.h"
#include "qgsabstractdatabaseproviderconnection.h"
QString QgsOgrLayerMetadataProvider::id() const
{
return QStringLiteral( "ogr" );
}
QgsLayerMetadataSearchResults QgsOgrLayerMetadataProvider::search( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback ) const
{
QgsLayerMetadataSearchResults results;
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( id( ) ) };
if ( md && ( ! feedback || ! feedback->isCanceled( ) ) )
{
const QMap<QString, QgsAbstractProviderConnection *> cConnections { md->connections( ) };
for ( const QgsAbstractProviderConnection *conn : std::as_const( cConnections ) )
{
if ( feedback && feedback->isCanceled() )
{
break;
}
if ( const QgsAbstractDatabaseProviderConnection *dbConn = static_cast<const QgsAbstractDatabaseProviderConnection *>( conn ) )
{
try
{
const QList<QgsLayerMetadataProviderResult> res { dbConn->searchLayerMetadata( searchContext, searchString, geographicExtent, feedback ) };
for ( const QgsLayerMetadataProviderResult &result : std::as_const( res ) )
{
results.addMetadata( result );
}
}
catch ( const QgsProviderConnectionException &ex )
{
results.addError( QObject::tr( "An error occurred while searching for metadata in connection %1: %2" ).arg( conn->uri(), ex.what() ) );
}
}
}
}
return results;
}

View File

@ -0,0 +1,29 @@
/***************************************************************************
qgsogrlayermetadataprovider.h - QgsOgrLayerMetadataProvider
---------------------
begin : 24.8.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSOGRLAYERMETADATAPROVIDER_H
#define QGSOGRLAYERMETADATAPROVIDER_H
#define SIP_NO_FILE
#include <qgsabstractlayermetadataprovider.h>
class QgsOgrLayerMetadataProvider : public QgsAbstractLayerMetadataProvider
{
public:
QString id() const override;
QgsLayerMetadataSearchResults search( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback = nullptr ) const override;
};
#endif // QGSOGRLAYERMETADATAPROVIDER_H

View File

@ -904,6 +904,9 @@ void QgsOgrProvider::loadFields()
void QgsOgrProvider::loadMetadata()
{
// Set default, may be overridden by stored metadata
mLayerMetadata.setCrs( crs() );
if ( mOgrOrigLayer )
{
QRecursiveMutex *mutex = nullptr;

View File

@ -20,6 +20,8 @@ email : nyall dot dawson at gmail dot com
#include "qgssettings.h"
#include "qgsmessagelog.h"
#include "qgsogrtransaction.h"
#include "qgsogrlayermetadataprovider.h"
#include "qgslayermetadataproviderregistry.h"
#include "qgsgeopackageprojectstorage.h"
#include "qgsapplication.h"
#include "qgsogrconnpool.h"
@ -32,6 +34,8 @@ email : nyall dot dawson at gmail dot com
#include "qgsgdalutils.h"
#include "qgsproviderregistry.h"
#include "qgsvectorfilewriter.h"
#include "qgsvectorlayer.h"
#include "qgsproject.h"
#include <gdal.h>
#include <QFileInfo>
@ -1044,6 +1048,7 @@ bool QgsOgrProviderMetadata::saveLayerMetadata( const QString &uri, const QgsLay
throw QgsNotSupportedException( QObject::tr( "Storing metadata for the specified uri is not supported" ) );
}
QgsTransaction *QgsOgrProviderMetadata::createTransaction( const QString &connString )
{
auto ds = QgsOgrProviderUtils::getAlreadyOpenedDataset( connString );
@ -1058,12 +1063,16 @@ QgsTransaction *QgsOgrProviderMetadata::createTransaction( const QString &connSt
}
QgsGeoPackageProjectStorage *gGeoPackageProjectStorage = nullptr; // when not null it is owned by QgsApplication::projectStorageRegistry()
QgsOgrLayerMetadataProvider *gOgrLayerMetadataProvider = nullptr; // when not null it is owned by QgsApplication::layerMetadataProviderRegistry()
void QgsOgrProviderMetadata::initProvider()
{
Q_ASSERT( !gGeoPackageProjectStorage );
gGeoPackageProjectStorage = new QgsGeoPackageProjectStorage;
QgsApplication::projectStorageRegistry()->registerProjectStorage( gGeoPackageProjectStorage ); // takes ownership
Q_ASSERT( !gOgrLayerMetadataProvider );
gOgrLayerMetadataProvider = new QgsOgrLayerMetadataProvider();
QgsApplication::layerMetadataProviderRegistry()->registerLayerMetadataProvider( gOgrLayerMetadataProvider ); // takes ownership
}
@ -1071,6 +1080,8 @@ void QgsOgrProviderMetadata::cleanupProvider()
{
QgsApplication::projectStorageRegistry()->unregisterProjectStorage( gGeoPackageProjectStorage ); // destroys the object
gGeoPackageProjectStorage = nullptr;
QgsApplication::layerMetadataProviderRegistry()->unregisterLayerMetadataProvider( gOgrLayerMetadataProvider );
gOgrLayerMetadataProvider = nullptr;
QgsOgrConnPool::cleanupInstance();
// NOTE: QgsApplication takes care of
// calling OGRCleanupAll();

View File

@ -22,6 +22,8 @@ email : nyall dot dawson at gmail dot com
///@cond PRIVATE
#define SIP_NO_FILE
class QgsLayerMetadataProviderResult;
/**
* Entry point for registration of the OGR data provider
* \since QGIS 3.10
@ -83,6 +85,7 @@ class QgsOgrProviderMetadata final: public QgsProviderMetadata
QgsAbstractProviderConnection *createConnection( const QString &uri, const QVariantMap &configuration ) override;
};
///@endcond

View File

@ -1080,6 +1080,16 @@ bool QgsAbstractDatabaseProviderConnection::tableExists( const QString &schema,
return false;
}
QList<QgsLayerMetadataProviderResult> QgsAbstractDatabaseProviderConnection::searchLayerMetadata( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback ) const
{
Q_UNUSED( feedback );
Q_UNUSED( searchContext );
Q_UNUSED( searchString );
Q_UNUSED( geographicExtent );
throw QgsNotSupportedException( QObject::tr( "Provider %1 has no %2 method" ).arg( providerKey(), QStringLiteral( "searchLayerMetadata" ) ) );
}
void QgsAbstractDatabaseProviderConnection::dropRasterTable( const QString &, const QString & ) const
{
checkCapability( Capability::DropRasterTable );

View File

@ -21,6 +21,7 @@
#include "qgis_core.h"
#include "qgsfields.h"
#include "qgsvectordataprovider.h"
#include "qgsabstractlayermetadataprovider.h"
#include <QObject>
@ -914,6 +915,28 @@ class CORE_EXPORT QgsAbstractDatabaseProviderConnection : public QgsAbstractProv
*/
virtual QgsProviderSqlQueryBuilder *queryBuilder() const SIP_FACTORY;
/**
* Search the stored layer metadata in the connection,
* optionally limiting the search to the metadata identifier, title,
* abstract, keywords and categories.
* \a searchContext context for the search
* \a searchString limit the search to metadata having an extent intersecting \a geographicExtent,
* an optional \a feedback can be used to monitor and control the search process.
*
* The default implementation raises a QgsNotSupportedException, data providers may implement
* the search functionality.
*
* A QgsProviderConnectionException is raised in case of errors happening during the search for
* providers that implement the search functionality.
*
* \returns a (possibly empty) list of QgsLayerMetadataProviderResult, throws a QgsProviderConnectionException
* if any error occurred during the search.
* \throws QgsProviderConnectionException
* \throws QgsNotSupportedException
* \since QGIS 3.28
*/
virtual QList<QgsLayerMetadataProviderResult> searchLayerMetadata( const QgsMetadataSearchContext &searchContext, const QString &searchString = QString(), const QgsRectangle &geographicExtent = QgsRectangle(), QgsFeedback *feedback = nullptr ) const SIP_THROW( QgsProviderConnectionException, QgsNotSupportedException );
protected:
///@cond PRIVATE

View File

@ -240,6 +240,7 @@ int QgsProviderMetadata::listStyles( const QString &, QStringList &, QStringList
return -1;
}
bool QgsProviderMetadata::styleExists( const QString &, const QString &, QString &errorCause )
{
errorCause.clear();
@ -315,7 +316,7 @@ QgsAbstractProviderConnection *QgsProviderMetadata::findConnection( const QStrin
QgsAbstractProviderConnection *QgsProviderMetadata::createConnection( const QString &name )
{
Q_UNUSED( name );
throw QgsProviderConnectionException( QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "connection" ) ) );
throw QgsProviderConnectionException( QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "createConnection" ) ) );
}
@ -323,7 +324,7 @@ QgsAbstractProviderConnection *QgsProviderMetadata::createConnection( const QStr
{
Q_UNUSED( configuration );
Q_UNUSED( uri );
throw QgsProviderConnectionException( QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "connection" ) ) );
throw QgsProviderConnectionException( QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "createConnection" ) ) );
}
void QgsProviderMetadata::deleteConnection( const QString &name )

View File

@ -32,6 +32,7 @@
#include "qgis_core.h"
#include <functional>
#include "qgsabstractproviderconnection.h"
#include "qgsabstractlayermetadataprovider.h"
#include "qgsfields.h"
#include "qgsexception.h"

View File

@ -195,7 +195,7 @@
* try/catch blocks around call and catch the correct exception, otherwise only
* unknown generic exceptions are available for Python code.
*/
#define SIP_THROW(name)
#define SIP_THROW(name, ...)
/*
* Will insert a `%End` directive in sip files

View File

@ -20,6 +20,7 @@
#include "qgsexception.h"
#include "qgsgeometry.h"
#include "qgsannotationitemregistry.h"
#include "qgslayermetadataproviderregistry.h"
#include "qgslayout.h"
#include "qgslayoutitemregistry.h"
#include "qgslogger.h"
@ -2478,6 +2479,11 @@ QgsConnectionRegistry *QgsApplication::connectionRegistry()
return members()->mConnectionRegistry;
}
QgsLayerMetadataProviderRegistry *QgsApplication::layerMetadataProviderRegistry()
{
return members()->mLayerMetadataProviderRegistry;
}
QgsPageSizeRegistry *QgsApplication::pageSizeRegistry()
{
return members()->mPageSizeRegistry;
@ -2515,7 +2521,7 @@ QgsScaleBarRendererRegistry *QgsApplication::scaleBarRendererRegistry()
QgsProjectStorageRegistry *QgsApplication::projectStorageRegistry()
{
return members()->mProjectStorageRegistry.get();
return members()->mProjectStorageRegistry;
}
QgsExternalStorageRegistry *QgsApplication::externalStorageRegistry()
@ -2552,6 +2558,16 @@ QgsApplication::ApplicationMembers::ApplicationMembers()
mConnectionRegistry = new QgsConnectionRegistry();
profiler->end();
}
{
profiler->start( tr( "Create project storage registry" ) );
mProjectStorageRegistry = new QgsProjectStorageRegistry();
profiler->end();
}
{
profiler->start( tr( "Create metadata provider registry" ) );
mLayerMetadataProviderRegistry = new QgsLayerMetadataProviderRegistry();
profiler->end();
}
{
profiler->start( tr( "Create font manager" ) );
mFontManager = new QgsFontManager();
@ -2682,7 +2698,12 @@ QgsApplication::ApplicationMembers::ApplicationMembers()
}
{
profiler->start( tr( "Setup project storage registry" ) );
mProjectStorageRegistry.reset( new QgsProjectStorageRegistry() );
mProjectStorageRegistry = new QgsProjectStorageRegistry();
profiler->end();
}
{
profiler->start( tr( "Setup layer metadata provider registry" ) );
mLayerMetadataProviderRegistry = new QgsLayerMetadataProviderRegistry();
profiler->end();
}
{
@ -2759,6 +2780,8 @@ QgsApplication::ApplicationMembers::~ApplicationMembers()
delete mNumericFormatRegistry;
delete mBookmarkManager;
delete mConnectionRegistry;
delete mProjectStorageRegistry;
delete mLayerMetadataProviderRegistry;
delete mFontManager;
delete mLocalizedDataPathRegistry;
delete mCrsRegistry;

View File

@ -39,6 +39,7 @@ class QgsPaintEffectRegistry;
class QgsProjectStorageRegistry;
class QgsExternalStorageRegistry;
class QgsLocalizedDataPathRegistry;
class QgsLayerMetadataProviderRegistry;
class QgsRendererRegistry;
class QgsSvgCache;
class QgsImageCache;
@ -935,6 +936,12 @@ class CORE_EXPORT QgsApplication : public QApplication
*/
static QgsProjectStorageRegistry *projectStorageRegistry() SIP_KEEPREFERENCE;
/**
* Returns registry of available layer metadata provider implementations.
* \since QGIS 3.28
*/
static QgsLayerMetadataProviderRegistry *layerMetadataProviderRegistry() SIP_KEEPREFERENCE;
/**
* Returns registry of available external storage implementations.
* \since QGIS 3.20
@ -1133,7 +1140,8 @@ class CORE_EXPORT QgsApplication : public QApplication
QgsClassificationMethodRegistry *mClassificationMethodRegistry = nullptr;
QgsProcessingRegistry *mProcessingRegistry = nullptr;
QgsConnectionRegistry *mConnectionRegistry = nullptr;
std::unique_ptr<QgsProjectStorageRegistry> mProjectStorageRegistry;
QgsProjectStorageRegistry *mProjectStorageRegistry = nullptr;
QgsLayerMetadataProviderRegistry *mLayerMetadataProviderRegistry = nullptr;
QgsExternalStorageRegistry *mExternalStorageRegistry = nullptr;
QgsPageSizeRegistry *mPageSizeRegistry = nullptr;
QgsRasterRendererRegistry *mRasterRendererRegistry = nullptr;

View File

@ -14,6 +14,8 @@ set(PG_SRCS
qgspostgresexpressioncompiler.cpp
qgspostgreslistener.cpp
qgspostgresproviderconnection.cpp
qgspostgreslayermetadataprovider.cpp
qgspostgresprovidermetadatautils.cpp
)
if (WITH_GUI)
@ -93,6 +95,7 @@ set(PGRASTER_SRCS
raster/qgspostgresrasterutils.cpp
qgspostgresconn.cpp
qgspostgresconnpool.cpp
qgspostgresprovidermetadatautils.cpp
)
# static library

View File

@ -0,0 +1,68 @@
/***************************************************************************
qgspostgreslayermetadataprovider.cpp - QgsPostgresLayerMetadataProvider
---------------------
begin : 17.8.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgspostgreslayermetadataprovider.h"
#include "qgsproviderregistry.h"
#include "qgsprovidermetadata.h"
#include "qgsabstractdatabaseproviderconnection.h"
#include "qgsfeedback.h"
QString QgsPostgresLayerMetadataProvider::id() const
{
return QStringLiteral( "postgres" );
}
QgsLayerMetadataSearchResults QgsPostgresLayerMetadataProvider::search( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback ) const
{
QgsLayerMetadataSearchResults results;
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "postgres" ) ) };
if ( md && ( ! feedback || ! feedback->isCanceled() ) )
{
const QMap<QString, QgsAbstractProviderConnection *> constConnections { md->connections( ) };
for ( const QgsAbstractProviderConnection *conn : std::as_const( constConnections ) )
{
if ( feedback && feedback->isCanceled() )
{
break;
}
if ( conn->configuration().value( QStringLiteral( "metadataInDatabase" ), false ).toBool() )
{
if ( const QgsAbstractDatabaseProviderConnection *dbConn = static_cast<const QgsAbstractDatabaseProviderConnection *>( conn ) )
{
try
{
const QList<QgsLayerMetadataProviderResult> res { dbConn->searchLayerMetadata( searchContext, searchString, geographicExtent, feedback ) };
for ( const QgsLayerMetadataProviderResult &result : std::as_const( res ) )
{
results.addMetadata( result );
}
}
catch ( const QgsProviderConnectionException &ex )
{
results.addError( QObject::tr( "An error occurred while searching for metadata in connection %1: %2" ).arg( conn->uri(), ex.what() ) );
}
}
}
}
}
return results;
}

View File

@ -0,0 +1,30 @@
/***************************************************************************
qgspostgreslayermetadataprovider.h - QgsPostgresLayerMetadataProvider
---------------------
begin : 17.8.2022
copyright : (C) 2022 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSPOSTGRESLAYERMETADATAPROVIDER_H
#define QGSPOSTGRESLAYERMETADATAPROVIDER_H
#include "qgsabstractlayermetadataprovider.h"
class QgsPostgresLayerMetadataProvider : public QgsAbstractLayerMetadataProvider
{
public:
QString id() const override;
QgsLayerMetadataSearchResults search( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback = nullptr ) const override;
};
#endif // QGSPOSTGRESLAYERMETADATAPROVIDER_H

View File

@ -22,6 +22,7 @@
#include "qgsmessageoutput.h"
#include "qgsmessagelog.h"
#include "qgsprojectstorageregistry.h"
#include "qgslayermetadataproviderregistry.h"
#include "qgsrectangle.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsxmlutils.h"
@ -42,11 +43,13 @@
#include "qgsstringutils.h"
#include "qgsjsonutils.h"
#include "qgsdbquerylog.h"
#include "qgsproject.h"
#include "qgspostgreslayermetadataprovider.h"
#include "qgspostgresprovider.h"
#include "qgsprovidermetadata.h"
#include "qgspostgresproviderconnection.h"
#include "qgspostgresprovidermetadatautils.h"
#include <QRegularExpression>
const QString QgsPostgresProvider::POSTGRES_KEY = QStringLiteral( "postgres" );
@ -210,6 +213,38 @@ QgsPostgresProvider::QgsPostgresProvider( QString const &uri, const ProviderOpti
mLayerExtent.setMinimal();
// Try to load metadata
const QString schemaQuery = QStringLiteral( "SELECT table_schema FROM information_schema.tables WHERE table_name = 'qgis_layer_metadata'" );
QgsPostgresResult res( mConnectionRO->LoggedPQexec( "QgsPostgresProvider", schemaQuery ) );
if ( res.PQntuples( ) > 0 )
{
const QString schemaName = res.PQgetvalue( 0, 0 );
// TODO: also filter CRS?
const QString selectQuery = QStringLiteral( R"SQL(
SELECT
qmd
FROM %4.qgis_layer_metadata
WHERE
f_table_schema=%1
AND f_table_name=%2
AND f_geometry_column %3
AND layer_type='vector'
)SQL" )
.arg( QgsPostgresConn::quotedValue( mUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( mUri.table() ) )
.arg( mUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( mUri.geometryColumn() ) ) )
.arg( QgsPostgresConn::quotedIdentifier( schemaName ) );
QgsPostgresResult res( mConnectionRO->LoggedPQexec( "QgsPostgresProvider", selectQuery ) );
if ( res.PQntuples() > 0 )
{
QgsLayerMetadata metadata;
QDomDocument doc;
doc.setContent( res.PQgetvalue( 0, 0 ) );
mLayerMetadata.readMetadataXml( doc.documentElement() );
}
}
// set the primary key
if ( !determinePrimaryKey() )
{
@ -924,19 +959,22 @@ bool QgsPostgresProvider::loadFields()
{
QgsDebugMsgLevel( QStringLiteral( "Loading fields for table %1" ).arg( mTableName ), 2 );
// Get the table description
sql = QStringLiteral( "SELECT description FROM pg_description WHERE objoid=regclass(%1)::oid AND objsubid=0" ).arg( quotedValue( mQuery ) );
QgsPostgresResult tresult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
if ( ! tresult.result() )
if ( mLayerMetadata.abstract().isEmpty() )
{
throw PGException( tresult );
}
// Get the table description
sql = QStringLiteral( "SELECT description FROM pg_description WHERE objoid=regclass(%1)::oid AND objsubid=0" ).arg( quotedValue( mQuery ) );
QgsPostgresResult tresult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) );
if ( tresult.PQntuples() > 0 )
{
mDataComment = tresult.PQgetvalue( 0, 0 );
mLayerMetadata.setAbstract( mDataComment );
if ( ! tresult.result() )
{
throw PGException( tresult );
}
if ( tresult.PQntuples() > 0 )
{
mDataComment = tresult.PQgetvalue( 0, 0 );
mLayerMetadata.setAbstract( mDataComment );
}
}
}
@ -5356,13 +5394,15 @@ bool QgsPostgresProviderMetadata::styleExists( const QString &uri, const QString
" WHERE f_table_catalog=%1"
" AND f_table_schema=%2"
" AND f_table_name=%3"
" AND f_geometry_column=%4"
" AND f_geometry_column %4"
" AND (type=%5 OR type IS NULL)"
" AND styleName=%6" )
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) )
.arg( dsUri.geometryColumn().isEmpty() ?
QStringLiteral( "IS NULL" ) :
QStringLiteral( "= %1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) )
.arg( wkbTypeString )
.arg( QgsPostgresConn::quotedValue( styleId.isEmpty() ? dsUri.table() : styleId ) );
@ -5488,7 +5528,7 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString &
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) )
.arg( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) )
.arg( wkbTypeString )
.arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) );
@ -5505,7 +5545,7 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString &
" WHERE f_table_catalog=%6"
" AND f_table_schema=%7"
" AND f_table_name=%8"
" AND f_geometry_column=%9"
" AND f_geometry_column %9"
" AND styleName=%10"
" AND (type=%2 OR type IS NULL)" )
.arg( useAsDefault ? "true" : "false" )
@ -5515,7 +5555,7 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString &
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) )
.arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) )
// Must be the final .arg replacement - see above
.arg( QgsPostgresConn::quotedValue( qmlStyle ),
@ -5529,12 +5569,12 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString &
" WHERE f_table_catalog=%1"
" AND f_table_schema=%2"
" AND f_table_name=%3"
" AND f_geometry_column=%4"
" AND f_geometry_column %4"
" AND (type=%5 OR type IS NULL)" )
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) )
.arg( dsUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) )
.arg( wkbTypeString );
sql = QStringLiteral( "BEGIN; %1; %2; COMMIT;" ).arg( removeDefaultSql, sql );
@ -5690,12 +5730,14 @@ int QgsPostgresProviderMetadata::listStyles( const QString &uri, QStringList &id
QString selectOthersQuery = QString( "SELECT id,styleName,description"
" FROM layer_styles"
" WHERE NOT (f_table_catalog=%1 AND f_table_schema=%2 AND f_table_name=%3 AND f_geometry_column=%4 AND type=%5)"
" WHERE NOT (f_table_catalog=%1 AND f_table_schema=%2 AND f_table_name=%3 AND f_geometry_column %4 AND type=%5)"
" ORDER BY update_time DESC" )
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) )
.arg( dsUri.geometryColumn().isEmpty() ?
QStringLiteral( "IS NULL" ) :
QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) )
.arg( wkbTypeString );
result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), selectOthersQuery );
@ -5819,18 +5861,25 @@ QgsAbstractProviderConnection *QgsPostgresProviderMetadata::createConnection( co
QgsPostgresProjectStorage *gPgProjectStorage = nullptr; // when not null it is owned by QgsApplication::projectStorageRegistry()
QgsPostgresLayerMetadataProvider *gPgLayerMetadataProvider = nullptr; // when not null it is owned by QgsApplication::layerMetadataProviderRegistry()
void QgsPostgresProviderMetadata::initProvider()
{
Q_ASSERT( !gPgProjectStorage );
gPgProjectStorage = new QgsPostgresProjectStorage;
QgsApplication::projectStorageRegistry()->registerProjectStorage( gPgProjectStorage ); // takes ownership
Q_ASSERT( !gPgLayerMetadataProvider );
gPgLayerMetadataProvider = new QgsPostgresLayerMetadataProvider();
QgsApplication::layerMetadataProviderRegistry()->registerLayerMetadataProvider( gPgLayerMetadataProvider ); // takes ownership
}
void QgsPostgresProviderMetadata::cleanupProvider()
{
QgsApplication::projectStorageRegistry()->unregisterProjectStorage( gPgProjectStorage ); // destroys the object
gPgProjectStorage = nullptr;
QgsApplication::layerMetadataProviderRegistry()->unregisterLayerMetadataProvider( gPgLayerMetadataProvider );
gPgLayerMetadataProvider = nullptr;
QgsPostgresConnPool::cleanupInstance();
}
@ -6067,3 +6116,14 @@ QList<QgsMapLayerType> QgsPostgresProviderMetadata::supportedLayerTypes() const
{
return { QgsMapLayerType::VectorLayer };
}
bool QgsPostgresProviderMetadata::saveLayerMetadata( const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage )
{
return QgsPostgresProviderMetadataUtils::saveLayerMetadata( QgsMapLayerType::VectorLayer, uri, metadata, errorMessage );
}
QgsProviderMetadata::ProviderCapabilities QgsPostgresProviderMetadata::providerCapabilities() const
{
return QgsProviderMetadata::ProviderCapability::SaveLayerMetadata;
}

View File

@ -630,6 +630,8 @@ class QgsPostgresProviderMetadata final: public QgsProviderMetadata
QVariantMap decodeUri( const QString &uri ) const override;
QString encodeUri( const QVariantMap &parts ) const override;
QList< QgsMapLayerType > supportedLayerTypes() const override;
bool saveLayerMetadata( const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage ) override;
QgsProviderMetadata::ProviderCapabilities providerCapabilities() const override;
};
// clazy:excludeall=qstring-allocations

View File

@ -16,6 +16,7 @@
#include "qgspostgresproviderconnection.h"
#include "qgspostgresconn.h"
#include "qgspostgresconnpool.h"
#include "qgspostgresprovidermetadatautils.h"
#include "qgssettings.h"
#include "qgspostgresprovider.h"
#include "qgsexception.h"
@ -32,6 +33,23 @@ extern "C"
#include <libpq-fe.h>
}
// From configuration
const QStringList QgsPostgresProviderConnection::CONFIGURATION_PARAMETERS =
{
QStringLiteral( "publicOnly" ),
QStringLiteral( "geometryColumnsOnly" ),
QStringLiteral( "dontResolveType" ),
QStringLiteral( "allowGeometrylessTables" ),
QStringLiteral( "saveUsername" ),
QStringLiteral( "savePassword" ),
QStringLiteral( "estimatedMetadata" ),
QStringLiteral( "projectsInDatabase" ),
QStringLiteral( "metadataInDatabase" ),
};
const QString QgsPostgresProviderConnection::SETTINGS_BASE_KEY = QStringLiteral( "/PostgreSQL/connections/" );
QgsPostgresProviderConnection::QgsPostgresProviderConnection( const QString &name )
: QgsAbstractDatabaseProviderConnection( name )
{
@ -39,6 +57,26 @@ QgsPostgresProviderConnection::QgsPostgresProviderConnection( const QString &nam
// Remove the sql and table empty parts
const QRegularExpression removePartsRe { R"raw(\s*sql=\s*|\s*table=""\s*)raw" };
setUri( QgsPostgresConn::connUri( name ).uri( false ).replace( removePartsRe, QString() ) );
QgsSettings settings;
settings.beginGroup( SETTINGS_BASE_KEY );
settings.beginGroup( name );
QVariantMap config;
for ( const QString &p : std::as_const( CONFIGURATION_PARAMETERS ) )
{
const QVariant val = settings.value( p );
if ( val.isValid() )
{
config.insert( p, val );
}
}
settings.endGroup();
settings.endGroup();
setConfiguration( config );
setDefaultCapabilities();
}
@ -700,12 +738,11 @@ QStringList QgsPostgresProviderConnection::schemas( ) const
void QgsPostgresProviderConnection::store( const QString &name ) const
{
// TODO: move this to class configuration?
QString baseKey = QStringLiteral( "/PostgreSQL/connections/" );
// delete the original entry first
remove( name );
QgsSettings settings;
settings.beginGroup( baseKey );
settings.beginGroup( SETTINGS_BASE_KEY );
settings.beginGroup( name );
// From URI
@ -719,19 +756,7 @@ void QgsPostgresProviderConnection::store( const QString &name ) const
settings.setValue( "authcfg", dsUri.authConfigId() );
settings.setEnumValue( "sslmode", dsUri.sslMode() );
// From configuration
static const QStringList configurationParameters
{
QStringLiteral( "publicOnly" ),
QStringLiteral( "geometryColumnsOnly" ),
QStringLiteral( "dontResolveType" ),
QStringLiteral( "allowGeometrylessTables" ),
QStringLiteral( "saveUsername" ),
QStringLiteral( "savePassword" ),
QStringLiteral( "estimatedMetadata" ),
QStringLiteral( "projectsInDatabase" )
};
for ( const auto &p : configurationParameters )
for ( const auto &p : std::as_const( CONFIGURATION_PARAMETERS ) )
{
if ( configuration().contains( p ) )
{
@ -783,6 +808,11 @@ QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions QgsPostgresProvider
return options;
}
QList<QgsLayerMetadataProviderResult> QgsPostgresProviderConnection::searchLayerMetadata( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback ) const
{
return QgsPostgresProviderMetadataUtils::searchLayerMetadata( searchContext, uri(), searchString, geographicExtent, feedback );
}
QgsVectorLayer *QgsPostgresProviderConnection::createSqlVectorLayer( const SqlVectorLayerOptions &options ) const
{
// Precondition

View File

@ -79,6 +79,10 @@ class QgsPostgresProviderConnection : public QgsAbstractDatabaseProviderConnecti
QgsVectorLayer *createSqlVectorLayer( const SqlVectorLayerOptions &options ) const override;
QMultiMap<Qgis::SqlKeywordCategory, QStringList> sqlDictionary() override;
SqlVectorLayerOptions sqlOptions( const QString &layerSource ) override;
QList<QgsLayerMetadataProviderResult> searchLayerMetadata( const QgsMetadataSearchContext &searchContext, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback ) const override;
static const QStringList CONFIGURATION_PARAMETERS;
static const QString SETTINGS_BASE_KEY;
private:
@ -88,6 +92,7 @@ class QgsPostgresProviderConnection : public QgsAbstractDatabaseProviderConnecti
void dropTablePrivate( const QString &schema, const QString &name ) const;
void renameTablePrivate( const QString &schema, const QString &name, const QString &newName ) const;
};

View File

@ -0,0 +1,356 @@
/***************************************************************************
qgspostgresprovidermetadatautils.cpp - QgsPostgresProviderMetadataUtils
---------------------
begin : 29.8.2022
copyright : (C) 2019 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgspostgresprovidermetadatautils.h"
#include "qgspostgresproviderconnection.h"
#include "qgscoordinatetransform.h"
QList<QgsLayerMetadataProviderResult> QgsPostgresProviderMetadataUtils::searchLayerMetadata( const QgsMetadataSearchContext &searchContext, const QString &uri, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback )
{
Q_UNUSED( searchContext );
QList<QgsLayerMetadataProviderResult> results;
QgsDataSourceUri dsUri( uri );
QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri.connectionInfo( false ), false );
if ( conn && ( ! feedback || ! feedback->isCanceled() ) )
{
QString schemaName { QStringLiteral( "public" ) };
const QString schemaQuery = QStringLiteral( "SELECT table_schema FROM information_schema.tables WHERE table_name = 'qgis_layer_metadata'" );
QgsPostgresResult res( conn->LoggedPQexec( "QgsPostgresProviderMetadata", schemaQuery ) );
if ( res.PQntuples( ) > 0 )
{
schemaName = res.PQgetvalue( 0, 0 );
}
QStringList where;
if ( ! searchString.isEmpty() )
{
where.push_back( QStringLiteral( R"SQL((
abstract ILIKE %1 OR
identifier ILIKE %1 OR
REGEXP_REPLACE(UPPER(array_to_string((xpath('//keyword', qmd))::varchar[], '')),'</?KEYWORD>', '', 'g') ILIKE %1
))SQL" ).arg( QgsPostgresConn::quotedValue( QString( searchString ).prepend( QChar( '%' ) ).append( QChar( '%' ) ) ) ) );
}
if ( ! geographicExtent.isEmpty() )
{
where.push_back( QStringLiteral( "ST_Intersects( extent, ST_GeomFromText( %1, 4326 ) )" ).arg( QgsPostgresConn::quotedValue( geographicExtent.asWktPolygon() ) ) );
}
const QString listQuery = QStringLiteral( R"SQL(
SELECT
f_table_catalog
,f_table_schema
,f_table_name
,f_geometry_column
,identifier
,title
,abstract
,geometry_type
,ST_AsText( extent )
,crs
,layer_type
,qmd
,owner
,update_time
FROM %1.qgis_layer_metadata
%2
)SQL" ).arg( QgsPostgresConn::quotedIdentifier( schemaName ), QStringLiteral( " WHERE %1 " ).arg( where.join( QStringLiteral( " AND " ) ) ) );
res = conn->LoggedPQexec( "QgsPostgresProviderMetadata", listQuery );
if ( res.PQresultStatus() != PGRES_TUPLES_OK )
{
throw QgsProviderConnectionException( QObject::tr( "Error while fetching metadata from %1: %2" ).arg( dsUri.connectionInfo( false ), res.PQresultErrorMessage() ) );
}
for ( int row = 0; row < res.PQntuples( ); ++row )
{
if ( feedback && feedback->isCanceled() )
{
break;
}
QgsLayerMetadata metadata;
QDomDocument doc;
doc.setContent( res.PQgetvalue( 0, 11 ) );
metadata.readMetadataXml( doc.documentElement() );
QgsLayerMetadataProviderResult result { metadata };
QgsDataSourceUri uri { dsUri };
uri.setDatabase( res.PQgetvalue( 0, 0 ) );
uri.setSchema( res.PQgetvalue( 0, 1 ) );
uri.setTable( res.PQgetvalue( 0, 2 ) );
uri.setGeometryColumn( res.PQgetvalue( 0, 3 ) );
result.setStandardUri( QStringLiteral( "http://mrcc.com/qgis.dtd" ) );
result.setGeometryType( QgsWkbTypes::geometryType( QgsWkbTypes::parseType( res.PQgetvalue( 0, 7 ) ) ) );
QgsPolygon geographicExtent;
geographicExtent.fromWkt( res.PQgetvalue( 0, 8 ) );
result.setGeographicExtent( geographicExtent );
result.setAuthid( res.PQgetvalue( 0, 9 ) );
const QString layerType { res.PQgetvalue( 0, 10 ) };
if ( layerType == QStringLiteral( "raster" ) )
{
result.setDataProviderName( QStringLiteral( "postgresraster" ) );
result.setLayerType( QgsMapLayerType::RasterLayer );
}
else if ( layerType == QStringLiteral( "vector" ) )
{
result.setDataProviderName( QStringLiteral( "postgres" ) );
result.setLayerType( QgsMapLayerType::VectorLayer );
}
else
{
QgsDebugMsg( QStringLiteral( "Unsupported layer type '%1': skipping metadata record" ).arg( layerType ) );
continue;
}
result.setUri( uri.uri() );
results.append( result );
}
}
else
{
throw QgsProviderConnectionException( QObject::tr( "Connection to database %1 failed" ).arg( dsUri.connectionInfo( false ) ) );
}
return results;
}
bool QgsPostgresProviderMetadataUtils::saveLayerMetadata( const QgsMapLayerType &layerType, const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage )
{
QgsDataSourceUri dsUri( uri );
QString layerTypeString;
if ( layerType == QgsMapLayerType::VectorLayer )
{
layerTypeString = QStringLiteral( "vector" );
}
else if ( layerType == QgsMapLayerType::RasterLayer )
{
layerTypeString = QStringLiteral( "raster" );
}
else
{
// Unsupported!
return false;
}
QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri.connectionInfo( false ), false );
if ( !conn )
{
errorMessage = QObject::tr( "Connection to database failed" );
return false;
}
if ( dsUri.database().isEmpty() ) // typically when a service file is used
{
dsUri.setDatabase( conn->currentDatabase() );
}
// Try to load metadata
QString schemaName { dsUri.schema().isEmpty() ? QStringLiteral( "public" ) : dsUri.schema() };
const QString schemaQuery = QStringLiteral( "SELECT table_schema FROM information_schema.tables WHERE table_name = 'qgis_layer_metadata'" );
QgsPostgresResult res( conn->LoggedPQexec( "QgsPostgresProviderMetadataUtils", schemaQuery ) );
const bool metadataTableFound { res.PQntuples( ) > 0 };
if ( metadataTableFound )
{
schemaName = res.PQgetvalue( 0, 0 ) ;
}
else
{
QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadataUtils" ),
QStringLiteral( R"SQL(
CREATE TABLE %1.qgis_layer_metadata (
id SERIAL PRIMARY KEY
,f_table_catalog VARCHAR NOT NULL
,f_table_schema VARCHAR NOT NULL
,f_table_name VARCHAR NOT NULL
,f_geometry_column VARCHAR
,identifier TEXT NOT NULL
,title TEXT NOT NULL
,abstract TEXT
,geometry_type VARCHAR
,extent GEOMETRY(POLYGON, 4326)
,crs VARCHAR
,layer_type VARCHAR NOT NULL
,qmd XML NOT NULL
,owner VARCHAR(63) DEFAULT CURRENT_USER
,update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (f_table_catalog, f_table_schema, f_table_name, f_geometry_column, geometry_type, crs, layer_type)
)
)SQL" ).arg( QgsPostgresConn::quotedIdentifier( schemaName ) ) ) );
if ( res.PQresultStatus() != PGRES_COMMAND_OK )
{
errorMessage = QObject::tr( "Unable to save layer metadata. It's not possible to create the destination table on the database. Maybe this is due to table permissions (user=%1). Please contact your database admin" ).arg( dsUri.username() );
conn->unref();
return false;
}
}
const QString wkbTypeString = QgsWkbTypes::geometryDisplayString( QgsWkbTypes::geometryType( dsUri.wkbType() ) );
const QgsCoordinateReferenceSystem metadataCrs { metadata.crs() };
QgsCoordinateReferenceSystem destCrs {QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) };
QgsRectangle extents;
const auto cExtents { metadata.extent().spatialExtents() };
for ( const auto &ext : std::as_const( cExtents ) )
{
QgsRectangle bbox { ext.bounds.toRectangle() };
// Note: a default transform context is used here because we don't need high accuracy
QgsCoordinateTransform ct { ext.extentCrs, QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QgsCoordinateTransformContext() };
ct.transform( bbox );
extents.combineExtentWith( bbox );
}
// export metadata to XML
QDomImplementation domImplementation;
QDomDocumentType documentType = domImplementation.createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
QDomDocument document( documentType );
QDomElement rootNode = document.createElement( QStringLiteral( "qgis" ) );
rootNode.setAttribute( QStringLiteral( "version" ), Qgis::version() );
document.appendChild( rootNode );
if ( !metadata.writeMetadataXml( rootNode, document ) )
{
errorMessage = QObject::tr( "Error exporting metadata to XML" );
return false;
}
QString metadataXml;
QTextStream textStream( &metadataXml );
document.save( textStream, 2 );
// Note: in the construction of the INSERT and UPDATE strings the qmd values
// can contain user entered strings, which may themselves include %## values that would be
// replaced by the QString.arg function. To ensure that the final SQL string is not corrupt these
// two values are both replaced in the final .arg call of the string construction.
QString upsertSql = QStringLiteral( R"SQL(
INSERT INTO %1.qgis_layer_metadata(
f_table_catalog
,f_table_schema
,f_table_name
,f_geometry_column
,identifier
,title
,abstract
,geometry_type
,extent
,crs
,layer_type
,qmd) VALUES (
%2,%3,%4,%5,%6,%7,%8,%9,ST_GeomFromText(%10, 4326),%11,%12,XMLPARSE(DOCUMENT %13))
)SQL" )
.arg( QgsPostgresConn::quotedIdentifier( schemaName ) )
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) )
.arg( QgsPostgresConn::quotedValue( metadata.identifier() ) )
.arg( QgsPostgresConn::quotedValue( metadata.title() ) )
.arg( QgsPostgresConn::quotedValue( metadata.abstract() ) )
.arg( QgsPostgresConn::quotedValue( wkbTypeString ) )
.arg( QgsPostgresConn::quotedValue( extents.asWktPolygon() ) )
.arg( QgsPostgresConn::quotedValue( metadataCrs.authid() ) )
.arg( QgsPostgresConn::quotedValue( layerTypeString ) )
// Must be the final .arg replacement - see above
.arg( QgsPostgresConn::quotedValue( metadataXml ) );
QString checkQuery = QStringLiteral( R"SQL(
SELECT
f_table_catalog
,f_table_schema
,f_table_name
,f_geometry_column
,identifier
FROM %1.qgis_layer_metadata
WHERE
f_table_catalog=%2
AND f_table_schema=%3
AND f_table_name=%4
AND f_geometry_column %5
AND identifier = %6
AND layer_type = %7
)SQL" )
.arg( QgsPostgresConn::quotedIdentifier( schemaName ) )
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn().isEmpty() ?
QStringLiteral( "IS NULL" ) :
QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) )
.arg( QgsPostgresConn::quotedValue( metadata.identifier() ) )
.arg( QgsPostgresConn::quotedValue( layerTypeString ) );
res = conn->LoggedPQexec( "QgsPostgresProviderMetadataUtils", checkQuery );
if ( res.PQntuples() > 0 )
{
upsertSql = QStringLiteral( R"SQL(
UPDATE %1.qgis_layer_metadata(
SET
owner=CURRENT_USER
,title=%8
,abstract=%9
,geometry_type=%10
,extent=ST_GeomFromText(%11, 4326)
,crs=%12
,qmd=XMLPARSE(DOCUMENT %13)
WHERE
f_table_catalog=%2
AND f_table_schema=%3
AND f_table_name=%4
AND f_geometry_column %5
AND identifier = %6
AND layer_type = %7
)SQL" )
.arg( QgsPostgresConn::quotedIdentifier( schemaName ) )
.arg( QgsPostgresConn::quotedValue( dsUri.database() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.table() ) )
.arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn().isEmpty() ?
QStringLiteral( "IS NULL" ) :
QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) )
.arg( QgsPostgresConn::quotedValue( metadata.identifier() ) )
.arg( QgsPostgresConn::quotedValue( layerTypeString ) )
.arg( QgsPostgresConn::quotedValue( metadata.title() ) )
.arg( QgsPostgresConn::quotedValue( metadata.abstract() ) )
.arg( QgsPostgresConn::quotedValue( wkbTypeString ) )
.arg( QgsPostgresConn::quotedValue( extents.asWktPolygon() ) )
.arg( QgsPostgresConn::quotedValue( metadataCrs.authid() ) )
// Must be the final .arg replacement - see above
.arg( QgsPostgresConn::quotedValue( metadataXml ) );
}
res = conn->LoggedPQexec( "QgsPostgresProviderMetadataUtils", upsertSql );
bool saved = res.PQresultStatus() == PGRES_COMMAND_OK;
if ( !saved )
errorMessage = QObject::tr( "Unable to save layer metadata. It's not possible to insert a new record into the qgis_layer_metadata table. Maybe this is due to table permissions (user=%1). Please contact your database administrator." ).arg( dsUri.username() );
conn->unref();
return saved;
}

View File

@ -0,0 +1,37 @@
/***************************************************************************
qgspostgresprovidermetadatautils.h - QgsPostgresProviderMetadataUtils
---------------------
begin : 29.8.2022
copyright : (C) 2019 by Alessandro Pasotti
email : elpaso at itopen dot it
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSPOSTGRESPROVIDERMETADATAUTILS_H
#define QGSPOSTGRESPROVIDERMETADATAUTILS_H
#include "qgsabstractlayermetadataprovider.h"
#include "qgsrectangle.h"
class QgsFeedback;
/**
* The QgsPostgresProviderMetadataUtils class
* provides utility functions for QgsPostgresProviderMetadata and QgsPostgresRasterProviderMetadata data providers.
*/
class QgsPostgresProviderMetadataUtils
{
public:
static QList<QgsLayerMetadataProviderResult> searchLayerMetadata( const QgsMetadataSearchContext &searchContext, const QString &uri, const QString &searchString, const QgsRectangle &geographicExtent, QgsFeedback *feedback );
static bool saveLayerMetadata( const QgsMapLayerType &layerType, const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage );
};
#endif // QGSPOSTGRESPROVIDERMETADATAUTILS_H

View File

@ -16,6 +16,8 @@
#include <cstring>
#include "qgspostgresrasterprovider.h"
#include "qgspostgresprovidermetadatautils.h"
#include "qgslayermetadataproviderregistry.h"
#include "qgspostgrestransaction.h"
#include "qgsmessagelog.h"
#include "qgsrectangle.h"
@ -117,9 +119,43 @@ QgsPostgresRasterProvider::QgsPostgresRasterProvider( const QString &uri, const
QStringLiteral( "PostGIS" ), Qgis::MessageLevel::Warning );
}
// Try to load metadata
const QString schemaQuery = QStringLiteral( "SELECT table_schema FROM information_schema.tables WHERE table_name = 'qgis_layer_metadata'" );
QgsPostgresResult res( mConnectionRO->LoggedPQexec( "QgsPostgresRasterProvider", schemaQuery ) );
if ( res.PQntuples( ) > 0 )
{
const QString schemaName = res.PQgetvalue( 0, 0 );
// TODO: also filter CRS?
const QString selectQuery = QStringLiteral( R"SQL(
SELECT
qmd
FROM %4.qgis_layer_metadata
WHERE
f_table_schema=%1
AND f_table_name=%2
AND f_geometry_column %3
AND layer_type='raster'
)SQL" )
.arg( QgsPostgresConn::quotedValue( mUri.schema() ) )
.arg( QgsPostgresConn::quotedValue( mUri.table() ) )
.arg( mUri.geometryColumn().isEmpty() ? QStringLiteral( "IS NULL" ) : QStringLiteral( "=%1" ).arg( QgsPostgresConn::quotedValue( mUri.geometryColumn() ) ) )
.arg( QgsPostgresConn::quotedIdentifier( schemaName ) );
QgsPostgresResult res( mConnectionRO->LoggedPQexec( "QgsPostgresRasterProvider", selectQuery ) );
if ( res.PQntuples() > 0 )
{
QgsLayerMetadata metadata;
QDomDocument doc;
doc.setContent( res.PQgetvalue( 0, 0 ) );
mLayerMetadata.readMetadataXml( doc.documentElement() );
QgsMessageLog::logMessage( tr( "PostgreSQL raster layer metadata loaded from the database." ), tr( "PostGIS" ) );
}
}
mLayerMetadata.setType( QStringLiteral( "dataset" ) );
mLayerMetadata.setCrs( crs() );
mValid = true;
}
@ -687,6 +723,16 @@ QList<QgsMapLayerType> QgsPostgresRasterProviderMetadata::supportedLayerTypes()
return { QgsMapLayerType::RasterLayer };
}
bool QgsPostgresRasterProviderMetadata::saveLayerMetadata( const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage )
{
return QgsPostgresProviderMetadataUtils::saveLayerMetadata( QgsMapLayerType::RasterLayer, uri, metadata, errorMessage );
}
QgsProviderMetadata::ProviderCapabilities QgsPostgresRasterProviderMetadata::providerCapabilities() const
{
return QgsProviderMetadata::ProviderCapability::SaveLayerMetadata;
}
QgsPostgresRasterProvider *QgsPostgresRasterProviderMetadata::createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags )
{
return new QgsPostgresRasterProvider( uri, options, flags );
@ -721,6 +767,11 @@ QgsPostgresRasterProvider *QgsPostgresRasterProvider::clone() const
return provider;
}
QgsRasterDataProvider::ProviderCapabilities QgsPostgresRasterProvider::providerCapabilities() const
{
return QgsRasterDataProvider::ProviderCapability::ReadLayerMetadata;
}
static inline QString dumpVariantMap( const QVariantMap &variantMap, const QString &title = QString() )
{
@ -2429,3 +2480,8 @@ QgsFields QgsPostgresRasterProvider::fields() const
{
return mAttributeFields;
}
QgsLayerMetadata QgsPostgresRasterProvider::layerMetadata() const
{
return mLayerMetadata;
}

View File

@ -67,6 +67,8 @@ class QgsPostgresRasterProvider : public QgsRasterDataProvider
virtual QString lastError() override;
int capabilities() const override;
QgsFields fields() const override;
QgsLayerMetadata layerMetadata() const override;
QgsRasterDataProvider::ProviderCapabilities providerCapabilities() const override;
// QgsRasterInterface interface
int xSize() const override;
@ -254,6 +256,8 @@ class QgsPostgresRasterProviderMetadata: public QgsProviderMetadata
QgsPostgresRasterProvider *createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options, QgsDataProvider::ReadFlags flags = QgsDataProvider::ReadFlags() ) override;
QString encodeUri( const QVariantMap &parts ) const override;
QList< QgsMapLayerType > supportedLayerTypes() const override;
bool saveLayerMetadata( const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage ) override;
QgsProviderMetadata::ProviderCapabilities providerCapabilities() const override;
};

View File

@ -143,6 +143,8 @@ ADD_PYTHON_TEST(PyQgsLabelObstacleSettings test_qgslabelobstaclesettings.py)
ADD_PYTHON_TEST(PyQgsLabelSettingsWidget test_qgslabelsettingswidget.py)
ADD_PYTHON_TEST(PyQgsLabelThinningSettings test_qgslabelthinningsettings.py)
ADD_PYTHON_TEST(PyQgsLayerMetadata test_qgslayermetadata.py)
ADD_PYTHON_TEST(PyQgsLayerMetadataProviderPython test_qgslayermetadataprovider_python.py)
ADD_PYTHON_TEST(PyQgsLayerMetadataProviderOgr test_qgslayermetadataprovider_ogr.py)
ADD_PYTHON_TEST(PyQgsLayerTreeMapCanvasBridge test_qgslayertreemapcanvasbridge.py)
ADD_PYTHON_TEST(PyQgsLayerTree test_qgslayertree.py)
ADD_PYTHON_TEST(PyQgsLayerTreeView test_qgslayertreeview.py)
@ -440,6 +442,7 @@ endif()
if (ENABLE_PGTEST)
ADD_PYTHON_TEST(PyQgsImportIntoPostGIS test_processing_importintopostgis.py)
ADD_PYTHON_TEST(PyQgsLayerMetadataProviderPostgres test_qgslayermetadataprovider_postgres.py)
ADD_PYTHON_TEST(PyQgsVectorFileWriterPostgres test_qgsvectorfilewriter_postgres.py)
ADD_PYTHON_TEST(PyQgsQueryResultModel test_qgsqueryresultmodel.py)
ADD_PYTHON_TEST(PyQgsVectorLayerUtilsPostgres test_qgsvectorlayerutils_postgres.py)
@ -473,7 +476,7 @@ if (ENABLE_PGTEST)
PyQgsRelationEditWidget PyQgsRelationPostgres PyQgsVectorLayerTools PyQgsProjectStoragePostgres
PyQgsAuthManagerPKIPostgresTest PyQgsAuthManagerPasswordPostgresTest PyQgsAuthManagerOgrPostgresTest
PyQgsDbManagerPostgis PyQgsDatabaseSchemaModel PyQgsDatabaseTableModel PyQgsDatabaseSchemaComboBox PyQgsDatabaseTableComboBox
PyQgsProviderConnectionPostgres PyQgsPostgresProviderLatency
PyQgsProviderConnectionPostgres PyQgsPostgresProviderLatency PyQgsLayerMetadataProviderPostgres
PROPERTIES LABELS "POSTGRES")
endif()

View File

@ -0,0 +1,141 @@
# coding=utf-8
""""Base test for layer metadata providers
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'elpaso@itopen.it'
__date__ = '2022-08-19'
__copyright__ = 'Copyright 2022, ItOpen'
import os
from qgis.core import (
QgsVectorLayer,
QgsRasterLayer,
QgsMapLayerType,
QgsProviderRegistry,
QgsWkbTypes,
QgsLayerMetadata,
QgsProviderMetadata,
QgsBox3d,
QgsRectangle,
QgsMetadataSearchContext,
)
from qgis.PyQt.QtCore import QCoreApplication
from utilities import compareWkt, unitTestDataPath
from qgis.testing import start_app
QGIS_APP = start_app()
TEST_DATA_DIR = unitTestDataPath()
class LayerMetadataProviderTestBase():
"""Base test for layer metadata providers
Provider tests must implement:
- getLayer() -> return a QgsVectorLayer or a QgsRasterLayer
- getMetadataProviderId() -> str returns the id of the metadata provider to be tested ('ogr', 'postgres' ...)
"""
@classmethod
def setUpClass(cls):
"""Run before all tests"""
QCoreApplication.setOrganizationName("QGIS_Test")
QCoreApplication.setOrganizationDomain(cls.__name__)
QCoreApplication.setApplicationName(cls.__name__)
def testMetadataWriteRead(self):
self.test_layer = self.getLayer()
self.assertTrue(self.test_layer.isValid())
extent_as_wkt = self.test_layer.extent().asWktPolygon()
layer_type = self.test_layer.type()
layer_authid = self.test_layer.crs().authid()
data_provider_name = self.test_layer.dataProvider().name()
m = self.test_layer.metadata()
m.setAbstract('QGIS Some Data')
m.setIdentifier('MD012345')
m.setTitle('QGIS Test Title')
m.setKeywords({'dtd1': ['Kw1', 'Kw2']})
m.setCategories(['Cat1', 'Cat2'])
ext = QgsLayerMetadata.Extent()
spatial_ext = QgsLayerMetadata.SpatialExtent()
spatial_ext.bounds = QgsBox3d(self.test_layer.extent())
spatial_ext.crs = self.test_layer.crs()
ext.setSpatialExtents([spatial_ext])
m.setExtent(ext)
self.test_layer.setMetadata(m)
md = QgsProviderRegistry.instance().providerMetadata(data_provider_name)
self.assertIsNotNone(md)
self.assertTrue(bool(md.providerCapabilities() & QgsProviderMetadata.ProviderCapability.SaveLayerMetadata))
layer_uri = self.test_layer.publicSource()
self.assertTrue(md.saveLayerMetadata(layer_uri, m)[0])
self.test_layer = self.getLayer()
m = self.test_layer.metadata()
self.assertEqual(m.title(), 'QGIS Test Title')
self.assertEqual(m.identifier(), 'MD012345')
self.assertEqual(m.abstract(), 'QGIS Some Data')
self.assertEqual(m.crs().authid(), layer_authid)
del self.test_layer
reg = QGIS_APP.layerMetadataProviderRegistry()
md_provider = reg.layerMetadataProviderFromId(self.getMetadataProviderId())
results = md_provider.search(QgsMetadataSearchContext(), 'QgIs SoMe DaTa')
self.assertEqual(len(results.metadata()), 1)
result = results.metadata()[0]
self.assertEqual(result.abstract(), 'QGIS Some Data')
self.assertEqual(result.identifier(), 'MD012345')
self.assertEqual(result.title(), 'QGIS Test Title')
self.assertEqual(result.layerType(), layer_type)
self.assertEqual(result.authid(), layer_authid)
# For raster is unknown
if layer_type != QgsMapLayerType.VectorLayer:
self.assertEqual(result.geometryType(), QgsWkbTypes.UnknownGeometry)
else:
self.assertEqual(result.geometryType(), QgsWkbTypes.PointGeometry)
self.assertEqual(result.dataProviderName(), data_provider_name)
self.assertEqual(result.standardUri(), 'http://mrcc.com/qgis.dtd')
self.assertTrue(compareWkt(result.geographicExtent().asWkt(), extent_as_wkt))
# Check layer load
if layer_type == QgsMapLayerType.VectorLayer:
test_layer = QgsVectorLayer(result.uri(), 'PG MD Layer', result.dataProviderName())
else:
test_layer = QgsRasterLayer(result.uri(), 'PG MD Layer', result.dataProviderName())
self.assertTrue(test_layer.isValid())
# Test search filters
results = md_provider.search(QgsMetadataSearchContext(), '', QgsRectangle(0, 0, 1, 1))
self.assertEqual(len(results.metadata()), 0)
results = md_provider.search(QgsMetadataSearchContext(), '', test_layer.extent())
self.assertEqual(len(results.metadata()), 1)
results = md_provider.search(QgsMetadataSearchContext(), 'NOT HERE!', test_layer.extent())
self.assertEqual(len(results.metadata()), 0)
results = md_provider.search(QgsMetadataSearchContext(), 'QGIS', test_layer.extent())
self.assertEqual(len(results.metadata()), 1)
# Test keywords
results = md_provider.search(QgsMetadataSearchContext(), 'kw')
self.assertEqual(len(results.metadata()), 1)
results = md_provider.search(QgsMetadataSearchContext(), 'kw2')
self.assertEqual(len(results.metadata()), 1)
# Test categories
results = md_provider.search(QgsMetadataSearchContext(), 'cat')
self.assertEqual(len(results.metadata()), 1)
results = md_provider.search(QgsMetadataSearchContext(), 'cat2')
self.assertEqual(len(results.metadata()), 1)

View File

@ -0,0 +1,56 @@
# coding=utf-8
""""Test for ogr layer metadata provider
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'elpaso@itopen.it'
__date__ = '2022-08-19'
__copyright__ = 'Copyright 2022, ItOpen'
import os
import shutil
from qgis.core import (
QgsVectorLayer,
QgsProviderRegistry,
)
from qgis.PyQt.QtCore import QTemporaryDir
from qgis.testing import unittest
from qgslayermetadataprovidertestbase import LayerMetadataProviderTestBase, TEST_DATA_DIR
class TestPostgresLayerMetadataProvider(unittest.TestCase, LayerMetadataProviderTestBase):
def getMetadataProviderId(self) -> str:
return 'ogr'
def getLayer(self) -> QgsVectorLayer:
return QgsVectorLayer('{}|layername=geopackage'.format(self.getConnectionUri()), "someData", 'ogr')
def getConnectionUri(self) -> str:
return self.conn
def setUp(self):
super().setUp()
self.temp_dir = QTemporaryDir()
self.temp_path = self.temp_dir.path()
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
shutil.copy(os.path.join(srcpath, 'geopackage.gpkg'), self.temp_path)
self.conn = os.path.join(self.temp_path, 'geopackage.gpkg')
md = QgsProviderRegistry.instance().providerMetadata('ogr')
conn = md.createConnection(self.getConnectionUri(), {})
conn.store('OGR Metadata Enabled Connection')
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,72 @@
# coding=utf-8
""""Test for postgres layer metadata provider
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'elpaso@itopen.it'
__date__ = '2022-08-19'
__copyright__ = 'Copyright 2022, ItOpen'
import os
from qgis.core import (
QgsVectorLayer,
QgsProviderRegistry,
)
from qgis.testing import unittest
from qgslayermetadataprovidertestbase import LayerMetadataProviderTestBase
class TestPostgresLayerMetadataProvider(unittest.TestCase, LayerMetadataProviderTestBase):
def getMetadataProviderId(self) -> str:
return 'postgres'
def getLayer(self):
return QgsVectorLayer('{} type=Point table="qgis_test"."someData" (geom) sql='.format(self.getConnectionUri()), "someData", 'postgres')
def getConnectionUri(self) -> str:
dbconn = 'service=qgis_test'
if 'QGIS_PGTEST_DB' in os.environ:
dbconn = os.environ['QGIS_PGTEST_DB']
return dbconn
def clearMetadataTable(self):
self.conn.execSql('DROP TABLE IF EXISTS qgis_test.qgis_layer_metadata')
def setUp(self):
super().setUp()
dbconn = 'service=qgis_test'
if 'QGIS_PGTEST_DB' in os.environ:
dbconn = os.environ['QGIS_PGTEST_DB']
md = QgsProviderRegistry.instance().providerMetadata('postgres')
conn = md.createConnection(self.getConnectionUri(), {})
conn.setConfiguration({'metadataInDatabase': True})
conn.store('PG Metadata Enabled Connection')
self.conn = conn
self.clearMetadataTable()
def tearDown(self):
super().tearDown()
self.clearMetadataTable()
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,63 @@
# coding=utf-8
""""Test for postgres layer metadata provider
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'elpaso@itopen.it'
__date__ = '2022-08-19'
__copyright__ = 'Copyright 2022, ItOpen'
import os
from qgis.core import (
QgsRasterLayer,
QgsProviderRegistry,
)
from qgis.PyQt.QtCore import QCoreApplication
from qgis.testing import unittest
from qgslayermetadataprovidertestbase import LayerMetadataProviderTestBase
class TestPostgresLayerMetadataProvider(unittest.TestCase, LayerMetadataProviderTestBase):
def getMetadataProviderId(self):
return 'postgres'
def getLayer(self):
return QgsRasterLayer('{} table="qgis_test"."Raster1" (Rast)'.format(self.getConnectionUri()), "someData", 'postgresraster')
def getConnectionUri(self) -> str:
dbconn = 'service=qgis_test'
if 'QGIS_PGTEST_DB' in os.environ:
dbconn = os.environ['QGIS_PGTEST_DB']
return dbconn
def setUp(self):
super().setUp()
dbconn = 'service=qgis_test'
if 'QGIS_PGTEST_DB' in os.environ:
dbconn = os.environ['QGIS_PGTEST_DB']
md = QgsProviderRegistry.instance().providerMetadata('postgres')
conn = md.createConnection(self.getConnectionUri(), {})
conn.execSql('DROP TABLE IF EXISTS qgis_test.qgis_layer_metadata')
conn.setConfiguration({'metadataInDatabase': True})
conn.store('PG Metadata Enabled Connection')
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,181 @@
""""Test for a python implementation of layer metadata provider
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'elpaso@itopen.it'
__date__ = '2022-08-19'
__copyright__ = 'Copyright 2022, ItOpen'
import os
import shutil
from functools import partial
from stat import S_IREAD, S_IRGRP, S_IROTH, S_IWUSR
from qgis.core import (
QgsPolygon,
QgsWkbTypes,
QgsRectangle,
QgsMapLayerType,
QgsProviderRegistry,
QgsAbstractLayerMetadataProvider,
QgsLayerMetadataSearchResults,
QgsLayerMetadataProviderResult,
QgsMetadataSearchContext,
QgsLayerMetadata,
QgsNotSupportedException,
QgsProviderConnectionException,
)
from qgis.PyQt.QtCore import QTemporaryDir
from qgis.PyQt.QtXml import QDomDocument
from qgis.testing import unittest, start_app
from utilities import unitTestDataPath
TEST_DATA_DIR = unitTestDataPath()
temp_dir = QTemporaryDir()
temp_path = temp_dir.path()
class PythonLayerMetadataProvider(QgsAbstractLayerMetadataProvider):
"""Python implementation of a layer metadata provider
This is mainly to test the Python bindings and API
"""
def __init__(self):
super().__init__()
def id(self):
return 'python'
def search(self, searchString='', geographicExtent=QgsRectangle(), feedback=None):
xml_md = """
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.27.0-Master">
<identifier>MD012345</identifier>
<parentidentifier></parentidentifier>
<language></language>
<type>dataset</type>
<title>QGIS Test Title</title>
<abstract>QGIS Some Data</abstract>
<links/>
<fees/>
<encoding></encoding>
<crs>
<spatialrefsys nativeFormat="Wkt">
<wkt>GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]]</wkt>
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
<srsid>3452</srsid>
<srid>4326</srid>
<authid>EPSG:4326</authid>
<description>WGS 84</description>
<projectionacronym>longlat</projectionacronym>
<ellipsoidacronym>EPSG:7030</ellipsoidacronym>
<geographicflag>true</geographicflag>
</spatialrefsys>
</crs>
<extent>
<spatial maxz="0" maxx="-65.31999999999999318" maxy="78.29999999999999716" minx="-71.12300000000000466" crs="" minz="0" dimensions="2" miny="66.32999999999999829"/>
</extent>
</qgis>
"""
doc = QDomDocument()
assert doc.setContent(xml_md)[0]
metadata = QgsLayerMetadata()
assert metadata.readMetadataXml(doc.documentElement())
result = QgsLayerMetadataProviderResult(metadata)
result.setStandardUri('http://mrcc.com/qgis.dtd')
result.setLayerType(QgsMapLayerType.VectorLayer)
result.setUri(os.path.join(temp_path, 'geopackage.gpkg'))
result.setAuthid('EPSG:4326')
result.setDataProviderName('ogr')
result.setGeometryType(QgsWkbTypes.GeometryType.PointGeometry)
poly = QgsPolygon()
poly.fromWkt(QgsRectangle(0, 0, 1, 1).asWktPolygon())
result.setGeographicExtent(poly)
assert result.identifier() == 'MD012345'
results = QgsLayerMetadataSearchResults()
results.addMetadata(result)
results.addError('Bad news from PythonLayerMetadataProvider :(')
return results
QGIS_APP = start_app()
class TestPythonLayerMetadataProvider(unittest.TestCase):
def setUp(self):
super().setUp()
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
shutil.copy(os.path.join(srcpath, 'geopackage.gpkg'), temp_path)
self.conn = os.path.join(temp_path, 'geopackage.gpkg')
shutil.copy(os.path.join(srcpath, 'spatialite.db'), temp_path)
self.conn_sl = os.path.join(temp_path, 'spatialite.db')
def test_metadataRegistryApi(self):
reg = QGIS_APP.layerMetadataProviderRegistry()
self.assertIsNone(reg.layerMetadataProviderFromId('python'))
reg.registerLayerMetadataProvider(PythonLayerMetadataProvider())
self.assertIsNotNone(reg.layerMetadataProviderFromId('python'))
md_provider = reg.layerMetadataProviderFromId('python')
results = md_provider.search(QgsMetadataSearchContext())
self.assertEqual(len(results.metadata()), 1)
self.assertEqual(len(results.errors()), 1)
result = results.metadata()[0]
self.assertEqual(result.abstract(), 'QGIS Some Data')
self.assertEqual(result.identifier(), 'MD012345')
self.assertEqual(result.title(), 'QGIS Test Title')
self.assertEqual(result.layerType(), QgsMapLayerType.VectorLayer)
self.assertEqual(result.authid(), 'EPSG:4326')
self.assertEqual(result.geometryType(), QgsWkbTypes.PointGeometry)
self.assertEqual(result.dataProviderName(), 'ogr')
self.assertEqual(result.standardUri(), 'http://mrcc.com/qgis.dtd')
reg.unregisterLayerMetadataProvider(md_provider)
self.assertIsNone(reg.layerMetadataProviderFromId('python'))
def testExceptions(self):
def _spatialite(path):
md = QgsProviderRegistry.instance().providerMetadata('spatialite')
conn = md.createConnection(path, {})
conn.searchLayerMetadata(QgsMetadataSearchContext())
def _ogr(path):
md = QgsProviderRegistry.instance().providerMetadata('ogr')
conn = md.createConnection(path, {})
os.chmod(path, S_IREAD | S_IRGRP | S_IROTH)
conn.searchLayerMetadata(QgsMetadataSearchContext())
self.assertRaises(QgsNotSupportedException, partial(_spatialite, self.conn_sl))
self.assertRaises(QgsProviderConnectionException, partial(_ogr, self.conn))
self.assertRaises(QgsNotSupportedException, partial(_ogr, self.conn_sl))
os.chmod(self.conn, S_IWUSR | S_IREAD)
os.chmod(self.conn_sl, S_IWUSR | S_IREAD)
if __name__ == '__main__':
unittest.main()