[api] Add method for creating relationship to QgsAbstractDatabaseProviderConnection

and implement for OGR provider when built on GDAL >= 3.6
This commit is contained in:
Nyall Dawson 2022-11-29 09:27:21 +10:00
parent 724bf9e970
commit ff3048d19a
9 changed files with 683 additions and 119 deletions

View File

@ -357,6 +357,7 @@ This information is calculated from the geometry columns types.
AddFieldDomain,
RenameField,
RetrieveRelationships,
AddRelationship,
};
typedef QFlags<QgsAbstractDatabaseProviderConnection::Capability> Capabilities;
@ -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/;

View File

@ -493,6 +493,10 @@ void QgsOgrProviderConnection::setDefaultCapabilities()
{
mCapabilities |= Capability::RetrieveRelationships;
}
if ( CSLFetchBoolean( driverMetadata, GDAL_DCAP_CREATE_RELATIONSHIP, false ) )
{
mCapabilities |= Capability::AddRelationship;
}
#endif
mSqlLayerDefinitionCapabilities =
@ -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() );
@ -949,124 +950,11 @@ 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;
case GRT_ASSOCIATION:
strength = Qgis::RelationshipStrength::Association;
break;
case GRT_AGGREGATION:
QgsLogger::warning( "Aggregation relationships are not supported" );
continue;
}
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;
}
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;
}
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;
}
}
output.append( QgsOgrUtils::convertRelationship( relationship, uri() ) );
}
return output;
}
@ -1080,4 +968,49 @@ QList<QgsWeakRelation> QgsOgrProviderConnection::relationships( const QString &s
#endif
}
void QgsOgrProviderConnection::addRelationship( const QgsWeakRelation &relationship ) const
{
checkCapability( Capability::AddRelationship );
#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 ( !GDALDatasetAddRelationship( hDS.get(), relationH.get(), &failureReason ) )
{
QString error( failureReason );
CPLFree( failureReason );
throw QgsProviderConnectionException( QObject::tr( "Could not create 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( "Adding relationships for datasets requires GDAL 3.6 or later" ) );
#endif
}
///@endcond

View File

@ -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:

View File

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

View File

@ -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 )
@ -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.
*

View File

@ -21,7 +21,6 @@
#include "qgslinestring.h"
#include "qgsmultipoint.h"
#include "qgsmultilinestring.h"
#include "qgsogrprovider.h"
#include "qgslinesymbollayer.h"
#include "qgspolygon.h"
#include "qgsmultipolygon.h"
@ -38,6 +37,10 @@
#include "qgsfielddomain.h"
#include "qgsfontmanager.h"
#include "qgsvariantutils.h"
#include "qgsweakrelation.h"
#include "qgsproviderregistry.h"
#include "qgsprovidermetadata.h"
#include "qgsogrproviderutils.h"
#include <QTextCodec>
#include <QUuid>
@ -103,6 +106,13 @@ void gdal::GDALWarpOptionsDeleter::operator()( GDALWarpOptions *options ) const
GDALDestroyWarpOptions( options );
}
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
void gdal::GDALRelationshipDeleter::operator()( GDALRelationshipH relationship ) const
{
GDALDestroyRelationship( relationship );
}
#endif
QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType type )
{
if ( !value || OGR_RawField_IsUnset( value ) || OGR_RawField_IsNull( value ) )
@ -2200,3 +2210,287 @@ OGRFieldDomainH QgsOgrUtils::convertFieldDomain( const QgsFieldDomain *domain )
}
#endif
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
QgsWeakRelation QgsOgrUtils::convertRelationship( GDALRelationshipH relationship, const QString &datasetUri )
{
QgsProviderMetadata *ogrProviderMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
const QVariantMap datasetUriParts = ogrProviderMetadata->decodeUri( datasetUri );
const QString leftTableName( GDALRelationshipGetLeftTableName( relationship ) );
QVariantMap leftTableUriParts = datasetUriParts;
leftTableUriParts.insert( QStringLiteral( "layerName" ), leftTableName );
const QString leftTableSource = ogrProviderMetadata->encodeUri( leftTableUriParts );
const QString rightTableName( GDALRelationshipGetRightTableName( relationship ) );
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;
case GRT_ASSOCIATION:
strength = Qgis::RelationshipStrength::Association;
break;
case GRT_AGGREGATION:
QgsLogger::warning( "Aggregation relationships are not supported, treating as association instead" );
break;
}
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;
}
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 );
return rel;
}
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 );
return rel;
}
}
return QgsWeakRelation();
}
gdal::relationship_unique_ptr QgsOgrUtils::convertRelationship( const QgsWeakRelation &relationship, QString &error )
{
GDALRelationshipCardinality gCardinality = GDALRelationshipCardinality::GRC_ONE_TO_MANY;
switch ( relationship.cardinality() )
{
case Qgis::RelationshipCardinality::OneToOne:
gCardinality = GDALRelationshipCardinality::GRC_ONE_TO_ONE;
break;
case Qgis::RelationshipCardinality::OneToMany:
gCardinality = GDALRelationshipCardinality::GRC_ONE_TO_MANY;
break;
case Qgis::RelationshipCardinality::ManyToOne:
gCardinality = GDALRelationshipCardinality::GRC_MANY_TO_ONE;
break;
case Qgis::RelationshipCardinality::ManyToMany:
gCardinality = GDALRelationshipCardinality::GRC_MANY_TO_MANY;
break;
}
QgsProviderMetadata *ogrProviderMetadata = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
const QVariantMap leftParts = ogrProviderMetadata->decodeUri( relationship.referencedLayerSource() );
const QString leftTableName = leftParts.value( QStringLiteral( "layerName" ) ).toString();
if ( leftTableName.isEmpty() )
{
error = QObject::tr( "Parent table name was not set" );
return nullptr;
}
const QVariantMap rightParts = ogrProviderMetadata->decodeUri( relationship.referencingLayerSource() );
const QString rightTableName = rightParts.value( QStringLiteral( "layerName" ) ).toString();
if ( rightTableName.isEmpty() )
{
error = QObject::tr( "Child table name was not set" );
return nullptr;
}
if ( leftParts.value( QStringLiteral( "path" ) ).toString() != rightParts.value( QStringLiteral( "path" ) ).toString() )
{
error = QObject::tr( "Parent and child table must be from the same dataset" );
return nullptr;
}
QString mappingTableName;
if ( !relationship.mappingTableSource().isEmpty() )
{
const QVariantMap mappingParts = ogrProviderMetadata->decodeUri( relationship.mappingTableSource() );
mappingTableName = mappingParts.value( QStringLiteral( "layerName" ) ).toString();
if ( leftParts.value( QStringLiteral( "path" ) ).toString() != mappingParts.value( QStringLiteral( "path" ) ).toString() )
{
error = QObject::tr( "Parent and mapping table must be from the same dataset" );
return nullptr;
}
}
gdal::relationship_unique_ptr relationH( GDALRelationshipCreate( relationship.name().toLocal8Bit().constData(),
leftTableName.toLocal8Bit().constData(),
rightTableName.toLocal8Bit().constData(),
gCardinality ) );
// set left table fields
const QStringList leftFieldNames = relationship.referencedLayerFields();
int count = leftFieldNames.count();
char **lst = new char *[count + 1];
if ( count > 0 )
{
int pos = 0;
for ( const QString &string : leftFieldNames )
{
lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
pos++;
}
}
lst[count] = nullptr;
GDALRelationshipSetLeftTableFields( relationH.get(), lst );
CSLDestroy( lst );
// set right table fields
const QStringList rightFieldNames = relationship.referencingLayerFields();
count = rightFieldNames.count();
lst = new char *[count + 1];
if ( count > 0 )
{
int pos = 0;
for ( const QString &string : rightFieldNames )
{
lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
pos++;
}
}
lst[count] = nullptr;
GDALRelationshipSetRightTableFields( relationH.get(), lst );
CSLDestroy( lst );
if ( !mappingTableName.isEmpty() )
{
GDALRelationshipSetMappingTableName( relationH.get(), mappingTableName.toLocal8Bit().constData() );
// set left mapping table fields
const QStringList leftFieldNames = relationship.mappingReferencedLayerFields();
int count = leftFieldNames.count();
char **lst = new char *[count + 1];
if ( count > 0 )
{
int pos = 0;
for ( const QString &string : leftFieldNames )
{
lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
pos++;
}
}
lst[count] = nullptr;
GDALRelationshipSetLeftMappingTableFields( relationH.get(), lst );
CSLDestroy( lst );
// set right table fields
const QStringList rightFieldNames = relationship.mappingReferencingLayerFields();
count = rightFieldNames.count();
lst = new char *[count + 1];
if ( count > 0 )
{
int pos = 0;
for ( const QString &string : rightFieldNames )
{
lst[pos] = CPLStrdup( string.toLocal8Bit().constData() );
pos++;
}
}
lst[count] = nullptr;
GDALRelationshipSetRightMappingTableFields( relationH.get(), lst );
CSLDestroy( lst );
}
// set type
switch ( relationship.strength() )
{
case Qgis::RelationshipStrength::Association:
GDALRelationshipSetType( relationH.get(), GDALRelationshipType::GRT_ASSOCIATION );
break;
case Qgis::RelationshipStrength::Composition:
GDALRelationshipSetType( relationH.get(), GDALRelationshipType::GRT_COMPOSITE );
break;
}
// set labels
if ( !relationship.forwardPathLabel().isEmpty() )
GDALRelationshipSetForwardPathLabel( relationH.get(), relationship.forwardPathLabel().toLocal8Bit().constData() );
if ( !relationship.backwardPathLabel().isEmpty() )
GDALRelationshipSetBackwardPathLabel( relationH.get(), relationship.backwardPathLabel().toLocal8Bit().constData() );
// set table type
if ( !relationship.relatedTableType().isEmpty() )
GDALRelationshipSetRelatedTableType( relationH.get(), relationship.relatedTableType().toLocal8Bit().constData() );
return relationH;
}
#endif

