Providers connections interface API

This is the implementation of the new DB connections API (grant proposal 2019).
Summary

The new API makes it available to QGIS core a new interface for provider connections and will allow to:

    replace the provider specific QgsSettings management in QGIS4 (save/load connections from the settings) NOT IN SCOPE FOR NOW.
    provide a unified API for common operations on DB connections:
        executeSql and get the results
        list tables names and properties and schemas
        create a new vector table (no rasters for now)
        create/rename/drop schemas and tables
        vacuum
       ....
This commit is contained in:
Alessandro Pasotti 2019-08-16 20:44:05 +02:00 committed by GitHub
parent 945ac8caf4
commit a3c4eb9947
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 3958 additions and 641 deletions

View File

@ -0,0 +1,6 @@
# The following has been generated automatically from src/core/qgsabstractdatabaseproviderconnection.h
QgsAbstractDatabaseProviderConnection.TableFlags.baseClass = QgsAbstractDatabaseProviderConnection
TableFlags = QgsAbstractDatabaseProviderConnection # dirty hack since SIP seems to introduce the flags in module
QgsAbstractDatabaseProviderConnection.Capability.baseClass = QgsAbstractDatabaseProviderConnection
QgsAbstractDatabaseProviderConnection.Capabilities.baseClass = QgsAbstractDatabaseProviderConnection
Capabilities = QgsAbstractDatabaseProviderConnection # dirty hack since SIP seems to introduce the flags in module

View File

@ -0,0 +1,417 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsabstractdatabaseproviderconnection.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsAbstractDatabaseProviderConnection : QgsAbstractProviderConnection
{
%Docstring
The QgsAbstractDatabaseProviderConnection class provides common functionality
for DB based connections.
This class performs low level DB operations without asking
the user for confirmation or handling currently opened layers and the registry
entries, it is responsibility of the client code to keep layers in sync.
The class methods will throw exceptions in case the requested operation
is not supported or cannot be performed without errors.
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsabstractdatabaseproviderconnection.h"
%End
public:
static const QMetaObject staticMetaObject;
public:
enum TableFlag
{
Aspatial,
Vector,
Raster,
View,
MaterializedView,
};
typedef QFlags<QgsAbstractDatabaseProviderConnection::TableFlag> TableFlags;
struct TableProperty
{
SIP_PYOBJECT __repr__();
%MethodCode
QString str = QStringLiteral( "<QgsAbstractDatabaseProviderConnection.TableProperty: '%1'>" ).arg( sipCpp->tableName() );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
struct GeometryColumnType
{
SIP_PYOBJECT __repr__();
%MethodCode
QString str = QStringLiteral( "<QgsAbstractDatabaseProviderConnection.TableProperty.GeometryColumnType: '%1, %2'>" ).arg( QgsWkbTypes::displayString( sipCpp->wkbType ), sipCpp->crs.authid() );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
%End
QgsWkbTypes::Type wkbType;
QgsCoordinateReferenceSystem crs;
bool operator==( const GeometryColumnType &other ) const;
};
public:
QString tableName() const;
%Docstring
Returns the table name
.. seealso:: :py:func:`defaultName`
%End
void setTableName( const QString &name );
%Docstring
Sets the table name to ``name``
.. seealso:: :py:func:`defaultName`
%End
void addGeometryColumnType( const QgsWkbTypes::Type &type, const QgsCoordinateReferenceSystem &crs );
%Docstring
Appends the geometry column ``type`` with the given ``srid`` to the geometry column types list
%End
QList<QgsAbstractDatabaseProviderConnection::TableProperty::GeometryColumnType> geometryColumnTypes() const;
%Docstring
Returns the list of geometry column types and CRSs.
The method returns a list of GeometryColumnType
%End
void setGeometryColumnTypes( const QList<QgsAbstractDatabaseProviderConnection::TableProperty::GeometryColumnType> &geometryColumnTypes );
%Docstring
Sets the geometry column types to ``geometryColumnTypes``
%End
QString defaultName() const;
%Docstring
Returns the default name for the table entry
It is usually the table name but in case there are multiple geometry
columns, the geometry column name is appendend to the table name.
.. seealso:: :py:func:`geometryColumnCount`
%End
TableProperty at( int index ) const;
%Docstring
Returns the table property corresponding to the geometry type at
the given ``index``
%End
QString schema() const;
%Docstring
Returns the schema or an empty string for backends that do not support a schema
%End
void setSchema( const QString &schema );
%Docstring
Sets the ``schema``
%End
QString geometryColumn() const;
%Docstring
Returns the geometry column name
%End
void setGeometryColumn( const QString &geometryColumn );
%Docstring
Sets the geometry column name to ``geometryColumn``
%End
QStringList primaryKeyColumns() const;
%Docstring
Returns the list of primary key column names
%End
void setPrimaryKeyColumns( const QStringList &primaryKeyColumns );
%Docstring
Sets the primary key column names to ``primaryKeyColumns``
%End
QList<QgsCoordinateReferenceSystem> crsList() const;
%Docstring
Returns the list of CRSs supported by the geometry column
%End
TableFlags flags() const;
%Docstring
Returns the table flags
%End
void setFlags( const TableFlags &flags );
%Docstring
Sets the table ``flags``
%End
QString comment() const;
%Docstring
Returns the table comment
%End
void setComment( const QString &comment );
%Docstring
Sets the table ``comment``
%End
QVariantMap info() const;
%Docstring
Returns additional information about the table
Provider classes may use this property
to store custom bits of information.
%End
void setInfo( const QVariantMap &info );
%Docstring
Sets additional information about the table to ``info``
Provider classes may use this property
to store custom bits of information.
%End
int geometryColumnCount() const;
%Docstring
Returns the number of geometry columns in the original table this entry refers to
This information is used internally to build the :py:func:`defaultName`
%End
void setGeometryColumnCount( int geometryColumnCount );
%Docstring
Sets the ``geometryColumnCount``
%End
void setFlag( const TableFlag &flag );
%Docstring
Sets a ``flag``
%End
int maxCoordinateDimensions() const;
%Docstring
Returns the maximum coordinate dimensions of the geometries of a vector table.
This information is calculated from the geometry columns types.
.. seealso:: :py:func:`geometryColumnTypes`
%End
};
enum Capability
{
CreateVectorTable,
DropRasterTable,
DropVectorTable,
RenameVectorTable,
RenameRasterTable,
CreateSchema,
DropSchema,
RenameSchema,
ExecuteSql,
Vacuum,
Tables,
Schemas,
SqlLayers,
TableExists,
Spatial,
};
typedef QFlags<QgsAbstractDatabaseProviderConnection::Capability> Capabilities;
QgsAbstractDatabaseProviderConnection( const QString &name );
%Docstring
Creates a new connection with ``name`` by reading its configuration from the settings.
If a connection with this name cannot be found, an empty connection will be returned.
%End
QgsAbstractDatabaseProviderConnection( const QString &name, const QString &uri );
%Docstring
Creates a new connection with ``name`` and initializes the connection from the ``uri``.
The connection is not automatically stored in the settings.
.. seealso:: :py:func:`store`
%End
Capabilities capabilities() const;
%Docstring
Returns connection capabilities
%End
virtual void createVectorTable( const QString &schema, const QString &name, const QgsFields &fields, QgsWkbTypes::Type wkbType, const QgsCoordinateReferenceSystem &srs, bool overwrite, const QMap<QString, QVariant> *options ) const throw( QgsProviderConnectionException );
%Docstring
Creates an empty table with ``name`` in the given ``schema`` (schema is ignored if not supported by the backend).
Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual bool tableExists( const QString &schema, const QString &name ) const throw( QgsProviderConnectionException );
%Docstring
Checks whether a table ``name`` exists in the given ``schema``.
Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual void dropVectorTable( const QString &schema, const QString &name ) const throw( QgsProviderConnectionException );
%Docstring
Drops a vector (or aspatial) table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
Raises a QgsProviderConnectionException if any errors are encountered.
.. note::
it is responsibility of the caller to handle open layers and registry entries.
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual void dropRasterTable( const QString &schema, const QString &name ) const throw( QgsProviderConnectionException );
%Docstring
Drops a raster table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
Raises a QgsProviderConnectionException if any errors are encountered.
.. note::
it is responsibility of the caller to handle open layers and registry entries.
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual void renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const throw( QgsProviderConnectionException );
%Docstring
Renames a vector or aspatial table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
Raises a QgsProviderConnectionException if any errors are encountered.
.. note::
it is responsibility of the caller to handle open layers and registry entries.
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual void renameRasterTable( const QString &schema, const QString &name, const QString &newName ) const throw( QgsProviderConnectionException );
%Docstring
Renames a raster table with given ``schema`` (schema is ignored if not supported by the backend) and ``name``.
Raises a QgsProviderConnectionException if any errors are encountered.
.. note::
it is responsibility of the caller to handle open layers and registry entries.
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual void createSchema( const QString &name ) const throw( QgsProviderConnectionException );
%Docstring
Creates a new schema with the specified ``name``
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual void dropSchema( const QString &name, bool force = false ) const throw( QgsProviderConnectionException );
%Docstring
Drops an entire schema with the specified name.
Raises a QgsProviderConnectionException if any errors are encountered.
:param name: name of the schema to be dropped
:param force: if ``True``, a DROP CASCADE will drop all related objects
.. note::
it is responsibility of the caller to handle open layers and registry entries.
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual void renameSchema( const QString &name, const QString &newName ) const throw( QgsProviderConnectionException );
%Docstring
Renames a schema with the specified ``name``.
Raises a QgsProviderConnectionException if any errors are encountered.
.. note::
it is responsibility of the caller to handle open layers and registry entries.
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual QList<QList<QVariant>> executeSql( const QString &sql ) const throw( QgsProviderConnectionException );
%Docstring
Executes raw ``sql`` and returns the (possibly empty) list of results in a multi-dimensional array.
Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual void vacuum( const QString &schema, const QString &name ) const throw( QgsProviderConnectionException );
%Docstring
Vacuum the database table with given ``schema`` and ``name`` (schema is ignored if not supported by the backend).
Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
%End
QList<QgsAbstractDatabaseProviderConnection::TableProperty> tablesInt( const QString &schema = QString(), const int flags = 0 ) const throw( QgsProviderConnectionException ) /PyName=tables/;
%Docstring
Returns information on the tables in the given schema.
Raises a QgsProviderConnectionException if any errors are encountered.
:param schema: name of the schema (ignored if not supported by the backend)
:param flags: filter tables by flags, this option completely overrides search options stored in the connection
:raises :: py:class:`QgsProviderConnectionException`
%End
virtual QStringList schemas( ) const throw( QgsProviderConnectionException );
%Docstring
Returns information about the existing schemas.
Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
%End
protected:
void checkCapability( Capability capability ) const;
%Docstring
Checks if ``capability`` is supported and throws and exception if it's not
:raises :: py:class:`QgsProviderConnectionException`
%End
};
QFlags<QgsAbstractDatabaseProviderConnection::Capability> operator|(QgsAbstractDatabaseProviderConnection::Capability f1, QFlags<QgsAbstractDatabaseProviderConnection::Capability> f2);
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsabstractdatabaseproviderconnection.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -0,0 +1,97 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsabstractproviderconnection.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsAbstractProviderConnection
{
%Docstring
The QgsAbstractProviderConnection provides an interface for data provider connections.
Connections objects can be created by passing the connection name and in this case
they are automatically loaded from the settings, or by passing a data source URI
in the constructor.
Concrete classes must implement methods to retrieve, save and remove connections from
the settings.
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgsabstractproviderconnection.h"
%End
%ConvertToSubClassCode
if ( dynamic_cast<QgsAbstractDatabaseProviderConnection *>( sipCpp ) != NULL )
{
sipType = sipType_QgsAbstractDatabaseProviderConnection;
}
else if ( dynamic_cast<QgsAbstractProviderConnection *>( sipCpp ) != NULL )
{
sipType = sipType_QgsAbstractProviderConnection;
}
else
{
sipType = 0;
}
%End
public:
QgsAbstractProviderConnection( const QString &name );
%Docstring
Creates a new connection with ``name`` by reading its configuration from the settings.
If a connection with this name cannot be found, an empty connection will be returned.
%End
QgsAbstractProviderConnection( const QString &name, const QString &uri );
%Docstring
Creates a new connection with ``name`` and initializes the connection from the ``uri``.
The connection is not automatically stored in the settings.
.. seealso:: :py:func:`store`
%End
virtual ~QgsAbstractProviderConnection();
virtual void store( const QVariantMap &configuration = QVariantMap() ) const = 0;
%Docstring
Stores the connection in the settings.
:param configuration: stores additional connection settings that are used by the
source select dialog and are not part of the data source URI
%End
virtual void remove( ) const = 0;
%Docstring
Deletes the connection from the settings.
%End
QString name() const;
%Docstring
Returns the connection name
%End
QString uri() const;
%Docstring
Returns the connection data source URI string representation
%End
void setUri( const QString &uri );
%Docstring
Sets the connection data source URI to ``uri``
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsabstractproviderconnection.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -320,6 +320,20 @@ Decodes SSL mode string into enum value. If the string is not recognized, SslPre
Encodes SSL mode enum value into a string.
.. versionadded:: 3.2
%End
void setTable( const QString &table );
%Docstring
Sets table to ``table``
.. versionadded:: 3.10
%End
void setGeometryColumn( const QString &geometryColumn );
%Docstring
Sets geometry column name to ``geometryColumn``
.. versionadded:: 3.10
%End
};

View File

@ -218,6 +218,91 @@ Returns new instance of transaction. Ownership is transferred to the caller
.. versionadded:: 3.10
%End
virtual QMap<QString, QgsAbstractProviderConnection *> connections( bool cached = true ) throw( QgsProviderConnectionException );
%Docstring
Returns a dictionary of stored provider connections,
the dictionary key is the connection identifier.
Ownership is not transferred.
Raises a QgsProviderConnectionException if any errors are encountered.
:param cached: if ``False`` connections will be re-read from the settings
:raises :: py:class:`QgsProviderConnectionException`
.. versionadded:: 3.10
%End
QMap<QString, QgsAbstractDatabaseProviderConnection *> dbConnections( bool cached = true ) throw( QgsProviderConnectionException );
%Docstring
Returns a dictionary of database provider connections,
the dictionary key is the connection identifier.
Ownership is not transferred.
Raises a QgsProviderConnectionException if any errors are encountered.
:param cached: if ``False`` connections will be re-read from the settings
:raises :: py:class:`QgsProviderConnectionException`
.. versionadded:: 3.10
%End
QgsAbstractProviderConnection *findConnection( const QString &name, bool cached = true ) throw( QgsProviderConnectionException );
%Docstring
Searches and returns a (possibly NULL) connection from the stored provider connections.
Ownership is not transferred.
Raises a QgsProviderConnectionException if any errors are encountered.
:param name: the connection name
:param cached: if ``False`` connections will be re-read from the settings
:raises :: py:class:`QgsProviderConnectionException`
.. versionadded:: 3.10
%End
virtual QgsAbstractProviderConnection *createConnection( const QString &name ) /Factory/;
%Docstring
Creates a new connection by loading the connection with the given ``name`` from the settings.
Ownership is transferred to the caller.
.. versionadded:: 3.10
%End
virtual QgsAbstractProviderConnection *createConnection( const QString &name, const QString &uri ) /Factory/;
%Docstring
Creates a new connection with the given ``name`` and data source ``uri``,
the newly created connection is not automatically stored in the settings, call
saveConnection() to save it.
Ownership is transferred to the caller.
.. seealso:: :py:func:`saveConnection`
.. versionadded:: 3.10
%End
virtual void deleteConnection( const QString &name ) throw( QgsProviderConnectionException );
%Docstring
Removes the connection with the given ``name`` from the settings.
Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
.. versionadded:: 3.10
%End
virtual void saveConnection( QgsAbstractProviderConnection *connection, const QVariantMap &configuration = QVariantMap() );
%Docstring
Stores the connection in the settings
:param connection: the connection to be stored in the settings
:param configuration: stores additional connection settings that not part of the data source URI
.. versionadded:: 3.10
%End
protected:
};

View File

@ -250,7 +250,7 @@ be returned.
Returns list of available providers by their keys
%End
const QgsProviderMetadata *providerMetadata( const QString &providerKey ) const;
QgsProviderMetadata *providerMetadata( const QString &providerKey ) const;
%Docstring
Returns metadata of the provider or ``None`` if not found
%End

View File

@ -6,6 +6,8 @@
%Include auto_generated/expression/qgsexpressionfunction.sip
%Include auto_generated/qgstessellator.sip
%Include auto_generated/qgis.sip
%Include auto_generated/qgsabstractproviderconnection.sip
%Include auto_generated/qgsabstractdatabaseproviderconnection.sip
%Include auto_generated/qgsaction.sip
%Include auto_generated/qgsactionscope.sip
%Include auto_generated/qgsactionmanager.sip

View File

@ -33,3 +33,16 @@
SIP_UNBLOCK_THREADS
%End
};
%Exception QgsProviderConnectionException(SIP_Exception) /PyName=QgsProviderConnectionException/
{
%TypeHeaderCode
#include <qgsexception.h>
%End
%RaiseCode
SIP_BLOCK_THREADS
PyErr_SetString(sipException_QgsProviderConnectionException, sipExceptionRef.what().toUtf8().constData() );
SIP_UNBLOCK_THREADS
%End
};

View File

@ -31,7 +31,18 @@ from .db_plugins import supportedDbTypes, createDbPlugin
from .db_plugins.plugin import BaseError, Table, Database
from .dlg_db_error import DlgDbError
from qgis.core import QgsApplication, QgsDataSourceUri, QgsVectorLayer, QgsRasterLayer, QgsMimeDataUtils
from qgis.core import (
QgsApplication,
QgsDataSourceUri,
QgsVectorLayer,
QgsRasterLayer,
QgsMimeDataUtils,
QgsProviderConnectionException,
QgsProviderRegistry,
QgsAbstractDatabaseProviderConnection,
QgsMessageLog,
)
from qgis.utils import OverrideCursor
from . import resources_rc # NOQA

View File

@ -30,6 +30,9 @@ from .plugin import DbError, ConnectionError
class DBConnector(object):
def __init__(self, uri):
"""Creates a new DB connector
"""
self.connection = None
self._uri = uri

View File

