Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Connections API: drop/add column
Also: expose it in the browser.

Implemented for:

- PG
- GPKG
- Spatialite
- MSSQL
  • Loading branch information
elpaso committed Jul 8, 2020
1 parent 9125690 commit f2858b2
Show file tree
Hide file tree
Showing 19 changed files with 438 additions and 51 deletions.
Expand Up @@ -234,8 +234,8 @@ This information is calculated from the geometry columns types.
CreateSpatialIndex,
SpatialIndexExists,
DeleteSpatialIndex,
DeleteField,
CreateField,
DropColumn,
AddColumn,
};

typedef QFlags<QgsAbstractDatabaseProviderConnection::Capability> Capabilities;
Expand Down Expand Up @@ -359,6 +359,40 @@ Raises a QgsProviderConnectionException if any errors are encountered.
:raises :: py:class:`QgsProviderConnectionException`
%End

virtual void dropColumn( const QString &fieldName, const QString &schema, const QString &tableName, bool force = false ) const throw( QgsProviderConnectionException );
%Docstring
Drops the column with the specified name.
Raises a QgsProviderConnectionException if any errors are encountered.

:param fieldName: name of the field to be dropped
:param schema: name of the schema (schema is ignored if not supported by the backend).
:param tableName: name of the table
: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 addColumn( const QgsField &field, const QString &schema, const QString &tableName ) const throw( QgsProviderConnectionException );
%Docstring
Adds a column
Raises a QgsProviderConnectionException if any errors are encountered.

:param field: specification of the new column
:param schema: name of the schema (schema is ignored if not supported by the backend).
:param tableName: name of the table

.. 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``.
Expand Down
41 changes: 27 additions & 14 deletions python/core/auto_generated/qgsdataitem.sip.in
Expand Up @@ -905,7 +905,9 @@ Creates a new data item from the specified path.
class QgsFieldsItem : QgsDataItem
{
%Docstring
A collection of field items
A collection of field items with some internal logic to retrieve
the fields and a the vector layer instance from a connection URI,
the schema and the table name.

.. versionadded:: 3.16
%End
Expand All @@ -916,7 +918,6 @@ A collection of field items
public:

QgsFieldsItem( QgsDataItem *parent /TransferThis/,
const QString &name,
const QString &path,
const QString &connectionUri,
const QString &providerKey,
Expand All @@ -930,9 +931,7 @@ but QgsDataItem items pointing to different logical locations should always use
The \connectionUri argument is the connection part of the layer URI that it is used internally to create
a connection and retrieve fields information.
The ``providerKey`` string can be used to specify the key for the QgsDataItemProvider that created this item.
The ``name`` argument specifies the text to show in the model for the item. A translated string should
be used wherever appropriate.
The ``schema`` and ``tableName`` are used to retrieve the field information from the ``connectionUri``.
The ``schema`` and ``tableName`` are used to retrieve the layer and field information from the ``connectionUri``.
%End

~QgsFieldsItem();
Expand All @@ -943,25 +942,37 @@ The ``schema`` and ``tableName`` are used to retrieve the field information from
virtual QIcon icon();


protected:
QString schema() const;
%Docstring
Returns the schema name
%End

QString tableName() const;
%Docstring
Returns the table name
%End

static QIcon openFieldsIcon();
QString connectionUri() const;
%Docstring
Shared open fields icon.
Returns the connection URI
%End

static QIcon fieldsIcon();
QgsVectorLayer *layer();
%Docstring
Shared closed fields icon.
Creates and returns a (possibly NULL) layer instance
from the connection URI and schema/table information
%End


};


class QgsFieldItem : QgsDataItem
{
%Docstring
A layer field item
A layer field item, information about the connection URI, the schema and the
table as well as the layer instance the field belongs to can be retrieved
from the parent QgsFieldsItem object.

.. versionadded:: 3.16
%End
Expand All @@ -974,9 +985,11 @@ A layer field item
QgsFieldItem( QgsDataItem *parent /TransferThis/,
const QgsField &field );
%Docstring
Constructor for QgsFieldItem, with the specified ``parent`` item and /a field.
The ``name`` argument specifies the text to show in the model for the item. A translated string should
be used wherever appropriate.
Constructor for QgsFieldItem, with the specified ``parent`` item and ``field``.

.. note::

parent item must be a :py:class:`QgsFieldsItem`
%End

~QgsFieldItem();
Expand Down
133 changes: 133 additions & 0 deletions src/app/browser/qgsinbuiltdataitemproviders.cpp
Expand Up @@ -37,6 +37,9 @@
#include "processing/qgsprojectstylealgorithms.h"
#include "qgsstylemanagerdialog.h"
#include "qgsproviderregistry.h"
#include "qgsaddattrdialog.h"
#include "qgsabstractdatabaseproviderconnection.h"
#include "qgsprovidermetadata.h"

#include <QFileInfo>
#include <QMenu>
Expand Down Expand Up @@ -688,3 +691,133 @@ bool QgsProjectItemGuiProvider::handleDoubleClick( QgsDataItem *item, QgsDataIte
return false;
}
}

QString QgsFieldsItemGuiProvider::name()
{
return QStringLiteral( "fields_item" );
}

void QgsFieldsItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *menu, const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context )
{
Q_UNUSED( selectedItems )

if ( !item || item->type() != QgsDataItem::Type::Fields )
return;


if ( QgsFieldsItem *fieldsItem = qobject_cast<QgsFieldsItem *>( item ) )
{
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( fieldsItem->providerKey() ) };
if ( md )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn { static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( fieldsItem->connectionUri(), {} ) ) };
// Check if it is supported
if ( conn && conn->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::AddColumn ) )
{
QAction *addColumnAction = new QAction( tr( "New field…" ), menu );
connect( addColumnAction, &QAction::triggered, this, [ = ]
{
std::unique_ptr<QgsVectorLayer> layer { fieldsItem->layer( ) };
if ( layer )
{
QgsAddAttrDialog dialog( layer.get(), menu );
if ( dialog.exec() == QDialog::Accepted )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn2 { static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( fieldsItem->connectionUri(), {} ) ) };
try
{
conn2->addColumn( dialog.field(), fieldsItem->schema(), fieldsItem->tableName() );
item->refresh();
}
catch ( const QgsProviderConnectionException &ex )
{
if ( context.messageBar() )
{
context.messageBar()->pushCritical( tr( "New field" ), tr( "Failed to create a ew field to '%1': %2" ).arg( fieldsItem->tableName(), ex.what() ) );
}
else
{
QMessageBox::critical( menu, tr( "New field" ), tr( "Failed to create a new field to '%1': %2" ).arg( fieldsItem->tableName(), ex.what() ) );
}
}
}
}
else
{
const QString message { tr( "Failed to create layer '%1'. Check application logs and user permissions." ).arg( fieldsItem->tableName() ) };
if ( context.messageBar() )
{
context.messageBar()->pushCritical( tr( "Add field" ), message );
}
else
{
QMessageBox::critical( menu, tr( "Add field" ), message );
}
}
} );
menu->addAction( addColumnAction );
}
}
}
}

