Skip to content

Commit

Permalink
GPKG Allow table creation from file browser
Browse files Browse the repository at this point in the history
Fixes #41342

(cherry picked from commit 95573cf)
  • Loading branch information
elpaso authored and nyalldawson committed Feb 19, 2021
1 parent a923341 commit 7b3c4f3
Showing 1 changed file with 114 additions and 80 deletions.
194 changes: 114 additions & 80 deletions src/app/browser/qgsinbuiltdataitemproviders.cpp
Expand Up @@ -877,105 +877,139 @@ QString QgsDatabaseItemGuiProvider::name()
void QgsDatabaseItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *menu, const QList<QgsDataItem *> &selectedItems, QgsDataItemGuiContext context )
{
Q_UNUSED( selectedItems )

// Small helper to get a connection from either a connection name (if it's stored) or a data item path
auto getConnectionFromDataItem = [ ]( QgsDataCollectionItem * collectionItem, bool isSchema ) -> QgsAbstractDatabaseProviderConnection *
{
// This is super messy: we need the QgsDataProvider key and NOT the QgsDataItemProvider key!
const QString dataProviderKey { QgsApplication::dataItemProviderRegistry()->dataProviderKey( collectionItem->providerKey() ) };
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( dataProviderKey ) };
if ( ! md )
return nullptr;

const QString connectionName { isSchema ? collectionItem->parent()->name() : collectionItem->name() };
QString connectionPath { isSchema ? collectionItem->parent()->path() : collectionItem->path() };
// The path is normally prefixed with the data item provider key (which it NOT necessarily the data provider name)
// such as "gpkg://<path_to_gpkg>": remove it.
connectionPath.remove( QRegularExpression( QStringLiteral( R"re(^[aZ]{2,}://)re" ) ) );
try
{
// If we are coming here from the browser, there is no stored connection
if ( md->findConnection( connectionName ) )
{
return static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( connectionName ) );
}
else
{
return static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( connectionPath, {} ) );
}
}
catch ( QgsProviderConnectionException &ex )
{
// This is expected and it is not an error in case the provider does not implement the connections API
}
return nullptr;
};