@ -31,7 +31,13 @@ from ..connector import DBConnector
from ..plugin import ConnectionError, DbError, Table
from qgis.utils import spatialite_connect
from qgis.core import QgsApplication
from qgis.core import (
QgsApplication,
QgsProviderRegistry,
QgsAbstractDatabaseProviderConnection,
QgsProviderConnectionException,
QgsWkbTypes,
)
import sqlite3
@ -44,12 +50,26 @@ def classFactory():
class GPKGDBConnector(DBConnector):
def __init__(self, uri):
DBConnector.__init__(self, uri)
def __init__(self, uri, connection):
"""Creates a new GPKG connector
:param uri: data source URI
:type uri: QgsDataSourceUri
:param connection: the GPKGDBPlugin parent instance
:type connection: GPKGDBPlugin
"""
DBConnector.__init__(self, uri)
self.dbname = uri.database()
self.connection = connection
md = QgsProviderRegistry.instance().providerMetadata(connection.providerName())
# QgsAbstractDatabaseProviderConnection instance
self.core_connection = md.findConnection(connection.connectionName())
if self.core_connection is None:
self.core_connection = md.createConnection(connection.connectionName(), uri.database())
self.has_raster = False
self.mapSridToName = {}
# To be removed when migration to new API is completed
self._opendb()
def _opendb(self):
@ -102,40 +122,12 @@ class GPKGDBConnector(DBConnector):
return unquoted
def _fetchOne(self, sql):
sql_lyr = self.gdal_ds.ExecuteSQL(sql)
if sql_lyr is None:
return None
f = sql_lyr.GetNextFeature()
if f is None:
ret = None
else:
ret = [f.GetField(i) for i in range(f.GetFieldCount())]
self.gdal_ds.ReleaseResultSet(sql_lyr)
return ret
return self.core_connection.executeSql(sql)
def _fetchAll(self, sql, include_fid_and_geometry=False):
sql_lyr = self.gdal_ds.ExecuteSQL(sql)
if sql_lyr is None:
return None
ret = []
while True:
f = sql_lyr.GetNextFeature()
if f is None:
break
else:
if include_fid_and_geometry:
field_vals = [f.GetFID()]
if sql_lyr.GetLayerDefn().GetGeomType() != ogr.wkbNone:
geom = f.GetGeometryRef()
if geom is not None:
geom = geom.ExportToWkt()
field_vals += [geom]
field_vals += [f.GetField(i) for i in range(f.GetFieldCount())]
ret.append(field_vals)
else:
ret.append([f.GetField(i) for i in range(f.GetFieldCount())])
self.gdal_ds.ReleaseResultSet(sql_lyr)
return ret
return self.core_connection.executeSql(sql)
def _fetchAllFromLayer(self, table):
@ -286,78 +278,66 @@ class GPKGDBConnector(DBConnector):
return sorted(items, key=cmp_to_key(lambda x, y: (x[1] > y[1]) - (x[1] < y[1])))
def getVectorTables(self, schema=None):
"""Returns a list of vector table information
"""
items = []
for i in range(self.gdal_ds.GetLayerCount()):
lyr = self.gdal_ds.GetLayer(i)
geomtype = lyr.GetGeomType()
if hasattr(ogr, 'GT_Flatten'):
geomtype_flatten = ogr.GT_Flatten(geomtype)
for table in self.core_connection.tables(schema, QgsAbstractDatabaseProviderConnection.Vector | QgsAbstractDatabaseProviderConnection.Aspatial):
if not (table.flags() & QgsAbstractDatabaseProviderConnection.Aspatial):
geom_type = table.geometryColumnTypes()[0]
# Use integer PG code for SRID
srid = geom_type.crs.postgisSrid()
geomtype_flatten = QgsWkbTypes.flatType(geom_type.wkbType)
geomname = 'GEOMETRY'
if geomtype_flatten == QgsWkbTypes.Point:
geomname = 'POINT'
elif geomtype_flatten == QgsWkbTypes.LineString:
geomname = 'LINESTRING'
elif geomtype_flatten == QgsWkbTypes.Polygon:
geomname = 'POLYGON'
elif geomtype_flatten == QgsWkbTypes.MultiPoint:
geomname = 'MULTIPOINT'
elif geomtype_flatten == QgsWkbTypes.MultiLineString:
geomname = 'MULTILINESTRING'
elif geomtype_flatten == QgsWkbTypes.MultiPolygon:
geomname = 'MULTIPOLYGON'
elif geomtype_flatten == QgsWkbTypes.GeometryCollection:
geomname = 'GEOMETRYCOLLECTION'
elif geomtype_flatten == QgsWkbTypes.CircularString:
geomname = 'CIRCULARSTRING'
elif geomtype_flatten == QgsWkbTypes.CompoundCurve:
geomname = 'COMPOUNDCURVE'
elif geomtype_flatten == QgsWkbTypes.CurvePolygon:
geomname = 'CURVEPOLYGON'
elif geomtype_flatten == QgsWkbTypes.MultiCurve:
geomname = 'MULTICURVE'
elif geomtype_flatten == QgsWkbTypes.MultiSurface:
geomname = 'MULTISURFACE'
geomdim = 'XY'
if QgsWkbTypes.hasZ(geom_type.wkbType):
geomdim += 'Z'
if QgsWkbTypes.hasM(geom_type.wkbType):
geomdim += 'M'
item = [
Table.VectorType,
table.tableName(),
bool(table.flags() & QgsAbstractDatabaseProviderConnection.View), # is_view
table.tableName(),
table.geometryColumn(),
geomname,
geomdim,
srid
]
self.mapSridToName[srid] = geom_type.crs.description()
else:
geomtype_flatten = geomtype
geomname = 'GEOMETRY'
if geomtype_flatten == ogr.wkbPoint:
geomname = 'POINT'
elif geomtype_flatten == ogr.wkbLineString:
geomname = 'LINESTRING'
elif geomtype_flatten == ogr.wkbPolygon:
geomname = 'POLYGON'
elif geomtype_flatten == ogr.wkbMultiPoint:
geomname = 'MULTIPOINT'
elif geomtype_flatten == ogr.wkbMultiLineString:
geomname = 'MULTILINESTRING'
elif geomtype_flatten == ogr.wkbMultiPolygon:
geomname = 'MULTIPOLYGON'
elif geomtype_flatten == ogr.wkbGeometryCollection:
geomname = 'GEOMETRYCOLLECTION'
elif geomtype_flatten == ogr.wkbCircularString:
geomname = 'CIRCULARSTRING'
elif geomtype_flatten == ogr.wkbCompoundCurve:
geomname = 'COMPOUNDCURVE'
elif geomtype_flatten == ogr.wkbCurvePolygon:
geomname = 'CURVEPOLYGON'
elif geomtype_flatten == ogr.wkbMultiCurve:
geomname = 'MULTICURVE'
elif geomtype_flatten == ogr.wkbMultiSurface:
geomname = 'MULTISURFACE'
geomdim = 'XY'
if hasattr(ogr, 'GT_HasZ') and ogr.GT_HasZ(lyr.GetGeomType()):
geomdim += 'Z'
if hasattr(ogr, 'GT_HasM') and ogr.GT_HasM(lyr.GetGeomType()):
geomdim += 'M'
srs = lyr.GetSpatialRef()
srid = None
if srs is not None:
if srs.IsProjected():
name = srs.GetAttrValue('PROJCS', 0)
elif srs.IsGeographic():
name = srs.GetAttrValue('GEOGCS', 0)
else:
name = None
srid = srs.GetAuthorityCode(None)
if srid is not None:
srid = int(srid)
else:
srid = self._fetchOne('SELECT srid FROM gpkg_spatial_ref_sys WHERE table_name = %s' % self.quoteString(lyr.GetName()))
if srid is not None:
srid = int(srid)
self.mapSridToName[srid] = name
item = [
Table.TableType,
table.tableName(),
bool(table.flags() & QgsAbstractDatabaseProviderConnection.View),
]
if geomtype == ogr.wkbNone:
item = list([Table.TableType,
lyr.GetName(),
False, # is_view
])
else:
item = list([Table.VectorType,
lyr.GetName(),
False, # is_view
lyr.GetName(),
lyr.GetGeometryColumn(),
geomname,
geomdim,
srid])
items.append(item)
return items
def getRasterTables(self, schema=None):
@ -371,15 +351,22 @@ class GPKGDBConnector(DBConnector):
srid
"""
sql = u"""SELECT table_name, 0 AS is_view, table_name AS r_table_name, '' AS r_geometry_column, srs_id FROM gpkg_contents WHERE data_type = 'tiles'"""
ret = self._fetchAll(sql)
if ret is None:
return []
items = []
for i, tbl in enumerate(ret):
item = list(tbl)
item.insert(0, Table.RasterType)
for table in self.core_connection.tables(schema, QgsAbstractDatabaseProviderConnection.Raster):
geom_type = table.geometryColumnTypes()[0]
# Use integer PG code for SRID
srid = geom_type.crs.postgisSrid()
item = [
Table.RasterType,
table.tableName(),
bool(table.flags() & QgsAbstractDatabaseProviderConnection.View),
table.tableName(),
table.geometryColumn(),
srid,
]
self.mapSridToName[srid] = geom_type.crs.description()
items.append(item)
return items
def getTableRowCount(self, table):
@ -441,7 +428,7 @@ class GPKGDBConnector(DBConnector):
return self._fetchAll(sql)
def deleteTableTrigger(self, trigger, table=None):
""" delete trigger """
"""Deletes trigger """
sql = u"DROP TRIGGER %s" % self.quoteId(trigger)
self._execute_and_commit(sql)
@ -486,7 +473,7 @@ class GPKGDBConnector(DBConnector):
sql = u"SELECT srs_name FROM gpkg_spatial_ref_sys WHERE srs_id = %s" % self.quoteString(srid)
res = self._fetchOne(sql)
if res is not None:
if res is not None and len(res) > 0:
res = res[0]
self.mapSridToName[srid] = res
return res
@ -503,7 +490,7 @@ class GPKGDBConnector(DBConnector):
if md is None or len(md) == 0:
sql = u"SELECT COUNT(*) FROM gpkg_contents WHERE data_type = 'tiles' AND table_name = %s" % self.quoteString(tablename)
ret = self._fetchOne(sql)
return ret is not None and ret[0] == 1
return ret != [] and ret[0][0] == 1
else:
subdataset_name = 'GPKG:%s:%s' % (self.gdal_ds.GetDescription(), tablename)
for key in md:
@ -572,7 +559,7 @@ class GPKGDBConnector(DBConnector):
return fld_defn
def createTable(self, table, field_defs, pkey):
""" create ordinary table
"""Creates ordinary table
'fields' is array containing field definitions
'pkey' is the primary key name
"""
@ -596,7 +583,7 @@ class GPKGDBConnector(DBConnector):
return True
def deleteTable(self, table):
""" delete table from the database """
"""Deletes table from the database """
if self.isRasterTable(table):
return False
@ -607,7 +594,7 @@ class GPKGDBConnector(DBConnector):
return False
def emptyTable(self, table):
""" delete all rows from table """
"""Deletes all rows from table """
if self.isRasterTable(table):
return False
@ -624,17 +611,16 @@ class GPKGDBConnector(DBConnector):
:return: true on success
:rtype: bool
"""
table_name = table[1]
provider = [p for p in QgsApplication.dataItemProviderRegistry().providers() if p.name() == 'OGR'][0]
collection_item = provider.createDataItem(self.dbname, None)
data_item = [c for c in collection_item.createChildren() if c.name() == table_name][0]
result = data_item.rename(new_table)
# we need to reopen after renaming since OGR doesn't update its
# internal state
if result:
self._opendb()
return result
try:
name = table[1] # 0 is schema
vector_table_names = [t.tableName() for t in self.core_connection.tables('', QgsAbstractDatabaseProviderConnection.Vector)]
if name in vector_table_names:
self.core_connection.renameVectorTable('', name, new_table)
else:
self.core_connection.renameRasterTable('', name, new_table)
return True
except QgsProviderConnectionException:
return False
def moveTable(self, table, new_table, new_schema=None):
return self.renameTable(table, new_table)
@ -644,7 +630,7 @@ class GPKGDBConnector(DBConnector):
self._execute_and_commit("VACUUM")
def addTableColumn(self, table, field_def):
""" add a column to table """
"""Adds a column to table """
_, tablename = self.getSchemaTableName(table)
lyr = self.gdal_ds.GetLayerByName(tablename)
@ -654,7 +640,7 @@ class GPKGDBConnector(DBConnector):
return lyr.CreateField(fld_defn) == 0
def deleteTableColumn(self, table, column):
""" delete column from a table """
"""Deletes column from a table """
if self.isGeometryColumn(table, column):
return False
@ -772,20 +758,20 @@ class GPKGDBConnector(DBConnector):
return False # not supported
def addTableUniqueConstraint(self, table, column):
""" add a unique constraint to a table """
"""Adds a unique constraint to a table """
return False # constraints not supported
def deleteTableConstraint(self, table, constraint):
""" delete constraint in a table """
"""Deletes constraint in a table """
return False # constraints not supported
def addTablePrimaryKey(self, table, column):
""" add a primery key (with one column) to a table """
"""Adds a primery key (with one column) to a table """
sql = u"ALTER TABLE %s ADD PRIMARY KEY (%s)" % (self.quoteId(table), self.quoteId(column))
self._execute_and_commit(sql)
def createTableIndex(self, table, name, column, unique=False):
""" create index on one column using default options """
"""Creates index on one column using default options """
unique_str = u"UNIQUE" if unique else ""
sql = u"CREATE %s INDEX %s ON %s (%s)" % (
unique_str, self.quoteId(name), self.quoteId(table), self.quoteId(column))
@ -802,8 +788,11 @@ class GPKGDBConnector(DBConnector):
_, tablename = self.getSchemaTableName(table)
sql = u"SELECT CreateSpatialIndex(%s, %s)" % (
self.quoteId(tablename), self.quoteId(geom_column))
res = self._fetchOne(sql)
return res is not None and res[0] == 1
try:
res = self._fetchOne(sql)
except QgsProviderConnectionException:
return False
return res is not None and res[0][0] == 1
def deleteSpatialIndex(self, table, geom_column):
if self.isRasterTable(table):
@ -812,7 +801,7 @@ class GPKGDBConnector(DBConnector):
sql = u"SELECT DisableSpatialIndex(%s, %s)" % (
self.quoteId(tablename), self.quoteId(geom_column))
res = self._fetchOne(sql)
return res is not None and res[0] == 1
return len(res) > 0 and len(res[0]) > 0 and res[0][0] == 1
def hasSpatialIndex(self, table, geom_column):
if self.isRasterTable(table) or geom_column is None:
@ -825,14 +814,14 @@ class GPKGDBConnector(DBConnector):
ret = self._fetchOne(sql)
gdal.PopErrorHandler()
if ret is None:
if len(ret) == 0:
# might be the case for GDAL < 2.1.2
sql = u"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name LIKE %s" % self.quoteString("%%rtree_" + tablename + "_%%")
ret = self._fetchOne(sql)
if ret is None:
if len(ret) == 0:
return False
else:
return ret[0] >= 1
return ret[0][0] >= 1
def execution_error_types(self):
return sqlite3.Error, sqlite3.ProgrammingError, sqlite3.Warning

View File

@ -27,7 +27,13 @@ from .connector import GPKGDBConnector
from qgis.PyQt.QtCore import Qt, QFileInfo, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QApplication, QAction, QFileDialog
from qgis.core import Qgis, QgsApplication, QgsDataSourceUri, QgsSettings
from qgis.core import (
Qgis,
QgsApplication,
QgsDataSourceUri,
QgsSettings,
QgsProviderRegistry,
)
from qgis.gui import QgsMessageBar
from ..plugin import DBPlugin, Database, Table, VectorTable, RasterTable, TableField, TableIndex, TableTrigger, \
@ -65,23 +71,22 @@ class GPKGDBPlugin(DBPlugin):
def connect(self, parent=None):
conn_name = self.connectionName()
settings = QgsSettings()
settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), conn_name))
if not settings.contains("path"): # non-existent entry?
md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
conn = md.findConnection(conn_name)
if conn is None: # non-existent entry?
raise InvalidDataException(self.tr(u'There is no defined database connection "{0}".').format(conn_name))
database = settings.value("path")
uri = QgsDataSourceUri()
uri.setDatabase(database)
uri.setDatabase(conn.uri())
return self.connectToUri(uri)
@classmethod
def addConnection(self, conn_name, uri):
settings = QgsSettings()
settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), conn_name))
settings.setValue("path", uri.database())
md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
conn = md.createConnection(conn_name, uri.database())
md.saveConnection(conn)
return True
@classmethod
@ -108,7 +113,7 @@ class GPKGDatabase(Database):
Database.__init__(self, connection, uri)
def connectorsFactory(self, uri):
return GPKGDBConnector(uri)
return GPKGDBConnector(uri, self.connection())
def dataTablesFactory(self, row, db, schema=None):
return GPKGTable(row, db, schema)
@ -184,6 +189,16 @@ class GPKGDatabase(Database):
class GPKGTable(Table):
def __init__(self, row, db, schema=None):
"""Constructs a GPKGTable
:param row: a three elements array with: [table_name, is_view, is_sys_table]
:type row: array [str, bool, bool]
:param db: database instance
:type db:
:param schema: schema name, defaults to None, ignored by GPKG
:type schema: str, optional
"""
Table.__init__(self, db, None)
self.name, self.isView, self.isSysTable = row

View File

@ -381,7 +381,7 @@ class VectorTableInfo(TableInfo):
if self.table.geomDim:
tbl.append((QApplication.translate("DBManagerPlugin", "Dimension:"), self.table.geomDim))
srid = self.table.srid if self.table.srid is not None else -1
srid = self.table.srid if self.table.srid not in (None, 0) else -1
sr_info = self.table.database().connector.getSpatialRefInfo(srid) if srid != -1 else QApplication.translate(
"DBManagerPlugin", "Undefined")
if sr_info:

View File

@ -426,7 +426,7 @@ class OracleDBConnector(DBConnector):
return sorted(items, key=cmp_to_key(lambda x, y: (x[1] > y[1]) - (x[1] < y[1])))
def updateCache(self, tableList, schema=None):
"""Update the SQLite cache of table list for a schema."""
"""Updates the SQLite cache of table list for a schema."""
data = []
# First, we treat the list
@ -1017,7 +1017,7 @@ class OracleDBConnector(DBConnector):
self._execute_and_commit(sql)
def deleteTableTrigger(self, trigger, table):
"""Delete the trigger on a table."""
"""Deletes the trigger on a table."""
schema, tablename = self.getSchemaTableName(table)
trigger = u".".join([self.quoteId(schema), self.quoteId(trigger)])
sql = u"DROP TRIGGER {}".format(trigger)
@ -1194,7 +1194,7 @@ class OracleDBConnector(DBConnector):
return False
def createTable(self, table, field_defs, pkey):
"""Create ordinary table
"""Creates ordinary table
'fields' is array containing field definitions
'pkey' is the primary key name
"""
@ -1211,7 +1211,7 @@ class OracleDBConnector(DBConnector):
return True
def deleteTable(self, table):
"""Delete table and its reference in sdo_geom_metadata."""
"""Deletes table and its reference in sdo_geom_metadata."""
schema, tablename = self.getSchemaTableName(table)
@ -1222,13 +1222,13 @@ class OracleDBConnector(DBConnector):
self._execute_and_commit(sql)
def emptyTable(self, table):
"""Delete all the rows of a table."""
"""Deletes all the rows of a table."""
sql = u"TRUNCATE TABLE {}".format(self.quoteId(table))
self._execute_and_commit(sql)
def renameTable(self, table, new_table):
"""Rename a table inside the database."""
"""Renames a table inside the database."""
schema, tablename = self.getSchemaTableName(table)
if new_table == tablename:
return
@ -1246,7 +1246,7 @@ class OracleDBConnector(DBConnector):
self._commit()
def createView(self, view, query):
"""Create a view as defined."""
"""Creates a view as defined."""
sql = u"CREATE VIEW {0} AS {1}".format(self.quoteId(view),
query)
self._execute_and_commit(sql)
@ -1281,7 +1281,7 @@ class OracleDBConnector(DBConnector):
return True
def deleteView(self, view):
"""Delete a view."""
"""Deletes a view."""
schema, tablename = self.getSchemaTableName(view)
if self.isVectorTable(view):
@ -1291,30 +1291,30 @@ class OracleDBConnector(DBConnector):
self._execute_and_commit(sql)
def createSchema(self, schema):
"""Create a new empty schema in database."""
"""Creates a new empty schema in database."""
# Not tested
sql = u"CREATE SCHEMA AUTHORIZATION {}".format(
self.quoteId(schema))
self._execute_and_commit(sql)
def deleteSchema(self, schema):
"""Drop (empty) schema from database."""
"""Drops (empty) schema from database."""
sql = u"DROP USER {} CASCADE".format(self.quoteId(schema))
self._execute_and_commit(sql)
def renameSchema(self, schema, new_schema):
"""Rename a schema in the database."""
"""Renames a schema in the database."""
# Unsupported in Oracle
pass
def addTableColumn(self, table, field_def):
"""Add a column to a table."""
"""Adds a column to a table."""
sql = u"ALTER TABLE {0} ADD {1}".format(self.quoteId(table),
field_def)
self._execute_and_commit(sql)
def deleteTableColumn(self, table, column):
"""Delete column from a table."""
"""Deletes column from a table."""
# Delete all the constraints for this column
constraints = [f[0] for f in self.getTableConstraints(table)
if f[2] == column]
@ -1337,7 +1337,7 @@ class OracleDBConnector(DBConnector):
def updateTableColumn(self, table, column, new_name=None,
data_type=None, not_null=None,
default=None, comment=None):
"""Update properties of a column in a table."""
"""Updates properties of a column in a table."""
schema, tablename = self.getSchemaTableName(table)
@ -1378,19 +1378,19 @@ class OracleDBConnector(DBConnector):
self._commit()
def renameTableColumn(self, table, column, new_name):
"""Rename column in a table."""
"""Renames column in a table."""
return self.updateTableColumn(table, column, new_name)
def setTableColumnType(self, table, column, data_type):
"""Change column type."""
"""Changes column type."""
return self.updateTableColumn(table, column, None, data_type)
def setTableColumnNull(self, table, column, is_null):
"""Change whether column can contain null values."""
"""Changes whether column can contain null values."""
return self.updateTableColumn(table, column, None, None, not is_null)
def setTableColumnDefault(self, table, column, default):
"""Change column's default value.
"""Changes column's default value.
If default=None or an empty string drop default value.
"""
return self.updateTableColumn(table, column, None, None, None, default)
@ -1417,7 +1417,7 @@ class OracleDBConnector(DBConnector):
return res
def refreshMView(self, table):
"""Refresh an MVIEW"""
"""Refreshes an MVIEW"""
schema, tablename = self.getSchemaTableName(table)
mview = u"{}.{}".format(schema, tablename) if schema else tablename
sql = u"""
@ -1429,7 +1429,7 @@ class OracleDBConnector(DBConnector):
self._execute_and_commit(sql)
def deleteMetadata(self, table, geom_column=None):
"""Delete the metadata entry for a table"""
"""Deletes the metadata entry for a table"""
schema, tablename = self.getSchemaTableName(table)
if not (self.getRawTablePrivileges('USER_SDO_GEOM_METADATA',
'MDSYS',
@ -1448,7 +1448,7 @@ class OracleDBConnector(DBConnector):
def updateMetadata(self, table, geom_column, new_geom_column=None,
new_table=None, extent=None, srid=None):
"""update the metadata table with the new information"""
"""Updates the metadata table with the new information"""
schema, tablename = self.getSchemaTableName(table)
if not (self.getRawTablePrivileges('USER_SDO_GEOM_METADATA',
@ -1499,7 +1499,7 @@ class OracleDBConnector(DBConnector):
self._execute_and_commit(sql)
def insertMetadata(self, table, geom_column, extent, srid, dim=2):
""" Insert a line for the table in Oracle Metadata table."""
"""Inserts a line for the table in Oracle Metadata table."""
schema, tablename = self.getSchemaTableName(table)
if not (self.getRawTablePrivileges('USER_SDO_GEOM_METADATA',
'MDSYS',
@ -1542,7 +1542,7 @@ class OracleDBConnector(DBConnector):
def addGeometryColumn(self, table, geom_column='GEOM',
geom_type=None, srid=-1, dim=2):
"""Add a geometry column and update Oracle Spatial
"""Adds a geometry column and update Oracle Spatial
metadata.
"""
@ -1565,48 +1565,48 @@ class OracleDBConnector(DBConnector):
srid, dim)
def deleteGeometryColumn(self, table, geom_column):
"""Delete a geometric column."""
"""Deletes a geometric column."""
return self.deleteTableColumn(table, geom_column)
def addTableUniqueConstraint(self, table, column):
"""Add a unique constraint to a table."""
"""Adds a unique constraint to a table."""
sql = u"ALTER TABLE {0} ADD UNIQUE ({1})".format(
self.quoteId(table), self.quoteId(column))
self._execute_and_commit(sql)
def deleteTableConstraint(self, table, constraint):
"""Delete constraint in a table."""
"""Deletes constraint in a table."""
sql = u"ALTER TABLE {0} DROP CONSTRAINT {1}".format(
self.quoteId(table), self.quoteId(constraint))
self._execute_and_commit(sql)
def addTablePrimaryKey(self, table, column):
"""Add a primary key (with one column) to a table."""
"""Adds a primary key (with one column) to a table."""
sql = u"ALTER TABLE {0} ADD PRIMARY KEY ({1})".format(
self.quoteId(table), self.quoteId(column))
self._execute_and_commit(sql)
def createTableIndex(self, table, name, column):
"""Create index on one column using default options."""
"""Creates index on one column using default options."""
sql = u"CREATE INDEX {0} ON {1} ({2})".format(
self.quoteId(name), self.quoteId(table),
self.quoteId(column))
self._execute_and_commit(sql)
def rebuildTableIndex(self, table, name):
"""Rebuild a table index"""
"""Rebuilds a table index"""
schema, tablename = self.getSchemaTableName(table)
sql = u"ALTER INDEX {} REBUILD".format(self.quoteId((schema, name)))
self._execute_and_commit(sql)
def deleteTableIndex(self, table, name):
"""Delete an index on a table."""
"""Deletes an index on a table."""
schema, tablename = self.getSchemaTableName(table)
sql = u"DROP INDEX {}".format(self.quoteId((schema, name)))
self._execute_and_commit(sql)
def createSpatialIndex(self, table, geom_column='GEOM'):
"""Create a spatial index on a geometric column."""
"""Creates a spatial index on a geometric column."""
geom_column = geom_column.upper()
schema, tablename = self.getSchemaTableName(table)
idx_name = self.quoteId(u"sidx_{0}_{1}".format(tablename,
@ -1620,7 +1620,7 @@ class OracleDBConnector(DBConnector):
self._execute_and_commit(sql)
def deleteSpatialIndex(self, table, geom_column='GEOM'):
"""Delete a spatial index of a geometric column."""
"""Deletes a spatial index of a geometric column."""
schema, tablename = self.getSchemaTableName(table)
idx_name = self.quoteId(u"sidx_{0}_{1}".format(tablename,
geom_column))

View File

@ -32,7 +32,9 @@ from qgis.core import (
QgsApplication,
QgsSettings,
QgsMapLayerType,
QgsWkbTypes
QgsWkbTypes,
QgsProviderConnectionException,
QgsProviderRegistry,
)
from ..db_plugins import createDbPlugin
@ -126,9 +128,16 @@ class DBPlugin(QObject):
return self.connect(self.parent())
def remove(self):
settings = QgsSettings()
settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), self.connectionName()))
settings.remove("")
# Try the new API first, fallback to legacy
try:
md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
md.deleteConnection(self.connectionName())
except (AttributeError, QgsProviderConnectionException) as ex:
settings = QgsSettings()
settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), self.connectionName()))
settings.remove("")
self.deleted.emit()
return True
@ -163,12 +172,21 @@ class DBPlugin(QObject):
@classmethod
def connections(self):
# get the list of connections
conn_list = []
settings = QgsSettings()
settings.beginGroup(self.connectionSettingsKey())
for name in settings.childGroups():
conn_list.append(createDbPlugin(self.typeName(), name))
settings.endGroup()
# First try with the new core API, if that fails, proceed with legacy code
try:
md = QgsProviderRegistry.instance().providerMetadata(self.providerName())
for name in md.dbConnections().keys():
conn_list.append(createDbPlugin(self.typeName(), name))
except (AttributeError, QgsProviderConnectionException) as ex:
settings = QgsSettings()
settings.beginGroup(self.connectionSettingsKey())
for name in settings.childGroups():
conn_list.append(createDbPlugin(self.typeName(), name))
settings.endGroup()
return conn_list
def databasesFactory(self, connection, uri):

View File

@ -574,7 +574,7 @@ class PostGisDBConnector(DBConnector):
self._execute_and_commit(sql)
def deleteTableTrigger(self, trigger, table):
""" delete trigger on table """
"""Deletes trigger on table """
sql = u"DROP TRIGGER %s ON %s" % (self.quoteId(trigger), self.quoteId(table))
self._execute_and_commit(sql)
@ -592,7 +592,7 @@ class PostGisDBConnector(DBConnector):
return res
def deleteTableRule(self, rule, table):
""" delete rule on table """
"""Deletes rule on table """
sql = u"DROP RULE %s ON %s" % (self.quoteId(rule), self.quoteId(table))
self._execute_and_commit(sql)
@ -689,7 +689,7 @@ class PostGisDBConnector(DBConnector):
return False
def createTable(self, table, field_defs, pkey):
""" create ordinary table
"""Creates ordinary table
'fields' is array containing field definitions
'pkey' is the primary key name
"""
@ -706,7 +706,7 @@ class PostGisDBConnector(DBConnector):
return True
def deleteTable(self, table):
""" delete table and its reference in either geometry_columns or raster_columns """
"""Deletes table and its reference in either geometry_columns or raster_columns """
schema, tablename = self.getSchemaTableName(table)
schema_part = u"%s, " % self.quoteString(schema) if schema is not None else ""
if self.isVectorTable(table):
@ -719,19 +719,19 @@ class PostGisDBConnector(DBConnector):
self._execute_and_commit(sql)
def emptyTable(self, table):
""" delete all rows from table """
"""Deletes all rows from table """
sql = u"TRUNCATE %s" % self.quoteId(table)
self._execute_and_commit(sql)
def renameTable(self, table, new_table):
""" rename a table in database """
def renamesTable(self, table, new_table):
"""Renames a table in database """
schema, tablename = self.getSchemaTableName(table)
if new_table == tablename:
return
c = self._get_cursor()
sql = u"ALTER TABLE %s RENAME TO %s" % (self.quoteId(table), self.quoteId(new_table))
sql = u"ALTER TABLE %s RENAME TO %s" % (self.quoteId(table), self.quoteId(new_table))
self._execute(c, sql)
# update geometry_columns if PostGIS is enabled
@ -798,13 +798,13 @@ class PostGisDBConnector(DBConnector):
c = self._get_cursor()
t = u"__new_table__"
sql = u"ALTER TABLE %s RENAME TO %s" % (self.quoteId(table), self.quoteId(t))
sql = u"ALTER TABLE %s RENAME TO %s" % (self.quoteId(table), self.quoteId(t))
self._execute(c, sql)
sql = u"ALTER TABLE %s SET SCHEMA %s" % (self.quoteId((schema, t)), self.quoteId(new_schema))
self._execute(c, sql)
sql = u"ALTER TABLE %s RENAME TO %s" % (self.quoteId((new_schema, t)), self.quoteId(table))
sql = u"ALTER TABLE %s RENAME TO %s" % (self.quoteId((new_schema, t)), self.quoteId(table))
self._execute(c, sql)
# update geometry_columns if PostGIS is enabled
@ -830,47 +830,47 @@ class PostGisDBConnector(DBConnector):
self._execute_and_commit(sql)
def renameView(self, view, new_name):
""" rename view in database """
"""Renames view in database """
self.renameTable(view, new_name)
def createSchema(self, schema):
""" create a new empty schema in database """
"""Creates a new empty schema in database """
sql = u"CREATE SCHEMA %s" % self.quoteId(schema)
self._execute_and_commit(sql)
def deleteSchema(self, schema):
""" drop (empty) schema from database """
"""Drops (empty) schema from database """
sql = u"DROP SCHEMA %s" % self.quoteId(schema)
self._execute_and_commit(sql)
def renameSchema(self, schema, new_schema):
""" rename a schema in database """
sql = u"ALTER SCHEMA %s RENAME TO %s" % (self.quoteId(schema), self.quoteId(new_schema))
def renamesSchema(self, schema, new_schema):
"""Renames a schema in database """
sql = u"ALTER SCHEMA %s RENAME TO %s" % (self.quoteId(schema), self.quoteId(new_schema))
self._execute_and_commit(sql)
def runVacuum(self):
""" run vacuum on the db """
"""Runs vacuum on the db """
self._execute_and_commit("VACUUM")
def runVacuumAnalyze(self, table):
""" run vacuum analyze on a table """
"""Runs vacuum analyze on a table """
sql = u"VACUUM ANALYZE %s" % self.quoteId(table)
self._execute(None, sql)
self._commit()
def runRefreshMaterializedView(self, table):
""" run refresh materialized view on a table """
"""Runs refresh materialized view on a table """
sql = u"REFRESH MATERIALIZED VIEW %s" % self.quoteId(table)
self._execute(None, sql)
self._commit()
def addTableColumn(self, table, field_def):
""" add a column to table """
"""Adds a column to table """
sql = u"ALTER TABLE %s ADD %s" % (self.quoteId(table), field_def)
self._execute_and_commit(sql)
def deleteTableColumn(self, table, column):
""" delete column from a table """
"""Deletes column from a table """
if self.isGeometryColumn(table, column):
# use PostGIS function to delete geometry column correctly
schema, tablename = self.getSchemaTableName(table)
@ -905,9 +905,9 @@ class PostGisDBConnector(DBConnector):
sql += u" %s %s," % (alter_col_str, a)
self._execute(c, sql[:-1])
# rename the column
#Renames the column
if new_name is not None and new_name != column:
sql = u"ALTER TABLE %s RENAME %s TO %s" % (
sql = u"ALTER TABLE %s RENAME %s TO %s" % (
self.quoteId(table), self.quoteId(column), self.quoteId(new_name))
self._execute(c, sql)
@ -928,20 +928,20 @@ class PostGisDBConnector(DBConnector):
self._commit()
def renameTableColumn(self, table, column, new_name):
""" rename column in a table """
def renamesTableColumn(self, table, column, new_name):
"""Renames column in a table """
return self.updateTableColumn(table, column, new_name)
def setTableColumnType(self, table, column, data_type):
""" change column type """
"""Changes column type """
return self.updateTableColumn(table, column, None, data_type)
def setTableColumnNull(self, table, column, is_null):
""" change whether column can contain null values """
"""Changes whether column can contain null values """
return self.updateTableColumn(table, column, None, None, not is_null)
def setTableColumnDefault(self, table, column, default):
""" change column's default value.
"""Changes column's default value.
If default=None or an empty string drop default value """
return self.updateTableColumn(table, column, None, None, None, default)
@ -970,22 +970,22 @@ class PostGisDBConnector(DBConnector):
return self.deleteTableColumn(table, geom_column)
def addTableUniqueConstraint(self, table, column):
""" add a unique constraint to a table """
"""Adds a unique constraint to a table """
sql = u"ALTER TABLE %s ADD UNIQUE (%s)" % (self.quoteId(table), self.quoteId(column))
self._execute_and_commit(sql)
def deleteTableConstraint(self, table, constraint):
""" delete constraint in a table """
"""Deletes constraint in a table """
sql = u"ALTER TABLE %s DROP CONSTRAINT %s" % (self.quoteId(table), self.quoteId(constraint))
self._execute_and_commit(sql)
def addTablePrimaryKey(self, table, column):
""" add a primery key (with one column) to a table """
"""Adds a primery key (with one column) to a table """
sql = u"ALTER TABLE %s ADD PRIMARY KEY (%s)" % (self.quoteId(table), self.quoteId(column))
self._execute_and_commit(sql)
def createTableIndex(self, table, name, column):
""" create index on one column using default options """
"""Creates index on one column using default options """
sql = u"CREATE INDEX %s ON %s (%s)" % (self.quoteId(name), self.quoteId(table), self.quoteId(column))
self._execute_and_commit(sql)

View File

@ -363,7 +363,7 @@ class SpatiaLiteDBConnector(DBConnector):
return c.fetchall()
def deleteTableTrigger(self, trigger, table=None):
""" delete trigger """
"""Deletes trigger """
sql = u"DROP TRIGGER %s" % self.quoteId(trigger)
self._execute_and_commit(sql)
@ -424,7 +424,7 @@ class SpatiaLiteDBConnector(DBConnector):
return False
def createTable(self, table, field_defs, pkey):
""" create ordinary table
"""Creates ordinary table
'fields' is array containing field definitions
'pkey' is the primary key name
"""
@ -441,7 +441,7 @@ class SpatiaLiteDBConnector(DBConnector):
return True
def deleteTable(self, table):
""" delete table from the database """
"""Deletes table from the database """
if self.isRasterTable(table):
return False
@ -456,7 +456,7 @@ class SpatiaLiteDBConnector(DBConnector):
return True
def emptyTable(self, table):
""" delete all rows from table """
"""Deletes all rows from table """
if self.isRasterTable(table):
return False
@ -560,7 +560,7 @@ class SpatiaLiteDBConnector(DBConnector):
self.connection.isolation_level = '' # reset to default isolation
def addTableColumn(self, table, field_def):
""" add a column to table """
"""Adds a column to table """
sql = u"ALTER TABLE %s ADD %s" % (self.quoteId(table), field_def)
self._execute(None, sql)
@ -574,7 +574,7 @@ class SpatiaLiteDBConnector(DBConnector):
return True
def deleteTableColumn(self, table, column):
""" delete column from a table """
"""Deletes column from a table """
if not self.isGeometryColumn(table, column):
return False # column editing not supported
@ -591,15 +591,15 @@ class SpatiaLiteDBConnector(DBConnector):
return False # column editing not supported
def setColumnType(self, table, column, data_type):
""" change column type """
"""Changes column type """
return False # column editing not supported
def setColumnDefault(self, table, column, default):
""" change column's default value. If default=None drop default value """
"""Changes column's default value. If default=None drop default value """
return False # column editing not supported
def setColumnNull(self, table, column, is_null):
""" change whether column can contain null values """
"""Changes whether column can contain null values """
return False # column editing not supported
def isGeometryColumn(self, table, column):
@ -622,20 +622,20 @@ class SpatiaLiteDBConnector(DBConnector):
return self.deleteTableColumn(table, geom_column)
def addTableUniqueConstraint(self, table, column):
""" add a unique constraint to a table """
"""Adds a unique constraint to a table """
return False # constraints not supported
def deleteTableConstraint(self, table, constraint):
""" delete constraint in a table """
"""Deletes constraint in a table """
return False # constraints not supported
def addTablePrimaryKey(self, table, column):
""" add a primery key (with one column) to a table """
"""Adds a primery key (with one column) to a table """
sql = u"ALTER TABLE %s ADD PRIMARY KEY (%s)" % (self.quoteId(table), self.quoteId(column))
self._execute_and_commit(sql)
def createTableIndex(self, table, name, column, unique=False):
""" create index on one column using default options """
"""Creates index on one column using default options """
unique_str = u"UNIQUE" if unique else ""
sql = u"CREATE %s INDEX %s ON %s (%s)" % (
unique_str, self.quoteId(name), self.quoteId(table), self.quoteId(column))

View File

@ -174,7 +174,7 @@ class DlgCreateTable(QDialog, Ui_Dialog):
self.cboPrimaryKey.setCurrentIndex(selRow)
def addField(self):
""" add new field to the end of field table """
"""Adds new field to the end of field table """
m = self.fields.model()
newRow = m.rowCount()
m.insertRows(newRow, 1)
@ -209,7 +209,7 @@ class DlgCreateTable(QDialog, Ui_Dialog):
return sel[0].row()
def deleteField(self):
""" delete selected field """
"""Deletes selected field """
row = self.selectedField()
if row is None:
QMessageBox.information(self, self.tr("DB Manager"), self.tr("No field selected."))
@ -259,7 +259,7 @@ class DlgCreateTable(QDialog, Ui_Dialog):
self.updatePkeyCombo()
def createTable(self):
""" create table with chosen fields, optionally add a geometry column """
"""Creates table with chosen fields, optionally add a geometry column """
if not self.hasSchemas:
schema = None
else:

View File

@ -165,7 +165,7 @@ class DlgImportVector(QDialog, Ui_Dialog):
self.cboInputLayer.setEditText(filename)
def reloadInputLayer(self):
""" create the input layer and update available options """
"""Creates the input layer and update available options """
if self.mode != self.ASK_FOR_INPUT_MODE:
return True

View File

@ -171,7 +171,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
DlgDbError.showError(e, self)
def deleteColumn(self):
""" delete currently selected column """
"""Deletes currently selected column """
index = self.currentColumn()
if index == -1:
return
@ -214,7 +214,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
self.tabs.setTabEnabled(index, False)
def addConstraint(self):
""" add primary key or unique constraint """
"""Adds primary key or unique constraint """
dlg = DlgCreateConstraint(self, self.table)
if not dlg.exec_():
@ -222,7 +222,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
self.refresh()
def deleteConstraint(self):
""" delete a constraint """
"""Deletes a constraint """
index = self.currentConstraint()
if index == -1:
@ -275,14 +275,14 @@ class DlgTableProperties(QDialog, Ui_Dialog):
self.tabs.setTabEnabled(index, False)
def createIndex(self):
""" create an index """
"""Creates an index """
dlg = DlgCreateIndex(self, self.table)
if not dlg.exec_():
return
self.refresh()
def createSpatialIndex(self):
""" create spatial index for the geometry column """
"""Creates spatial index for the geometry column """
if self.table.type != self.table.VectorType:
QMessageBox.information(self, self.tr("DB Manager"), self.tr("The selected table has no geometry."))
return
@ -313,7 +313,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
return indexes[0].row()
def deleteIndex(self):
""" delete currently selected index """
"""Deletes currently selected index """
index = self.currentIndex()
if index == -1:
return

View File

@ -148,6 +148,7 @@ SET(QGIS_CORE_SRCS
providers/ogr/qgsogrconnpool.cpp
providers/ogr/qgsogrexpressioncompiler.cpp
providers/ogr/qgsgeopackagedataitems.cpp
providers/ogr/qgsgeopackageproviderconnection.cpp
providers/ogr/qgsgeopackagerasterwriter.cpp
providers/ogr/qgsgeopackagerasterwritertask.cpp
providers/ogr/qgsgeopackageprojectstorage.cpp
@ -162,6 +163,8 @@ SET(QGIS_CORE_SRCS
qgis.cpp
qgsabstractcontentcache.cpp
qgsabstractproviderconnection.cpp
qgsabstractdatabaseproviderconnection.cpp
qgsapplication.cpp
qgsaction.cpp
qgsactionscope.cpp
@ -627,6 +630,7 @@ ENDIF(NOT MSVC)
SET(QGIS_CORE_MOC_HDRS
qgsabstractcontentcache.h
qgsabstractdatabaseproviderconnection.h
qgsapplication.h
qgsactionmanager.h
qgsactionscoperegistry.h
@ -882,6 +886,8 @@ SET(QGIS_CORE_HDRS
qgis.h
qgis_sip.h
qgsabstractproviderconnection.h
qgsabstractdatabaseproviderconnection.h
qgsaction.h
qgsactionscope.h
qgsactionmanager.h
@ -1126,6 +1132,8 @@ SET(QGIS_CORE_HDRS
providers/memory/qgsmemoryproviderutils.h
providers/ogr/qgsgeopackageprojectstorage.h
providers/ogr/qgsgeopackageproviderconnection.h
providers/ogr/qgsogrprovider.h
raster/qgsbilinearrasterresampler.h
raster/qgsbrightnesscontrastfilter.h

View File

@ -20,8 +20,6 @@
#include <QFileDialog>
#include <QInputDialog>
#include <sqlite3.h>
#include "qgssqliteutils.h"
#include "qgsgeopackagedataitems.h"
#include "qgsogrdbconnection.h"
@ -41,6 +39,7 @@
#include "qgsproxyprogresstask.h"
#include "qgsprojectstorageregistry.h"
#include "qgsgeopackageprojectstorage.h"
#include "qgsgeopackageproviderconnection.h"
QString QgsGeoPackageDataItemProvider::name()
{
@ -146,6 +145,54 @@ bool QgsGeoPackageCollectionItem::equal( const QgsDataItem *other )
}
bool QgsGeoPackageCollectionItem::deleteRasterLayer( const QString &layerName, QString &errCause )
{
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) ) };
QgsAbstractDatabaseProviderConnection *conn { static_cast<QgsAbstractDatabaseProviderConnection *>( md->findConnection( name() ) ) };
if ( conn )
{
try
{
conn->dropRasterTable( QString(), layerName );
}
catch ( QgsProviderConnectionException &ex )
{
errCause = ex.what();
return false;
}
}
else
{
errCause = QObject::tr( "There was an error retrieving the connection %1!" ).arg( name() );
return false;
}
return true;
}
bool QgsGeoPackageCollectionItem::deleteVectorLayer( const QString &layerName, QString &errCause )
{
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) ) };
QgsAbstractDatabaseProviderConnection *conn { static_cast<QgsAbstractDatabaseProviderConnection *>( md->findConnection( name() ) ) };
if ( conn )
{
try
{
conn->dropVectorTable( QString(), layerName );
}
catch ( QgsProviderConnectionException &ex )
{
errCause = ex.what();
return false;
}
}
else
{
errCause = QObject::tr( "There was an error retrieving the connection %1!" ).arg( name() );
return false;
}
return true;
}
QWidget *QgsGeoPackageRootItem::paramWidget()
{
return nullptr;
@ -165,156 +212,29 @@ void QgsGeoPackageCollectionItem::deleteConnection()
mParent->refreshConnections();
}
bool QgsGeoPackageCollectionItem::vacuumGeoPackageDb( const QString &path, const QString &name, QString &errCause )
bool QgsGeoPackageCollectionItem::vacuumGeoPackageDb( const QString &name, QString &errCause )
{
QgsScopedProxyProgressTask task( tr( "Vacuuming %1" ).arg( name ) );
bool result = false;
// Better safe than sorry
if ( ! path.isEmpty( ) )
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) ) };
QgsAbstractDatabaseProviderConnection *conn { static_cast<QgsAbstractDatabaseProviderConnection *>( md->findConnection( name ) ) };
if ( conn )
{
char *errmsg = nullptr;
sqlite3_database_unique_ptr database;
int status = database.open_v2( path, SQLITE_OPEN_READWRITE, nullptr );
if ( status != SQLITE_OK )
try
{
errCause = sqlite3_errmsg( database.get() );
conn->vacuum( QString(), QString() );
}
else
catch ( QgsProviderConnectionException &ex )
{
( void )sqlite3_exec(
database.get(), /* An open database */
"VACUUM", /* SQL to be evaluated */
nullptr, /* Callback function */
nullptr, /* 1st argument to callback */
&errmsg /* Error msg written here */
);
}
if ( status != SQLITE_OK || errmsg )
{
errCause = tr( "There was an error compacting (VACUUM) the database <b>%1</b>: %2" )
.arg( name,
QString::fromUtf8( errmsg ) );
}
else
{
result = true;
}
sqlite3_free( errmsg );
}
else
{
// This should never happen!
errCause = tr( "Layer path is empty: layer cannot be deleted!" );
}
return result;
}
bool QgsGeoPackageCollectionItem::deleteGeoPackageRasterLayer( const QString &uri, QString &errCause )
{
bool result = false;
// Better safe than sorry
if ( ! uri.isEmpty( ) )
{
QVariantMap pieces( QgsProviderRegistry::instance()->decodeUri( QStringLiteral( "gdal" ), uri ) );
QString baseUri = pieces[QStringLiteral( "path" )].toString();
QString layerName = pieces[QStringLiteral( "layerName" )].toString();
if ( baseUri.isEmpty() || layerName.isEmpty() )
{
errCause = QStringLiteral( "Layer URI is malformed: layer <b>%1</b> cannot be deleted!" ).arg( uri );
}
else
{
sqlite3_database_unique_ptr database;
int status = database.open_v2( baseUri, SQLITE_OPEN_READWRITE, nullptr );
if ( status != SQLITE_OK )
{
errCause = sqlite3_errmsg( database.get() );
}
else
{
// Remove table
char *errmsg = nullptr;
char *sql = sqlite3_mprintf(
"DROP table IF EXISTS \"%w\";"
"DELETE FROM gpkg_contents WHERE table_name = '%q';"
"DELETE FROM gpkg_tile_matrix WHERE table_name = '%q';"
"DELETE FROM gpkg_tile_matrix_set WHERE table_name = '%q';",
layerName.toUtf8().constData(),
layerName.toUtf8().constData(),
layerName.toUtf8().constData(),
layerName.toUtf8().constData() );
status = sqlite3_exec(
database.get(), /* An open database */
sql, /* SQL to be evaluated */
nullptr, /* Callback function */
nullptr, /* 1st argument to callback */
&errmsg /* Error msg written here */
);
sqlite3_free( sql );
// Remove from optional tables, may silently fail
QStringList optionalTables;
optionalTables << QStringLiteral( "gpkg_extensions" )
<< QStringLiteral( "gpkg_metadata_reference" );
for ( const QString &tableName : qgis::as_const( optionalTables ) )
{
char *sql = sqlite3_mprintf( "DELETE FROM %w WHERE table_name = '%q'",
tableName.toUtf8().constData(),
layerName.toUtf8().constData() );
( void )sqlite3_exec(
database.get(), /* An open database */
sql, /* SQL to be evaluated */
nullptr, /* Callback function */
nullptr, /* 1st argument to callback */
nullptr /* Error msg written here */
);
sqlite3_free( sql );
}
// Other tables, ignore errors
{
char *sql = sqlite3_mprintf( "DELETE FROM gpkg_2d_gridded_coverage_ancillary WHERE tile_matrix_set_name = '%q'",
layerName.toUtf8().constData() );
( void )sqlite3_exec(
database.get(), /* An open database */
sql, /* SQL to be evaluated */
nullptr, /* Callback function */
nullptr, /* 1st argument to callback */
nullptr /* Error msg written here */
);
sqlite3_free( sql );
}
{
char *sql = sqlite3_mprintf( "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE tpudt_name = '%q'",
layerName.toUtf8().constData() );
( void )sqlite3_exec(
database.get(), /* An open database */
sql, /* SQL to be evaluated */
nullptr, /* Callback function */
nullptr, /* 1st argument to callback */
nullptr /* Error msg written here */
);
sqlite3_free( sql );
}
if ( status == SQLITE_OK )
{
result = true;
}
else
{
errCause = tr( "There was an error deleting the layer %1: %2" ).arg( layerName, QString::fromUtf8( errmsg ) );
}
sqlite3_free( errmsg );
}
errCause = ex.what();
return false;
}
}
else
{
// This should never happen!
errCause = tr( "Layer URI is empty: layer cannot be deleted!" );
errCause = QObject::tr( "There was an error retrieving the connection %1!" ).arg( name );
return false;
}
return result;
return true;
}
QgsGeoPackageConnectionItem::QgsGeoPackageConnectionItem( QgsDataItem *parent, const QString &name, const QString &path )
@ -336,54 +256,25 @@ bool QgsGeoPackageConnectionItem::equal( const QgsDataItem *other )
QgsGeoPackageAbstractLayerItem::QgsGeoPackageAbstractLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, QgsLayerItem::LayerType layerType, const QString &providerKey )
: QgsLayerItem( parent, name, path, uri, layerType, providerKey )
, mCollection( qobject_cast<QgsGeoPackageCollectionItem*>( parent ) )
{
mCapabilities |= Delete;
mToolTip = uri;
setState( Populated ); // no children are expected
}
bool QgsGeoPackageAbstractLayerItem::executeDeleteLayer( QString &errCause )
{
errCause = QObject::tr( "The layer <b>%1</b> cannot be deleted because this feature is not yet implemented for this kind of layers." ).arg( mName );
return false;
}
static int collect_strings( void *names, int, char **argv, char ** )
{
*static_cast<QList<QString>*>( names ) << QString::fromUtf8( argv[ 0 ] );
return 0;
}
QStringList QgsGeoPackageAbstractLayerItem::tableNames()
QStringList QgsGeoPackageAbstractLayerItem::tableNames() const
{
QStringList names;
QVariantMap pieces( QgsProviderRegistry::instance()->decodeUri( providerKey(), mUri ) );
QString baseUri = pieces[QStringLiteral( "path" )].toString();
if ( !baseUri.isEmpty() )
// note: not using providerKey() because GPKG methods are implemented in OGR
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) ) };
QgsGeoPackageProviderConnection *conn { static_cast<QgsGeoPackageProviderConnection *>( md->findConnection( parent()->name() ) ) };
if ( conn )
{
char *errmsg = nullptr;
sqlite3_database_unique_ptr database;
int status = database.open_v2( baseUri, SQLITE_OPEN_READONLY, nullptr );
if ( status == SQLITE_OK )
for ( const QgsGeoPackageProviderConnection::TableProperty &p : conn->tables( ) )
{
char *sql = sqlite3_mprintf( "SELECT table_name FROM gpkg_contents;" );
status = sqlite3_exec(
database.get(), /* An open database */
sql, /* SQL to be evaluated */
collect_strings, /* Callback function */
&names, /* 1st argument to callback */
&errmsg /* Error msg written here */
);
sqlite3_free( sql );
if ( status != SQLITE_OK )
{
QgsDebugMsg( QStringLiteral( "There was an error reading tables from GPKG layer %1: %2" ).arg( mUri, QString::fromUtf8( errmsg ) ) );
}
sqlite3_free( errmsg );
}
else
{
QgsDebugMsg( QStringLiteral( "There was an error opening GPKG %1" ).arg( mUri ) );
names.push_back( p.tableName() );
}
}
return names;
@ -405,6 +296,11 @@ QList<QgsMapLayer *> QgsGeoPackageAbstractLayerItem::layersInProject() const
return layersList;
}
QgsGeoPackageCollectionItem *QgsGeoPackageAbstractLayerItem::collection() const
{
return mCollection;
}
QgsGeoPackageVectorLayerItem::QgsGeoPackageVectorLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType )
: QgsGeoPackageAbstractLayerItem( parent, name, path, uri, layerType, QStringLiteral( "ogr" ) )
{
@ -415,17 +311,54 @@ QgsGeoPackageVectorLayerItem::QgsGeoPackageVectorLayerItem( QgsDataItem *parent,
QgsGeoPackageRasterLayerItem::QgsGeoPackageRasterLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri )
: QgsGeoPackageAbstractLayerItem( parent, name, path, uri, QgsLayerItem::LayerType::Raster, QStringLiteral( "gdal" ) )
{
}
bool QgsGeoPackageRasterLayerItem::executeDeleteLayer( QString &errCause )
{
return QgsGeoPackageCollectionItem::deleteGeoPackageRasterLayer( mUri, errCause );
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( providerKey() ) };
QgsAbstractDatabaseProviderConnection *conn { static_cast<QgsAbstractDatabaseProviderConnection *>( md->findConnection( parent()->name() ) ) };
if ( conn )
{
try
{
conn->dropRasterTable( QString(), collection()->name() );
}
catch ( QgsProviderConnectionException &ex )
{
errCause = ex.what();
return false;
}
}
else
{
errCause = QObject::tr( "There was an error retrieving the connection %1!" ).arg( collection()->name() );
return false;
}
return true;
}
bool QgsGeoPackageVectorLayerItem::executeDeleteLayer( QString &errCause )
{
return QgsOgrProviderUtils::deleteLayer( mUri, errCause );
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( providerKey() ) };
QgsAbstractDatabaseProviderConnection *conn { static_cast<QgsAbstractDatabaseProviderConnection *>( md->findConnection( parent()->name() ) ) };
if ( conn )
{
try
{
conn->dropVectorTable( QString(), collection()->name() );
}
catch ( QgsProviderConnectionException &ex )
{
errCause = ex.what();
return false;
}
}
else
{
errCause = QObject::tr( "There was an error retrieving the connection %1!" ).arg( collection()->name() );
return false;
}
return true;
}
///@endcond