View File

@ -32,6 +32,7 @@ class QgsCoordinateReferenceSystem;
class QgsFieldDomain;
class QTextCodec;
class QgsWeakRelation;
namespace gdal
{
@ -114,6 +115,22 @@ namespace gdal
};
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
/**
* Closes and cleanups GDAL relationship.
*/
struct GDALRelationshipDeleter
{
/**
* Destroys GDAL \a relationship, using the correct gdal calls.
*/
void CORE_EXPORT operator()( GDALRelationshipH relationship ) const;
};
#endif
/**
* Scoped OGR data source.
*/
@ -153,6 +170,14 @@ namespace gdal
* Scoped GDAL warp options.
*/
using warp_options_unique_ptr = std::unique_ptr< GDALWarpOptions, GDALWarpOptionsDeleter >;
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
/**
* Scoped GDAL relationship.
*/
using relationship_unique_ptr = std::unique_ptr< std::remove_pointer<GDALRelationshipH>::type, GDALRelationshipDeleter >;
#endif
}
/**
@ -426,6 +451,30 @@ class CORE_EXPORT QgsOgrUtils
static OGRFieldDomainH convertFieldDomain( const QgsFieldDomain *domain );
#endif
#endif
#ifndef SIP_RUN
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
/**
* Converts an GDAL \a relationship definition to a QgsWeakRelation equivalent.
*
* \note Requires GDAL >= 3.6
* \note Not available in Python bindings
* \since QGIS 3.30
*/
static QgsWeakRelation convertRelationship( GDALRelationshipH relationship, const QString &datasetUri );
/**
* Converts a QGIS relation to a GDAL relationship equivalent.
*
* \note Requires GDAL >= 3.6
* \note Not available in Python bindings
* \since QGIS 3.30
*/
static gdal::relationship_unique_ptr convertRelationship( const QgsWeakRelation &relation, QString &error );
#endif
#endif
};
#endif // QGSOGRUTILS_H

