Skip to content

Commit

Permalink
[api] Add method for creating relationship to QgsAbstractDatabaseProv…
Browse files Browse the repository at this point in the history
…iderConnection

and implement for OGR provider when built on GDAL >= 3.6
  • Loading branch information
nyalldawson committed Nov 30, 2022
1 parent 724bf9e commit ff3048d
Show file tree
Hide file tree
Showing 9 changed files with 680 additions and 116 deletions.
Expand Up @@ -357,6 +357,7 @@ This information is calculated from the geometry columns types.
AddFieldDomain,
RenameField,
RetrieveRelationships,
AddRelationship,
};
typedef QFlags<QgsAbstractDatabaseProviderConnection::Capability> Capabilities;

Expand Down Expand Up @@ -796,6 +797,15 @@ forms the left (or "parent" / "referenced") side of the relationship are retriev
:raises QgsProviderConnectionException: if any errors are encountered.

.. versionadded:: 3.28
%End

virtual void addRelationship( const QgsWeakRelation &relationship ) const throw( QgsProviderConnectionException );
%Docstring
Adds a new field ``relationship`` to the database.

:raises QgsProviderConnectionException: if any errors are encountered.

.. versionadded:: 3.30
%End

virtual QgsProviderSqlQueryBuilder *queryBuilder() const /Factory/;
Expand Down
161 changes: 47 additions & 114 deletions src/core/providers/ogr/qgsogrproviderconnection.cpp
Expand Up @@ -493,6 +493,10 @@ void QgsOgrProviderConnection::setDefaultCapabilities()
{
mCapabilities |= Capability::RetrieveRelationships;
}
if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_CREATE_RELATIONSHIP, false ) )
{
mCapabilities |= Capability::AddRelationship;
}
#endif

mSqlLayerDefinitionCapabilities =
Expand Down Expand Up @@ -936,9 +940,6 @@ QList<QgsWeakRelation> QgsOgrProviderConnection::relationships( const QString &s
const QStringList names = QgsOgrUtils::cStringListToQStringList( relationNames );
CSLDestroy( relationNames );

QgsProviderMetadata *ogrProviderMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
const QVariantMap datasetUriParts = ogrProviderMetadata->decodeUri( uri() );

for ( const QString &name : names )
{
GDALRelationshipH relationship = GDALDatasetGetRelationship( hDS.get(), name.toUtf8().constData() );
Expand All @@ -949,134 +950,66 @@ QList<QgsWeakRelation> QgsOgrProviderConnection::relationships( const QString &s
if ( !tableName.isEmpty() && leftTableName != tableName )
continue;

QVariantMap leftTableUriParts = datasetUriParts;
leftTableUriParts.insert( QStringLiteral( "layerName" ), leftTableName );
const QString leftTableSource = ogrProviderMetadata->encodeUri( leftTableUriParts );

const QString rightTableName( GDALRelationshipGetRightTableName( relationship ) );
if ( rightTableName.isEmpty() )
continue;
QVariantMap rightTableUriParts = datasetUriParts;
rightTableUriParts.insert( QStringLiteral( "layerName" ), rightTableName );
const QString rightTableSource = ogrProviderMetadata->encodeUri( rightTableUriParts );

const QString mappingTableName( GDALRelationshipGetMappingTableName( relationship ) );
QString mappingTableSource;
if ( !mappingTableName.isEmpty() )
{
QVariantMap mappingTableUriParts = datasetUriParts;
mappingTableUriParts.insert( QStringLiteral( "layerName" ), mappingTableName );
mappingTableSource = ogrProviderMetadata->encodeUri( mappingTableUriParts );
}

const QString relationshipName( GDALRelationshipGetName( relationship ) );

char **cslLeftTableFieldNames = GDALRelationshipGetLeftTableFields( relationship );
const QStringList leftTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslLeftTableFieldNames );
CSLDestroy( cslLeftTableFieldNames );

char **cslRightTableFieldNames = GDALRelationshipGetRightTableFields( relationship );
const QStringList rightTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslRightTableFieldNames );
CSLDestroy( cslRightTableFieldNames );

char **cslLeftMappingTableFieldNames = GDALRelationshipGetLeftMappingTableFields( relationship );
const QStringList leftMappingTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslLeftMappingTableFieldNames );
CSLDestroy( cslLeftMappingTableFieldNames );

char **cslRightMappingTableFieldNames = GDALRelationshipGetRightMappingTableFields( relationship );
const QStringList rightMappingTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslRightMappingTableFieldNames );
CSLDestroy( cslRightMappingTableFieldNames );

const QString forwardPathLabel( GDALRelationshipGetForwardPathLabel( relationship ) );
const QString backwardPathLabel( GDALRelationshipGetBackwardPathLabel( relationship ) );
const QString relatedTableType( GDALRelationshipGetRelatedTableType( relationship ) );

const GDALRelationshipType relationshipType = GDALRelationshipGetType( relationship );
Qgis::RelationshipStrength strength = Qgis::RelationshipStrength::Association;
switch ( relationshipType )
{
case GRT_COMPOSITE:
strength = Qgis::RelationshipStrength::Composition;
break;
output.append( QgsOgrUtils::convertRelationship( relationship, uri() ) );
}
return output;
}
else
{
throw QgsProviderConnectionException( QObject::tr( "There was an error opening the dataset %1!" ).arg( uri() ) );
}
#else
Q_UNUSED( tableName )
throw QgsProviderConnectionException( QObject::tr( "Retrieving relationships for datasets requires GDAL 3.6 or later" ) );
#endif
}