View File

@ -26,53 +26,6 @@
///@cond PRIVATE
#define SIP_NO_FILE
/**
* \brief The QgsGeoPackageAbstractLayerItem class is the base class for GeoPackage raster and vector layers
*/
class CORE_EXPORT QgsGeoPackageAbstractLayerItem : public QgsLayerItem
{
Q_OBJECT
public:
/**
* Returns a list of all table names for the geopackage
*/
QStringList tableNames();
//! Checks if the data source has any layer in the current project returns them
QList<QgsMapLayer *> layersInProject() const;
/**
* Deletes a layer.
* Subclasses need to implement this function with
* the real deletion implementation
*/
virtual bool executeDeleteLayer( QString &errCause );
protected:
QgsGeoPackageAbstractLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType, const QString &providerKey );
};
class CORE_EXPORT QgsGeoPackageRasterLayerItem : public QgsGeoPackageAbstractLayerItem
{
Q_OBJECT
public:
QgsGeoPackageRasterLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri );
bool executeDeleteLayer( QString &errCause ) override;
};
class CORE_EXPORT QgsGeoPackageVectorLayerItem : public QgsGeoPackageAbstractLayerItem
{
Q_OBJECT
public:
QgsGeoPackageVectorLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType );
bool executeDeleteLayer( QString &errCause ) override;
};
/**
* \brief The QgsGeoPackageCollectionItem class is the base class for
@ -91,16 +44,18 @@ class CORE_EXPORT QgsGeoPackageCollectionItem : public QgsDataCollectionItem
static QgsLayerItem::LayerType layerTypeFromDb( const QString &geometryType );
//! Deletes a geopackage raster layer
static bool deleteGeoPackageRasterLayer( const QString &uri, QString &errCause );
bool deleteRasterLayer( const QString &layerName, QString &errCause );
//! Deletes a geopackage vector layer
bool deleteVectorLayer( const QString &layerName, QString &errCause );
/**
* Compacts (VACUUM) a geopackage database
* \param path DB path
* \param name DB name
* \param name DB connection name
* \param errCause contains the error message
* \return true on success
*/
static bool vacuumGeoPackageDb( const QString &path, const QString &name, QString &errCause );
static bool vacuumGeoPackageDb( const QString &name, QString &errCause );
void addConnection();
void deleteConnection();
@ -110,6 +65,66 @@ class CORE_EXPORT QgsGeoPackageCollectionItem : public QgsDataCollectionItem
};
/**
* \brief The QgsGeoPackageAbstractLayerItem class is the base class for GeoPackage raster and vector layers
*/
class CORE_EXPORT QgsGeoPackageAbstractLayerItem : public QgsLayerItem
{
Q_OBJECT
public:
/**
* Returns a list of all table names for the geopackage
*/
QStringList tableNames() const;
//! Checks if the data source has any layer in the current project returns them
QList<QgsMapLayer *> layersInProject() const;
/**
* Deletes a layer.
* Subclasses need to implement this function with
* the real deletion implementation
*/
virtual bool executeDeleteLayer( QString &errCause ) = 0;
/**
* Returns the parent collection item
* \since QGIS 3.10
*/
QgsGeoPackageCollectionItem *collection() const;
protected:
QgsGeoPackageAbstractLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType, const QString &providerKey );
private:
//! Store a casted pointer to the parent collection
QgsGeoPackageCollectionItem *mCollection = nullptr;
};
class CORE_EXPORT QgsGeoPackageRasterLayerItem : public QgsGeoPackageAbstractLayerItem
{
Q_OBJECT
public:
QgsGeoPackageRasterLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri );
bool executeDeleteLayer( QString &errCause ) override;
};
class CORE_EXPORT QgsGeoPackageVectorLayerItem : public QgsGeoPackageAbstractLayerItem
{
Q_OBJECT
public:
QgsGeoPackageVectorLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri, LayerType layerType );
bool executeDeleteLayer( QString &errCause ) override;
};
/**
* \brief The QgsGeoPackageConnectionItem class adds the stored
* connection management to QgsGeoPackageCollectionItem

View File

@ -0,0 +1,321 @@
/***************************************************************************
QgsGeoPackageProviderConnection.cpp - QgsGeoPackageProviderConnection
---------------------
begin : 6.8.2019
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 "qgsgeopackageproviderconnection.h"
#include "qgsogrdbconnection.h"
#include "qgssettings.h"
#include "qgsogrprovider.h"
#include "qgsmessagelog.h"
#include "qgsproviderregistry.h"
// List of GPKG quoted system and dummy tables names to be excluded from the tables listing
static const QStringList excludedTableNames { { QStringLiteral( "\"ogr_empty_table\"" ) } };
QgsGeoPackageProviderConnection::QgsGeoPackageProviderConnection( const QString &name ):
QgsAbstractDatabaseProviderConnection( name )
{
setDefaultCapabilities();
QgsSettings settings;
settings.beginGroup( QStringLiteral( "ogr" ), QgsSettings::Section::Providers );
settings.beginGroup( QStringLiteral( "GPKG" ) );
settings.beginGroup( QStringLiteral( "connections" ) );
settings.beginGroup( name );
setUri( settings.value( QStringLiteral( "path" ) ).toString() );
}
QgsGeoPackageProviderConnection::QgsGeoPackageProviderConnection( const QString &name, const QString &uri ):
QgsAbstractDatabaseProviderConnection( name )
{
setDefaultCapabilities();
setUri( uri );
}
void QgsGeoPackageProviderConnection::store( const QVariantMap &configuration ) const
{
Q_UNUSED( configuration );
QgsSettings settings;
settings.beginGroup( QStringLiteral( "ogr" ), QgsSettings::Section::Providers );
settings.beginGroup( QStringLiteral( "GPKG" ) );
settings.beginGroup( QStringLiteral( "connections" ) );
settings.beginGroup( name() );
settings.setValue( QStringLiteral( "path" ), uri() );
}
void QgsGeoPackageProviderConnection::remove() const
{
QgsSettings settings;
settings.beginGroup( QStringLiteral( "ogr" ), QgsSettings::Section::Providers );
settings.beginGroup( QStringLiteral( "GPKG" ) );
settings.beginGroup( QStringLiteral( "connections" ) );
settings.remove( name() );
}
void QgsGeoPackageProviderConnection::createVectorTable( const QString &schema,
const QString &name,
const QgsFields &fields,
QgsWkbTypes::Type wkbType,
const QgsCoordinateReferenceSystem &srs,
bool overwrite,
const QMap<QString, QVariant> *options ) const
{
checkCapability( Capability::CreateVectorTable );
if ( ! schema.isEmpty() )
{
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by GPKG, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
}
QMap<QString, QVariant> opts { *options };
opts[ QStringLiteral( "layerName" ) ] = QVariant( name );
opts[ QStringLiteral( "update" ) ] = true;
QMap<int, int> map;
QString errCause;
QgsVectorLayerExporter::ExportError errCode = QgsOgrProvider::createEmptyLayer(
uri(),
fields,
wkbType,
srs,
overwrite,
&map,
&errCause,
&opts
);
if ( errCode != QgsVectorLayerExporter::ExportError::NoError )
{
throw QgsProviderConnectionException( QObject::tr( "An error occurred while creating the vector layer: %1" ).arg( errCause ) );
}
}
void QgsGeoPackageProviderConnection::dropVectorTable( const QString &schema, const QString &name ) const
{
checkCapability( Capability::DropVectorTable );
if ( ! schema.isEmpty() )
{
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by GPKG, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
}
QString errCause;
const QString layerUri { QStringLiteral( "%1|layername=%2" ).arg( uri(), name ) };
if ( ! QgsOgrProviderUtils::deleteLayer( layerUri, errCause ) )
{
throw QgsProviderConnectionException( QObject::tr( "Error deleting vector/aspatial table %1: %2" ).arg( name ).arg( errCause ) );
}
}
void QgsGeoPackageProviderConnection::dropRasterTable( const QString &schema, const QString &name ) const
{
checkCapability( Capability::DropRasterTable );
if ( ! schema.isEmpty() )
{
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by GPKG, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
}
executeGdalSqlPrivate( QStringLiteral( "DROP TABLE %1" ).arg( name ) );
}
void QgsGeoPackageProviderConnection::renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const
{
checkCapability( Capability::RenameVectorTable );
if ( ! schema.isEmpty() )
{
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by GPKG, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
}
// TODO: maybe an index?
QString sql( QStringLiteral( "ALTER TABLE %1 RENAME TO %2" )
.arg( QgsSqliteUtils::quotedIdentifier( name ),
QgsSqliteUtils::quotedIdentifier( newName ) ) );
executeGdalSqlPrivate( sql );
sql = QStringLiteral( "UPDATE layer_styles SET f_table_name = %2 WHERE f_table_name = %1" )
.arg( QgsSqliteUtils::quotedString( name ),
QgsSqliteUtils::quotedString( newName ) );
try
{
executeGdalSqlPrivate( sql );
}
catch ( QgsProviderConnectionException &ex )
{
QgsDebugMsgLevel( QStringLiteral( "Warning: error while updating the styles, perhaps there are no styles stored in this GPKG: %1" ).arg( ex.what() ), 4 );
}
}
QList<QList<QVariant>> QgsGeoPackageProviderConnection::executeSql( const QString &sql ) const
{
checkCapability( Capability::ExecuteSql );
return executeGdalSqlPrivate( sql );
}
void QgsGeoPackageProviderConnection::vacuum( const QString &schema, const QString &name ) const
{
Q_UNUSED( name );
checkCapability( Capability::Vacuum );
if ( ! schema.isEmpty() )
{
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by GPKG, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
}
executeGdalSqlPrivate( QStringLiteral( "VACUUM" ) );
}
QList<QgsGeoPackageProviderConnection::TableProperty> QgsGeoPackageProviderConnection::tables( const QString &schema, const TableFlags &flags ) const
{
checkCapability( Capability::Tables );
if ( ! schema.isEmpty() )
{
QgsMessageLog::logMessage( QStringLiteral( "Schema is not supported by GPKG, ignoring" ), QStringLiteral( "OGR" ), Qgis::Info );
}
QList<QgsGeoPackageProviderConnection::TableProperty> tableInfo;
QString errCause;
QList<QVariantList> results;
try
{
const QString sql { QStringLiteral( "SELECT c.table_name, data_type, description, c.srs_id, g.geometry_type_name, g.column_name "
"FROM gpkg_contents c LEFT JOIN gpkg_geometry_columns g ON (c.table_name = g.table_name) "
"WHERE c.table_name NOT IN (%1)" ).arg( excludedTableNames.join( ',' ) ) };
results = executeSql( sql );
for ( const auto &row : qgis::as_const( results ) )
{
if ( row.size() != 6 )
{
throw QgsProviderConnectionException( QObject::tr( "Error listing tables from %1: wrong number of columns returned by query" ).arg( name() ) );
}
QgsGeoPackageProviderConnection::TableProperty property;
property.setTableName( row.at( 0 ).toString() );
property.setPrimaryKeyColumns( { QStringLiteral( "fid" ) } );
property.setGeometryColumnCount( 0 );
static const QStringList aspatialTypes = { QStringLiteral( "attributes" ), QStringLiteral( "aspatial" ) };
const QString dataType = row.at( 1 ).toString();
// Table type
if ( dataType == QStringLiteral( "tiles" ) || dataType == QStringLiteral( "2d-gridded-coverage" ) )
{
property.setFlag( QgsGeoPackageProviderConnection::Raster );
}
else if ( dataType == QStringLiteral( "features" ) )
{
property.setFlag( QgsGeoPackageProviderConnection::Vector );
property.setGeometryColumn( row.at( 5 ).toString() );
property.setGeometryColumnCount( 1 );
}
if ( aspatialTypes.contains( dataType ) )
{
property.setFlag( QgsGeoPackageProviderConnection::Aspatial );
property.addGeometryColumnType( QgsWkbTypes::Type::NoGeometry, QgsCoordinateReferenceSystem() );
}
else
{
bool ok;
int srid = row.at( 3 ).toInt( &ok );
if ( !ok )
{
throw QgsProviderConnectionException( QObject::tr( "Error fetching srs_id table information: %1" ).arg( row.at( 3 ).toString() ) );
}
QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromEpsgId( srid );
property.addGeometryColumnType( QgsWkbTypes::parseType( row.at( 4 ).toString() ), crs );
}
property.setComment( row.at( 4 ).toString() );
tableInfo.push_back( property );
}
}
catch ( QgsProviderConnectionException &ex )
{
errCause = ex.what();
}
if ( ! errCause.isEmpty() )
{
throw QgsProviderConnectionException( QObject::tr( "Error listing tables from %1: %2" ).arg( name() ).arg( errCause ) );
}
// Filters
if ( flags )
{
tableInfo.erase( std::remove_if( tableInfo.begin(), tableInfo.end(), [ & ]( const QgsAbstractDatabaseProviderConnection::TableProperty & ti )
{
return !( ti.flags() & flags );
} ), tableInfo.end() );
}
return tableInfo ;
}
void QgsGeoPackageProviderConnection::setDefaultCapabilities()
{
mCapabilities =
{
Capability::Tables,
Capability::CreateVectorTable,
Capability::DropVectorTable,
Capability::RenameVectorTable,
Capability::Vacuum,
Capability::Spatial,
Capability::TableExists,
Capability::ExecuteSql,
};
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0)
mCapabilities |= Capability::DropRasterTable;
#endif
}
QList<QVariantList> QgsGeoPackageProviderConnection::executeGdalSqlPrivate( const QString &sql ) const
{
QString errCause;
QList<QVariantList> results;
gdal::ogr_datasource_unique_ptr hDS( GDALOpenEx( uri().toUtf8().constData(), GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr, nullptr, nullptr ) );
if ( hDS )
{
OGRLayerH ogrLayer( GDALDatasetExecuteSQL( hDS.get(), sql.toUtf8().constData(), nullptr, nullptr ) );
if ( ogrLayer )
{
gdal::ogr_feature_unique_ptr fet;
QgsFields fields;
while ( fet.reset( OGR_L_GetNextFeature( ogrLayer ) ), fet )
{
QVariantList row;
// Try to get the right type for the returned values
if ( fields.isEmpty() )
{
fields = QgsOgrUtils::readOgrFields( fet.get(), QTextCodec::codecForName( "UTF-8" ) );
}
if ( ! fields.isEmpty() )
{
QgsFeature f { QgsOgrUtils::readOgrFeature( fet.get(), fields, QTextCodec::codecForName( "UTF-8" ) ) };
const QgsAttributes &constAttrs { f.attributes() };
for ( int i = 0; i < constAttrs.length(); i++ )
{
row.push_back( constAttrs.at( i ) );
}
}
else // Fallback to strings
{
for ( int i = 0; i < OGR_F_GetFieldCount( fet.get() ); i++ )
{
row.push_back( QVariant( QString::fromUtf8( OGR_F_GetFieldAsString( fet.get(), i ) ) ) );
}
}
results.push_back( row );
}
GDALDatasetReleaseResultSet( hDS.get(), ogrLayer );
}
errCause = CPLGetLastErrorMsg( );
}
else
{
errCause = QObject::tr( "There was an error opening GPKG %1!" ).arg( uri() );
}
if ( ! errCause.isEmpty() )
{
throw QgsProviderConnectionException( QObject::tr( "Error executing SQL %1: %2" ).arg( sql ).arg( errCause ) );
}
return results;
}

View File

@ -0,0 +1,54 @@
/***************************************************************************
QgsGeoPackageProviderConnection.h - QgsGeoPackageProviderConnection
---------------------
begin : 6.8.2019
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 QGSGEOPACKAGEPROVIDERCONNECTION_H
#define QGSGEOPACKAGEPROVIDERCONNECTION_H
#include "qgsabstractdatabaseproviderconnection.h"
///@cond PRIVATE
#define SIP_NO_FILE
class QgsGeoPackageProviderConnection : public QgsAbstractDatabaseProviderConnection
{
public:
QgsGeoPackageProviderConnection( const QString &name );
QgsGeoPackageProviderConnection( const QString &name, const QString &uri );
// QgsAbstractProviderConnection interface
public:
void store( const QVariantMap &configuration ) const override;
void remove() const override;
void createVectorTable( const QString &schema, const QString &name, const QgsFields &fields, QgsWkbTypes::Type wkbType, const QgsCoordinateReferenceSystem &srs, bool overwrite, const QMap<QString, QVariant> *options ) const override;
void dropVectorTable( const QString &schema, const QString &name ) const override;
void dropRasterTable( const QString &schema, const QString &name ) const override;
void renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const override;
QList<QList<QVariant>> executeSql( const QString &sql ) const override;
void vacuum( const QString &schema, const QString &name ) const override;
QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables( const QString &schema = QString(),
const TableFlags &flags = nullptr ) const override;
private:
void setDefaultCapabilities();
//! Use GDAL to execute SQL
QList<QVariantList> executeGdalSqlPrivate( const QString &sql ) const;
};
///@endcond
#endif // QGSGEOPACKAGEPROVIDERCONNECTION_H

View File

@ -372,7 +372,7 @@ QVector<QgsDataItem *> QgsOgrDataCollectionItem::createChildren()
return children;
}
bool QgsOgrDataCollectionItem::storeConnection( const QString &path, const QString &ogrDriverName )
bool QgsOgrDataCollectionItem::saveConnection( const QString &path, const QString &ogrDriverName )
{
QFileInfo fileInfo( path );
QString connName = fileInfo.fileName();
@ -400,7 +400,7 @@ bool QgsOgrDataCollectionItem::storeConnection( const QString &path, const QStri
bool QgsOgrDataCollectionItem::createConnection( const QString &name, const QString &extensions, const QString &ogrDriverName )
{
QString path = QFileDialog::getOpenFileName( nullptr, tr( "Open %1" ).arg( name ), QString(), extensions );
return storeConnection( path, ogrDriverName );
return saveConnection( path, ogrDriverName );
}
// ---------------------------------------------------------------------------

View File

@ -87,7 +87,7 @@ class CORE_EXPORT QgsOgrDataCollectionItem : public QgsDataCollectionItem
* \param path to the DB
* \param ogrDriverName the OGR/GDAL driver name (e.g. "GPKG")
*/
static bool storeConnection( const QString &path, const QString &ogrDriverName );
static bool saveConnection( const QString &path, const QString &ogrDriverName );
/**
* Utility function to create and store a new DB connection

View File

@ -70,10 +70,10 @@ QString QgsOgrDbConnection::connectionsPath( const QString &settingsKey )
return QStringLiteral( "%1/connections" ).arg( fullKey( settingsKey ) );
}
const QStringList QgsOgrDbConnection::connectionList( const QString &settingsKey )
const QStringList QgsOgrDbConnection::connectionList( const QString &driverName )
{
QgsSettings settings;
settings.beginGroup( connectionsPath( settingsKey ) );
settings.beginGroup( connectionsPath( driverName ) );
return settings.childGroups();
}

View File

@ -37,7 +37,7 @@ class CORE_EXPORT QgsOgrDbConnection : public QObject
//! Constructor
explicit QgsOgrDbConnection( const QString &connName, const QString &settingsKey );
static const QStringList connectionList( const QString &settingsKey );
static const QStringList connectionList( const QString &driverName = QStringLiteral( "GPKG" ) );
static void deleteConnection( const QString &connName, const QString &settingsKey );
static QString selectedConnection( const QString &settingsKey );
static void setSelectedConnection( const QString &connName, const QString &settingsKey );

View File

@ -43,6 +43,8 @@ email : sherman at mrcc.com
#include "qgsgeopackageprojectstorage.h"
#include "qgsprojectstorageregistry.h"
#include "qgsprovidermetadata.h"
#include "qgsogrdbconnection.h"
#include "qgsgeopackageproviderconnection.h"
#include "qgis.h"
@ -251,116 +253,10 @@ QgsVectorLayerExporter::ExportError QgsOgrProviderMetadata::createEmptyLayer( co
QString &errorMessage,
const QMap<QString, QVariant> *options )
{
QString encoding;
QString driverName = QStringLiteral( "GPKG" );
QStringList dsOptions, layerOptions;
QString layerName;
if ( options )
{
if ( options->contains( QStringLiteral( "fileEncoding" ) ) )
encoding = options->value( QStringLiteral( "fileEncoding" ) ).toString();
if ( options->contains( QStringLiteral( "driverName" ) ) )
driverName = options->value( QStringLiteral( "driverName" ) ).toString();
if ( options->contains( QStringLiteral( "datasourceOptions" ) ) )
dsOptions << options->value( QStringLiteral( "datasourceOptions" ) ).toStringList();
if ( options->contains( QStringLiteral( "layerOptions" ) ) )
layerOptions << options->value( QStringLiteral( "layerOptions" ) ).toStringList();
if ( options->contains( QStringLiteral( "layerName" ) ) )
layerName = options->value( QStringLiteral( "layerName" ) ).toString();
}
oldToNewAttrIdxMap.clear();
errorMessage.clear();
QgsVectorFileWriter::ActionOnExistingFile action( QgsVectorFileWriter::CreateOrOverwriteFile );
bool update = false;
if ( options && options->contains( QStringLiteral( "update" ) ) )
{
update = options->value( QStringLiteral( "update" ) ).toBool();
if ( update )
{
if ( !overwrite && !layerName.isEmpty() )
{
gdal::dataset_unique_ptr hDS( GDALOpenEx( uri.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
if ( hDS )
{
if ( GDALDatasetGetLayerByName( hDS.get(), layerName.toUtf8().constData() ) )
{
errorMessage += QObject::tr( "Layer %2 of %1 exists and overwrite flag is false." )
.arg( uri, layerName );
return QgsVectorLayerExporter::ErrCreateDataSource;
}
}
}
if ( QFileInfo::exists( uri ) )
action = QgsVectorFileWriter::CreateOrOverwriteLayer;
}
}
if ( !overwrite && !update )
{
if ( QFileInfo::exists( uri ) )
{
errorMessage += QObject::tr( "Unable to create the datasource. %1 exists and overwrite flag is false." )
.arg( uri );
return QgsVectorLayerExporter::ErrCreateDataSource;
}
}
QString newLayerName( layerName );
std::unique_ptr< QgsVectorFileWriter > writer = qgis::make_unique< QgsVectorFileWriter >(
uri, encoding, fields, wkbType,
srs, driverName, dsOptions, layerOptions, nullptr,
QgsVectorFileWriter::NoSymbology, nullptr,
layerName, action, &newLayerName );
layerName = newLayerName;
QgsVectorFileWriter::WriterError error = writer->hasError();
if ( error )
{
errorMessage += writer->errorMessage();
return ( QgsVectorLayerExporter::ExportError ) error;
}
QMap<int, int> attrIdxMap = writer->attrIdxToOgrIdx();
writer.reset();
{
bool firstFieldIsFid = false;
if ( !layerName.isEmpty() )
{
gdal::dataset_unique_ptr hDS( GDALOpenEx( uri.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
if ( hDS )
{
OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS.get(), layerName.toUtf8().constData() );
if ( hLayer )
{
// Expose the OGR FID if it comes from a "real" column (typically GPKG)
// and make sure that this FID column is not exposed as a regular OGR field (shouldn't happen normally)
firstFieldIsFid = !( EQUAL( OGR_L_GetFIDColumn( hLayer ), "" ) ) &&
OGR_FD_GetFieldIndex( OGR_L_GetLayerDefn( hLayer ), OGR_L_GetFIDColumn( hLayer ) ) < 0 &&
fields.indexFromName( OGR_L_GetFIDColumn( hLayer ) ) < 0;
}
}
}
for ( QMap<int, int>::const_iterator attrIt = attrIdxMap.constBegin(); attrIt != attrIdxMap.constEnd(); ++attrIt )
{
oldToNewAttrIdxMap.insert( attrIt.key(), *attrIt + ( firstFieldIsFid ? 1 : 0 ) );
}
}
QgsOgrProviderUtils::invalidateCachedLastModifiedDate( uri );
return QgsVectorLayerExporter::NoError;
return QgsOgrProvider::createEmptyLayer(
uri, fields, wkbType, srs, overwrite,
&oldToNewAttrIdxMap, &errorMessage, options
);
}
static QString AnalyzeURI( QString const &uri,
@ -439,6 +335,131 @@ static QString AnalyzeURI( QString const &uri,
}
QgsVectorLayerExporter::ExportError QgsOgrProvider::createEmptyLayer( const QString &uri,
const QgsFields &fields,
QgsWkbTypes::Type wkbType,
const QgsCoordinateReferenceSystem &srs,
bool overwrite,
QMap<int, int> *oldToNewAttrIdxMap,
QString *errorMessage,
const QMap<QString, QVariant> *options )
{
QString encoding;
QString driverName = QStringLiteral( "GPKG" );
QStringList dsOptions, layerOptions;
QString layerName;
if ( options )
{
if ( options->contains( QStringLiteral( "fileEncoding" ) ) )
encoding = options->value( QStringLiteral( "fileEncoding" ) ).toString();
if ( options->contains( QStringLiteral( "driverName" ) ) )
driverName = options->value( QStringLiteral( "driverName" ) ).toString();
if ( options->contains( QStringLiteral( "datasourceOptions" ) ) )
dsOptions << options->value( QStringLiteral( "datasourceOptions" ) ).toStringList();
if ( options->contains( QStringLiteral( "layerOptions" ) ) )
layerOptions << options->value( QStringLiteral( "layerOptions" ) ).toStringList();
if ( options->contains( QStringLiteral( "layerName" ) ) )
layerName = options->value( QStringLiteral( "layerName" ) ).toString();
}
oldToNewAttrIdxMap->clear();
if ( errorMessage )
errorMessage->clear();
QgsVectorFileWriter::ActionOnExistingFile action( QgsVectorFileWriter::CreateOrOverwriteFile );
bool update = false;
if ( options && options->contains( QStringLiteral( "update" ) ) )
{
update = options->value( QStringLiteral( "update" ) ).toBool();
if ( update )
{
if ( !overwrite && !layerName.isEmpty() )
{
gdal::dataset_unique_ptr hDS( GDALOpenEx( uri.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
if ( hDS )
{
if ( GDALDatasetGetLayerByName( hDS.get(), layerName.toUtf8().constData() ) )
{
if ( errorMessage )
*errorMessage += QObject::tr( "Layer %2 of %1 exists and overwrite flag is false." )
.arg( uri, layerName );
return QgsVectorLayerExporter::ErrCreateDataSource;
}
}
}
if ( QFileInfo::exists( uri ) )
action = QgsVectorFileWriter::CreateOrOverwriteLayer;
}
}
if ( !overwrite && !update )
{
if ( QFileInfo::exists( uri ) )
{
if ( errorMessage )
*errorMessage += QObject::tr( "Unable to create the datasource. %1 exists and overwrite flag is false." )
.arg( uri );
return QgsVectorLayerExporter::ErrCreateDataSource;
}
}
QString newLayerName( layerName );
std::unique_ptr< QgsVectorFileWriter > writer = qgis::make_unique< QgsVectorFileWriter >(
uri, encoding, fields, wkbType,
srs, driverName, dsOptions, layerOptions, nullptr,
QgsVectorFileWriter::NoSymbology, nullptr,
layerName, action, &newLayerName );
layerName = newLayerName;
QgsVectorFileWriter::WriterError error = writer->hasError();
if ( error )
{
if ( errorMessage )
*errorMessage += writer->errorMessage();
return static_cast<QgsVectorLayerExporter::ExportError>( error );
}
QMap<int, int> attrIdxMap = writer->attrIdxToOgrIdx();
writer.reset();
{
bool firstFieldIsFid = false;
if ( !layerName.isEmpty() )
{
gdal::dataset_unique_ptr hDS( GDALOpenEx( uri.toUtf8().constData(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
if ( hDS )
{
OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS.get(), layerName.toUtf8().constData() );
if ( hLayer )
{
// Expose the OGR FID if it comes from a "real" column (typically GPKG)
// and make sure that this FID column is not exposed as a regular OGR field (shouldn't happen normally)
firstFieldIsFid = !( EQUAL( OGR_L_GetFIDColumn( hLayer ), "" ) ) &&
OGR_FD_GetFieldIndex( OGR_L_GetLayerDefn( hLayer ), OGR_L_GetFIDColumn( hLayer ) ) < 0 &&
fields.indexFromName( OGR_L_GetFIDColumn( hLayer ) ) < 0;
}
}
}
for ( QMap<int, int>::const_iterator attrIt = attrIdxMap.constBegin(); attrIt != attrIdxMap.constEnd(); ++attrIt )
{
oldToNewAttrIdxMap->insert( attrIt.key(), *attrIt + ( firstFieldIsFid ? 1 : 0 ) );
}
}
QgsOgrProviderUtils::invalidateCachedLastModifiedDate( uri );
return QgsVectorLayerExporter::NoError;
}
QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &options )
: QgsVectorDataProvider( uri, options )
{
@ -6646,4 +6667,31 @@ QString QgsOgrProviderMetadata::filters( FilterType type )
}
}
QMap<QString, QgsAbstractProviderConnection *> QgsOgrProviderMetadata::connections( bool cached )
{
return connectionsProtected<QgsGeoPackageProviderConnection, QgsOgrDbConnection>( cached );
}
QgsAbstractProviderConnection *QgsOgrProviderMetadata::createConnection( const QString &connName )
{
return new QgsGeoPackageProviderConnection( connName );
}
QgsAbstractProviderConnection *QgsOgrProviderMetadata::createConnection( const QString &connName, const QString &uri )
{
return new QgsGeoPackageProviderConnection( connName, uri );
}
void QgsOgrProviderMetadata::deleteConnection( const QString &name )
{
deleteConnectionProtected<QgsGeoPackageProviderConnection>( name );
}
void QgsOgrProviderMetadata::saveConnection( QgsAbstractProviderConnection *conn, const QVariantMap &configuration )
{
saveConnectionProtected( conn, configuration );
}
///@endcond

View File

@ -77,7 +77,7 @@ class QgsOgrProvider : public QgsVectorDataProvider
bool overwrite,
QMap<int, int> *oldToNewAttrIdxMap,
QString *errorMessage = nullptr,
const QMap<QString, QVariant> *coordinateTransformContext = nullptr
const QMap<QString, QVariant> *options = nullptr
);
/**
@ -743,7 +743,9 @@ class QgsOgrLayer
class QgsOgrProviderMetadata: public QgsProviderMetadata
{
public:
QgsOgrProviderMetadata();
void initProvider() override;
void cleanupProvider() override;
QList< QgsDataItemProvider * > dataItemProviders() const override;
@ -772,6 +774,15 @@ class QgsOgrProviderMetadata: public QgsProviderMetadata
// -----
QgsTransaction *createTransaction( const QString &connString ) override;
// QgsProviderMetadata interface
public:
QMap<QString, QgsAbstractProviderConnection *> connections( bool cached ) override;
QgsAbstractProviderConnection *createConnection( const QString &name ) override;
QgsAbstractProviderConnection *createConnection( const QString &name, const QString &uri ) override;
void deleteConnection( const QString &name ) override;
void saveConnection( QgsAbstractProviderConnection *createConnection, const QVariantMap &configuration ) override;
};
///@endcond

View File

@ -0,0 +1,302 @@
/***************************************************************************
qgsabstractdatabaseproviderconnection.cpp - QgsAbstractDatabaseProviderConnection
---------------------
begin : 2.8.2019
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 "qgsabstractdatabaseproviderconnection.h"
#include "qgsexception.h"
#include <QVariant>
#include <QObject>
QgsAbstractDatabaseProviderConnection::QgsAbstractDatabaseProviderConnection( const QString &name ):
QgsAbstractProviderConnection( name )
{
}
QgsAbstractDatabaseProviderConnection::QgsAbstractDatabaseProviderConnection( const QString &name, const QString &uri ):
QgsAbstractProviderConnection( name, uri )
{
}
QgsAbstractDatabaseProviderConnection::Capabilities QgsAbstractDatabaseProviderConnection::capabilities() const
{
return mCapabilities;
}
///@cond PRIVATE
void QgsAbstractDatabaseProviderConnection::checkCapability( QgsAbstractDatabaseProviderConnection::Capability capability ) const
{
if ( ! mCapabilities & capability )
{
static QMetaEnum metaEnum = QMetaEnum::fromType<QgsAbstractDatabaseProviderConnection::Capability>();
const QString capName { metaEnum.valueToKey( capability ) };
throw QgsProviderConnectionException( QObject::tr( "Operation '%1' is not supported for this connection" ).arg( capName ) );
}
}
///@endcond
void QgsAbstractDatabaseProviderConnection::createVectorTable( const QString &schema,
const QString &name,
const QgsFields &fields,
QgsWkbTypes::Type wkbType,
const QgsCoordinateReferenceSystem &srs,
bool overwrite,
const QMap<QString, QVariant> *
options ) const
{
Q_UNUSED( schema );
Q_UNUSED( name );
Q_UNUSED( fields );
Q_UNUSED( srs );
Q_UNUSED( overwrite );
Q_UNUSED( options );
Q_UNUSED( wkbType );
throw QgsProviderConnectionException( QObject::tr( "Operation 'createVectorTable' is not supported" ) );
}
void QgsAbstractDatabaseProviderConnection::renameVectorTable( const QString &, const QString &, const QString & ) const
{
checkCapability( Capability::RenameVectorTable );
}
void QgsAbstractDatabaseProviderConnection::renameRasterTable( const QString &, const QString &, const QString & ) const
{
checkCapability( Capability::RenameRasterTable );
}
void QgsAbstractDatabaseProviderConnection::dropVectorTable( const QString &, const QString & ) const
{
checkCapability( Capability::DropVectorTable );
}
bool QgsAbstractDatabaseProviderConnection::tableExists( const QString &schema, const QString &name ) const
{
checkCapability( Capability::TableExists );
const QList<QgsAbstractDatabaseProviderConnection::TableProperty> constTables { tables( schema ) };
for ( const auto &t : constTables )
{
if ( t.tableName() == name )
{
return true;
}
}
return false;
}
void QgsAbstractDatabaseProviderConnection::dropRasterTable( const QString &, const QString & ) const
{
checkCapability( Capability::DropRasterTable );
}
void QgsAbstractDatabaseProviderConnection::createSchema( const QString & ) const
{
checkCapability( Capability::CreateSchema );
}
void QgsAbstractDatabaseProviderConnection::dropSchema( const QString &, bool ) const
{
checkCapability( Capability::DropSchema );
}
void QgsAbstractDatabaseProviderConnection::renameSchema( const QString &, const QString & ) const
{
checkCapability( Capability::RenameSchema );
}
QList<QList<QVariant>> QgsAbstractDatabaseProviderConnection::executeSql( const QString & ) const
{
checkCapability( Capability::ExecuteSql );
return QList<QList<QVariant>>();
}
void QgsAbstractDatabaseProviderConnection::vacuum( const QString &, const QString & ) const
{
checkCapability( Capability::Vacuum );
}
QList<QgsAbstractDatabaseProviderConnection::TableProperty> QgsAbstractDatabaseProviderConnection::tables( const QString &, const QgsAbstractDatabaseProviderConnection::TableFlags & ) const
{
checkCapability( Capability::Tables );
return QList<QgsAbstractDatabaseProviderConnection::TableProperty>();
}
QList<QgsAbstractDatabaseProviderConnection::TableProperty> QgsAbstractDatabaseProviderConnection::tablesInt( const QString &schema, const int flags ) const
{
return tables( schema, static_cast<QgsAbstractDatabaseProviderConnection::TableFlags>( flags ) );
}
QStringList QgsAbstractDatabaseProviderConnection::schemas( ) const
{
checkCapability( Capability::Schemas );
return QStringList();
}
QString QgsAbstractDatabaseProviderConnection::TableProperty::tableName() const
{
return mTableName;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setTableName( const QString &name )
{
mTableName = name;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::addGeometryColumnType( const QgsWkbTypes::Type &type, const QgsCoordinateReferenceSystem &crs )
{
// Do not add the type if it's already present
const QgsAbstractDatabaseProviderConnection::TableProperty::GeometryColumnType toAdd { type, crs };
for ( const auto &t : qgis::as_const( mGeometryColumnTypes ) )
{
if ( t == toAdd )
{
return;
}
}
mGeometryColumnTypes.push_back( toAdd );
}
QList<QgsAbstractDatabaseProviderConnection::TableProperty::GeometryColumnType> QgsAbstractDatabaseProviderConnection::TableProperty::geometryColumnTypes() const
{
return mGeometryColumnTypes;
}
QString QgsAbstractDatabaseProviderConnection::TableProperty::defaultName() const
{
QString n = mTableName;
if ( mGeometryColumnCount > 1 ) n += '.' + mGeometryColumn;
return n;
}
QgsAbstractDatabaseProviderConnection::TableProperty QgsAbstractDatabaseProviderConnection::TableProperty::at( int index ) const
{
TableProperty property;
Q_ASSERT( index >= 0 && index < mGeometryColumnTypes.size() );
property.mGeometryColumnTypes << mGeometryColumnTypes[ index ];
property.mSchema = mSchema;
property.mTableName = mTableName;
property.mGeometryColumn = mGeometryColumn;
property.mPkColumns = mPkColumns;
property.mGeometryColumnCount = mGeometryColumnCount;
property.mFlags = mFlags;
property.mComment = mComment;
property.mInfo = mInfo;
return property;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setFlag( const QgsAbstractDatabaseProviderConnection::TableFlag &flag )
{
mFlags.setFlag( flag );
}
int QgsAbstractDatabaseProviderConnection::TableProperty::maxCoordinateDimensions() const
{
int res = 0;
for ( const TableProperty::GeometryColumnType &ct : qgis::as_const( mGeometryColumnTypes ) )
{
res = std::max( res, QgsWkbTypes::coordDimensions( ct.wkbType ) );
}
return res;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setGeometryColumnTypes( const QList<QgsAbstractDatabaseProviderConnection::TableProperty::GeometryColumnType> &columnTypes )
{
mGeometryColumnTypes = columnTypes;
}
int QgsAbstractDatabaseProviderConnection::TableProperty::geometryColumnCount() const
{
return mGeometryColumnCount;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setGeometryColumnCount( int geometryColumnCount )
{
mGeometryColumnCount = geometryColumnCount;
}
QVariantMap QgsAbstractDatabaseProviderConnection::TableProperty::info() const
{
return mInfo;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setInfo( const QVariantMap &info )
{
mInfo = info;
}
QString QgsAbstractDatabaseProviderConnection::TableProperty::comment() const
{
return mComment;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setComment( const QString &comment )
{
mComment = comment;
}
QgsAbstractDatabaseProviderConnection::TableFlags QgsAbstractDatabaseProviderConnection::TableProperty::flags() const
{
return mFlags;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setFlags( const QgsAbstractDatabaseProviderConnection::TableFlags &flags )
{
mFlags = flags;
}
QList<QgsCoordinateReferenceSystem> QgsAbstractDatabaseProviderConnection::TableProperty::crsList() const
{
QList<QgsCoordinateReferenceSystem> crss;
for ( const auto &t : qgis::as_const( mGeometryColumnTypes ) )
{
crss.push_back( t.crs );
}
return crss;
}
QStringList QgsAbstractDatabaseProviderConnection::TableProperty::primaryKeyColumns() const
{
return mPkColumns;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setPrimaryKeyColumns( const QStringList &pkColumns )
{
mPkColumns = pkColumns;
}
QString QgsAbstractDatabaseProviderConnection::TableProperty::geometryColumn() const
{
return mGeometryColumn;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setGeometryColumn( const QString &geometryColumn )
{
mGeometryColumn = geometryColumn;
}
QString QgsAbstractDatabaseProviderConnection::TableProperty::schema() const
{
return mSchema;
}
void QgsAbstractDatabaseProviderConnection::TableProperty::setSchema( const QString &schema )
{
mSchema = schema;
}

View File

@ -0,0 +1,450 @@
/***************************************************************************
qgsabstractdatabaseproviderconnection.h - QgsAbstractDatabaseProviderConnection
---------------------
begin : 2.8.2019
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 QGSABSTRACTDATABASEPROVIDERCONNECTION_H
#define QGSABSTRACTDATABASEPROVIDERCONNECTION_H
#include "qgsabstractproviderconnection.h"
#include "qgscoordinatereferencesystem.h"
#include "qgis_core.h"
#include "qgsfields.h"
#include "qgsexception.h"
#include <QObject>
/**
* The QgsAbstractDatabaseProviderConnection class provides common functionality
* for DB based connections.
*
* This class performs low level DB operations without asking
* the user for confirmation or handling currently opened layers and the registry
* entries, it is responsibility of the client code to keep layers in sync.
* The class methods will throw exceptions in case the requested operation
* is not supported or cannot be performed without errors.
*
* \ingroup core
* \since QGIS 3.10
*/
class CORE_EXPORT QgsAbstractDatabaseProviderConnection : public QgsAbstractProviderConnection
{
Q_GADGET
public:
/**
* Flags for table properties.
*
* Flags can be useful for filtering the tables returned
* from tables().
*/
enum TableFlag
{
Aspatial = 1 << 1, //!< Aspatial table (it does not contain any geometry column)
Vector = 1 << 2, //!< Vector table (it does contain one geometry column)
Raster = 1 << 3, //!< Raster table
View = 1 << 4, //!< View table
MaterializedView = 1 << 5, //!< Materialized view table
};
Q_ENUMS( TableFlag )
Q_DECLARE_FLAGS( TableFlags, TableFlag )
Q_FLAG( TableFlags )
/**
* The TableProperty class represents a database table or view.
*
* In case the table is a vector spatial table and it has multiple
* geometry columns, separate entries for each geometry column must
* be created.
*
* In case the table is a vector spatial table and the geometry column
* can contain multiple geometry types and/or CRSs, a clone of the property
* for the individual geometry type/CRS can be retrieved with at(i)
*/
struct TableProperty
{
#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode
QString str = QStringLiteral( "<QgsAbstractDatabaseProviderConnection.TableProperty: '%1'>" ).arg( sipCpp->tableName() );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
#endif
/**
* The GeometryColumnType struct represents the combination
* of geometry type and CRS for the table geometry column.
*/
struct GeometryColumnType
{
#ifdef SIP_RUN
SIP_PYOBJECT __repr__();
% MethodCode
QString str = QStringLiteral( "<QgsAbstractDatabaseProviderConnection.TableProperty.GeometryColumnType: '%1, %2'>" ).arg( QgsWkbTypes::displayString( sipCpp->wkbType ), sipCpp->crs.authid() );
sipRes = PyUnicode_FromString( str.toUtf8().constData() );
% End
#endif
QgsWkbTypes::Type wkbType;
QgsCoordinateReferenceSystem crs;
inline bool operator==( const GeometryColumnType &other ) const
{
return this->crs == other.crs && this->wkbType == other.wkbType;
}
};
public:
/**
* Returns the table name
* \see defaultName()
*/
QString tableName() const;
/**
* Sets the table name to \a name
* \see defaultName()
*/
void setTableName( const QString &name );
/**
* Appends the geometry column \a type with the given \a srid to the geometry column types list
*/
void addGeometryColumnType( const QgsWkbTypes::Type &type, const QgsCoordinateReferenceSystem &crs );
/**
* Returns the list of geometry column types and CRSs.
* The method returns a list of GeometryColumnType
*/
QList<QgsAbstractDatabaseProviderConnection::TableProperty::GeometryColumnType> geometryColumnTypes() const;
/**
* Sets the geometry column types to \a geometryColumnTypes
*/
void setGeometryColumnTypes( const QList<QgsAbstractDatabaseProviderConnection::TableProperty::GeometryColumnType> &geometryColumnTypes );
/**
* Returns the default name for the table entry
*
* It is usually the table name but in case there are multiple geometry
* columns, the geometry column name is appendend to the table name.
* \see geometryColumnCount()
*/
QString defaultName() const;
/**
* Returns the table property corresponding to the geometry type at
* the given \a index
*/
TableProperty at( int index ) const;
/**
* Returns the schema or an empty string for backends that do not support a schema
*/
QString schema() const;
/**
* Sets the \a schema
*/
void setSchema( const QString &schema );
/**
* Returns the geometry column name
*/
QString geometryColumn() const;
/**
* Sets the geometry column name to \a geometryColumn
*/
void setGeometryColumn( const QString &geometryColumn );
/**
* Returns the list of primary key column names
*/
QStringList primaryKeyColumns() const;
/**
* Sets the primary key column names to \a primaryKeyColumns
*/
void setPrimaryKeyColumns( const QStringList &primaryKeyColumns );
/**
* Returns the list of CRSs supported by the geometry column
*/
QList<QgsCoordinateReferenceSystem> crsList() const;
/**
* Returns the table flags
*/
TableFlags flags() const;
/**
* Sets the table \a flags
*/
void setFlags( const TableFlags &flags );
/**
* Returns the table comment
*/
QString comment() const;
/**
* Sets the table \a comment
*/
void setComment( const QString &comment );
/**
* Returns additional information about the table
*
* Provider classes may use this property
* to store custom bits of information.
*/
QVariantMap info() const;
/**
* Sets additional information about the table to \a info
*
* Provider classes may use this property
* to store custom bits of information.
*/
void setInfo( const QVariantMap &info );
/**
* Returns the number of geometry columns in the original table this entry refers to
*
* This information is used internally to build the \see defaultName()
*/
int geometryColumnCount() const;
/**
* Sets the \a geometryColumnCount
*/
void setGeometryColumnCount( int geometryColumnCount );
/**
* Sets a \a flag
*/
void setFlag( const TableFlag &flag );
/**
* Returns the maximum coordinate dimensions of the geometries of a vector table.
* This information is calculated from the geometry columns types.
* \see geometryColumnTypes()
*/
int maxCoordinateDimensions() const;
private:
//! Holds the list of geometry wkb types and srids supported by the table
QList<GeometryColumnType> mGeometryColumnTypes;
//! Table schema
QString mSchema;
//! Table name
QString mTableName;
//! Name of the geometry column
QString mGeometryColumn;
//! The number of geometry columns in the table
int mGeometryColumnCount;
//! PK columns
QStringList mPkColumns;
TableFlags mFlags;
QString mComment;
//! Additional unstructured information about the table
QVariantMap mInfo;
};
/**
* The Capability enum represent the operations supported by the connection
*/
enum Capability
{
CreateVectorTable = 1 << 1, //!< Can CREATE a vector (or aspatial) table/layer
DropRasterTable = 1 << 2, //!< Can DROP a raster table/layer
DropVectorTable = 1 << 3, //!< Can DROP a vector (or aspatial) table/layer
RenameVectorTable = 1 << 4, //!< Can RENAME a vector (or aspatial) table/layer
RenameRasterTable = 1 << 5, //!< Can RENAME a raster table/layer
CreateSchema = 1 << 6, //!< Can CREATE a schema
DropSchema = 1 << 7, //!< Can DROP a schema
RenameSchema = 1 << 8, //!< Can RENAME a schema
ExecuteSql = 1 << 9, //!< Can execute raw SQL queries (without returning results)
Vacuum = 1 << 10, //!< Can run vacuum
Tables = 1 << 11, //!< Can list tables
Schemas = 1 << 12, //!< Can list schemas (if not set, the connection does not support schemas)
SqlLayers = 1 << 13, //!< Can create vector layers from SQL SELECT queries
TableExists = 1 << 14, //!< Can check if table exists
Spatial = 1 << 15, //!< The connection supports spatial tables
};
Q_ENUM( Capability )
Q_DECLARE_FLAGS( Capabilities, Capability )
Q_FLAG( Capabilities )
/**
* Creates a new connection with \a name by reading its configuration from the settings.
* If a connection with this name cannot be found, an empty connection will be returned.
*/
QgsAbstractDatabaseProviderConnection( const QString &name );
/**
* Creates a new connection with \a name and initializes the connection from the \a uri.
* The connection is not automatically stored in the settings.
* \see store()
*/
QgsAbstractDatabaseProviderConnection( const QString &name, const QString &uri );
// Public interface
/**
* Returns connection capabilities
*/
Capabilities capabilities() const;
// Operations interface
/**
* Creates an empty table with \a name in the given \a schema (schema is ignored if not supported by the backend).
* Raises a QgsProviderConnectionException if any errors are encountered.
* \throws QgsProviderConnectionException
*/
virtual void createVectorTable( const QString &schema, const QString &name, const QgsFields &fields, QgsWkbTypes::Type wkbType, const QgsCoordinateReferenceSystem &srs, bool overwrite, const QMap<QString, QVariant> *options ) const SIP_THROW( QgsProviderConnectionException );
/**
* Checks whether a table \a name exists in the given \a schema.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \throws QgsProviderConnectionException
*/
virtual bool tableExists( const QString &schema, const QString &name ) const SIP_THROW( QgsProviderConnectionException );
/**
* Drops a vector (or aspatial) table with given \a schema (schema is ignored if not supported by the backend) and \a name.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \note it is responsibility of the caller to handle open layers and registry entries.
* \throws QgsProviderConnectionException
*/
virtual void dropVectorTable( const QString &schema, const QString &name ) const SIP_THROW( QgsProviderConnectionException );
/**
* Drops a raster table with given \a schema (schema is ignored if not supported by the backend) and \a name.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \note it is responsibility of the caller to handle open layers and registry entries.
* \throws QgsProviderConnectionException
*/
virtual void dropRasterTable( const QString &schema, const QString &name ) const SIP_THROW( QgsProviderConnectionException );
/**
* Renames a vector or aspatial table with given \a schema (schema is ignored if not supported by the backend) and \a name.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \note it is responsibility of the caller to handle open layers and registry entries.
* \throws QgsProviderConnectionException
*/
virtual void renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const SIP_THROW( QgsProviderConnectionException );
/**
* Renames a raster table with given \a schema (schema is ignored if not supported by the backend) and \a name.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \note it is responsibility of the caller to handle open layers and registry entries.
* \throws QgsProviderConnectionException
*/
virtual void renameRasterTable( const QString &schema, const QString &name, const QString &newName ) const SIP_THROW( QgsProviderConnectionException );
/**
* Creates a new schema with the specified \a name
* \throws QgsProviderConnectionException
*/
virtual void createSchema( const QString &name ) const SIP_THROW( QgsProviderConnectionException );
/**
* Drops an entire schema with the specified name.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \param name name of the schema to be dropped
* \param force if TRUE, a DROP CASCADE will drop all related objects
* \note it is responsibility of the caller to handle open layers and registry entries.
* \throws QgsProviderConnectionException
*/
virtual void dropSchema( const QString &name, bool force = false ) const SIP_THROW( QgsProviderConnectionException );
/**
* Renames a schema with the specified \a name.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \note it is responsibility of the caller to handle open layers and registry entries.
* \throws QgsProviderConnectionException
*/
virtual void renameSchema( const QString &name, const QString &newName ) const SIP_THROW( QgsProviderConnectionException );
/**
* Executes raw \a sql and returns the (possibly empty) list of results in a multi-dimensional array.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \throws QgsProviderConnectionException
*/
virtual QList<QList<QVariant>> executeSql( const QString &sql ) const SIP_THROW( QgsProviderConnectionException );
/**
* Vacuum the database table with given \a schema and \a name (schema is ignored if not supported by the backend).
* Raises a QgsProviderConnectionException if any errors are encountered.
* \throws QgsProviderConnectionException
*/
virtual void vacuum( const QString &schema, const QString &name ) const SIP_THROW( QgsProviderConnectionException );
/**
* Returns information on the tables in the given schema .
* Raises a QgsProviderConnectionException if any errors are encountered.
* \param schema name of the schema (ignored if not supported by the backend)
* \param flags filter tables by flags, this option completely overrides search options stored in the connection
* \throws QgsProviderConnectionException
* \note Not available in Python bindings
*/
virtual QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables( const QString &schema = QString(), const QgsAbstractDatabaseProviderConnection::TableFlags &flags = nullptr ) const SIP_SKIP;
/**
* Returns information on the tables in the given schema.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \param schema name of the schema (ignored if not supported by the backend)
* \param flags filter tables by flags, this option completely overrides search options stored in the connection
* \throws QgsProviderConnectionException
*/
QList<QgsAbstractDatabaseProviderConnection::TableProperty> tablesInt( const QString &schema = QString(), const int flags = 0 ) const SIP_THROW( QgsProviderConnectionException ) SIP_PYNAME( tables );
// TODO: return more schema information and not just the name
/**
* Returns information about the existing schemas.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \throws QgsProviderConnectionException
*/
virtual QStringList schemas( ) const SIP_THROW( QgsProviderConnectionException );
protected:
///@cond PRIVATE
/**
* Checks if \a capability is supported and throws and exception if it's not
* \throws QgsProviderConnectionException
*/
void checkCapability( Capability capability ) const;
///@endcond
Capabilities mCapabilities = nullptr SIP_SKIP;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsAbstractDatabaseProviderConnection::Capabilities )
#endif // QGSABSTRACTDATABASEPROVIDERCONNECTION_H