View File

@ -39,6 +39,7 @@
#include "qgsfontutils.h"
#include "qgssymbol.h"
#include "qgsfielddomain.h"
#include "qgsweakrelation.h"
class TestQgsOgrUtils: public QObject
{
@ -78,6 +79,11 @@ class TestQgsOgrUtils: public QObject
void testConvertToFieldDomain();
#endif
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
void testConvertGdalRelationship();
void testConvertToGdalRelationship();
#endif
private:
QString mTestDataDir;
@ -1133,5 +1139,203 @@ void TestQgsOgrUtils::testConvertToFieldDomain()
}
#endif
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,6,0)
void TestQgsOgrUtils::testConvertGdalRelationship()
{
gdal::relationship_unique_ptr relationH( GDALRelationshipCreate( "relation_name",
"left_table",
"right_table",
GDALRelationshipCardinality::GRC_ONE_TO_ONE ) );
QgsWeakRelation rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.name(), QStringLiteral( "relation_name" ) );
QCOMPARE( rel.referencedLayerSource(), QStringLiteral( "/some_data.gdb|layername=left_table" ) );
QCOMPARE( rel.referencingLayerSource(), QStringLiteral( "/some_data.gdb|layername=right_table" ) );
QCOMPARE( rel.cardinality(), Qgis::RelationshipCardinality::OneToOne );
relationH.reset( GDALRelationshipCreate( "relation_name",
"left_table",
"right_table",
GDALRelationshipCardinality::GRC_ONE_TO_MANY ) );
rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.cardinality(), Qgis::RelationshipCardinality::OneToMany );
relationH.reset( GDALRelationshipCreate( "relation_name",
"left_table",
"right_table",
GDALRelationshipCardinality::GRC_MANY_TO_ONE ) );
rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.cardinality(), Qgis::RelationshipCardinality::ManyToOne );
relationH.reset( GDALRelationshipCreate( "relation_name",
"left_table",
"right_table",
GDALRelationshipCardinality::GRC_MANY_TO_MANY ) );
rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.cardinality(), Qgis::RelationshipCardinality::ManyToMany );
const char *const fieldsLeft[] {"fielda", "fieldb", nullptr};
GDALRelationshipSetLeftTableFields( relationH.get(), fieldsLeft );
const char *const fieldsRight[] {"fieldc", "fieldd", nullptr};
GDALRelationshipSetRightTableFields( relationH.get(), fieldsRight );
rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.referencedLayerFields(), QStringList() << QStringLiteral( "fielda" ) << QStringLiteral( "fieldb" ) );
QCOMPARE( rel.referencingLayerFields(), QStringList() << QStringLiteral( "fieldc" ) << QStringLiteral( "fieldd" ) );
QCOMPARE( rel.mappingTableSource(), QString() );
GDALRelationshipSetMappingTableName( relationH.get(), "mapping_table" );
const char *const mappingFieldsLeft[] {"fieldd", "fielde", nullptr};
GDALRelationshipSetLeftMappingTableFields( relationH.get(), mappingFieldsLeft );
const char *const mappingFieldsRight[] {"fieldf", "fieldg", nullptr};
GDALRelationshipSetRightMappingTableFields( relationH.get(), mappingFieldsRight );
rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.mappingTableSource(), QStringLiteral( "/some_data.gdb|layername=mapping_table" ) );
QCOMPARE( rel.referencedLayerFields(), QStringList() << QStringLiteral( "fielda" ) << QStringLiteral( "fieldb" ) );
QCOMPARE( rel.referencingLayerFields(), QStringList() << QStringLiteral( "fieldc" ) << QStringLiteral( "fieldd" ) );
QCOMPARE( rel.mappingReferencedLayerFields(), QStringList() << QStringLiteral( "fieldd" ) << QStringLiteral( "fielde" ) );
QCOMPARE( rel.mappingReferencingLayerFields(), QStringList() << QStringLiteral( "fieldf" ) << QStringLiteral( "fieldg" ) );
GDALRelationshipSetType( relationH.get(), GRT_COMPOSITE );
rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.strength(), Qgis::RelationshipStrength::Composition );
GDALRelationshipSetType( relationH.get(), GRT_ASSOCIATION );
rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.strength(), Qgis::RelationshipStrength::Association );
GDALRelationshipSetForwardPathLabel( relationH.get(), "forward label" );
GDALRelationshipSetBackwardPathLabel( relationH.get(), "backward label" );
rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.forwardPathLabel(), QStringLiteral( "forward label" ) );
QCOMPARE( rel.backwardPathLabel(), QStringLiteral( "backward label" ) );
GDALRelationshipSetRelatedTableType( relationH.get(), "table_type" );
rel = QgsOgrUtils::convertRelationship( relationH.get(), QStringLiteral( "/some_data.gdb" ) );
QCOMPARE( rel.relatedTableType(), QStringLiteral( "table_type" ) );
}
void TestQgsOgrUtils::testConvertToGdalRelationship()
{
QgsWeakRelation rel( QStringLiteral( "id" ), QStringLiteral( "name" ),
Qgis::RelationshipStrength::Association,
QStringLiteral( "referencing_layer_id" ),
QStringLiteral( "referencing_layer_name" ),
QStringLiteral( "/some_data.gdb|layername=referencing" ),
QStringLiteral( "ogr" ),
QStringLiteral( "referenced_layer_id" ),
QStringLiteral( "referenced_layer_name" ),
QStringLiteral( "/some_data.gdb|layername=referenced" ),
QStringLiteral( "ogr" ) );
rel.setReferencedLayerFields( QStringList() << QStringLiteral( "fielda" ) << QStringLiteral( "fieldb" ) );
rel.setReferencingLayerFields( QStringList() << QStringLiteral( "fieldc" ) << QStringLiteral( "fieldd" ) );
rel.setCardinality( Qgis::RelationshipCardinality::OneToMany );
QString error;
gdal::relationship_unique_ptr relationH = QgsOgrUtils::convertRelationship( rel, error );
QCOMPARE( QString( GDALRelationshipGetName( relationH.get() ) ), QStringLiteral( "name" ) );
QCOMPARE( QString( GDALRelationshipGetLeftTableName( relationH.get() ) ), QStringLiteral( "referenced" ) );
QCOMPARE( QString( GDALRelationshipGetRightTableName( relationH.get() ) ), QStringLiteral( "referencing" ) );
char **cslLeftTableFieldNames = GDALRelationshipGetLeftTableFields( relationH.get() );
const QStringList leftTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslLeftTableFieldNames );
CSLDestroy( cslLeftTableFieldNames );
QCOMPARE( leftTableFieldNames, QStringList() << QStringLiteral( "fielda" ) << QStringLiteral( "fieldb" ) );
char **cslRightTableFieldNames = GDALRelationshipGetRightTableFields( relationH.get() );
const QStringList rightTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslRightTableFieldNames );
CSLDestroy( cslRightTableFieldNames );
QCOMPARE( rightTableFieldNames, QStringList() << QStringLiteral( "fieldc" ) << QStringLiteral( "fieldd" ) );
QCOMPARE( GDALRelationshipGetCardinality( relationH.get() ), GDALRelationshipCardinality::GRC_ONE_TO_MANY );
rel.setCardinality( Qgis::RelationshipCardinality::OneToOne );
relationH = QgsOgrUtils::convertRelationship( rel, error );
QCOMPARE( GDALRelationshipGetCardinality( relationH.get() ), GDALRelationshipCardinality::GRC_ONE_TO_ONE );
rel.setCardinality( Qgis::RelationshipCardinality::ManyToOne );
relationH = QgsOgrUtils::convertRelationship( rel, error );
QCOMPARE( GDALRelationshipGetCardinality( relationH.get() ), GDALRelationshipCardinality::GRC_MANY_TO_ONE );
rel.setCardinality( Qgis::RelationshipCardinality::ManyToMany );
relationH = QgsOgrUtils::convertRelationship( rel, error );
QCOMPARE( GDALRelationshipGetCardinality( relationH.get() ), GDALRelationshipCardinality::GRC_MANY_TO_MANY );
QCOMPARE( GDALRelationshipGetType( relationH.get() ), GDALRelationshipType::GRT_ASSOCIATION );
rel = QgsWeakRelation( QStringLiteral( "id" ), QStringLiteral( "name" ),
Qgis::RelationshipStrength::Composition,
QStringLiteral( "referencing_layer_id" ),
QStringLiteral( "referencing_layer_name" ),
QStringLiteral( "/some_data.gdb|layername=referencing" ),
QStringLiteral( "ogr" ),
QStringLiteral( "referenced_layer_id" ),
QStringLiteral( "referenced_layer_name" ),
QStringLiteral( "/some_data.gdb|layername=referenced" ),
QStringLiteral( "ogr" ) );
relationH = QgsOgrUtils::convertRelationship( rel, error );
QCOMPARE( GDALRelationshipGetType( relationH.get() ), GDALRelationshipType::GRT_COMPOSITE );
rel.setForwardPathLabel( QStringLiteral( "forward" ) );
rel.setBackwardPathLabel( QStringLiteral( "backward" ) );
relationH = QgsOgrUtils::convertRelationship( rel, error );
QCOMPARE( QString( GDALRelationshipGetForwardPathLabel( relationH.get() ) ), QStringLiteral( "forward" ) );
QCOMPARE( QString( GDALRelationshipGetBackwardPathLabel( relationH.get() ) ), QStringLiteral( "backward" ) );
rel.setRelatedTableType( QStringLiteral( "table_type" ) );
relationH = QgsOgrUtils::convertRelationship( rel, error );
QCOMPARE( QString( GDALRelationshipGetRelatedTableType( relationH.get() ) ), QStringLiteral( "table_type" ) );
rel.setMappingTable( QgsVectorLayerRef( QStringLiteral( "mapping_id" ),
QStringLiteral( "mapping_name" ),
QStringLiteral( "/some_data.gdb|layername=mapping" ),
QStringLiteral( "ogr" ) ) );
rel.setMappingReferencedLayerFields( QStringList() << QStringLiteral( "fielde" ) << QStringLiteral( "fieldf" ) );
rel.setMappingReferencingLayerFields( QStringList() << QStringLiteral( "fieldh" ) << QStringLiteral( "fieldi" ) );
relationH = QgsOgrUtils::convertRelationship( rel, error );
QCOMPARE( QString( GDALRelationshipGetMappingTableName( relationH.get() ) ), QStringLiteral( "mapping" ) );
char **cslLeftMappingTableFieldNames = GDALRelationshipGetLeftMappingTableFields( relationH.get() );
const QStringList leftMappingTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslLeftMappingTableFieldNames );
CSLDestroy( cslLeftMappingTableFieldNames );
QCOMPARE( leftMappingTableFieldNames, QStringList() << QStringLiteral( "fielde" ) << QStringLiteral( "fieldf" ) );
char **cslRightMappingTableFieldNames = GDALRelationshipGetRightMappingTableFields( relationH.get() );
const QStringList rightMappingTableFieldNames = QgsOgrUtils::cStringListToQStringList( cslRightMappingTableFieldNames );
CSLDestroy( cslRightMappingTableFieldNames );
QCOMPARE( rightMappingTableFieldNames, QStringList() << QStringLiteral( "fieldh" ) << QStringLiteral( "fieldi" ) );
// check that error is raised when tables from different dataset
rel.setMappingTable( QgsVectorLayerRef( QStringLiteral( "mapping_id" ),
QStringLiteral( "mapping_name" ),
QStringLiteral( "/some_other_data.gdb|layername=mapping" ),
QStringLiteral( "ogr" ) ) );
relationH = QgsOgrUtils::convertRelationship( rel, error );
QVERIFY( !relationH.get() );
QCOMPARE( error, QStringLiteral( "Parent and mapping table must be from the same dataset" ) );
error.clear();
rel = QgsWeakRelation( QStringLiteral( "id" ), QStringLiteral( "name" ),
Qgis::RelationshipStrength::Composition,
QStringLiteral( "referencing_layer_id" ),
QStringLiteral( "referencing_layer_name" ),
QStringLiteral( "/some_data.gdb|layername=referencing" ),
QStringLiteral( "ogr" ),
QStringLiteral( "referenced_layer_id" ),
QStringLiteral( "referenced_layer_name" ),
QStringLiteral( "/some_other_data.gdb|layername=referenced" ),
QStringLiteral( "ogr" ) );
relationH = QgsOgrUtils::convertRelationship( rel, error );
QVERIFY( !relationH.get() );
QCOMPARE( error, QStringLiteral( "Parent and child table must be from the same dataset" ) );
error.clear();
}
#endif
QGSTEST_MAIN( TestQgsOgrUtils )
#include "testqgsogrutils.moc"