// Add create new table for collection items but not not if it is a root item
if ( ! qobject_cast<QgsConnectionsRootItem *>( item ) )
{
if ( QgsDataCollectionItem * collectionItem { qobject_cast<QgsDataCollectionItem *>( item ) } )
{
// This is super messy: we need the QgsDataProvider key and NOT the QgsDataItemProvider key!
const QString dataProviderKey { QgsApplication::dataItemProviderRegistry()->dataProviderKey( collectionItem->providerKey() ) };
QgsProviderMetadata *md { QgsProviderRegistry::instance()->providerMetadata( dataProviderKey ) };
if ( md )
// Note: we could have used layerCollection() but casting to QgsDatabaseSchemaItem is more explicit
const bool isSchema { qobject_cast<QgsDatabaseSchemaItem *>( collectionItem ) != nullptr };
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn( getConnectionFromDataItem( collectionItem, isSchema ) );

if ( conn && conn->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::CreateVectorTable ) )
{
// Note: we could have used layerCollection() but casting to QgsDatabaseSchemaItem is more explicit
const bool isSchema { qobject_cast<QgsDatabaseSchemaItem *>( item ) != nullptr };
const QString connectionName { isSchema ? collectionItem->parent()->name() : collectionItem->name() };
// Not all data providers implement the connections API, let's try ...
try

QAction *newTableAction = new QAction( QObject::tr( "New Table…" ), menu );

QObject::connect( newTableAction, &QAction::triggered, collectionItem, [ &getConnectionFromDataItem, isSchema, collectionItem, context]
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn( static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( connectionName ) ) );
if ( conn && conn->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::CreateVectorTable ) )

std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn2( getConnectionFromDataItem( collectionItem, isSchema ) );
// This should never happen but let's play safe
if ( ! conn2 )
{
QgsMessageLog::logMessage( tr( "Connection to the database (%1) was lost." ).arg( collectionItem->name() ) );
return;
}

QgsNewVectorTableDialog dlg { conn2.get(), nullptr };
dlg.setCrs( QgsProject::instance()->defaultCrsForNewLayers() );

if ( isSchema )
{
dlg.setSchemaName( collectionItem->name() );
}

if ( dlg.exec() == QgsNewVectorTableDialog::DialogCode::Accepted )
{
QAction *newTableAction = new QAction( QObject::tr( "New Table…" ), menu );
QObject::connect( newTableAction, &QAction::triggered, collectionItem, [ collectionItem, connectionName, md, isSchema, context]
const QgsFields fields { dlg.fields() };
const QString tableName { dlg.tableName() };
const QString schemaName { dlg.schemaName() };
const QString geometryColumn { dlg.geometryColumnName() };
const QgsWkbTypes::Type geometryType { dlg.geometryType() };
const bool createSpatialIndex { dlg.createSpatialIndex() &&
geometryType != QgsWkbTypes::NoGeometry &&
geometryType != QgsWkbTypes::Unknown };
const QgsCoordinateReferenceSystem crs { dlg.crs( ) };
// This flag tells to the provider that field types do not need conversion
QMap<QString, QVariant> options { { QStringLiteral( "skipConvertFields" ), true } };

if ( ! geometryColumn.isEmpty() )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn2 { static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( connectionName ) ) };
QgsNewVectorTableDialog dlg { conn2.get(), nullptr };
dlg.setCrs( QgsProject::instance()->defaultCrsForNewLayers() );
if ( isSchema )
{
dlg.setSchemaName( collectionItem->name() );
}
if ( dlg.exec() == QgsNewVectorTableDialog::DialogCode::Accepted )
{
const QgsFields fields { dlg.fields() };
const QString tableName { dlg.tableName() };
const QString schemaName { dlg.schemaName() };
const QString geometryColumn { dlg.geometryColumnName() };
const QgsWkbTypes::Type geometryType { dlg.geometryType() };
const bool createSpatialIndex { dlg.createSpatialIndex() &&
geometryType != QgsWkbTypes::NoGeometry &&
geometryType != QgsWkbTypes::Unknown };
const QgsCoordinateReferenceSystem crs { dlg.crs( ) };
// This flag tells to the provider that field types do not need conversion
QMap<QString, QVariant> options { { QStringLiteral( "skipConvertFields" ), true } };

if ( ! geometryColumn.isEmpty() )
{
options[ QStringLiteral( "geometryColumn" ) ] = geometryColumn;
}
options[ QStringLiteral( "geometryColumn" ) ] = geometryColumn;
}

try
{
conn2->createVectorTable( schemaName, tableName, fields, geometryType, crs, true, &options );
if ( createSpatialIndex && conn2->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::CreateSpatialIndex ) )
{
try
{
conn2->createVectorTable( schemaName, tableName, fields, geometryType, crs, true, &options );
if ( createSpatialIndex && conn2->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::CreateSpatialIndex ) )
{
try
{
conn2->createSpatialIndex( schemaName, tableName );
}
catch ( QgsProviderConnectionException &ex )
{
notify( QObject::tr( "Create Spatial Index" ), QObject::tr( "Could not create spatial index for table '%1':%2." ).arg( tableName, ex.what() ), context, Qgis::MessageLevel::Warning );
}
}
// Ok, here is the trick: we cannot refresh the connection item because the refresh is not
// recursive.
// So, we check if the item is a schema or not, if it's not it means we initiated the new table from
// the parent connection item, hence we search for the schema item and refresh it instead of refreshing
// the connection item (the parent) with no effects.
if ( ! isSchema && conn2->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::Schemas ) )
{
const auto constChildren { collectionItem->children() };
for ( const auto &c : constChildren )
{
if ( c->name() == schemaName )
{
c->refresh();
}
}
}
else
{
collectionItem->refresh( );
}
notify( QObject::tr( "New Table Created" ), QObject::tr( "Table '%1' was created successfully." ).arg( tableName ), context, Qgis::MessageLevel::Success );
conn2->createSpatialIndex( schemaName, tableName );
}
catch ( QgsProviderConnectionException &ex )
{
notify( QObject::tr( "New Table Creation Error" ), QObject::tr( "Error creating new table '%1': %2" ).arg( tableName, ex.what() ), context, Qgis::MessageLevel::Critical );
notify( QObject::tr( "Create Spatial Index" ), QObject::tr( "Could not create spatial index for table '%1':%2." ).arg( tableName, ex.what() ), context, Qgis::MessageLevel::Warning );
}

}
} );
menu->addAction( newTableAction );
// Ok, here is the trick: we cannot refresh the connection item because the refresh is not
// recursive.
// So, we check if the item is a schema or not, if it's not it means we initiated the new table from
// the parent connection item, hence we search for the schema item and refresh it instead of refreshing
// the connection item (the parent) with no effects.
if ( ! isSchema && conn2->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::Schemas ) )
{
const auto constChildren { collectionItem->children() };
for ( const auto &c : constChildren )
{
if ( c->name() == schemaName )
{
c->refresh();
}
}
}
else
{
collectionItem->refresh( );
}
notify( QObject::tr( "New Table Created" ), QObject::tr( "Table '%1' was created successfully." ).arg( tableName ), context, Qgis::MessageLevel::Success );
}
catch ( QgsProviderConnectionException &ex )
{
notify( QObject::tr( "New Table Creation Error" ), QObject::tr( "Error creating new table '%1': %2" ).arg( tableName, ex.what() ), context, Qgis::MessageLevel::Critical );
}

}
}
catch ( QgsProviderConnectionException &ex )
{
// This is expected and it is not an error in case the provider does not implement the connections API
}
} );
menu->addAction( newTableAction );
}
}
}
}


0 comments on commit 7b3c4f3

Please sign in to comment.