View File

@ -0,0 +1,46 @@
/***************************************************************************
qgsabstractproviderconnection.cpp - QgsAbstractProviderConnection
---------------------
begin : 2.8.2019
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 "qgsabstractproviderconnection.h"
QgsAbstractProviderConnection::QgsAbstractProviderConnection( const QString &name )
: mConnectionName( name )
{
// Note: concrete classes must implement the logic to read the configuration from the settings
// and create mUri
}
QgsAbstractProviderConnection::QgsAbstractProviderConnection( const QString &name, const QString &uri )
: mConnectionName( name )
, mUri( uri )
{
}
QString QgsAbstractProviderConnection::name() const
{
return mConnectionName;
}
QString QgsAbstractProviderConnection::uri() const
{
return mUri;
}
void QgsAbstractProviderConnection::setUri( const QString &uri )
{
mUri = uri;
}

View File

@ -0,0 +1,110 @@
/***************************************************************************
qgsabstractproviderconnection.h - QgsAbstractProviderConnection
---------------------
begin : 2.8.2019
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 QGSABSTRACTPROVIDERCONNECTION_H
#define QGSABSTRACTPROVIDERCONNECTION_H
#include <QString>
#include <QVariantMap>
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgsdatasourceuri.h"
#include "qgsexception.h"
/**
* The QgsAbstractProviderConnection provides an interface for data provider connections.
*
* Connections objects can be created by passing the connection name and in this case
* they are automatically loaded from the settings, or by passing a data source URI
* in the constructor.
*
* Concrete classes must implement methods to retrieve, save and remove connections from
* the settings.
* \ingroup core
* \since QGIS 3.10
*/
class CORE_EXPORT QgsAbstractProviderConnection
{
#ifdef SIP_RUN
SIP_CONVERT_TO_SUBCLASS_CODE
if ( dynamic_cast<QgsAbstractDatabaseProviderConnection *>( sipCpp ) != NULL )
{
sipType = sipType_QgsAbstractDatabaseProviderConnection;
}
else if ( dynamic_cast<QgsAbstractProviderConnection *>( sipCpp ) != NULL )
{
sipType = sipType_QgsAbstractProviderConnection;
}
else
{
sipType = 0;
}
SIP_END
#endif
public:
/**
* Creates a new connection with \a name by reading its configuration from the settings.
* If a connection with this name cannot be found, an empty connection will be returned.
*/
QgsAbstractProviderConnection( const QString &name );
/**
* Creates a new connection with \a name and initializes the connection from the \a uri.
* The connection is not automatically stored in the settings.
* \see store()
*/
QgsAbstractProviderConnection( const QString &name, const QString &uri );
virtual ~QgsAbstractProviderConnection() = default;
/**
* Stores the connection in the settings.
* \param configuration stores additional connection settings that are used by the
* source select dialog and are not part of the data source URI
*/
virtual void store( const QVariantMap &configuration = QVariantMap() ) const = 0;
/**
* Deletes the connection from the settings.
*/
virtual void remove( ) const = 0;
/**
* Returns the connection name
*/
QString name() const;
/**
* Returns the connection data source URI string representation
*/
QString uri() const;
/**
* Sets the connection data source URI to \a uri
*/
void setUri( const QString &uri );
private:
QString mConnectionName;
QString mUri;
};
#endif // QGSABSTRACTPROVIDERCONNECTION_H

