Skip to content
Permalink
Browse files

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 f2858b24f20aca964be600ee280f63b3326fa796
@@ -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;
@@ -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``.
@@ -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
@@ -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,
@@ -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();
@@ -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
@@ -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();
@@ -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>
@@ -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() ) );
}
}
}
@@ -25,6 +25,8 @@
class QgsDirectoryItem;
class QgsFavoriteItem;
class QgsLayerItem;
class QgsFieldsItem;
class QgsFieldItem;

class QgsAppDirectoryItemGuiProvider : public QObject, public QgsDataItemGuiProvider
{
@@ -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
@@ -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 );
@@ -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;
}

@@ -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;
@@ -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() ) );

0 comments on commit f2858b2

Please sign in to comment.
You can’t perform that action at this time.