Index: python/core/qgsdatasourceuri.sip =================================================================== --- python/core/qgsdatasourceuri.sip (revision 13029) +++ python/core/qgsdatasourceuri.sip (working copy) @@ -60,6 +60,11 @@ QString table() const; QString sql() const; QString geometryColumn() const; + + //! set use Estimated Metadata + // added in 1.5 + void setUseEstimatedMetadata( bool theFlag ); + bool useEstimatedMetadata() const; // added in 1.1 QString host() const; Index: src/app/postgres/qgspgnewconnection.cpp =================================================================== --- src/app/postgres/qgspgnewconnection.cpp (revision 13029) +++ src/app/postgres/qgspgnewconnection.cpp (working copy) @@ -67,6 +67,11 @@ // Ensure that cb_plublicSchemaOnly is set correctly on_cb_geometryColumnsOnly_clicked(); + s = Qt::Unchecked; + if ( settings.value( key + "/estimatedMetadata", false ).toBool() ) + s = Qt::Checked; + cb_useEstimatedMetadata->setCheckState( s ); + cbxSSLmode->setCurrentIndex( cbxSSLmode->findData( settings.value( key + "/sslmode", QgsDataSourceURI::SSLprefer ).toInt() ) ); if ( settings.value( key + "/saveUsername" ).toString() == "true" ) @@ -132,6 +137,7 @@ settings.setValue( baseKey + "/sslmode", cbxSSLmode->itemData( cbxSSLmode->currentIndex() ).toInt() ); settings.setValue( baseKey + "/saveUsername", chkStoreUsername->isChecked() ? "true" : "false" ); settings.setValue( baseKey + "/savePassword", chkStorePassword->isChecked() ? "true" : "false" ); + settings.setValue( baseKey + "/estimatedMetadata", cb_useEstimatedMetadata->isChecked() ); // remove old save setting settings.remove( baseKey + "/save" ); Index: src/app/postgres/qgspgsourceselect.cpp =================================================================== --- src/app/postgres/qgspgsourceselect.cpp (revision 13029) +++ src/app/postgres/qgspgsourceselect.cpp (working copy) @@ -41,6 +41,10 @@ #include #endif +// Note: Because the the geometry type select SQL is also in the qgspostgresprovider +// code this parameter is duplicated there. +static const int sGeomTypeSelectLimit = 100; + QgsPgSourceSelect::QgsPgSourceSelect( QWidget *parent, Qt::WFlags fl ) : QDialog( parent, fl ), mColumnTypeThread( NULL ), pd( 0 ) { @@ -142,6 +146,7 @@ settings.remove( key + "/sslmode" ); settings.remove( key + "/publicOnly" ); settings.remove( key + "/geometryColumnsOnly" ); + settings.remove( key + "/estimatedMetadata" ); settings.remove( key + "/saveUsername" ); settings.remove( key + "/savePassword" ); settings.remove( key + "/save" ); @@ -352,6 +357,11 @@ uri += QString( " key=\"%1\"" ).arg( pkColumnName ); } + if ( mURI.useEstimatedMetadata() ) + { + uri += QString( " estimatedmetadata=true" ); + } + uri += QString( " table=\"%1\".\"%2\" (%3) sql=%4" ) .arg( schemaName ).arg( tableName ) .arg( geomColumnName ) @@ -409,8 +419,7 @@ QString username = settings.value( key + "/username" ).toString(); QString password = settings.value( key + "/password" ).toString(); - QgsDataSourceURI uri; - uri.setConnection( settings.value( key + "/host" ).toString(), + mURI.setConnection( settings.value( key + "/host" ).toString(), settings.value( key + "/port" ).toString(), database, username, @@ -419,12 +428,12 @@ bool searchPublicOnly = settings.value( key + "/publicOnly" ).toBool(); bool searchGeometryColumnsOnly = settings.value( key + "/geometryColumnsOnly" ).toBool(); - + mURI.setUseEstimatedMetadata( settings.value( key + "/estimatedMetadata" ).toBool() ); // Need to escape the password to allow for single quotes and backslashes - QgsDebugMsg( "Connection info: " + uri.connectionInfo() ); + QgsDebugMsg( "Connection info: " + mURI.connectionInfo() ); - m_connInfo = uri.connectionInfo(); + m_connInfo = mURI.connectionInfo(); // Tidy up an existing connection if one exists. if ( pd != 0 ) @@ -562,7 +571,7 @@ if ( mColumnTypeThread == NULL ) { mColumnTypeThread = new QgsGeomColumnTypeThread(); - mColumnTypeThread->setConnInfo( m_privConnInfo ); + mColumnTypeThread->setConnInfo( m_privConnInfo, mURI ); } mColumnTypeThread->addGeometryColumn( schema, table, column ); } @@ -794,9 +803,10 @@ { } -void QgsGeomColumnTypeThread::setConnInfo( QString s ) +void QgsGeomColumnTypeThread::setConnInfo( QString s, QgsDataSourceURI& uri ) { mConnInfo = s; + mUseEstimatedMetadata = uri.useEstimatedMetadata(); } void QgsGeomColumnTypeThread::addGeometryColumn( QString schema, QString table, QString column ) @@ -828,8 +838,19 @@ " when geometrytype(%1) IN ('LINESTRING','MULTILINESTRING') THEN 'LINESTRING'" " when geometrytype(%1) IN ('POLYGON','MULTIPOLYGON') THEN 'POLYGON'" " end " - "from " - "\"%2\".\"%3\"" ).arg( "\"" + columns[i] + "\"" ).arg( schemas[i] ).arg( tables[i] ); + "from " ).arg( "\"" + columns[i] + "\"" ); + if ( mUseEstimatedMetadata ) + { + query += QString("(select %1 from %2 where %1 is not null limit %3) as t") + .arg( "\"" + columns[i] + "\"" ) + .arg( "\"" + schemas[i] + "\".\"" + tables[i] + "\"" ) + .arg( sGeomTypeSelectLimit ); + } + else + { + query += "\"" + schemas[i] + "\".\"" + tables[i] + "\""; + } + PGresult* gresult = PQexec( pd, query.toUtf8() ); QString type; if ( PQresultStatus( gresult ) == PGRES_TUPLES_OK ) Index: src/app/postgres/qgspgsourceselect.h =================================================================== --- src/app/postgres/qgspgsourceselect.h (revision 13029) +++ src/app/postgres/qgspgsourceselect.h (working copy) @@ -22,6 +22,7 @@ #include "qgsdbfilterproxymodel.h" #include "qgsdbtablemodel.h" #include "qgscontexthelp.h" +#include "qgsdatasourceuri.h" extern "C" { @@ -179,6 +180,7 @@ // Our thread for doing long running queries QgsGeomColumnTypeThread* mColumnTypeThread; QString m_connInfo; + QgsDataSourceURI mURI; QString m_privConnInfo; QStringList m_selectedTables; // Storage for the range of layer type icons @@ -205,7 +207,7 @@ Q_OBJECT public: - void setConnInfo( QString s ); + void setConnInfo( QString s, QgsDataSourceURI& uri ); void addGeometryColumn( QString schema, QString table, QString column ); // These functions get the layer types and pass that information out @@ -226,6 +228,7 @@ private: QString mConnInfo; + bool mUseEstimatedMetadata; bool mStopped; std::vector schemas, tables, columns; }; Index: src/core/qgsdatasourceuri.cpp =================================================================== --- src/core/qgsdatasourceuri.cpp (revision 13029) +++ src/core/qgsdatasourceuri.cpp (working copy) @@ -23,12 +23,12 @@ #include #include -QgsDataSourceURI::QgsDataSourceURI() : mSSLmode( SSLprefer ), mKeyColumn( "" ) +QgsDataSourceURI::QgsDataSourceURI() : mSSLmode( SSLprefer ), mKeyColumn( "" ), mUseEstimatedMetadata( false ) { // do nothing } -QgsDataSourceURI::QgsDataSourceURI( QString uri ) : mSSLmode( SSLprefer ), mKeyColumn( "" ) +QgsDataSourceURI::QgsDataSourceURI( QString uri ) : mSSLmode( SSLprefer ), mKeyColumn( "" ), mUseEstimatedMetadata( false ) { int i = 0; while ( i < uri.length() ) @@ -108,6 +108,13 @@ { mKeyColumn = pval; } + else if ( pname == "estimatedmetadata" ) + { + if ( pval == "true" ) + mUseEstimatedMetadata = true; + else + mUseEstimatedMetadata = false; + } else if ( pname == "service" ) { QgsDebugMsg( "service keyword ignored" ); @@ -281,6 +288,17 @@ mKeyColumn = column; } + +void QgsDataSourceURI::setUseEstimatedMetadata( bool theFlag ) +{ + mUseEstimatedMetadata = theFlag; +} + +bool QgsDataSourceURI::useEstimatedMetadata() const +{ + return mUseEstimatedMetadata; +} + void QgsDataSourceURI::setSql( QString sql ) { mSql = sql; @@ -419,6 +437,11 @@ theUri += QString( " key='%1'" ).arg( escape( mKeyColumn ) ); } + if ( mUseEstimatedMetadata == true ) + { + theUri += QString(" estimatedmetadata=true" ); + } + theUri += QString( " table=%1 (%2) sql=%3" ) .arg( quotedTablename() ) .arg( mGeometryColumn ) Index: src/core/qgsdatasourceuri.h =================================================================== --- src/core/qgsdatasourceuri.h (revision 13029) +++ src/core/qgsdatasourceuri.h (working copy) @@ -85,6 +85,11 @@ QString sql() const; QString geometryColumn() const; + //! set use Estimated Metadata + // added in 1.5 + void setUseEstimatedMetadata( bool theFlag ); + bool useEstimatedMetadata() const; + void clearSchema(); void setSql( QString sql ); @@ -128,6 +133,8 @@ enum SSLmode mSSLmode; //! key column QString mKeyColumn; + //Use estimated metadata flag + bool mUseEstimatedMetadata; }; #endif //QGSDATASOURCEURI_H Index: src/providers/postgres/qgspostgresprovider.cpp =================================================================== --- src/providers/postgres/qgspostgresprovider.cpp (revision 13029) +++ src/providers/postgres/qgspostgresprovider.cpp (working copy) @@ -49,6 +49,10 @@ const QString POSTGRES_KEY = "postgres"; const QString POSTGRES_DESCRIPTION = "PostgreSQL/PostGIS data provider"; +// Note: Because the the geometry type select SQL is also in the qgspgsourceselect +// code this parameter is duplicated there. +static const int sGeomTypeSelectLimit = 100; + QMap QgsPostgresProvider::Conn::connectionsRO; QMap QgsPostgresProvider::Conn::connectionsRW; QMap QgsPostgresProvider::Conn::passwordCache; @@ -59,7 +63,9 @@ mFetching( false ), geomType( QGis::WKBUnknown ), mFeatureQueueSize( 200 ), - mPrimaryKeyDefault( QString::null ) + mPrimaryKeyDefault( QString::null ), + mIsDbPrimaryKey(false), + mUseEstimatedMetadata(false) { // assume this is a valid layer until we determine otherwise valid = true; @@ -77,6 +83,7 @@ geometryColumn = mUri.geometryColumn(); sqlWhereClause = mUri.sql(); primaryKey = mUri.keyColumn(); + mUseEstimatedMetadata = mUri.useEstimatedMetadata(); // Keep a schema qualified table name for convenience later on. mSchemaTableName = mUri.quotedTablename(); @@ -1029,6 +1036,12 @@ QString QgsPostgresProvider::getPrimaryKey() { + + // If we find a database primary key we will set this to true. If it is a column which is serving + // as a primary key, then this will remain false. + + mIsDbPrimaryKey = false; + // check to see if there is an unique index on the relation, which // can be used as a key into the table. Primary keys are always // unique indices, so we catch them as well. @@ -1188,6 +1201,7 @@ log.append( tr( "The unique index on column '%1' is unsuitable because Qgis does not currently " "support non-int4 type columns as a key into the table.\n" ).arg( columnName ) ); else + mIsDbPrimaryKey = true; suitableKeyColumns.push_back( std::make_pair( columnName, columnType ) ); } else @@ -2647,7 +2661,7 @@ sqlWhereClause = theSQL; - if ( !uniqueData( mSchemaName, mTableName, primaryKey ) ) + if ( !mIsDbPrimaryKey && !uniqueData( mSchemaName, mTableName, primaryKey ) ) { sqlWhereClause = prevWhere; return false; @@ -2672,17 +2686,25 @@ // First get an approximate count; then delegate to // a thread the task of getting the full count. + QString(sql); #ifdef POSTGRESQL_THREADS - QString sql = QString( "select reltuples from pg_catalog.pg_class where relname=%1" ).arg( quotedValue( tableName ) ); + sql = QString( "select reltuples::int from pg_catalog.pg_class where oid=regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) ); QgsDebugMsg( "Running SQL: " + sql ); #else - QString sql = QString( "select count(*) from %1" ).arg( mSchemaTableName ); - - if ( sqlWhereClause.length() > 0 ) + if( mUseEstimatedMetadata ) { - sql += " where " + sqlWhereClause; + sql = QString( "select reltuples::int from pg_catalog.pg_class where oid=regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) ); } + else + { + sql = QString( "select count(*) from %1" ).arg( mSchemaTableName ); + + if ( sqlWhereClause.length() > 0 ) + { + sql += " where " + sqlWhereClause; + } + } #endif Result result = connectionRO->PQexec( sql ); @@ -2746,7 +2768,7 @@ QString ext; // get the extents - if ( sqlWhereClause.isEmpty() && !connectionRO->hasNoExtentEstimate() ) + if ( ( mUseEstimatedMetadata || sqlWhereClause.isEmpty() ) && !connectionRO->hasNoExtentEstimate() ) { result = connectionRO->PQexec( QString( "select estimated_extent(%1,%2,%3)" ) .arg( quotedValue( mSchemaName ) ) @@ -2955,7 +2977,19 @@ " when geometrytype(%1) IN ('LINESTRING','MULTILINESTRING') THEN 'LINESTRING'" " when geometrytype(%1) IN ('POLYGON','MULTIPOLYGON') THEN 'POLYGON'" " end " - "from %2" ).arg( quotedIdentifier( geometryColumn ) ).arg( mSchemaTableName ); + "from " ).arg( quotedIdentifier( geometryColumn ) ); + if ( mUseEstimatedMetadata ) + { + sql += QString("(select %1 from %2 where %1 is not null limit %3) as t") + .arg( quotedIdentifier( geometryColumn ) ) + .arg( mSchemaTableName ) + .arg( sGeomTypeSelectLimit ); + } + else + { + sql += mSchemaTableName; + } + if ( mUri.sql() != "" ) sql += " where " + mUri.sql(); Index: src/providers/postgres/qgspostgresprovider.h =================================================================== --- src/providers/postgres/qgspostgresprovider.h (revision 13029) +++ src/providers/postgres/qgspostgresprovider.h (working copy) @@ -408,6 +408,7 @@ * the oid is used to fetch features. */ QString primaryKey; + bool mIsDbPrimaryKey; /** * Data type for the primary key */ @@ -461,6 +462,9 @@ bool deduceEndian(); bool getGeometryDetails(); + /* Use estimated metadata. Uses fast table counts, geometry type and extent determination */ + bool mUseEstimatedMetadata; + // Produces a QMessageBox with the given title and text. Doesn't // return until the user has dismissed the dialog box. static void showMessageBox( const QString& title, const QString& text ); Index: src/ui/qgspgnewconnectionbase.ui =================================================================== --- src/ui/qgspgnewconnectionbase.ui (revision 13029) +++ src/ui/qgspgnewconnectionbase.ui (working copy) @@ -228,6 +228,27 @@ + + + + Use estimated table statistics for the layer metadata. + + + <html> +<body> +<p>When the layer is setup various metadata is required for the PostGIS table. This includes information such as the table row count, geometry type and spatial extents of the data in the geometry column. If the table contains a large number of rows determining this metadata is time consuming.</p> +<p>By activating this option the following fast table metadata operations are done:</p> +<p>1) Row count is determined from table statistics obtained from running the PostgreSQL table analyse function.</p> +<p>2) Table extents are always determined with the estimated_extent PostGIS function even if a layer filter is applied.</p> +<p>3) If the table geometry type is unknown and is not exclusively taken from the geometry_columns table, then it is determined from the first 100 non-null geometry rows in the table.</p> +</body> +</html> + + + Use estimated table metadata + + + @@ -246,10 +267,15 @@ txtHost txtDatabase txtPort + cbxSSLmode txtUsername txtPassword + chkStoreUsername + chkStorePassword cb_geometryColumnsOnly cb_publicSchemaOnly + cb_useEstimatedMetadata + btnConnect buttonBox