PG: faster information retrieval for table info

Information about single tables does not need to
scan for all tables anymore.

Fix #56069
This commit is contained in:
Alessandro Pasotti 2024-01-30 18:01:06 +01:00
parent 0aed505843
commit 1ed4113ef1
4 changed files with 171 additions and 130 deletions

View File

@ -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 ); QMutexLocker locker( &mLock );
int nColumns = 0; int nColumns = 0;
@ -601,8 +601,6 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP
QgsPostgresResult result; QgsPostgresResult result;
QString query; QString query;
mLayersSupported.clear();
for ( int i = SctGeometry; i <= SctRaster; ++i ) for ( int i = SctGeometry; i <= SctRaster; ++i )
{ {
QString sql, tableName, schemaName, columnName, typeName, sridName, gtableName, dimName; QString sql, tableName, schemaName, columnName, typeName, sridName, gtableName, dimName;
@ -710,6 +708,9 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP
if ( !schema.isEmpty() ) if ( !schema.isEmpty() )
sql += QStringLiteral( " AND %1='%2'" ).arg( schemaName, schema ); 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" ); sql += QString( " GROUP BY 1,2,3,4,5,6,7,c.oid,11" );
foundInTables |= 1 << i; foundInTables |= 1 << i;
@ -848,7 +849,10 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP
sql += QLatin1String( " AND n.nspname='public'" ); sql += QLatin1String( " AND n.nspname='public'" );
if ( !schema.isEmpty() ) 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 // skip columns of which we already derived information from the metadata tables
if ( nColumns > 0 ) if ( nColumns > 0 )
@ -987,7 +991,10 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP
sql += QLatin1String( " AND pg_namespace.nspname='public'" ); sql += QLatin1String( " AND pg_namespace.nspname='public'" );
if ( !schema.isEmpty() ) 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" ); sql += QLatin1String( " GROUP BY 1,2,3,4" );
@ -1062,12 +1069,14 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP
return true; return true;
} }
bool QgsPostgresConn::supportedLayers( QVector<QgsPostgresLayerProperty> &layers, bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema ) bool QgsPostgresConn::supportedLayers( QVector<QgsPostgresLayerProperty> &layers, bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, const QString &schema, const QString &table )
{ {
QMutexLocker locker( &mLock ); QMutexLocker locker( &mLock );
mLayersSupported.clear();
// Get the list of supported tables // 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" ) ); QgsMessageLog::logMessage( tr( "Unable to get list of spatially enabled tables from the database" ), tr( "PostGIS" ) );
return false; return false;
@ -2172,7 +2181,7 @@ void QgsPostgresConn::retrieveLayerTypes( QVector<QgsPostgresLayerProperty *> &l
sql = QStringLiteral( "SELECT %1, " sql = QStringLiteral( "SELECT %1, "
"array_agg(DISTINCT st_srid(%2) || ':RASTER:-1') " "array_agg(DISTINCT st_srid(%2) || ':RASTER:-1') "
"FROM %3 " "FROM %3 "
"%2 IS NOT NULL " "WHERE %2 IS NOT NULL "
"%4" // SQL clause "%4" // SQL clause
"%5" ) "%5" )
.arg( i - 1 ) .arg( i - 1 )

View File

@ -385,13 +385,15 @@ class QgsPostgresConn : public QObject
* \param searchPublicOnly * \param searchPublicOnly
* \param allowGeometrylessTables * \param allowGeometrylessTables
* \param schema restrict layers to layers within specified schema * \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 * \returns true if layers were fetched successfully
*/ */
bool supportedLayers( QVector<QgsPostgresLayerProperty> &layers, bool supportedLayers( QVector<QgsPostgresLayerProperty> &layers,
bool searchGeometryColumnsOnly = true, bool searchGeometryColumnsOnly = true,
bool searchPublicOnly = true, bool searchPublicOnly = true,
bool allowGeometrylessTables = false, bool allowGeometrylessTables = false,
const QString &schema = QString() ); const QString &schema = QString(),
const QString &table = QString() );
/** /**
* Gets the list of database schemas * Gets the list of database schemas
@ -418,10 +420,11 @@ class QgsPostgresConn : public QObject
* \param searchPublicOnly * \param searchPublicOnly
* \param allowGeometrylessTables * \param allowGeometrylessTables
* \param schema restrict tables to those within specified schema * \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 * \returns true if tables were successfully queried
*/ */
bool getTableInfo( bool searchGeometryColumnsOnly, bool searchPublicOnly, bool allowGeometrylessTables, 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 ); qint64 getBinaryInt( QgsPostgresResult &queryResult, int row, int col );

View File

@ -222,6 +222,142 @@ void QgsPostgresProviderConnection::renameTablePrivate( const QString &schema, c
QgsPostgresConn::quotedIdentifier( newName ) ) ); QgsPostgresConn::quotedIdentifier( newName ) ) );
} }
QList<QgsAbstractDatabaseProviderConnection::TableProperty> QgsPostgresProviderConnection::tablesPrivate( const QString &schema, const QString &table, const TableFlags &flags, QgsFeedback *feedback ) const
{
checkCapability( Capability::Tables );
QList<QgsPostgresProviderConnection::TableProperty> 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<QgsPostgresLayerProperty> 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<int>::min() ) == std::numeric_limits<int>::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<int>( 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<QVariantList> 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 void QgsPostgresProviderConnection::renameVectorTable( const QString &schema, const QString &name, const QString &newName ) const
{ {
checkCapability( Capability::RenameVectorTable ); checkCapability( Capability::RenameVectorTable );
@ -601,131 +737,21 @@ void QgsPostgresProviderConnection::setFieldComment( const QString &fieldName, c
QList<QgsPostgresProviderConnection::TableProperty> QgsPostgresProviderConnection::tables( const QString &schema, const TableFlags &flags, QgsFeedback *feedback ) const QList<QgsPostgresProviderConnection::TableProperty> QgsPostgresProviderConnection::tables( const QString &schema, const TableFlags &flags, QgsFeedback *feedback ) const
{ {
checkCapability( Capability::Tables ); return tablesPrivate( schema, QString(), flags, feedback );
QList<QgsPostgresProviderConnection::TableProperty> 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 ) QgsAbstractDatabaseProviderConnection::TableProperty QgsPostgresProviderConnection::table( const QString &schema, const QString &table, QgsFeedback *feedback ) const
{
const QList<QgsPostgresProviderConnection::TableProperty> properties { tablesPrivate( schema, table, TableFlags(), feedback ) };
if ( ! properties.empty() )
{ {
errCause = QObject::tr( "Connection failed: %1" ).arg( uri() ); return properties.first();
} }
else else
{ {
QVector<QgsPostgresLayerProperty> properties; throw QgsProviderConnectionException( QObject::tr( "Table '%1' was not found in schema '%2'" )
const bool aspatial { ! flags || flags.testFlag( TableFlag::Aspatial ) }; .arg( table, schema ) );
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<int>::min() ) == std::numeric_limits<int>::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<int>( 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<QVariantList> 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;
} }
QStringList QgsPostgresProviderConnection::schemas( ) const QStringList QgsPostgresProviderConnection::schemas( ) const
@ -776,6 +802,7 @@ void QgsPostgresProviderConnection::store( const QString &name ) const
// From URI // From URI
const QgsDataSourceUri dsUri { uri() }; const QgsDataSourceUri dsUri { uri() };
qDebug() << settings.fileName();
settings.setValue( "service", dsUri.service() ); settings.setValue( "service", dsUri.service() );
settings.setValue( "host", dsUri.host() ); settings.setValue( "host", dsUri.host() );
settings.setValue( "port", dsUri.port() ); settings.setValue( "port", dsUri.port() );

View File

@ -72,6 +72,7 @@ class QgsPostgresProviderConnection : public QgsAbstractDatabaseProviderConnecti
void setFieldComment( const QString &fieldName, const QString &schema, const QString &tableName, const QString &comment ) const override; void setFieldComment( const QString &fieldName, const QString &schema, const QString &tableName, const QString &comment ) const override;
QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables( const QString &schema, QList<QgsAbstractDatabaseProviderConnection::TableProperty> tables( const QString &schema,
const TableFlags &flags = TableFlags(), QgsFeedback *feedback = nullptr ) const override; 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; QStringList schemas( ) const override;
void store( const QString &name ) const override; void store( const QString &name ) const override;
void remove( const QString &name ) const override; void remove( const QString &name ) const override;
@ -92,7 +93,8 @@ class QgsPostgresProviderConnection : public QgsAbstractDatabaseProviderConnecti
void setDefaultCapabilities(); void setDefaultCapabilities();
void dropTablePrivate( const QString &schema, const QString &name ) const; void dropTablePrivate( const QString &schema, const QString &name ) const;
void renameTablePrivate( const QString &schema, const QString &name, const QString &newName ) const; void renameTablePrivate( const QString &schema, const QString &name, const QString &newName ) const;
QList<QgsAbstractDatabaseProviderConnection::TableProperty> tablesPrivate( const QString &schema, const QString &table,
const TableFlags &flags = TableFlags(), QgsFeedback *feedback = nullptr ) const;
}; };