View File

@ -385,6 +385,16 @@ QString QgsDataSourceUri::escape( const QString &val, QChar delim = '\'' ) const
return escaped;
}
void QgsDataSourceUri::setGeometryColumn( const QString &geometryColumn )
{
mGeometryColumn = geometryColumn;
}
void QgsDataSourceUri::setTable( const QString &table )
{
mTable = table;
}
void QgsDataSourceUri::skipBlanks( const QString &uri, int &i )
{
// skip space before value

View File

@ -289,6 +289,18 @@ class CORE_EXPORT QgsDataSourceUri
*/
static QString encodeSslMode( SslMode sslMode );
/**
* Sets table to \a table
* \since QGIS 3.10
*/
void setTable( const QString &table );
/**
* Sets geometry column name to \a geometryColumn
* \since QGIS 3.10
*/
void setGeometryColumn( const QString &geometryColumn );
private:
void skipBlanks( const QString &uri, int &i );
QString getValue( const QString &uri, int &i );

View File

@ -90,4 +90,23 @@ class CORE_EXPORT QgsProcessingException : public QgsException
};
/**
* \class QgsProviderConnectionException
* \ingroup core
* Custom exception class for provider connection related exceptions.
* \since QGIS 3.10
*/
class CORE_EXPORT QgsProviderConnectionException: public QgsException
{
public:
/**
* Constructor for QgsProviderConnectionException, with the specified error \a message.
*/
QgsProviderConnectionException( const QString &message ) : QgsException( message ) {}
};
#endif