QString QgsFieldItemGuiProvider::name()
{
return QStringLiteral( "field_item" );
}

void QgsFieldItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *menu, const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context )
{
Q_UNUSED( selectedItems )

if ( !item || item->type() != QgsDataItem::Type::Field )
return;

if ( QgsFieldItem *fieldItem = qobject_cast<QgsFieldItem *>( item ) )
{
// Retrieve the connection from the parent
QgsFieldsItem *fieldsItem { static_cast<QgsFieldsItem *>( fieldItem->parent() ) };
if ( fieldsItem )
{
// Check if it is supported
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( fieldsItem->providerKey() ) };
if ( md )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn { static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( fieldsItem->connectionUri(), {} ) ) };
if ( conn && conn->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::DropColumn ) )
{
QAction *dropColumnAction = new QAction( tr( "Delete field…" ), menu );
connect( dropColumnAction, &QAction::triggered, this, [ = ]
{
if ( QMessageBox::warning( menu, tr( "Delete field" ), tr( "Delete '%1' permanently (with CASCADE)?" ).arg( item->name() ), QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Ok )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn2 { static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( fieldsItem->connectionUri(), {} ) ) };
try
{
conn2->dropColumn( item->name(), fieldsItem->schema(), fieldsItem->tableName(), true );
fieldsItem->refresh();
}
catch ( const QgsProviderConnectionException &ex )
{
if ( context.messageBar() )
{
context.messageBar()->pushCritical( tr( "Delete field" ), tr( "Failed to delete field '%1': %2" ).arg( item->name(), ex.what() ) );
}
else
{
QMessageBox::critical( menu, tr( "Delete field" ), tr( "Failed to delete field '%1': %2" ).arg( item->name(), ex.what() ) );
}
}
}
} );
menu->addAction( dropColumnAction );
}
}
}
else
{
// This should never happen!
QgsDebugMsg( QStringLiteral( "Error getting parent fields for %1" ).arg( item->name() ) );
}
}
}
35 changes: 35 additions & 0 deletions src/app/browser/qgsinbuiltdataitemproviders.h
Expand Up @@ -25,6 +25,8 @@
class QgsDirectoryItem;
class QgsFavoriteItem;
class QgsLayerItem;
class QgsFieldsItem;
class QgsFieldItem;

