From be4c4d3f5ace95f532e14ffbeaef8a7615d6a0a1 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Fri, 4 Oct 2019 12:49:59 +0200 Subject: [PATCH 1/2] Fix featureCount on postgres view when flag estimatedmetadata is set --- .../postgres/qgspostgresprovider.cpp | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 3674e91ccd83..60d0e77979ae 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -3332,20 +3332,35 @@ long QgsPostgresProvider::featureCount() const // use estimated metadata even when there is a where clause, // although we get an incorrect feature count for the subset // - but make huge dataset usable. + long num = 0; if ( !mIsQuery && mUseEstimatedMetadata ) { - sql = QStringLiteral( "SELECT reltuples::bigint FROM pg_catalog.pg_class WHERE oid=regclass(%1)::oid" ).arg( quotedValue( mQuery ) ); + // parse explain output to estimate feature count + // we don't use pg_class reltuples because it returns 0 for view + sql = QStringLiteral( "EXPLAIN (FORMAT JSON) SELECT count(*) FROM %1%2" ).arg( mQuery, filterWhereClause() ); + QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + + const QString json = result.PQgetvalue( 0, 0 ); + const QVariantList explain = QgsJsonUtils::parseJson( json ).toList(); + const QVariantMap countPlan = explain.count() ? explain[0].toMap().value( "Plan" ).toMap() : QVariantMap(); + const QVariantList queryPlan = countPlan.value( "Plans" ).toList(); + const QVariant nbRows = queryPlan.count() ? queryPlan[0].toMap().value( "Plan Rows" ) : QVariant(); + + if ( nbRows.isValid() ) + num = nbRows.toInt(); + else + QgsLogger::warning( QStringLiteral( "Cannot parse JSON explain result to estimate feature count (%1) : %2" ).arg( sql, json ) ); } else { sql = QStringLiteral( "SELECT count(*) FROM %1%2" ).arg( mQuery, filterWhereClause() ); - } + QgsPostgresResult result( connectionRO()->PQexec( sql ) ); - QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + QgsDebugMsg( "number of features as text: " + result.PQgetvalue( 0, 0 ) ); - QgsDebugMsg( "number of features as text: " + result.PQgetvalue( 0, 0 ) ); + num = result.PQgetvalue( 0, 0 ).toLong(); + } - long num = result.PQgetvalue( 0, 0 ).toLong(); mShared->setFeaturesCounted( num ); QgsDebugMsg( "number of features: " + QString::number( num ) ); From 5f43b3f1fa39955007c6570328db3a48de9ae896 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Thu, 7 Nov 2019 14:41:22 +0100 Subject: [PATCH 2/2] Add pg version guard and test on estimated count for view --- .../postgres/qgspostgresprovider.cpp | 35 ++++++++++--------- tests/src/python/test_provider_postgres.py | 10 ++++++ 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 60d0e77979ae..d1a1c15ddfcc 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -3332,24 +3332,27 @@ long QgsPostgresProvider::featureCount() const // use estimated metadata even when there is a where clause, // although we get an incorrect feature count for the subset // - but make huge dataset usable. - long num = 0; + long num = -1; if ( !mIsQuery && mUseEstimatedMetadata ) { - // parse explain output to estimate feature count - // we don't use pg_class reltuples because it returns 0 for view - sql = QStringLiteral( "EXPLAIN (FORMAT JSON) SELECT count(*) FROM %1%2" ).arg( mQuery, filterWhereClause() ); - QgsPostgresResult result( connectionRO()->PQexec( sql ) ); - - const QString json = result.PQgetvalue( 0, 0 ); - const QVariantList explain = QgsJsonUtils::parseJson( json ).toList(); - const QVariantMap countPlan = explain.count() ? explain[0].toMap().value( "Plan" ).toMap() : QVariantMap(); - const QVariantList queryPlan = countPlan.value( "Plans" ).toList(); - const QVariant nbRows = queryPlan.count() ? queryPlan[0].toMap().value( "Plan Rows" ) : QVariant(); - - if ( nbRows.isValid() ) - num = nbRows.toInt(); - else - QgsLogger::warning( QStringLiteral( "Cannot parse JSON explain result to estimate feature count (%1) : %2" ).arg( sql, json ) ); + if ( connectionRO()->pgVersion() >= 90000 ) + { + // parse explain output to estimate feature count + // we don't use pg_class reltuples because it returns 0 for view + sql = QStringLiteral( "EXPLAIN (FORMAT JSON) SELECT count(*) FROM %1%2" ).arg( mQuery, filterWhereClause() ); + QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + + const QString json = result.PQgetvalue( 0, 0 ); + const QVariantList explain = QgsJsonUtils::parseJson( json ).toList(); + const QVariantMap countPlan = explain.count() ? explain[0].toMap().value( "Plan" ).toMap() : QVariantMap(); + const QVariantList queryPlan = countPlan.value( "Plans" ).toList(); + const QVariant nbRows = queryPlan.count() ? queryPlan[0].toMap().value( "Plan Rows" ) : QVariant(); + + if ( nbRows.isValid() ) + num = nbRows.toInt(); + else + QgsLogger::warning( QStringLiteral( "Cannot parse JSON explain result to estimate feature count (%1) : %2" ).arg( sql, json ) ); + } } else { diff --git a/tests/src/python/test_provider_postgres.py b/tests/src/python/test_provider_postgres.py index 65e2672d8bef..4c6d8e01786b 100644 --- a/tests/src/python/test_provider_postgres.py +++ b/tests/src/python/test_provider_postgres.py @@ -1415,6 +1415,16 @@ def testCheckTidPkOnViews(self): for f in vl0.getFeatures(): self.assertNotEqual(f.attribute(0), NULL) + def testFeatureCountEstimatedOnView(self): + """ + Test feature count on view when estimated data is enabled + """ + self.execSQLCommand('DROP VIEW IF EXISTS qgis_test.somedataview') + self.execSQLCommand('CREATE VIEW qgis_test.somedataview AS SELECT * FROM qgis_test."someData"') + vl = QgsVectorLayer(self.dbconn + ' sslmode=disable key=\'pk\' estimatedmetadata=true srid=4326 type=POINT table="qgis_test"."somedataview" (geom) sql=', 'test', 'postgres') + self.assertTrue(vl.isValid()) + self.assertTrue(self.source.featureCount() > 0) + class TestPyQgsPostgresProviderCompoundKey(unittest.TestCase, ProviderTestCase):