case GRT_ASSOCIATION:
strength = Qgis::RelationshipStrength::Association;
break;
void QgsOgrProviderConnection::addRelationship( const QgsWeakRelation &relationship ) const
{
checkCapability( Capability::AddRelationship );

case GRT_AGGREGATION:
QgsLogger::warning( "Aggregation relationships are not supported" );
continue;
}
#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 GDALRelationshipCardinality eCardinality = GDALRelationshipGetCardinality( relationship );
Qgis::RelationshipCardinality cardinality = Qgis::RelationshipCardinality::OneToOne;
switch ( eCardinality )
{
case GRC_ONE_TO_ONE:
cardinality = Qgis::RelationshipCardinality::OneToOne;
break;
case GRC_ONE_TO_MANY:
cardinality = Qgis::RelationshipCardinality::OneToMany;
break;
case GRC_MANY_TO_ONE:
cardinality = Qgis::RelationshipCardinality::ManyToOne;
break;
case GRC_MANY_TO_MANY:
cardinality = Qgis::RelationshipCardinality::ManyToMany;
break;
}
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" ) );

switch ( cardinality )
{
case Qgis::RelationshipCardinality::OneToOne:
case Qgis::RelationshipCardinality::OneToMany:
case Qgis::RelationshipCardinality::ManyToOne:
{
QgsWeakRelation rel( relationshipName,
relationshipName,
strength,
QString(), QString(), rightTableSource, QStringLiteral( "ogr" ),
QString(), QString(), leftTableSource, QStringLiteral( "ogr" ) );
rel.setCardinality( cardinality );
rel.setForwardPathLabel( forwardPathLabel );
rel.setBackwardPathLabel( backwardPathLabel );
rel.setRelatedTableType( relatedTableType );
rel.setReferencedLayerFields( leftTableFieldNames );
rel.setReferencingLayerFields( rightTableFieldNames );
output.append( rel );
break;
}
QString error;
gdal::relationship_unique_ptr relationH = QgsOgrUtils::convertRelationship( relationship, error );
if ( !relationH )
{
throw QgsProviderConnectionException( error );
}

case Qgis::RelationshipCardinality::ManyToMany:
{
QgsWeakRelation rel( relationshipName,
relationshipName,
strength,
QString(), QString(), rightTableSource, QStringLiteral( "ogr" ),
QString(), QString(), leftTableSource, QStringLiteral( "ogr" ) );
rel.setCardinality( cardinality );
rel.setForwardPathLabel( forwardPathLabel );
rel.setBackwardPathLabel( backwardPathLabel );
rel.setRelatedTableType( relatedTableType );
rel.setMappingTable( QgsVectorLayerRef( QString(), QString(), mappingTableSource, QStringLiteral( "ogr" ) ) );
rel.setReferencedLayerFields( leftTableFieldNames );
rel.setMappingReferencedLayerFields( leftMappingTableFieldNames );
rel.setReferencingLayerFields( rightTableFieldNames );
rel.setMappingReferencingLayerFields( rightMappingTableFieldNames );
output.append( rel );
break;
}
}
char *failureReason = nullptr;
if ( !GDALDatasetAddRelationship( hDS.get(), relationH.get(), &failureReason ) )
{
QString error( failureReason );
CPLFree( failureReason );
throw QgsProviderConnectionException( QObject::tr( "Could not create relationship: %1" ).arg( error ) );
}
return output;

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( "Retrieving relationships for datasets requires GDAL 3.6 or later" ) );
throw QgsProviderConnectionException( QObject::tr( "Adding relationships for datasets requires GDAL 3.6 or later" ) );
#endif
}

Expand Down
1 change: 1 addition & 0 deletions src/core/providers/ogr/qgsogrproviderconnection.h
Expand Up @@ -91,6 +91,7 @@ class QgsOgrProviderConnection : public QgsAbstractDatabaseProviderConnection
void renameField( const QString &schema, const QString &tableName, const QString &name, const QString &newName ) const override;
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;

protected:

Expand Down
5 changes: 5 additions & 0 deletions src/core/providers/qgsabstractdatabaseproviderconnection.cpp
Expand Up @@ -1340,6 +1340,11 @@ QList< QgsWeakRelation > QgsAbstractDatabaseProviderConnection::relationships( c
return {};
}

void QgsAbstractDatabaseProviderConnection::addRelationship( const QgsWeakRelation & ) const
{
checkCapability( Capability::AddRelationship );
}

QString QgsAbstractDatabaseProviderConnection::TableProperty::defaultName() const
{
QString n = mTableName;
Expand Down
9 changes: 9 additions & 0 deletions src/core/providers/qgsabstractdatabaseproviderconnection.h
Expand Up @@ -509,6 +509,7 @@ class CORE_EXPORT QgsAbstractDatabaseProviderConnection : public QgsAbstractProv
AddFieldDomain = 1 << 25, //!< Can add new field domains to the database via addFieldDomain() (since QGIS 3.26)
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)
};
Q_ENUM( Capability )
Q_DECLARE_FLAGS( Capabilities, Capability )
Expand Down Expand Up @@ -907,6 +908,14 @@ class CORE_EXPORT QgsAbstractDatabaseProviderConnection : public QgsAbstractProv
*/
virtual QList< QgsWeakRelation > relationships( const QString &schema = QString(), const QString &tableName = QString() ) const SIP_THROW( QgsProviderConnectionException );

/**
* Adds a new field \a relationship to the database.
*
* \throws QgsProviderConnectionException if any errors are encountered.
* \since QGIS 3.30
*/
virtual void addRelationship( 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

0 comments on commit ff3048d

Please sign in to comment.