diff --git a/src/providers/postgres/qgspostgresconn.cpp b/src/providers/postgres/qgspostgresconn.cpp index 32d75b495c9..7275d005bce 100644 --- a/src/providers/postgres/qgspostgresconn.cpp +++ b/src/providers/postgres/qgspostgresconn.cpp @@ -593,7 +593,7 @@ void QgsPostgresConn::addColumnInfo( QgsPostgresLayerProperty &layerProperty, co } -bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema ) +bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema, const QString &name ) { QMutexLocker locker( &mLock ); int nColumns = 0; @@ -601,8 +601,6 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP QgsPostgresResult result; QString query; - mLayersSupported.clear(); - for ( int i = SctGeometry; i <= SctRaster; ++i ) { QString sql, tableName, schemaName, columnName, typeName, sridName, gtableName, dimName; @@ -710,6 +708,9 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP if ( !schema.isEmpty() ) sql += QStringLiteral( " AND %1='%2'" ).arg( schemaName, schema ); + if ( !name.isEmpty() ) + sql += QStringLiteral( " AND %1='%2'" ).arg( tableName, name ); + sql += QString( " GROUP BY 1,2,3,4,5,6,7,c.oid,11" ); foundInTables |= 1 << i; @@ -848,7 +849,10 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP sql += QLatin1String( " AND n.nspname='public'" ); if ( !schema.isEmpty() ) - sql += QStringLiteral( " AND n.nspname='%2'" ).arg( schema ); + sql += QStringLiteral( " AND n.nspname='%1'" ).arg( schema ); + + if ( !name.isEmpty() ) + sql += QStringLiteral( " AND c.relname ='%1'" ).arg( name ); // skip columns of which we already derived information from the metadata tables if ( nColumns > 0 ) @@ -987,7 +991,10 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP sql += QLatin1String( " AND pg_namespace.nspname='public'" ); if ( !schema.isEmpty() ) - sql += QStringLiteral( " AND pg_namespace.nspname='%2'" ).arg( schema ); + sql += QStringLiteral( " AND pg_namespace.nspname='%1'" ).arg( schema ); + + if ( !name.isEmpty() ) + sql += QStringLiteral( " AND pg_class.relname='%1'" ).arg( name ); sql += QLatin1String( " GROUP BY 1,2,3,4" ); @@ -1062,12 +1069,14 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP return true; } -bool QgsPostgresConn::supportedLayers( QVector &layers, bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema ) +bool QgsPostgresConn::supportedLayers( QVector &layers, bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema, const QString &table ) { QMutexLocker locker( &mLock ); + mLayersSupported.clear(); + // Get the list of supported tables - if ( !getTableInfo( searchGeometryColumnsOnly, searchPublicOnly, allowGeometrylessTables, schema ) ) + if ( !getTableInfo( searchGeometryColumnsOnly, searchPublicOnly, allowGeometrylessTables, schema, table ) ) { QgsMessageLog::logMessage( tr( "Unable to get list of spatially enabled tables from the database" ), tr( "PostGIS" ) ); return false; @@ -2172,7 +2181,7 @@ void QgsPostgresConn::retrieveLayerTypes( QVector &l sql = QStringLiteral( "SELECT %1, " "array_agg(DISTINCT st_srid(%2) || ':RASTER:-1') " "FROM %3 " - "%2 IS NOT NULL " + "WHERE %2 IS NOT NULL " "%4" // SQL clause "%5" ) .arg( i - 1 ) diff --git a/src/providers/postgres/qgspostgresconn.h b/src/providers/postgres/qgspostgresconn.h index 38e313d9841..21ee22d7f74 100644 --- a/src/providers/postgres/qgspostgresconn.h +++ b/src/providers/postgres/qgspostgresconn.h @@ -385,13 +385,15 @@ class QgsPostgresConn : public QObject * \param searchPublicOnly * \param allowGeometrylessTables * \param schema restrict layers to layers within specified schema + * \param table restrict tables to those with specified table * \returns true if layers were fetched successfully */ bool supportedLayers( QVector &layers, bool searchGeometryColumnsOnly = true, bool searchPublicOnly = true, bool allowGeometrylessTables = false, - const QString &schema = QString() ); + const QString &schema = QString(), + const QString &table = QString() ); /** * Gets the list of database schemas @@ -418,10 +420,11 @@ class QgsPostgresConn : public QObject * \param searchPublicOnly * \param allowGeometrylessTables * \param schema restrict tables to those within specified schema + * \param name restrict tables to those with specified name * \returns true if tables were successfully queried */ bool getTableInfo( bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, - const QString &schema = QString() ); + const QString &schema = QString(), const QString &name = QString() ); qint64 getBinaryInt( QgsPostgresResult &queryResult, int row, int col ); diff --git a/src/providers/postgres/qgspostgresproviderconnection.cpp b/src/providers/postgres/qgspostgresproviderconnection.cpp index 21d2c339d98..ca18204c2e5 100644 --- a/src/providers/postgres/qgspostgresproviderconnection.cpp +++ b/src/providers/postgres/qgspostgresproviderconnection.cpp @@ -222,6 +222,142 @@ void QgsPostgresProviderConnection::renameTablePrivate( const QString &schema, c QgsPostgresConn::quotedIdentifier( newName ) ) ); } +QList QgsPostgresProviderConnection::tablesPrivate( const QString &schema, const QString &table, const TableFlags &flags, QgsFeedback *feedback ) const +{ + checkCapability( Capability::Tables ); + QList tables; + QString errCause; + // TODO: set flags from the connection if flags argument is 0 + const QgsDataSourceUri dsUri { uri() }; + QgsPostgresConn *conn = QgsPostgresConnPool::instance()->acquireConnection( dsUri.connectionInfo( false ), -1, false, feedback ); + if ( feedback && feedback->isCanceled() ) + return {}; + + if ( !conn ) + { + errCause = QObject::tr( "Connection failed: %1" ).arg( uri() ); + } + else + { + QVector properties; + const bool aspatial { ! flags || flags.testFlag( TableFlag::Aspatial ) }; + bool ok = conn->supportedLayers( properties, false, schema == QStringLiteral( "public" ), aspatial, schema, table ); + if ( ! ok ) + { + if ( ! table.isEmpty() ) + { + errCause = QObject::tr( "Could not retrieve table '%2' from %1" ).arg( uri(), table ); + } + else + { + errCause = QObject::tr( "Could not retrieve tables: %1" ).arg( uri() ); + } + } + else + { + + bool dontResolveType = configuration().value( QStringLiteral( "dontResolveType" ), false ).toBool(); + bool useEstimatedMetadata = configuration().value( QStringLiteral( "estimatedMetadata" ), false ).toBool(); + + // Cannot be const: + for ( auto &pr : properties ) + { + // Classify + TableFlags prFlags; + if ( pr.relKind == Qgis::PostgresRelKind::View || pr.relKind == Qgis::PostgresRelKind::MaterializedView ) + { + prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::View ); + } + if ( pr.relKind == Qgis::PostgresRelKind::MaterializedView ) + { + prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::MaterializedView ); + } + if ( pr.relKind == Qgis::PostgresRelKind::ForeignTable ) + { + prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Foreign ); + } + if ( pr.isRaster ) + { + prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Raster ); + } + else if ( pr.nSpCols != 0 ) + { + prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Vector ); + } + else + { + prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Aspatial ); + } + // Filter + if ( ! flags || ( prFlags & flags ) ) + { + // retrieve layer types if needed + if ( ! dontResolveType && ( !pr.geometryColName.isNull() && + ( pr.types.value( 0, Qgis::WkbType::Unknown ) == Qgis::WkbType::Unknown || + pr.srids.value( 0, std::numeric_limits::min() ) == std::numeric_limits::min() ) ) ) + { + conn->retrieveLayerTypes( pr, useEstimatedMetadata, feedback ); + } + QgsPostgresProviderConnection::TableProperty property; + property.setFlags( prFlags ); + for ( int i = 0; i < std::min( pr.types.size(), pr.srids.size() ) ; i++ ) + { + property.addGeometryColumnType( pr.types.at( i ), QgsCoordinateReferenceSystem::fromEpsgId( pr.srids.at( i ) ) ); + } + property.setTableName( pr.tableName ); + property.setSchema( pr.schemaName ); + property.setGeometryColumn( pr.geometryColName ); + // These are candidates, not actual PKs + // property.setPrimaryKeyColumns( pr.pkCols ); + property.setGeometryColumnCount( static_cast( pr.nSpCols ) ); + property.setComment( pr.tableComment ); + + // Get PKs + if ( pr.relKind == Qgis::PostgresRelKind::View + || pr.relKind == Qgis::PostgresRelKind::MaterializedView + || pr.relKind == Qgis::PostgresRelKind::ForeignTable ) + { + // Set the candidates + property.setPrimaryKeyColumns( pr.pkCols ); + } + else // Fetch and set the real pks + { + try + { + const QList pks = executeSqlPrivate( QStringLiteral( R"( + WITH pkrelid AS ( + SELECT indexrelid AS idxri FROM pg_index WHERE indrelid='%1.%2'::regclass AND (indisprimary OR indisunique) + ORDER BY CASE WHEN indisprimary THEN 1 ELSE 2 END LIMIT 1) + SELECT attname FROM pg_index,pg_attribute, pkrelid + WHERE indexrelid=pkrelid.idxri AND indrelid=attrelid AND pg_attribute.attnum=any(pg_index.indkey); + )" ).arg( QgsPostgresConn::quotedIdentifier( pr.schemaName ), + QgsPostgresConn::quotedIdentifier( pr.tableName ) ), false ); + QStringList pkNames; + for ( const QVariantList &pk : std::as_const( pks ) ) + { + pkNames.push_back( pk.first().toString() ); + } + property.setPrimaryKeyColumns( pkNames ); + } + catch ( const QgsProviderConnectionException &ex ) + { + QgsDebugError( QStringLiteral( "Error retrieving primary keys: %1" ).arg( ex.what() ) ); + } + } + + tables.push_back( property ); + } + } + } + QgsPostgresConnPool::instance()->releaseConnection( conn ); + } + if ( ! errCause.isEmpty() ) + { + throw QgsProviderConnectionException( errCause ); + } + return tables; +} + void QgsPostgresProviderConnection::renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const { checkCapability( Capability::RenameVectorTable ); @@ -601,131 +737,21 @@ void QgsPostgresProviderConnection::setFieldComment( const QString &fieldName, c QList QgsPostgresProviderConnection::tables( const QString &schema, const TableFlags &flags, QgsFeedback *feedback ) const { - checkCapability( Capability::Tables ); - QList tables; - QString errCause; - // TODO: set flags from the connection if flags argument is 0 - const QgsDataSourceUri dsUri { uri() }; - QgsPostgresConn *conn = QgsPostgresConnPool::instance()->acquireConnection( dsUri.connectionInfo( false ), -1, false, feedback ); - if ( feedback && feedback->isCanceled() ) - return {}; + return tablesPrivate( schema, QString(), flags, feedback ); +} - if ( !conn ) +QgsAbstractDatabaseProviderConnection::TableProperty QgsPostgresProviderConnection::table( const QString &schema, const QString &table, QgsFeedback *feedback ) const +{ + const QList properties { tablesPrivate( schema, table, TableFlags(), feedback ) }; + if ( ! properties.empty() ) { - errCause = QObject::tr( "Connection failed: %1" ).arg( uri() ); + return properties.first(); } else { - QVector properties; - const bool aspatial { ! flags || flags.testFlag( TableFlag::Aspatial ) }; - bool ok = conn->supportedLayers( properties, false, schema == QStringLiteral( "public" ), aspatial, schema ); - if ( ! ok ) - { - errCause = QObject::tr( "Could not retrieve tables: %1" ).arg( uri() ); - } - else - { - - bool dontResolveType = configuration().value( QStringLiteral( "dontResolveType" ), false ).toBool(); - bool useEstimatedMetadata = configuration().value( QStringLiteral( "estimatedMetadata" ), false ).toBool(); - - // Cannot be const: - for ( auto &pr : properties ) - { - // Classify - TableFlags prFlags; - if ( pr.relKind == Qgis::PostgresRelKind::View || pr.relKind == Qgis::PostgresRelKind::MaterializedView ) - { - prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::View ); - } - if ( pr.relKind == Qgis::PostgresRelKind::MaterializedView ) - { - prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::MaterializedView ); - } - if ( pr.relKind == Qgis::PostgresRelKind::ForeignTable ) - { - prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Foreign ); - } - if ( pr.isRaster ) - { - prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Raster ); - } - else if ( pr.nSpCols != 0 ) - { - prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Vector ); - } - else - { - prFlags.setFlag( QgsPostgresProviderConnection::TableFlag::Aspatial ); - } - // Filter - if ( ! flags || ( prFlags & flags ) ) - { - // retrieve layer types if needed - if ( ! dontResolveType && ( !pr.geometryColName.isNull() && - ( pr.types.value( 0, Qgis::WkbType::Unknown ) == Qgis::WkbType::Unknown || - pr.srids.value( 0, std::numeric_limits::min() ) == std::numeric_limits::min() ) ) ) - { - conn->retrieveLayerTypes( pr, useEstimatedMetadata, feedback ); - } - QgsPostgresProviderConnection::TableProperty property; - property.setFlags( prFlags ); - for ( int i = 0; i < std::min( pr.types.size(), pr.srids.size() ) ; i++ ) - { - property.addGeometryColumnType( pr.types.at( i ), QgsCoordinateReferenceSystem::fromEpsgId( pr.srids.at( i ) ) ); - } - property.setTableName( pr.tableName ); - property.setSchema( pr.schemaName ); - property.setGeometryColumn( pr.geometryColName ); - // These are candidates, not actual PKs - // property.setPrimaryKeyColumns( pr.pkCols ); - property.setGeometryColumnCount( static_cast( pr.nSpCols ) ); - property.setComment( pr.tableComment ); - - // Get PKs - if ( pr.relKind == Qgis::PostgresRelKind::View - || pr.relKind == Qgis::PostgresRelKind::MaterializedView - || pr.relKind == Qgis::PostgresRelKind::ForeignTable ) - { - // Set the candidates - property.setPrimaryKeyColumns( pr.pkCols ); - } - else // Fetch and set the real pks - { - try - { - const QList pks = executeSqlPrivate( QStringLiteral( R"( - WITH pkrelid AS ( - SELECT indexrelid AS idxri FROM pg_index WHERE indrelid='%1.%2'::regclass AND (indisprimary OR indisunique) - ORDER BY CASE WHEN indisprimary THEN 1 ELSE 2 END LIMIT 1) - SELECT attname FROM pg_index,pg_attribute, pkrelid - WHERE indexrelid=pkrelid.idxri AND indrelid=attrelid AND pg_attribute.attnum=any(pg_index.indkey); - )" ).arg( QgsPostgresConn::quotedIdentifier( pr.schemaName ), - QgsPostgresConn::quotedIdentifier( pr.tableName ) ), false ); - QStringList pkNames; - for ( const QVariantList &pk : std::as_const( pks ) ) - { - pkNames.push_back( pk.first().toString() ); - } - property.setPrimaryKeyColumns( pkNames ); - } - catch ( const QgsProviderConnectionException &ex ) - { - QgsDebugError( QStringLiteral( "Error retrieving primary keys: %1" ).arg( ex.what() ) ); - } - } - - tables.push_back( property ); - } - } - } - QgsPostgresConnPool::instance()->releaseConnection( conn ); + throw QgsProviderConnectionException( QObject::tr( "Table '%1' was not found in schema '%2'" ) + .arg( table, schema ) ); } - if ( ! errCause.isEmpty() ) - { - throw QgsProviderConnectionException( errCause ); - } - return tables; } QStringList QgsPostgresProviderConnection::schemas( ) const @@ -776,6 +802,7 @@ void QgsPostgresProviderConnection::store( const QString &name ) const // From URI const QgsDataSourceUri dsUri { uri() }; + qDebug() << settings.fileName(); settings.setValue( "service", dsUri.service() ); settings.setValue( "host", dsUri.host() ); settings.setValue( "port", dsUri.port() ); diff --git a/src/providers/postgres/qgspostgresproviderconnection.h b/src/providers/postgres/qgspostgresproviderconnection.h index 930f53c4fae..f9583944b56 100644 --- a/src/providers/postgres/qgspostgresproviderconnection.h +++ b/src/providers/postgres/qgspostgresproviderconnection.h @@ -72,6 +72,7 @@ class QgsPostgresProviderConnection : public QgsAbstractDatabaseProviderConnecti void setFieldComment( const QString &fieldName, const QString &schema, const QString &tableName, const QString &comment ) const override; QList tables( const QString &schema, const TableFlags &flags = TableFlags(), QgsFeedback *feedback = nullptr ) const override; + QgsAbstractDatabaseProviderConnection::TableProperty table( const QString &schema, const QString &table, QgsFeedback *feedback = nullptr ) const override; QStringList schemas( ) const override; void store( const QString &name ) const override; void remove( const QString &name ) const override; @@ -92,7 +93,8 @@ class QgsPostgresProviderConnection : public QgsAbstractDatabaseProviderConnecti void setDefaultCapabilities(); void dropTablePrivate( const QString &schema, const QString &name ) const; void renameTablePrivate( const QString &schema, const QString &name, const QString &newName ) const; - + QList tablesPrivate( const QString &schema, const QString &table, + const TableFlags &flags = TableFlags(), QgsFeedback *feedback = nullptr ) const; };