View File

@ -540,7 +540,7 @@ QgsGeometry QgsOgrUtils::ogrGeometryToQgsGeometry( OGRGeometryH geom )
// get the wkb representation
int memorySize = OGR_G_WkbSize( geom );
unsigned char *wkb = new unsigned char[memorySize];
OGR_G_ExportToWkb( geom, ( OGRwkbByteOrder ) QgsApplication::endian(), wkb );
OGR_G_ExportToWkb( geom, static_cast<OGRwkbByteOrder>( QgsApplication::endian() ), wkb );
// Read original geometry type
uint32_t origGeomType;

View File

@ -19,6 +19,7 @@
#include "qgsprovidermetadata.h"
#include "qgsdataprovider.h"
#include "qgsmaplayer.h"
#include "qgsexception.h"
QgsProviderMetadata::QgsProviderMetadata( QString const &key,
QString const &description,
@ -36,7 +37,7 @@ QgsProviderMetadata::QgsProviderMetadata( const QString &key, const QString &des
QgsProviderMetadata::~QgsProviderMetadata()
{
qDeleteAll( mProviderConnections );
}
QString QgsProviderMetadata::key() const
@ -152,7 +153,7 @@ QString QgsProviderMetadata::loadStyle( const QString &, QString &errCause )
bool QgsProviderMetadata::createDb( const QString &, QString &errCause )
{
errCause = QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "errCause" ) );
errCause = QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "createDb" ) );
return false;
}
@ -160,3 +161,83 @@ QgsTransaction *QgsProviderMetadata::createTransaction( const QString & )
{
return nullptr;
}
QMap<QString, QgsAbstractProviderConnection *> QgsProviderMetadata::connections( bool cached )
{
Q_UNUSED( cached );
throw QgsProviderConnectionException( QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "connections" ) ) );
}
QMap<QString, QgsAbstractDatabaseProviderConnection *> QgsProviderMetadata::dbConnections( bool cached )
{
return connections<QgsAbstractDatabaseProviderConnection>( cached ) ;
}
QgsAbstractProviderConnection *QgsProviderMetadata::findConnection( const QString &name, bool cached )
{
const QMap<QString, QgsAbstractProviderConnection *> constConns { connections( cached ) };
for ( QgsAbstractProviderConnection *conn : constConns )
{
if ( conn->name() == name )
{
return conn;
}
}
return nullptr;
}
QgsAbstractProviderConnection *QgsProviderMetadata::createConnection( const QString &name )
{
Q_UNUSED( name );
throw QgsProviderConnectionException( QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "connection" ) ) );
}
QgsAbstractProviderConnection *QgsProviderMetadata::createConnection( const QString &name, const QString &uri )
{
Q_UNUSED( name );
Q_UNUSED( uri );
throw QgsProviderConnectionException( QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "connection" ) ) );
}
void QgsProviderMetadata::deleteConnection( const QString &name )
{
Q_UNUSED( name );
throw QgsProviderConnectionException( QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "deleteConnection" ) ) );
}
void QgsProviderMetadata::saveConnection( QgsAbstractProviderConnection *connection, const QVariantMap &configuration )
{
Q_UNUSED( connection );
Q_UNUSED( configuration );
throw QgsProviderConnectionException( QObject::tr( "Provider %1 has no %2 method" ).arg( key(), QStringLiteral( "saveConnection" ) ) );
}
///@cond PRIVATE
void QgsProviderMetadata::saveConnectionProtected( QgsAbstractProviderConnection *conn, const QVariantMap &configuration )
{
conn->store( configuration );
mProviderConnections.clear();
}
///@endcond
template<typename T>
QMap<QString, T *> QgsProviderMetadata::connections( bool cached )
{
QMap<QString, T *> result;
const auto constConns { connections( cached ) };
const QStringList constConnKeys { constConns.keys() };
for ( const auto &c : constConnKeys )
{
T *casted { static_cast<T *>( constConns.value( c ) ) };
if ( casted )
{
result.insert( c, casted );
}
}
return result;
}

View File

@ -32,7 +32,10 @@
#include "qgis_core.h"
#include <functional>
#include "qgsvectorlayerexporter.h"
#include "qgsabstractproviderconnection.h"
#include "qgsabstractdatabaseproviderconnection.h"
#include "qgsfields.h"
#include "qgsexception.h"
class QgsDataItem;
class QgsDataItemProvider;
@ -158,12 +161,22 @@ class CORE_EXPORT QgsProviderMetadata
*/
virtual QgsDataProvider *createProvider( const QString &uri, const QgsDataProvider::ProviderOptions &options ) SIP_FACTORY;
#ifndef SIP_RUN
/**
* Creates new empty vector layer
* \note not available in Python bindings
* \since QGIS 3.10
*/
SIP_SKIP virtual QgsVectorLayerExporter::ExportError createEmptyLayer( const QString &uri, const QgsFields &fields, QgsWkbTypes::Type wkbType, const QgsCoordinateReferenceSystem &srs, bool overwrite, QMap<int, int> &oldToNewAttrIdxMap, QString &errorMessage, const QMap<QString, QVariant> *options );
virtual QgsVectorLayerExporter::ExportError createEmptyLayer( const QString &uri,
const QgsFields &fields,
QgsWkbTypes::Type wkbType,
const QgsCoordinateReferenceSystem &srs,
bool overwrite,
QMap<int, int> &oldToNewAttrIdxMap,
QString &errorMessage,
const QMap<QString, QVariant> *options );
#endif
/**
* Creates a new instance of the raster data provider.
@ -249,6 +262,123 @@ class CORE_EXPORT QgsProviderMetadata
*/
virtual QgsTransaction *createTransaction( const QString &connString ) SIP_FACTORY;
/**
* Returns a dictionary of stored provider connections,
* the dictionary key is the connection identifier.
* Ownership is not transferred.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \param cached if FALSE connections will be re-read from the settings
* \throws QgsProviderConnectionException
* \since QGIS 3.10
*/
virtual QMap<QString, QgsAbstractProviderConnection *> connections( bool cached = true ) SIP_THROW( QgsProviderConnectionException );
/**
* Returns a dictionary of database provider connections,
* the dictionary key is the connection identifier.
* Ownership is not transferred.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \param cached if FALSE connections will be re-read from the settings
* \throws QgsProviderConnectionException
* \since QGIS 3.10
*/
QMap<QString, QgsAbstractDatabaseProviderConnection *> dbConnections( bool cached = true ) SIP_THROW( QgsProviderConnectionException );
/**
* Searches and returns a (possibly NULL) connection from the stored provider connections.
* Ownership is not transferred.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \param name the connection name
* \param cached if FALSE connections will be re-read from the settings
* \throws QgsProviderConnectionException
* \since QGIS 3.10
*/
QgsAbstractProviderConnection *findConnection( const QString &name, bool cached = true ) SIP_THROW( QgsProviderConnectionException );
#ifndef SIP_RUN
/**
* Returns a dictionary of provider connections of the specified type T,
* the dictionary key is the connection identifier.
* \param cached if FALSE connections will be re-read from the settings
* \note not available in Python bindings
* \since QGIS 3.10
*/
template <typename T> QMap<QString, T *>connections( bool cached = true );
#endif
/**
* Creates a new connection by loading the connection with the given \a name from the settings.
* Ownership is transferred to the caller.
* \since QGIS 3.10
*/
virtual QgsAbstractProviderConnection *createConnection( const QString &name ) SIP_FACTORY ;
/**
* Creates a new connection with the given \a name and data source \a uri,
* the newly created connection is not automatically stored in the settings, call
* saveConnection() to save it.
* Ownership is transferred to the caller.
* \see saveConnection()
* \since QGIS 3.10
*/
virtual QgsAbstractProviderConnection *createConnection( const QString &name, const QString &uri ) SIP_FACTORY;
/**
* Removes the connection with the given \a name from the settings.
* Raises a QgsProviderConnectionException if any errors are encountered.
* \throws QgsProviderConnectionException
* \since QGIS 3.10
*/
virtual void deleteConnection( const QString &name ) SIP_THROW( QgsProviderConnectionException );
/**
* Stores the connection in the settings
* \param connection the connection to be stored in the settings
* \param configuration stores additional connection settings that not part of the data source URI
* \since QGIS 3.10
*/
virtual void saveConnection( QgsAbstractProviderConnection *connection, const QVariantMap &configuration = QVariantMap() );
protected:
#ifndef SIP_RUN
///@cond PRIVATE
// Common functionality for connections management,to be moved into the class
// when all the providers are ready
// T_provider_conn: subclass of QgsAbstractProviderConnection,
// T_conn: provider connection class (such as QgsOgrDbConnection or QgsPostgresConn)
// TODO QGIS4: remove all old provider conn classes and move functionality into QgsAbstractProviderConnection subclasses
template <class T_provider_conn, class T_conn> QMap<QString, QgsAbstractProviderConnection *> connectionsProtected( bool cached = true )
{
if ( ! cached || mProviderConnections.isEmpty() )
{
qDeleteAll( mProviderConnections );
mProviderConnections.clear();
const auto connNames { T_conn::connectionList() };
for ( const auto &cname : connNames )
{
mProviderConnections.insert( cname, new T_provider_conn( cname ) );
}
}
return mProviderConnections;
}
template <class T_provider_conn> void deleteConnectionProtected( const QString &name )
{
T_provider_conn conn( name );
conn.remove();
mProviderConnections.clear();
}
virtual void saveConnectionProtected( QgsAbstractProviderConnection *connection, const QVariantMap &configuration = QVariantMap() );
//! Provider connections cache
QMap<QString, QgsAbstractProviderConnection *> mProviderConnections;
/// @endcond
#endif
private:
@ -263,6 +393,7 @@ class CORE_EXPORT QgsProviderMetadata
QString mLibrary;
CreateDataProviderFunction mCreateFunction = nullptr;
};
#endif //QGSPROVIDERMETADATA_H

View File

@ -644,7 +644,7 @@ QStringList QgsProviderRegistry::providerList() const
return lst;
}
const QgsProviderMetadata *QgsProviderRegistry::providerMetadata( const QString &providerKey ) const
QgsProviderMetadata *QgsProviderRegistry::providerMetadata( const QString &providerKey ) const
{
return findMetadata_( mProviders, providerKey );
}

View File

