mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-16 00:05:45 -04:00
Merge pull request #50015 from elpaso/layer-metadata-provider
Layer metadata provider API (part 1)
This commit is contained in:
commit
0c577f07ba
@ -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 *
|
||||
************************************************************************/
|
@ -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 *
|
||||
************************************************************************/
|
@ -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
|
||||
|
||||
|
@ -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/;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
118
src/core/metadata/qgsabstractlayermetadataprovider.cpp
Normal file
118
src/core/metadata/qgsabstractlayermetadataprovider.cpp
Normal 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;
|
||||
}
|
232
src/core/metadata/qgsabstractlayermetadataprovider.h
Normal file
232
src/core/metadata/qgsabstractlayermetadataprovider.h
Normal 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
|
70
src/core/metadata/qgslayermetadataproviderregistry.cpp
Normal file
70
src/core/metadata/qgslayermetadataproviderregistry.cpp
Normal 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;
|
||||
}
|
77
src/core/metadata/qgslayermetadataproviderregistry.h
Normal file
77
src/core/metadata/qgslayermetadataproviderregistry.h
Normal 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
|
@ -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 )
|
||||
|
@ -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;
|
||||
|
63
src/core/providers/ogr/qgsogrlayermetadataprovider.cpp
Normal file
63
src/core/providers/ogr/qgsogrlayermetadataprovider.cpp
Normal 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;
|
||||
}
|
29
src/core/providers/ogr/qgsogrlayermetadataprovider.h
Normal file
29
src/core/providers/ogr/qgsogrlayermetadataprovider.h
Normal 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
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
|
@ -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
|
||||
|
@ -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 )
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "qgis_core.h"
|
||||
#include <functional>
|
||||
#include "qgsabstractproviderconnection.h"
|
||||
#include "qgsabstractlayermetadataprovider.h"
|
||||
#include "qgsfields.h"
|
||||
#include "qgsexception.h"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
68
src/providers/postgres/qgspostgreslayermetadataprovider.cpp
Normal file
68
src/providers/postgres/qgspostgreslayermetadataprovider.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
30
src/providers/postgres/qgspostgreslayermetadataprovider.h
Normal file
30
src/providers/postgres/qgspostgreslayermetadataprovider.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
356
src/providers/postgres/qgspostgresprovidermetadatautils.cpp
Normal file
356
src/providers/postgres/qgspostgresprovidermetadatautils.cpp
Normal 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;
|
||||
}
|
||||
|
37
src/providers/postgres/qgspostgresprovidermetadatautils.h
Normal file
37
src/providers/postgres/qgspostgresprovidermetadatautils.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
141
tests/src/python/qgslayermetadataprovidertestbase.py
Normal file
141
tests/src/python/qgslayermetadataprovidertestbase.py
Normal 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)
|
56
tests/src/python/test_qgslayermetadataprovider_ogr.py
Normal file
56
tests/src/python/test_qgslayermetadataprovider_ogr.py
Normal 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()
|
72
tests/src/python/test_qgslayermetadataprovider_postgres.py
Normal file
72
tests/src/python/test_qgslayermetadataprovider_postgres.py
Normal 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()
|
@ -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()
|
181
tests/src/python/test_qgslayermetadataprovider_python.py
Normal file
181
tests/src/python/test_qgslayermetadataprovider_python.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user