View File

@ -23,6 +23,7 @@ from qgis.PyQt.QtXml import QDomDocument
from qgis.core import (
NULL,
QgsCoordinateReferenceSystem,
QgsAuthMethodConfig,
QgsApplication,
QgsCoordinateTransformContext,
@ -54,7 +55,8 @@ from qgis.core import (
QgsProviderMetadata,
QgsRelation,
QgsUnsetAttributeValue,
QgsFieldConstraints
QgsFieldConstraints,
QgsWeakRelation
)
from qgis.gui import (
@ -2892,6 +2894,63 @@ class PyQgsOGRProvider(unittest.TestCase):
relationships = conn.relationships('', 'table2')
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):
"""
Test creating relationship via the connections API
"""
metadata = QgsProviderRegistry.instance().providerMetadata('ogr')
with tempfile.TemporaryDirectory() as temp_dir:
tmpfile = os.path.join(temp_dir, 'test_gdb.gdb')
ok, err = metadata.createDatabase(tmpfile)
self.assertTrue(ok)
self.assertFalse(err)
conn = metadata.createConnection(tmpfile, {})
self.assertTrue(conn)
conn.createVectorTable('', 'child', QgsFields(), QgsWkbTypes.Point, QgsCoordinateReferenceSystem('EPSG:4326'), False, {})
layer = QgsVectorLayer(tmpfile + '|layername=child')
self.assertTrue(layer.isValid())
conn.createVectorTable('', 'parent', QgsFields(), QgsWkbTypes.Point, QgsCoordinateReferenceSystem('EPSG:4326'), False, {})
layer = QgsVectorLayer(tmpfile + '|layername=parent')
self.assertTrue(layer.isValid())
del layer
self.assertTrue(
conn.capabilities() & QgsAbstractDatabaseProviderConnection.AddRelationship)
relationships = conn.relationships()
self.assertFalse(relationships)
rel = QgsWeakRelation('id',
'rel_name',
Qgis.RelationshipStrength.Association,
'referencing_id',
'referencing_name',
tmpfile + '|layername=child',
'ogr',
'referenced_id',
'referenced_name',
tmpfile + '|layername=parent',
'ogr'
)
rel.setReferencedLayerFields(['fielda'])
rel.setReferencingLayerFields(['fieldb'])
conn.addRelationship(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(), ['fieldb'])
self.assertEqual(result.referencedLayerFields(), ['fielda'])
def testUniqueGeometryType(self):
"""
Test accessing a layer of type wkbUnknown that contains a single geometry type but also null geometries