@ -266,7 +266,7 @@ class CORE_EXPORT QgsProviderRegistry
QStringList providerList() const;
//! Returns metadata of the provider or NULLPTR if not found
const QgsProviderMetadata *providerMetadata( const QString &providerKey ) const;
QgsProviderMetadata *providerMetadata( const QString &providerKey ) const;
/**
* Returns vector file filter string

View File

@ -37,6 +37,7 @@
#include "gdal.h"
#include "qgsogrdataitems.h"
#include "qgsogrdbconnection.h"
#include "qgsgeopackageproviderconnection.h"
void QgsGeoPackageItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *menu,
const QList<QgsDataItem *> &,
@ -44,7 +45,7 @@ void QgsGeoPackageItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu
{
if ( QgsGeoPackageVectorLayerItem *layerItem = qobject_cast< QgsGeoPackageVectorLayerItem * >( item ) )
{
// Check capabilities: for now rename is only available for vectors
// Check capabilities
if ( layerItem->capabilities2() & QgsDataItem::Capability::Rename )
{
QAction *actionRenameLayer = new QAction( tr( "Rename Layer '%1'…" ).arg( layerItem->name() ), this );
@ -189,12 +190,12 @@ void QgsGeoPackageItemGuiProvider::addTable()
}
}
bool QgsGeoPackageItemGuiProvider::rename( QgsDataItem *item, const QString &name, QgsDataItemGuiContext )
bool QgsGeoPackageItemGuiProvider::rename( QgsDataItem *item, const QString &newName, QgsDataItemGuiContext )
{
if ( QgsGeoPackageVectorLayerItem *layerItem = qobject_cast< QgsGeoPackageVectorLayerItem * >( item ) )
{
// Checks that name does not exist yet
if ( layerItem->tableNames().contains( name ) )
if ( layerItem->tableNames().contains( newName ) )
{
return false;
}
@ -236,33 +237,25 @@ bool QgsGeoPackageItemGuiProvider::rename( QgsDataItem *item, const QString &nam
QgsProject::instance()->removeMapLayers( layersList );
}
// TODO: maybe an index?
QString oldName = parts.value( QStringLiteral( "layerName" ) ).toString();
GDALDatasetH hDS = GDALOpenEx( filePath.toUtf8().constData(), GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr, nullptr, nullptr );
if ( hDS )
// Actually rename
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) ) };
QgsGeoPackageProviderConnection *conn { static_cast<QgsGeoPackageProviderConnection *>( md->findConnection( layerItem->collection()->name() ) ) };
if ( ! conn )
{
QString sql( QStringLiteral( "ALTER TABLE %1 RENAME TO %2" )
.arg( QgsSqliteUtils::quotedIdentifier( oldName ),
QgsSqliteUtils::quotedIdentifier( name ) ) );
OGRLayerH ogrLayer( GDALDatasetExecuteSQL( hDS, sql.toUtf8().constData(), nullptr, nullptr ) );
if ( ogrLayer )
GDALDatasetReleaseResultSet( hDS, ogrLayer );
errCause = CPLGetLastErrorMsg( );
if ( errCause.isEmpty() )
{
sql = QStringLiteral( "UPDATE layer_styles SET f_table_name = %2 WHERE f_table_name = %1" )
.arg( QgsSqliteUtils::quotedString( oldName ),
QgsSqliteUtils::quotedString( name ) );
ogrLayer = GDALDatasetExecuteSQL( hDS, sql.toUtf8().constData(), nullptr, nullptr );
if ( ogrLayer )
GDALDatasetReleaseResultSet( hDS, ogrLayer );
}
GDALClose( hDS );
errCause = QObject::tr( "There was an error retrieving the connection %1!" ).arg( layerItem->collection()->name() );
}
else
{
errCause = QObject::tr( "There was an error opening %1!" ).arg( filePath );
// TODO: maybe an index?
QString oldName = parts.value( QStringLiteral( "layerName" ) ).toString();
try
{
conn->renameVectorTable( QString(), oldName, newName );
}
catch ( QgsProviderConnectionException &ex )
{
errCause = ex.what();
}
}
}
@ -369,8 +362,9 @@ bool QgsGeoPackageItemGuiProvider::deleteLayer( QgsLayerItem *layerItem, QgsData
void QgsGeoPackageItemGuiProvider::vacuumGeoPackageDbAction( const QString &path, const QString &name )
{
Q_UNUSED( path );
QString errCause;
bool result = QgsGeoPackageCollectionItem::vacuumGeoPackageDb( path, name, errCause );
bool result = QgsGeoPackageCollectionItem::vacuumGeoPackageDb( name, errCause );
if ( !result || !errCause.isEmpty() )
{
QMessageBox::warning( nullptr, tr( "Database compact (VACUUM)" ), errCause );
@ -397,7 +391,7 @@ void QgsGeoPackageItemGuiProvider::createDatabase()
dialog.setCrs( QgsProject::instance()->defaultCrsForNewLayers() );
if ( dialog.exec() == QDialog::Accepted )
{
if ( QgsOgrDataCollectionItem::storeConnection( dialog.databasePath(), QStringLiteral( "GPKG" ) ) )
if ( QgsOgrDataCollectionItem::saveConnection( dialog.databasePath(), QStringLiteral( "GPKG" ) ) )
{
item->refreshConnections();
}
@ -562,7 +556,7 @@ bool QgsGeoPackageItemGuiProvider::handleDropGeopackage( QgsGeoPackageCollection
// Always try to delete the imported raster, in case the gpkg has been left
// in an inconsistent status. Ignore delete errors.
QString deleteErr;
item->deleteGeoPackageRasterLayer( QStringLiteral( "GPKG:%1:%2" ).arg( item->path(), dropUri.name ), deleteErr );
item->deleteRasterLayer( dropUri.name, deleteErr );
} );
}

View File

@ -16,7 +16,6 @@
#include "qgsowsdataitems.h"
#include "qgsowsprovider.h"
#include "qgslogger.h"
#include "qgsdatasourceuri.h"
#include "qgsowsconnection.h"
#include "qgsdataitemprovider.h"

View File

@ -16,7 +16,6 @@
#define QGSOWSDATAITEMS_H
#include "qgsdataitem.h"
#include "qgsdatasourceuri.h"
#include "qgswkbtypes.h"
#include "qgsdataitemprovider.h"

View File

@ -13,6 +13,7 @@ SET(PG_SRCS
qgscolumntypethread.cpp
qgspostgresexpressioncompiler.cpp
qgspostgreslistener.cpp
qgspostgresproviderconnection.cpp
)
SET(PG_MOC_HDRS
@ -44,6 +45,7 @@ ENDIF ()
SET(PG_HDRS
qgspostgresexpressioncompiler.h
qgspostgresproviderconnection.h
)
########################################################

View File

@ -559,7 +559,7 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP
bool isView = relkind == QLatin1String( "v" ) || relkind == QLatin1String( "m" );
bool isMaterializedView = relkind == QLatin1String( "m" );
bool isForeignTable = relkind == QLatin1String( "f" );
bool isRaster = type == QStringLiteral( "RASTER" );
bool isRaster = type == QLatin1String( "RASTER" );
QString comment = result.PQgetvalue( idx, 7 );
int srid = ssrid.isEmpty() ? std::numeric_limits<int>::min() : ssrid.toInt();
@ -796,6 +796,7 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP
layerProperty.geometryColType = SctNone;
layerProperty.relKind = relkind;
layerProperty.isView = isView;
layerProperty.isRaster = false;
layerProperty.isMaterializedView = isMaterializedView;
layerProperty.tableComment = comment;
@ -1566,7 +1567,7 @@ void QgsPostgresConn::retrieveLayerTypes( QgsPostgresLayerProperty &layerPropert
layerProperty.srids.append( srid );
}
}
else
else // vectors
{
// our estimatation ignores that a where clause might restrict the feature type or srid
if ( useEstimatedMetadata )
@ -1623,6 +1624,12 @@ void QgsPostgresConn::retrieveLayerTypes( QgsPostgresLayerProperty &layerPropert
if ( gresult.PQresultStatus() == PGRES_TUPLES_OK )
{
// Remove unknown entry
if ( gresult.PQntuples() > 0 )
{
layerProperty.srids.clear();
layerProperty.types.clear();
}
for ( int i = 0; i < gresult.PQntuples(); i++ )
{
QString type = gresult.PQgetvalue( i, 0 );

View File

@ -95,7 +95,7 @@ struct QgsPostgresLayerProperty
return n;
}
QgsPostgresLayerProperty at( int i ) const
QgsPostgresLayerProperty at( unsigned int i ) const
{
QgsPostgresLayerProperty property;

View File

@ -38,6 +38,7 @@
#include "qgspostgrestransaction.h"
#include "qgspostgreslistener.h"
#include "qgspostgresprojectstorage.h"
#include "qgspostgresproviderconnection.h"
#include "qgslogger.h"
#include "qgsfeedback.h"
#include "qgssettings.h"
@ -45,6 +46,7 @@
#include "qgspostgresprovider.h"
#include "qgsprovidermetadata.h"
#include "qgspostgresproviderconnection.h"
const QString QgsPostgresProvider::POSTGRES_KEY = QStringLiteral( "postgres" );
@ -5031,6 +5033,31 @@ QgsTransaction *QgsPostgresProviderMetadata::createTransaction( const QString &c
return new QgsPostgresTransaction( connString );
}
QMap<QString, QgsAbstractProviderConnection *> QgsPostgresProviderMetadata::connections( bool cached )
{
return connectionsProtected<QgsPostgresProviderConnection, QgsPostgresConn>( cached );
}
QgsAbstractProviderConnection *QgsPostgresProviderMetadata::createConnection( const QString &name, const QString &uri )
{
return new QgsPostgresProviderConnection( name, uri );
}
void QgsPostgresProviderMetadata::deleteConnection( const QString &name )
{
deleteConnectionProtected<QgsPostgresProviderConnection>( name );
}
void QgsPostgresProviderMetadata::saveConnection( QgsAbstractProviderConnection *conn, const QVariantMap &configuration )
{
saveConnectionProtected( conn, configuration );
}
QgsAbstractProviderConnection *QgsPostgresProviderMetadata::createConnection( const QString &name )
{
return new QgsPostgresProviderConnection( name );
}
QgsPostgresProjectStorage *gProjectStorage = nullptr; // when not null it is owned by QgsApplication::projectStorageRegistry()

View File

@ -84,7 +84,7 @@ class QgsPostgresProvider : public QgsVectorDataProvider
bool overwrite,
QMap<int, int> *oldToNewAttrIdxMap,
QString *errorMessage = nullptr,
const QMap<QString, QVariant> *coordinateTransformContext = nullptr
const QMap<QString, QVariant> *options = nullptr
);
/**
@ -564,8 +564,14 @@ class QgsPostgresProviderMetadata: public QgsProviderMetadata
bool deleteStyleById( const QString &uri, QString styleId, QString &errCause ) override;
QString getStyleById( const QString &uri, QString styleId, QString &errCause ) override;
QgsTransaction *createTransaction( const QString &connString ) override;
QMap<QString, QgsAbstractProviderConnection *> connections( bool cached = true ) override;
QgsAbstractProviderConnection *createConnection( const QString &name ) override;
QgsAbstractProviderConnection *createConnection( const QString &name, const QString &uri ) override;
void deleteConnection( const QString &name ) override;
void saveConnection( QgsAbstractProviderConnection *createConnection, const QVariantMap &configuration = QVariantMap() ) override;
void initProvider() override;
void cleanupProvider() override;
};
// clazy:excludeall=qstring-allocations

View File

@ -0,0 +1,475 @@
/***************************************************************************
qgspostgresproviderconnection.cpp - QgsPostgresProviderConnection
---------------------
begin : 2.8.2019
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 "qgspostgresproviderconnection.h"
#include "qgspostgresconn.h"
#include "qgspostgresconnpool.h"
#include "qgssettings.h"
#include "qgspostgresprovider.h"
#include "qgsexception.h"
extern "C"
{
#include <libpq-fe.h>
}
QgsPostgresProviderConnection::QgsPostgresProviderConnection( const QString &name ):
QgsAbstractDatabaseProviderConnection( name )
{
// Remove the sql and table empty parts
static const QRegularExpression removePartsRe { R"raw(\s*sql=\s*|\s*table=""\s*)raw" };
setUri( QgsPostgresConn::connUri( name ).uri().replace( removePartsRe, QString() ) );
setDefaultCapabilities();
}
QgsPostgresProviderConnection::QgsPostgresProviderConnection( const QString &name, const QString &uri ):
QgsAbstractDatabaseProviderConnection( name, uri )
{
setUri( uri );
setDefaultCapabilities();
}
void QgsPostgresProviderConnection::setDefaultCapabilities()
{
// TODO: we might check at this point if the user actually has the privileges and return
// properly filtered capabilities instead of all of them
mCapabilities =
{
Capability::DropVectorTable,
Capability::CreateVectorTable,
Capability::RenameSchema,
Capability::DropSchema,
Capability::CreateSchema,
Capability::RenameVectorTable,
Capability::RenameRasterTable,
Capability::Vacuum,
Capability::ExecuteSql,
Capability::SqlLayers,
//Capability::Transaction,
Capability::Tables,
Capability::Schemas,
Capability::Spatial,
Capability::TableExists
};
}
void QgsPostgresProviderConnection::dropTablePrivate( const QString &schema, const QString &name ) const
{
executeSqlPrivate( QStringLiteral( "DROP TABLE %1.%2" )
.arg( QgsPostgresConn::quotedIdentifier( schema ) )
.arg( QgsPostgresConn::quotedIdentifier( name ) ) );
}
void QgsPostgresProviderConnection::createVectorTable( const QString &schema,
const QString &name,
const QgsFields &fields,
QgsWkbTypes::Type wkbType,
const QgsCoordinateReferenceSystem &srs,
bool overwrite,
const QMap<QString,
QVariant> *options ) const
{
checkCapability( Capability::CreateVectorTable );
QgsDataSourceUri newUri { uri() };
newUri.setSchema( schema );
newUri.setTable( name );
// Set geometry column if it's not aspatial
if ( wkbType != QgsWkbTypes::Type::Unknown && wkbType != QgsWkbTypes::Type::NoGeometry )
{
newUri.setGeometryColumn( options->value( QStringLiteral( "geometryColumn" ), QStringLiteral( "geom" ) ).toString() );
}
QMap<int, int> map;
QString errCause;
QgsVectorLayerExporter::ExportError errCode = QgsPostgresProvider::createEmptyLayer(
newUri.uri(),
fields,
wkbType,
srs,
overwrite,
&map,
&errCause,
options
);
if ( errCode != QgsVectorLayerExporter::ExportError::NoError )
{
throw QgsProviderConnectionException( QObject::tr( "An error occurred while creating the vector layer: %1" ).arg( errCause ) );
}
}
void QgsPostgresProviderConnection::dropVectorTable( const QString &schema, const QString &name ) const
{
checkCapability( Capability::DropVectorTable );
dropTablePrivate( schema, name );
}
void QgsPostgresProviderConnection::dropRasterTable( const QString &schema, const QString &name ) const
{
checkCapability( Capability::DropRasterTable );
dropTablePrivate( schema, name );
}
void QgsPostgresProviderConnection::renameTablePrivate( const QString &schema, const QString &name, const QString &newName ) const
{
executeSqlPrivate( QStringLiteral( "ALTER TABLE %1.%2 RENAME TO %3" )
.arg( QgsPostgresConn::quotedIdentifier( schema ) )
.arg( QgsPostgresConn::quotedIdentifier( name ) )
.arg( QgsPostgresConn::quotedIdentifier( newName ) ) );
}
void QgsPostgresProviderConnection::renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const
{
checkCapability( Capability::RenameVectorTable );
renameTablePrivate( schema, name, newName );
}
void QgsPostgresProviderConnection::renameRasterTable( const QString &schema, const QString &name, const QString &newName ) const
{
checkCapability( Capability::RenameRasterTable );
renameTablePrivate( schema, name, newName );
}
void QgsPostgresProviderConnection::createSchema( const QString &name ) const
{
checkCapability( Capability::CreateSchema );
executeSqlPrivate( QStringLiteral( "CREATE SCHEMA %1" )
.arg( QgsPostgresConn::quotedIdentifier( name ) ) );
}
void QgsPostgresProviderConnection::dropSchema( const QString &name, bool force ) const
{
checkCapability( Capability::DropSchema );
executeSqlPrivate( QStringLiteral( "DROP SCHEMA %1 %2" )
.arg( QgsPostgresConn::quotedIdentifier( name ) )
.arg( force ? QStringLiteral( "CASCADE" ) : QString() ) );
}
void QgsPostgresProviderConnection::renameSchema( const QString &name, const QString &newName ) const
{
checkCapability( Capability::RenameSchema );
executeSqlPrivate( QStringLiteral( "ALTER SCHEMA %1 RENAME TO %2" )
.arg( QgsPostgresConn::quotedIdentifier( name ) )
.arg( QgsPostgresConn::quotedIdentifier( newName ) ) );
}
QList<QVariantList> QgsPostgresProviderConnection::executeSql( const QString &sql ) const
{
checkCapability( Capability::ExecuteSql );
return executeSqlPrivate( sql );
}
QList<QVariantList> QgsPostgresProviderConnection::executeSqlPrivate( const QString &sql, bool resolveTypes ) const
{
const QgsDataSourceUri dsUri { uri() };
QList<QVariantList> results;
QgsPostgresConn *conn = QgsPostgresConnPool::instance()->acquireConnection( dsUri.connectionInfo( false ) );
if ( !conn )
{
throw QgsProviderConnectionException( QObject::tr( "Connection failed: %1" ).arg( uri() ) );
}
else
{
QgsPostgresResult res( conn->PQexec( sql ) );
QString errCause;
if ( conn->PQstatus() != CONNECTION_OK || ! res.result() )
{
errCause = QObject::tr( "Connection error: %1 returned %2 [%3]" )
.arg( sql ).arg( conn->PQstatus() )
.arg( conn->PQerrorMessage() );
}
else
{
const QString err { conn->PQerrorMessage() };
if ( ! err.isEmpty() )
{
errCause = QObject::tr( "SQL error: %1 returned %2 [%3]" )
.arg( sql )
.arg( conn->PQstatus() )
.arg( err );
}
}
if ( res.PQntuples() > 0 )
{
// Try to convert value types at least for basic simple types that can be directly mapped to Python
QMap<int, QVariant::Type> typeMap;
if ( resolveTypes )
{
for ( int rowIdx = 0; rowIdx < res.PQnfields(); rowIdx++ )
{
const Oid oid { res.PQftype( rowIdx ) };
QList<QVariantList> typeRes { executeSqlPrivate( QStringLiteral( "SELECT typname FROM pg_type WHERE oid = %1" ).arg( oid ), false ) };
// Set the default to string
QVariant::Type vType { QVariant::Type::String };
if ( typeRes.size() > 0 && typeRes.first().size() > 0 )
{
static const QStringList intTypes = { QStringLiteral( "oid" ),
QStringLiteral( "char" ),
QStringLiteral( "int2" ),
QStringLiteral( "int4" ),
QStringLiteral( "int8" )
};
static const QStringList floatTypes = { QStringLiteral( "float4" ),
QStringLiteral( "float8" ),
QStringLiteral( "numeric" )
};
const QString typName { typeRes.first().first().toString() };
if ( floatTypes.contains( typName ) )
{
vType = QVariant::Double;
}
else if ( intTypes.contains( typName ) )
{
vType = QVariant::LongLong;
}
else if ( typName == QStringLiteral( "date" ) )
{
vType = QVariant::Date;
}
else if ( typName.startsWith( QStringLiteral( "timestamp" ) ) )
{
vType = QVariant::DateTime;
}
else if ( typName == QStringLiteral( "time" ) )
{
vType = QVariant::Time;
}
else if ( typName == QStringLiteral( "bool" ) )
{
vType = QVariant::Bool;
}
}
typeMap[ rowIdx ] = vType;
}
}
for ( int rowIdx = 0; rowIdx < res.PQntuples(); rowIdx++ )
{
QVariantList row;
for ( int colIdx = 0; colIdx < res.PQnfields(); colIdx++ )
{
if ( resolveTypes )
{
const QVariant::Type vType { typeMap.value( colIdx, QVariant::Type::String ) };
QVariant val { res.PQgetvalue( rowIdx, colIdx ) };
if ( val.canConvert( static_cast<int>( vType ) ) )
{
val.convert( static_cast<int>( vType ) );
}
row.push_back( val );
}
else
{
row.push_back( res.PQgetvalue( rowIdx, colIdx ) );
}
}
results.push_back( row );
}
}
QgsPostgresConnPool::instance()->releaseConnection( conn );
if ( ! errCause.isEmpty() )
{
throw QgsProviderConnectionException( errCause );
}
}
return results;
}
void QgsPostgresProviderConnection::vacuum( const QString &schema, const QString &name ) const
{
checkCapability( Capability::Vacuum );
executeSql( QStringLiteral( "VACUUM FULL ANALYZE %1.%2" )
.arg( QgsPostgresConn::quotedIdentifier( schema ) )
.arg( QgsPostgresConn::quotedIdentifier( name ) ) );
}
QList<QgsPostgresProviderConnection::TableProperty> QgsPostgresProviderConnection::tables( const QString &schema, const TableFlags &flags ) const
{
checkCapability( Capability::Tables );
QList<QgsPostgresProviderConnection::TableProperty> tables;
QString errCause;
// TODO: set flags from the connection if flags argument is 0
const QgsDataSourceUri dsUri { uri() };
QgsPostgresConn *conn = QgsPostgresConnPool::instance()->acquireConnection( dsUri.connectionInfo( false ) );
if ( !conn )
{
errCause = QObject::tr( "Connection failed: %1" ).arg( uri() );
}
else
{
bool ok = conn->getTableInfo( false, false, true, schema );
if ( ! ok )
{
errCause = QObject::tr( "Could not retrieve tables: %1" ).arg( uri() );
}
else
{
QVector<QgsPostgresLayerProperty> properties;
const bool aspatial { ! flags || flags.testFlag( TableFlag::Aspatial ) };
conn->supportedLayers( properties, false, schema == QStringLiteral( "public" ), aspatial, schema );
bool dontResolveType = QgsPostgresConn::dontResolveType( name() );
// Cannot be const:
for ( auto &pr : properties )
{
// Classify
TableFlags prFlags;
if ( pr.isView )
{
prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::View );
}
if ( pr.isMaterializedView )
{
prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::MaterializedView );
}
// Table type
if ( pr.isRaster )
{
prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Raster );
}
else if ( pr.nSpCols != 0 )
{
prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Vector );
}
else
{
prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Aspatial );
}
// Filter
if ( ! flags || ( prFlags & flags ) )
{
// retrieve layer types if needed
if ( ! dontResolveType && ( !pr.geometryColName.isNull() &&
( pr.types.value( 0, QgsWkbTypes::Unknown ) == QgsWkbTypes::Unknown ||
pr.srids.value( 0, std::numeric_limits<int>::min() ) == std::numeric_limits<int>::min() ) ) )
{
conn->retrieveLayerTypes( pr, true /* useEstimatedMetadata */ );
}
QgsPostgresProviderConnection::TableProperty property;
property.setFlags( prFlags );
for ( int i = 0; i < std::min( pr.types.size(), pr.srids.size() ) ; i++ )
{
property.addGeometryColumnType( pr.types.at( i ), QgsCoordinateReferenceSystem::fromEpsgId( pr.srids.at( i ) ) );
}
property.setTableName( pr.tableName );
property.setSchema( pr.schemaName );
property.setGeometryColumn( pr.geometryColName );
property.setPrimaryKeyColumns( pr.pkCols );
property.setGeometryColumnCount( static_cast<int>( pr.nSpCols ) );
property.setComment( pr.tableComment );
tables.push_back( property );
}
}
}
QgsPostgresConnPool::instance()->releaseConnection( conn );
}
if ( ! errCause.isEmpty() )
{
throw QgsProviderConnectionException( errCause );
}
return tables;
}
QStringList QgsPostgresProviderConnection::schemas( ) const
{
checkCapability( Capability::Schemas );
QStringList schemas;
QString errCause;
const QgsDataSourceUri dsUri { uri() };
QgsPostgresConn *conn = QgsPostgresConnPool::instance()->acquireConnection( dsUri.connectionInfo( false ) );
if ( !conn )
{
errCause = QObject::tr( "Connection failed: %1" ).arg( uri() );
}
else
{
QList<QgsPostgresSchemaProperty> schemaProperties;
bool ok = conn->getSchemas( schemaProperties );
QgsPostgresConnPool::instance()->releaseConnection( conn );
if ( ! ok )
{
errCause = QObject::tr( "Could not retrieve schemas: %1" ).arg( uri() );
}
else
{
for ( const auto &s : qgis::as_const( schemaProperties ) )
{
schemas.push_back( s.name );
}
}
}
if ( ! errCause.isEmpty() )
{
throw QgsProviderConnectionException( errCause );
}
return schemas;
}
void QgsPostgresProviderConnection::store( const QVariantMap &configuration ) const
{
// TODO: move this to class configuration?
QString baseKey = QStringLiteral( "/PostgreSQL/connections/" );
// delete the original entry first
remove( );
QgsSettings settings;
settings.beginGroup( baseKey );
settings.beginGroup( name() );
// From URI
const QgsDataSourceUri dsUri { uri() };
settings.setValue( "service", dsUri.service() );
settings.setValue( "host", dsUri.host() );
settings.setValue( "port", dsUri.port() );
settings.setValue( "database", dsUri.database() );
settings.setValue( "username", dsUri.username() );
settings.setValue( "password", dsUri.password() );
settings.setValue( "authcfg", dsUri.authConfigId() );
settings.setValue( "sslmode", dsUri.sslMode() );
// From GUI config
static const QStringList guiParameters
{
QStringLiteral( "publicOnly" ),
QStringLiteral( "geometryColumnsOnly" ),
QStringLiteral( "dontResolveType" ),
QStringLiteral( "allowGeometrylessTables" ),
QStringLiteral( "saveUsername" ),
QStringLiteral( "savePassword" ),
QStringLiteral( "estimatedMetadata" ),
QStringLiteral( "projectsInDatabase" )
};
for ( const auto &p : guiParameters )
{
if ( configuration.contains( p ) )
{
settings.setValue( p, configuration.value( p ) );
}
}
settings.endGroup();
settings.endGroup();
}
void QgsPostgresProviderConnection::remove() const
{
QgsPostgresConn::deleteConnection( name() );
}

View File

