Skip to content

Commit

Permalink
Add update and delete relationship api
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Nov 30, 2022
1 parent ff3048d commit 30899be
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 1 deletion.
Expand Up @@ -358,6 +358,8 @@ This information is calculated from the geometry columns types.
RenameField,
RetrieveRelationships,
AddRelationship,
UpdateRelationship,
DeleteRelationship,
};
typedef QFlags<QgsAbstractDatabaseProviderConnection::Capability> Capabilities;

Expand Down Expand Up @@ -805,6 +807,24 @@ Adds a new field ``relationship`` to the database.

:raises QgsProviderConnectionException: if any errors are encountered.

.. versionadded:: 3.30
%End

virtual void updateRelationship( const QgsWeakRelation &relationship ) const throw( QgsProviderConnectionException );
%Docstring
Updates an existing ``relationship`` in the database.

:raises QgsProviderConnectionException: if any errors are encountered.

.. versionadded:: 3.30
%End

virtual void deleteRelationship( const QgsWeakRelation &relationship ) const throw( QgsProviderConnectionException );
%Docstring
Deletes an existing ``relationship`` in the database.

:raises QgsProviderConnectionException: if any errors are encountered.

.. versionadded:: 3.30
%End

Expand Down
83 changes: 83 additions & 0 deletions src/core/providers/ogr/qgsogrproviderconnection.cpp
Expand Up @@ -497,6 +497,14 @@ void QgsOgrProviderConnection::setDefaultCapabilities()
{
mCapabilities |= Capability::AddRelationship;
}
if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_UPDATE_RELATIONSHIP, false ) )
{
mCapabilities |= Capability::UpdateRelationship;
}
if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_DELETE_RELATIONSHIP, false ) )
{
mCapabilities |= Capability::DeleteRelationship;
}
#endif

mSqlLayerDefinitionCapabilities =
Expand Down Expand Up @@ -1013,4 +1021,79 @@ void QgsOgrProviderConnection::addRelationship( const QgsWeakRelation &relations
#endif
}

void QgsOgrProviderConnection::updateRelationship( const QgsWeakRelation &relationship ) const
{
checkCapability( Capability::UpdateRelationship );

#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
gdal::ogr_datasource_unique_ptr hDS( GDALOpenEx( uri().toUtf8().constData(), GDAL_OF_UPDATE | GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
if ( hDS )
{
const QVariantMap leftParts = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) )->decodeUri( relationship.referencedLayerSource() );
const QString leftTableName = leftParts.value( QStringLiteral( "layerName" ) ).toString();
if ( leftTableName.isEmpty() )
throw QgsProviderConnectionException( QObject::tr( "Parent table name was not set" ) );

const QVariantMap rightParts = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) )->decodeUri( relationship.referencingLayerSource() );
const QString rightTableName = rightParts.value( QStringLiteral( "layerName" ) ).toString();
if ( rightTableName.isEmpty() )
throw QgsProviderConnectionException( QObject::tr( "Child table name was not set" ) );

QString error;
gdal::relationship_unique_ptr relationH = QgsOgrUtils::convertRelationship( relationship, error );
if ( !relationH )
{
throw QgsProviderConnectionException( error );
}

char *failureReason = nullptr;
if ( !GDALDatasetUpdateRelationship( hDS.get(), relationH.get(), &failureReason ) )
{
QString error( failureReason );
CPLFree( failureReason );
throw QgsProviderConnectionException( QObject::tr( "Could not update relationship: %1" ).arg( error ) );
}

CPLFree( failureReason );
}
else
{
throw QgsProviderConnectionException( QObject::tr( "There was an error opening the dataset %1!" ).arg( uri() ) );
}
#else
Q_UNUSED( tableName )
throw QgsProviderConnectionException( QObject::tr( "Updating relationships for datasets requires GDAL 3.6 or later" ) );
#endif
}

void QgsOgrProviderConnection::deleteRelationship( const QgsWeakRelation &relationship ) const
{
checkCapability( Capability::DeleteRelationship );

#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
gdal::ogr_datasource_unique_ptr hDS( GDALOpenEx( uri().toUtf8().constData(), GDAL_OF_UPDATE | GDAL_OF_VECTOR, nullptr, nullptr, nullptr ) );
if ( hDS )
{
const QString relationshipName = relationship.name();

char *failureReason = nullptr;
if ( !GDALDatasetDeleteRelationship( hDS.get(), relationshipName.toLocal8Bit().constData(), &failureReason ) )
{
QString error( failureReason );
CPLFree( failureReason );
throw QgsProviderConnectionException( QObject::tr( "Could not delete relationship: %1" ).arg( error ) );
}

CPLFree( failureReason );
}
else
{
throw QgsProviderConnectionException( QObject::tr( "There was an error opening the dataset %1!" ).arg( uri() ) );
}
#else
Q_UNUSED( tableName )
throw QgsProviderConnectionException( QObject::tr( "Deleting relationships for datasets requires GDAL 3.6 or later" ) );
#endif
}