class QgsAppDirectoryItemGuiProvider : public QObject, public QgsDataItemGuiProvider
{
Expand Down Expand Up @@ -105,6 +107,39 @@ class QgsLayerItemGuiProvider : public QObject, public QgsDataItemGuiProvider
};


class QgsFieldsItemGuiProvider : public QObject, public QgsDataItemGuiProvider
{
Q_OBJECT

public:

QgsFieldsItemGuiProvider() = default;

QString name() override;

void populateContextMenu( QgsDataItem *item, QMenu *menu,
const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context ) override;

};


class QgsFieldItemGuiProvider : public QObject, public QgsDataItemGuiProvider
{
Q_OBJECT

public:

QgsFieldItemGuiProvider() = default;

QString name() override;

void populateContextMenu( QgsDataItem *item, QMenu *menu,
const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context ) override;

};



class QgsProjectItemGuiProvider : public QObject, public QgsDataItemGuiProvider
{
Q_OBJECT
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -1212,6 +1212,8 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsFavoritesItemGuiProvider() );
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsLayerItemGuiProvider() );
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsBookmarksItemGuiProvider() );
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsFieldsItemGuiProvider() );
QgsGui::instance()->dataItemGuiProviderRegistry()->addProvider( new QgsFieldItemGuiProvider() );

QShortcut *showBrowserDock = new QShortcut( QKeySequence( tr( "Ctrl+2" ) ), this );
connect( showBrowserDock, &QShortcut::activated, mBrowserWidget, &QgsDockWidget::toggleUserVisible );
Expand Down
2 changes: 1 addition & 1 deletion src/core/providers/ogr/qgsgeopackagedataitems.cpp
Expand Up @@ -323,7 +323,7 @@ QgsGeoPackageVectorLayerItem::QgsGeoPackageVectorLayerItem( QgsDataItem *parent,
QVector<QgsDataItem *> QgsGeoPackageVectorLayerItem::createChildren()
{
QVector<QgsDataItem *> children;
children.push_back( new QgsFieldsItem( this, tr( "Columns" ), collection()->path() + QStringLiteral( "/columns/ " ), collection()->path(), providerKey(), QString(), name() ) );
children.push_back( new QgsFieldsItem( this, collection()->path() + QStringLiteral( "/columns/ " ), collection()->path(), providerKey(), QString(), name() ) );
return children;
}

Expand Down
4 changes: 3 additions & 1 deletion src/core/providers/ogr/qgsgeopackageproviderconnection.cpp
Expand Up @@ -317,7 +317,9 @@ void QgsGeoPackageProviderConnection::setDefaultCapabilities()
Capability::ExecuteSql,
Capability::CreateSpatialIndex,
Capability::SpatialIndexExists,
Capability::DeleteSpatialIndex
Capability::DeleteSpatialIndex,
Capability::DropColumn,
Capability::AddColumn
};
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,4,0)
mCapabilities |= Capability::DropRasterTable;
Expand Down
2 changes: 1 addition & 1 deletion src/core/providers/ogr/qgsogrdataitems.cpp
Expand Up @@ -68,7 +68,7 @@ QVector<QgsDataItem *> QgsOgrLayerItem::createChildren()
// Proxy to spatialite provider data items because it implements the connections API
if ( mDriverName == QStringLiteral( "SQLite" ) )
{
children.push_back( new QgsFieldsItem( this, tr( "Columns" ),
children.push_back( new QgsFieldsItem( this,
path() + QStringLiteral( "/columns/ " ),
QStringLiteral( R"(dbname="%1")" ).arg( parent()->path().replace( '"', QStringLiteral( R"(\")" ) ) ),
QStringLiteral( "spatialite" ), QString(), name() ) );
Expand Down

0 comments on commit f2858b2

Please sign in to comment.