@ -0,0 +1,63 @@
/***************************************************************************
qgspostgresproviderconnection.h - QgsPostgresProviderConnection
---------------------
begin : 2.8.2019
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 QGSPOSTGRESPROVIDERCONNECTION_H
#define QGSPOSTGRESPROVIDERCONNECTION_H
#include "qgsabstractdatabaseproviderconnection.h"
class QgsPostgresProviderConnection : public QgsAbstractDatabaseProviderConnection
{
public:
QgsPostgresProviderConnection( const QString &name );
QgsPostgresProviderConnection( const QString &name, const QString &uri );
// QgsAbstractProviderConnection interface
public:
void createVectorTable( const QString &schema,
const QString &name,
const QgsFields &fields,
QgsWkbTypes::Type wkbType,
const QgsCoordinateReferenceSystem &srs, bool overwrite,
const QMap<QString, QVariant> *options ) const override;
void dropVectorTable( const QString &schema, const QString &name ) const override;
void dropRasterTable( const QString &schema, const QString &name ) const override;
void renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const override;
void renameRasterTable( const QString &schema, const QString &name, const QString &newName ) const override;
void createSchema( const QString &name ) const override;
void dropSchema( const QString &name, bool force = false ) const override;
void renameSchema( const QString &name, const QString &newName ) const override;
QList<QVariantList> executeSql( const QString &sql ) const override;
void vacuum( const QString &schema, const QString &name ) const override;
QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables( const QString &schema,
const TableFlags &flags = nullptr ) const override;
QStringList schemas( ) const override;
void store( const QVariantMap &configuration = QVariantMap() ) const override;
void remove() const override;
private:
QList<QVariantList> executeSqlPrivate( const QString &sql, bool resolveTypes = true ) const;
void setDefaultCapabilities();
void dropTablePrivate( const QString &schema, const QString &name ) const;
void renameTablePrivate( const QString &schema, const QString &name, const QString &newName ) const;
};
#endif // QGSPOSTGRESPROVIDERCONNECTION_H

View File

@ -6058,3 +6058,4 @@ QGISEXTERN QgsProviderMetadata *providerMetadataFactory()
{
return new QgsSpatiaLiteProviderMetadata();
}

View File

@ -29,7 +29,6 @@ extern "C"
#include "qgsvirtuallayerfeatureiterator.h"
#include "qgsvectorlayer.h"
#include "qgsproject.h"
#include "qgsdatasourceuri.h"
#include "qgslogger.h"
#include "qgsapplication.h"

View File

@ -775,7 +775,7 @@ namespace QgsWms
for ( int i = 0; i < wfsLayerIds.size(); ++i )
{
QgsMapLayer *layer = project->mapLayer( wfsLayerIds.at( i ) );
if ( layer->type() != QgsMapLayerType::VectorLayer )
if ( ! layer || layer->type() != QgsMapLayerType::VectorLayer )
{
continue;
}

View File

@ -11,6 +11,8 @@ ENDIF (WITH_SERVER)
ADD_PYTHON_TEST(PyCoreAdittions test_core_additions.py)
ADD_PYTHON_TEST(PyPythonRepr test_python_repr.py)
ADD_PYTHON_TEST(PyQgsActionManager test_qgsactionmanager.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionPostgres test_qgsproviderconnection_postgres.py)
ADD_PYTHON_TEST(PyQgsProviderConnectionGpkg test_qgsproviderconnection_ogr_gpkg.py)
ADD_PYTHON_TEST(PyQgsAFSProvider test_provider_afs.py)
ADD_PYTHON_TEST(PyQgsAlignmentComboBox test_qgsalignmentcombobox.py)
ADD_PYTHON_TEST(PyQgsPythonProvider test_provider_python.py)

View File

@ -196,6 +196,10 @@ class TestPyQgsDBManagerGpkg(unittest.TestCase):
self.assertTrue(new_table.createSpatialIndex())
self.assertTrue(new_table.hasSpatialIndex())
# Test delete spatial index
self.assertTrue(new_table.deleteSpatialIndex())
self.assertFalse(new_table.hasSpatialIndex())
self.assertTrue(new_table.delete())
tables = db.tables()
@ -314,7 +318,7 @@ class TestPyQgsDBManagerGpkg(unittest.TestCase):
break
self.assertIsNotNone(table)
info = table.info()
expected_html = """<div class="section"><h2>General info</h2><div><table><tr><td>Relation type:&nbsp;</td><td>Table&nbsp;</td></tr><tr><td>Rows:&nbsp;</td><td>Unknown (<a href="action:rows/count">find out</a>)&nbsp;</td></tr></table></div></div><div class="section"><h2>GeoPackage</h2><div><table><tr><td>Column:&nbsp;</td><td>&nbsp;</td></tr><tr><td>Geometry:&nbsp;</td><td>RASTER&nbsp;</td></tr><tr><td>Spatial ref:&nbsp;</td><td>WGS 84 geodetic (4326)&nbsp;</td></tr><tr><td>Extent:&nbsp;</td><td>2.00000, 48.80000 - 2.20000, 49.00000&nbsp;</td></tr></table></div></div><div class="section"><h2>Fields</h2><div><table class="header"><tr><th>#&nbsp;</th><th>Name&nbsp;</th><th>Type&nbsp;</th><th>Null&nbsp;</th><th>Default&nbsp;</th></tr><tr><td>0&nbsp;</td><td class="underline">id&nbsp;</td><td>INTEGER&nbsp;</td><td>Y&nbsp;</td><td>&nbsp;</td></tr><tr><td>1&nbsp;</td><td>zoom_level&nbsp;</td><td>INTEGER&nbsp;</td><td>N&nbsp;</td><td>&nbsp;</td></tr><tr><td>2&nbsp;</td><td>tile_column&nbsp;</td><td>INTEGER&nbsp;</td><td>N&nbsp;</td><td>&nbsp;</td></tr><tr><td>3&nbsp;</td><td>tile_row&nbsp;</td><td>INTEGER&nbsp;</td><td>N&nbsp;</td><td>&nbsp;</td></tr><tr><td>4&nbsp;</td><td>tile_data&nbsp;</td><td>BLOB&nbsp;</td><td>N&nbsp;</td><td>&nbsp;</td></tr></table></div></div><div class="section"><h2>Indexes</h2><div><table class="header"><tr><th>Name&nbsp;</th><th>Column(s)&nbsp;</th></tr><tr><td>sqlite_autoindex_raster_table_1&nbsp;</td><td>zoom_level<br>tile_column<br>tile_row&nbsp;</td></tr></table></div></div>"""
expected_html = """<div class="section"><h2>General info</h2><div><table><tr><td>Relation type:&nbsp;</td><td>Table&nbsp;</td></tr><tr><td>Rows:&nbsp;</td><td>Unknown (<a href="action:rows/count">find out</a>)&nbsp;</td></tr></table></div></div><div class="section"><h2>GeoPackage</h2><div><table><tr><td>Column:&nbsp;</td><td>&nbsp;</td></tr><tr><td>Geometry:&nbsp;</td><td>RASTER&nbsp;</td></tr><tr><td>Spatial ref:&nbsp;</td><td>WGS 84 (4326)&nbsp;</td></tr><tr><td>Extent:&nbsp;</td><td>2.00000, 48.80000 - 2.20000, 49.00000&nbsp;</td></tr></table></div></div><div class="section"><h2>Fields</h2><div><table class="header"><tr><th>#&nbsp;</th><th>Name&nbsp;</th><th>Type&nbsp;</th><th>Null&nbsp;</th><th>Default&nbsp;</th></tr><tr><td>0&nbsp;</td><td class="underline">id&nbsp;</td><td>INTEGER&nbsp;</td><td>Y&nbsp;</td><td>&nbsp;</td></tr><tr><td>1&nbsp;</td><td>zoom_level&nbsp;</td><td>INTEGER&nbsp;</td><td>N&nbsp;</td><td>&nbsp;</td></tr><tr><td>2&nbsp;</td><td>tile_column&nbsp;</td><td>INTEGER&nbsp;</td><td>N&nbsp;</td><td>&nbsp;</td></tr><tr><td>3&nbsp;</td><td>tile_row&nbsp;</td><td>INTEGER&nbsp;</td><td>N&nbsp;</td><td>&nbsp;</td></tr><tr><td>4&nbsp;</td><td>tile_data&nbsp;</td><td>BLOB&nbsp;</td><td>N&nbsp;</td><td>&nbsp;</td></tr></table></div></div><div class="section"><h2>Indexes</h2><div><table class="header"><tr><th>Name&nbsp;</th><th>Column(s)&nbsp;</th></tr><tr><td>sqlite_autoindex_raster_table_1&nbsp;</td><td>zoom_level<br>tile_column<br>tile_row&nbsp;</td></tr></table></div></div>"""
self.assertEqual(info.toHtml(), expected_html)

View File

@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
"""QGIS Base Unit tests for QgsAbastractProviderConnection API.
Providers must implement a test based on TestPyQgsProviderConnectionBase
.. 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__ = 'Alessandro Pasotti'
__date__ = '05/08/2019'
__copyright__ = 'Copyright 2019, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
import shutil
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.testing import start_app, unittest
from qgis.core import (
QgsSettings,
QgsProviderRegistry,
QgsWkbTypes,
QgsVectorLayer,
QgsFields,
QgsCoordinateReferenceSystem,
QgsField,
QgsAbstractDatabaseProviderConnection,
QgsProviderConnectionException,
)
from qgis.PyQt import QtCore
class TestPyQgsProviderConnectionBase():
# Provider test cases must define the string URI for the test
uri = ''
# Provider test cases must define the provider name (e.g. "postgres" or "ogr")
providerKey = ''
@classmethod
def setUpClass(cls):
"""Run before all tests"""
QCoreApplication.setOrganizationName("QGIS_Test")
QCoreApplication.setOrganizationDomain(cls.__name__)
QCoreApplication.setApplicationName(cls.__name__)
start_app()
@classmethod
def tearDownClass(cls):
"""Run after all tests"""
pass
def setUp(self):
QgsSettings().clear()
def _test_save_load(self, md, uri):
"""Common tests on connection save and load"""
conn = md.createConnection('qgis_test1', self.uri)
md.saveConnection(conn)
# Check that we retrieve the new connection
self.assertTrue('qgis_test1' in md.connections().keys())
self.assertTrue('qgis_test1' in md.dbConnections().keys())
return md.connections()['qgis_test1']
def _table_names(self, table_properties):
"""Return table default names from table property list"""
return [p.defaultName() for p in table_properties]
def _table_by_name(self, table_properties, name):
"""Filter table properties by table name"""
try:
return [p for p in table_properties if p.defaultName() == name][0]
except IndexError:
return None
def _test_operations(self, md, conn):
"""Common tests"""
capabilities = conn.capabilities()
# Schema operations
if (capabilities & QgsAbstractDatabaseProviderConnection.CreateSchema and
capabilities & QgsAbstractDatabaseProviderConnection.Schemas and
capabilities & QgsAbstractDatabaseProviderConnection.RenameSchema and
capabilities & QgsAbstractDatabaseProviderConnection.DropSchema):
if capabilities & QgsAbstractDatabaseProviderConnection.DropSchema and 'myNewSchema' in conn.schemas():
conn.dropSchema('myNewSchema', True)
# Create
conn.createSchema('myNewSchema')
schemas = conn.schemas()
self.assertTrue('myNewSchema' in schemas)
# Create again
with self.assertRaises(QgsProviderConnectionException) as ex:
conn.createSchema('myNewSchema')
# Rename
conn.renameSchema('myNewSchema', 'myVeryNewSchema')
schemas = conn.schemas()
self.assertTrue('myVeryNewSchema' in schemas)
self.assertFalse('myNewSchema' in schemas)
# Drop
conn.dropSchema('myVeryNewSchema')
schemas = conn.schemas()
self.assertFalse('myVeryNewSchema' in schemas)
# Table operations
if (capabilities & QgsAbstractDatabaseProviderConnection.CreateVectorTable and
capabilities & QgsAbstractDatabaseProviderConnection.Tables and
capabilities & QgsAbstractDatabaseProviderConnection.RenameVectorTable and
capabilities & QgsAbstractDatabaseProviderConnection.DropVectorTable):
if capabilities & QgsAbstractDatabaseProviderConnection.DropSchema and 'myNewSchema' in conn.schemas():
conn.dropSchema('myNewSchema', True)
if capabilities & QgsAbstractDatabaseProviderConnection.CreateSchema:
schema = 'myNewSchema'
conn.createSchema('myNewSchema')
else:
schema = 'public'
if 'myNewTable' in self._table_names(conn.tables(schema)):
conn.dropVectorTable(schema, 'myNewTable')
fields = QgsFields()
fields.append(QgsField("string", QVariant.String))
fields.append(QgsField("long", QVariant.LongLong))
fields.append(QgsField("double", QVariant.Double))
fields.append(QgsField("integer", QVariant.Int))
fields.append(QgsField("date", QVariant.Date))
fields.append(QgsField("datetime", QVariant.DateTime))
fields.append(QgsField("time", QVariant.Time))
options = {}
crs = QgsCoordinateReferenceSystem.fromEpsgId(3857)
typ = QgsWkbTypes.LineString
# Create
conn.createVectorTable(schema, 'myNewTable', fields, typ, crs, True, options)
table_names = self._table_names(conn.tables(schema))
self.assertTrue('myNewTable' in table_names)
# Check table information
table_properties = conn.tables(schema)
table_property = self._table_by_name(table_properties, 'myNewTable')
self.assertEqual(table_property.maxCoordinateDimensions(), 2)
self.assertIsNotNone(table_property)
self.assertEqual(table_property.tableName(), 'myNewTable')
self.assertEqual(table_property.geometryColumnCount(), 1)
self.assertEqual(table_property.geometryColumnTypes()[0].wkbType, QgsWkbTypes.LineString)
self.assertEqual(table_property.geometryColumnTypes()[0].crs, QgsCoordinateReferenceSystem.fromEpsgId(3857))
self.assertEqual(table_property.defaultName(), 'myNewTable')
# Check aspatial tables
conn.createVectorTable(schema, 'myNewAspatialTable', fields, QgsWkbTypes.NoGeometry, crs, True, options)
table_properties = conn.tables(schema, QgsAbstractDatabaseProviderConnection.Aspatial)
table_property = self._table_by_name(table_properties, 'myNewAspatialTable')
self.assertIsNotNone(table_property)
self.assertEqual(table_property.maxCoordinateDimensions(), 0)
self.assertEqual(table_property.tableName(), 'myNewAspatialTable')
self.assertEqual(table_property.geometryColumnCount(), 0)
self.assertEqual(table_property.geometryColumn(), '')
self.assertEqual(table_property.defaultName(), 'myNewAspatialTable')
self.assertEqual(table_property.geometryColumnTypes()[0].wkbType, QgsWkbTypes.NoGeometry)
self.assertFalse(table_property.geometryColumnTypes()[0].crs.isValid())
self.assertFalse(table_property.flags() & QgsAbstractDatabaseProviderConnection.Raster)
self.assertFalse(table_property.flags() & QgsAbstractDatabaseProviderConnection.Vector)
self.assertTrue(table_property.flags() & QgsAbstractDatabaseProviderConnection.Aspatial)
# Check executeSql
has_schema = capabilities & QgsAbstractDatabaseProviderConnection.Schemas
if capabilities & QgsAbstractDatabaseProviderConnection.ExecuteSql:
if has_schema:
table = "\"%s\".\"myNewAspatialTable\"" % schema
else:
table = 'myNewAspatialTable'
sql = "INSERT INTO %s (string, long, double, integer, date, datetime, time) VALUES ('QGIS Rocks - \U0001f604', 666, 1.234, 1234, '2019-07-08', '2019-07-08T12:00:12', '12:00:13.00')" % table
res = conn.executeSql(sql)
self.assertEqual(res, [])
sql = "SELECT string, long, double, integer, date, datetime FROM %s" % table
res = conn.executeSql(sql)
# GPKG has no type for time
self.assertEqual(res, [['QGIS Rocks - \U0001f604', 666, 1.234, 1234, QtCore.QDate(2019, 7, 8), QtCore.QDateTime(2019, 7, 8, 12, 0, 12)]])
sql = "SELECT time FROM %s" % table
res = conn.executeSql(sql)
self.assertIn(res, ([[QtCore.QTime(12, 0, 13)]], [['12:00:13.00']]))
sql = "DELETE FROM %s WHERE string = 'QGIS Rocks - \U0001f604'" % table
res = conn.executeSql(sql)
self.assertEqual(res, [])
sql = "SELECT string, integer FROM %s" % table
res = conn.executeSql(sql)
self.assertEqual(res, [])
# Check that we do NOT get the aspatial table when querying for vectors
table_names = self._table_names(conn.tables(schema, QgsAbstractDatabaseProviderConnection.Vector))
self.assertTrue('myNewTable' in table_names)
self.assertFalse('myNewAspatialTable' in table_names)
# Query for rasters (in qgis_test schema or no schema for GPKG)
table_properties = conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster)
# At leasy one raster should be there
self.assertTrue(len(table_properties) >= 1)
table_property = table_properties[0]
self.assertTrue(table_property.flags() & QgsAbstractDatabaseProviderConnection.Raster)
self.assertFalse(table_property.flags() & QgsAbstractDatabaseProviderConnection.Vector)
self.assertFalse(table_property.flags() & QgsAbstractDatabaseProviderConnection.Aspatial)
# Rename
conn.renameVectorTable(schema, 'myNewTable', 'myVeryNewTable')
tables = self._table_names(conn.tables(schema))
self.assertFalse('myNewTable' in tables)
self.assertTrue('myVeryNewTable' in tables)
# Vacuum
if capabilities & QgsAbstractDatabaseProviderConnection.Vacuum:
conn.vacuum('myNewSchema', 'myVeryNewTable')
if capabilities & QgsAbstractDatabaseProviderConnection.DropSchema:
# Drop schema (should fail)
with self.assertRaises(QgsProviderConnectionException) as ex:
conn.dropSchema('myNewSchema')
# Check some column types operations
table = self._table_by_name(conn.tables(schema), 'myVeryNewTable')
self.assertEqual(len(table.geometryColumnTypes()), 1)
ct = table.geometryColumnTypes()[0]
self.assertEqual(ct.crs, QgsCoordinateReferenceSystem.fromEpsgId(3857))
self.assertEqual(ct.wkbType, QgsWkbTypes.LineString)
# Add a new (existing type)
table.addGeometryColumnType(QgsWkbTypes.LineString, QgsCoordinateReferenceSystem.fromEpsgId(3857))
self.assertEqual(len(table.geometryColumnTypes()), 1)
ct = table.geometryColumnTypes()[0]
self.assertEqual(ct.crs, QgsCoordinateReferenceSystem.fromEpsgId(3857))
self.assertEqual(ct.wkbType, QgsWkbTypes.LineString)
# Add a new one
table.addGeometryColumnType(QgsWkbTypes.LineString, QgsCoordinateReferenceSystem.fromEpsgId(4326))
self.assertEqual(len(table.geometryColumnTypes()), 2)
ct = table.geometryColumnTypes()[0]
self.assertEqual(ct.crs, QgsCoordinateReferenceSystem.fromEpsgId(3857))
self.assertEqual(ct.wkbType, QgsWkbTypes.LineString)
ct = table.geometryColumnTypes()[1]
self.assertEqual(ct.crs, QgsCoordinateReferenceSystem.fromEpsgId(4326))
self.assertEqual(ct.wkbType, QgsWkbTypes.LineString)
# Drop table
conn.dropVectorTable(schema, 'myVeryNewTable')
conn.dropVectorTable(schema, 'myNewAspatialTable')
table_names = self._table_names(conn.tables(schema))
self.assertFalse('myVeryNewTable' in table_names)
if capabilities & QgsAbstractDatabaseProviderConnection.DropSchema:
# Drop schema
conn.dropSchema('myNewSchema')
self.assertFalse('myNewSchema' in conn.schemas())
conns = md.connections()
self.assertTrue(isinstance(list(conns.values())[0], QgsAbstractDatabaseProviderConnection))
# Remove connection
md.deleteConnection(conn.name())
self.assertEqual(list(md.connections().values()), [])
def test_errors(self):
"""Test SQL errors"""
md = QgsProviderRegistry.instance().providerMetadata(self.providerKey)
conn = self._test_save_load(md, self.uri)
if conn.capabilities() & QgsAbstractDatabaseProviderConnection.Schemas:
with self.assertRaises(QgsProviderConnectionException) as ex:
conn.createVectorTable('notExists', 'notReally', QgsFields(), QgsWkbTypes.Point, QgsCoordinateReferenceSystem(), False, {})
if conn.capabilities() & QgsAbstractDatabaseProviderConnection.DropVectorTable:
with self.assertRaises(QgsProviderConnectionException) as ex:
conn.executeSql('DROP TABLE "notExists"')
# Remove connection
md.deleteConnection(conn.name())
self.assertEqual(list(md.connections().values()), [])
def test_connections(self):
"""Main test"""
md = QgsProviderRegistry.instance().providerMetadata(self.providerKey)
# Run common tests
conn = self._test_save_load(md, self.uri)
self._test_operations(md, conn)

View File

@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for OGR GeoPackage QgsAbastractProviderConnection API.
.. 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__ = 'Alessandro Pasotti'
__date__ = '10/08/2019'
__copyright__ = 'Copyright 2019, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
import shutil
from test_qgsproviderconnection_base import TestPyQgsProviderConnectionBase
from qgis.core import (
QgsWkbTypes,
QgsAbstractDatabaseProviderConnection,
QgsProviderConnectionException,
QgsVectorLayer,
QgsProviderRegistry,
QgsFields,
QgsCoordinateReferenceSystem,
)
from qgis.testing import unittest
from utilities import unitTestDataPath
TEST_DATA_DIR = unitTestDataPath()
class TestPyQgsProviderConnectionGpkg(unittest.TestCase, TestPyQgsProviderConnectionBase):
# Provider test cases must define the string URI for the test
uri = ''
# Provider test cases must define the provider name (e.g. "postgres" or "ogr")
providerKey = 'ogr'
@classmethod
def setUpClass(cls):
"""Run before all tests"""
TestPyQgsProviderConnectionBase.setUpClass()
gpkg_original_path = '{}/qgis_server/test_project_wms_grouped_layers.gpkg'.format(TEST_DATA_DIR)
cls.gpkg_path = '{}/qgis_server/test_project_wms_grouped_layers_test.gpkg'.format(TEST_DATA_DIR)
shutil.copy(gpkg_original_path, cls.gpkg_path)
vl = QgsVectorLayer('{}|cdb_lines'.format(cls.gpkg_path), 'test', 'ogr')
assert vl.isValid()
cls.uri = cls.gpkg_path
@classmethod
def tearDownClass(cls):
"""Run after all tests"""
os.unlink(cls.gpkg_path)
def test_gpkg_connections(self):
"""Create some connections and retrieve them"""
md = QgsProviderRegistry.instance().providerMetadata('ogr')
conn = md.createConnection('qgis_test1', self.uri)
md.saveConnection(conn)
# Retrieve capabilities
capabilities = conn.capabilities()
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.Tables))
self.assertFalse(bool(capabilities & QgsAbstractDatabaseProviderConnection.Schemas))
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.CreateVectorTable))
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.DropVectorTable))
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.RenameVectorTable))
self.assertFalse(bool(capabilities & QgsAbstractDatabaseProviderConnection.RenameRasterTable))
crs = QgsCoordinateReferenceSystem.fromEpsgId(3857)
typ = QgsWkbTypes.LineString
conn.createVectorTable('', 'myNewAspatialTable', QgsFields(), QgsWkbTypes.NoGeometry, crs, True, {})
conn.createVectorTable('', 'myNewTable', QgsFields(), typ, crs, True, {})
# Check filters and special cases
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster))
self.assertTrue('osm' in table_names)
self.assertFalse('myNewTable' in table_names)
self.assertFalse('myNewAspatialTable' in table_names)
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.View))
self.assertFalse('osm' in table_names)
self.assertFalse('myNewTable' in table_names)
self.assertFalse('myNewAspatialTable' in table_names)
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Aspatial))
self.assertFalse('osm' in table_names)
self.assertFalse('myNewTable' in table_names)
self.assertTrue('myNewAspatialTable' in table_names)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for Postgres QgsAbastractProviderConnection API.
.. 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__ = 'Alessandro Pasotti'
__date__ = '10/08/2019'
__copyright__ = 'Copyright 2019, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
from test_qgsproviderconnection_base import TestPyQgsProviderConnectionBase
from qgis.core import (
QgsWkbTypes,
QgsAbstractDatabaseProviderConnection,
QgsProviderConnectionException,
QgsVectorLayer,
QgsProviderRegistry,
QgsCoordinateReferenceSystem,
QgsRasterLayer,
)
from qgis.testing import unittest
from osgeo import gdal
class TestPyQgsProviderConnectionPostgres(unittest.TestCase, TestPyQgsProviderConnectionBase):
# Provider test cases must define the string URI for the test
uri = ''
# Provider test cases must define the provider name (e.g. "postgres" or "ogr")
providerKey = 'postgres'
@classmethod
def setUpClass(cls):
"""Run before all tests"""
TestPyQgsProviderConnectionBase.setUpClass()
cls.postgres_conn = 'dbname=\'qgis_test\''
if 'QGIS_PGTEST_DB' in os.environ:
cls.postgres_conn = os.environ['QGIS_PGTEST_DB']
# Create test layers
vl = QgsVectorLayer(cls.postgres_conn + ' sslmode=disable key=\'"key1","key2"\' srid=4326 type=POINT table="qgis_test"."someData" (geom) sql=', 'test', 'postgres')
assert vl.isValid()
cls.uri = cls.postgres_conn + ' port=5432 sslmode=disable '
def test_postgis_connections(self):
"""Create some connections and retrieve them"""
md = QgsProviderRegistry.instance().providerMetadata('postgres')
conn = md.createConnection('qgis_test1', self.uri)
md.saveConnection(conn)
# Retrieve capabilities
capabilities = conn.capabilities()
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.Tables))
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.Schemas))
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.CreateVectorTable))
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.DropVectorTable))
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.RenameVectorTable))
self.assertTrue(bool(capabilities & QgsAbstractDatabaseProviderConnection.RenameRasterTable))
# Check filters and special cases
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster))
self.assertTrue('Raster1' in table_names)
self.assertFalse('geometryless_table' in table_names)
self.assertFalse('geometries_table' in table_names)
self.assertFalse('geometries_view' in table_names)
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.View))
self.assertFalse('Raster1' in table_names)
self.assertFalse('geometryless_table' in table_names)
self.assertFalse('geometries_table' in table_names)
self.assertTrue('geometries_view' in table_names)
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Aspatial))
self.assertFalse('Raster1' in table_names)
self.assertTrue('geometryless_table' in table_names)
self.assertFalse('geometries_table' in table_names)
self.assertFalse('geometries_view' in table_names)
geometries_table = self._table_by_name(conn.tables('qgis_test'), 'geometries_table')
srids = [t.crs.postgisSrid() for t in geometries_table.geometryColumnTypes()]
self.assertEqual(srids, [0, 0, 0, 3857, 4326, 0])
types = [t.wkbType for t in geometries_table.geometryColumnTypes()]
self.assertEqual(types, [7, 2, 1, 1, 1, 3])
# error: ERROR: relation "qgis_test.raster1" does not exist
@unittest.skipIf(gdal.VersionInfo() < '2040000', 'This test requires GDAL >= 2.4.0')
def test_postgis_raster_rename(self):
"""Test raster rename"""
md = QgsProviderRegistry.instance().providerMetadata('postgres')
conn = md.createConnection('qgis_test1', self.uri)
md.saveConnection(conn)
table = self._table_by_name(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster), 'Raster1')
self.assertTrue(QgsRasterLayer("PG: %s schema='qgis_test' column='%s' table='%s'" % (conn.uri(), table.geometryColumn(), table.tableName()), 'r1', 'gdal').isValid())
conn.renameRasterTable('qgis_test', table.tableName(), 'Raster2')
table = self._table_by_name(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster), 'Raster2')
self.assertTrue(QgsRasterLayer("PG: %s schema='qgis_test' column='%s' table='%s'" % (conn.uri(), table.geometryColumn(), table.tableName()), 'r1', 'gdal').isValid())
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster))
self.assertFalse('Raster1' in table_names)
self.assertTrue('Raster2' in table_names)
conn.renameRasterTable('qgis_test', table.tableName(), 'Raster1')
table_names = self._table_names(conn.tables('qgis_test', QgsAbstractDatabaseProviderConnection.Raster))
self.assertFalse('Raster2' in table_names)
self.assertTrue('Raster1' in table_names)
if __name__ == '__main__':
unittest.main()

View File

@ -617,3 +617,24 @@ CREATE OR REPLACE VIEW qgis_test.b21839_pk_unicity_view AS
b21839_pk_unicity.geom
FROM qgis_test.b21839_pk_unicity;
---------------------------------------------
--
-- Table and views for tests on QgsAbstractProviderConnection
--
CREATE TABLE qgis_test.geometries_table (name VARCHAR, geom GEOMETRY);
INSERT INTO qgis_test.geometries_table VALUES
('Point', 'POINT(0 0)'),
('Point4326', 'SRID=4326;POINT(7 45)'),
('Point3857', 'SRID=3857;POINT(100 100)'),
('Linestring', 'LINESTRING(0 0, 1 1, 2 1, 2 2)'),
('Polygon', 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'),
('PolygonWithHole', 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1))'),
('Collection', 'GEOMETRYCOLLECTION(POINT(2 0),POLYGON((0 0, 1 0, 1 1, 0 1, 0 0)))');
CREATE VIEW qgis_test.geometries_view AS (SELECT * FROM qgis_test.geometries_table);
CREATE TABLE qgis_test.geometryless_table (name VARCHAR, value INTEGER);