///@endcond
2 changes: 2 additions & 0 deletions src/core/providers/ogr/qgsogrproviderconnection.h
Expand Up @@ -92,6 +92,8 @@ class QgsOgrProviderConnection : public QgsAbstractDatabaseProviderConnection
SqlVectorLayerOptions sqlOptions( const QString &layerSource ) override;
QList< QgsWeakRelation > relationships( const QString &schema = QString(), const QString &tableName = QString() ) const override;
void addRelationship( const QgsWeakRelation &relationship ) const override;
void updateRelationship( const QgsWeakRelation &relationship ) const override;
void deleteRelationship( const QgsWeakRelation &relationship ) const override;

protected:

Expand Down
10 changes: 10 additions & 0 deletions src/core/providers/qgsabstractdatabaseproviderconnection.cpp
Expand Up @@ -1345,6 +1345,16 @@ void QgsAbstractDatabaseProviderConnection::addRelationship( const QgsWeakRelati
checkCapability( Capability::AddRelationship );
}

void QgsAbstractDatabaseProviderConnection::updateRelationship( const QgsWeakRelation & ) const
{
checkCapability( Capability::UpdateRelationship );
}

void QgsAbstractDatabaseProviderConnection::deleteRelationship( const QgsWeakRelation & ) const
{
checkCapability( Capability::DeleteRelationship );
}

QString QgsAbstractDatabaseProviderConnection::TableProperty::defaultName() const
{
QString n = mTableName;
Expand Down
18 changes: 18 additions & 0 deletions src/core/providers/qgsabstractdatabaseproviderconnection.h
Expand Up @@ -510,6 +510,8 @@ class CORE_EXPORT QgsAbstractDatabaseProviderConnection : public QgsAbstractProv
RenameField = 1 << 26, //!< Can rename existing fields via renameField() (since QGIS 3.28)
RetrieveRelationships = 1 << 27, //!< Can retrieve relationships from the database (since QGIS 3.28)
AddRelationship = 1 << 28, //!< Can add new relationships to the database via addRelationship() (since QGIS 3.30)
UpdateRelationship = 1 << 29, //!< Can update existing relationships in the database via updateRelationship() (since QGIS 3.30)
DeleteRelationship = 1 << 30, //!< Can delete existing relationships from the database via deleteRelationship() (since QGIS 3.30)
};
Q_ENUM( Capability )
Q_DECLARE_FLAGS( Capabilities, Capability )
Expand Down Expand Up @@ -916,6 +918,22 @@ class CORE_EXPORT QgsAbstractDatabaseProviderConnection : public QgsAbstractProv
*/
virtual void addRelationship( const QgsWeakRelation &relationship ) const SIP_THROW( QgsProviderConnectionException );

/**
* Updates an existing \a relationship in the database.
*
* \throws QgsProviderConnectionException if any errors are encountered.
* \since QGIS 3.30
*/
virtual void updateRelationship( const QgsWeakRelation &relationship ) const SIP_THROW( QgsProviderConnectionException );

/**
* Deletes an existing \a relationship in the database.
*
* \throws QgsProviderConnectionException if any errors are encountered.
* \since QGIS 3.30
*/
virtual void deleteRelationship( const QgsWeakRelation &relationship ) const SIP_THROW( QgsProviderConnectionException );

/**
* Returns a SQL query builder for the connection, which provides an interface for provider-specific creation of SQL queries.
*
Expand Down
41 changes: 40 additions & 1 deletion tests/src/python/test_provider_ogr.py
Expand Up @@ -2895,7 +2895,7 @@ def test_provider_connection_relationships(self):
self.assertFalse(relationships)

@unittest.skipIf(int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(3, 6, 0), "GDAL 3.6 required")
def test_provider_connection_create_relationship(self):
def test_provider_connection_modify_relationship(self):
"""
Test creating relationship via the connections API
"""
Expand Down Expand Up @@ -2951,6 +2951,45 @@ def test_provider_connection_create_relationship(self):
self.assertEqual(result.referencingLayerFields(), ['fieldb'])
self.assertEqual(result.referencedLayerFields(), ['fielda'])

# update relationship
rel.setReferencedLayerFields(['fieldc'])
rel.setReferencingLayerFields(['fieldd'])

conn.updateRelationship(rel)
relationships = conn.relationships()
self.assertEqual(len(relationships), 1)

result = relationships[0]
self.assertEqual(result.name(), 'rel_name')
self.assertEqual(result.referencingLayerSource(), tmpfile + '|layername=child')
self.assertEqual(result.referencedLayerSource(), tmpfile + '|layername=parent')
self.assertEqual(result.referencingLayerFields(), ['fieldd'])
self.assertEqual(result.referencedLayerFields(), ['fieldc'])

# try updating non-existing relationship
rel2 = QgsWeakRelation('id',
'dont_exist',
Qgis.RelationshipStrength.Association,
'referencing_id',
'referencing_name',
tmpfile + '|layername=child',
'ogr',
'referenced_id',
'referenced_name',
tmpfile + '|layername=parent',
'ogr'
)
with self.assertRaises(QgsProviderConnectionException):
conn.updateRelationship(rel2)

# delete
with self.assertRaises(QgsProviderConnectionException):
conn.deleteRelationship(rel2)

conn.deleteRelationship(rel)
relationships = conn.relationships()
self.assertFalse(relationships)

def testUniqueGeometryType(self):
"""
Test accessing a layer of type wkbUnknown that contains a single geometry type but also null geometries
Expand Down

0 comments on commit 30899be

Please sign in to comment.