QGIS/src/providers/postgres/qgspostgresfeatureiterator.cpp
2013-09-15 17:00:36 +02:00

564 lines
16 KiB
C++

/***************************************************************************
qgspostgresfeatureiterator.cpp
---------------------
begin : Juli 2012
copyright : (C) 2012 by Martin Dobias
email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgspostgresfeatureiterator.h"
#include "qgspostgresprovider.h"
#include "qgslogger.h"
#include "qgsmessagelog.h"
#include <QObject>
// provider:
// - mProviderId
// - mGeometryColumn
// - mSpatialColType
// - mRequestedSrid
// - mDetectedSrid
// - mRequestedGeomType
// - mDetectedGeomType
// - mSqlWhereClause
// - mPrimaryKeyType
// - mPrimaryKeyAttrs
// - mAttributeFields
// - mFeaturesCounted
// - field()
// - convertValue()
// - lookupFid()
// - quotedIdentifier()
// - endianString()
const int QgsPostgresFeatureIterator::sFeatureQueueSize = 2000;
QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresProvider* p, const QgsFeatureRequest& request )
: QgsAbstractFeatureIterator( request ), P( p )
, mFeatureQueueSize( sFeatureQueueSize )
{
mCursorName = QString( "qgisf%1_%2" ).arg( P->mProviderId ).arg( P->mIteratorCounter++ );
P->mActiveIterators << this;
QString whereClause;
if ( request.filterType() == QgsFeatureRequest::FilterRect && !P->mGeometryColumn.isNull() )
{
whereClause = whereClauseRect();
}
else if ( request.filterType() == QgsFeatureRequest::FilterFid )
{
whereClause = P->whereClause( request.filterFid() );
}
if ( !P->mSqlWhereClause.isEmpty() )
{
if ( !whereClause.isEmpty() )
whereClause += " AND ";
whereClause += "(" + P->mSqlWhereClause + ")";
}
if ( !declareCursor( whereClause ) )
{
mClosed = true;
return;
}
mFetched = 0;
}
QgsPostgresFeatureIterator::~QgsPostgresFeatureIterator()
{
close();
}
bool QgsPostgresFeatureIterator::nextFeature( QgsFeature& feature )
{
feature.setValid( false );
if ( mClosed )
return false;
if ( mFeatureQueue.empty() )
{
QString fetch = QString( "FETCH FORWARD %1 FROM %2" ).arg( mFeatureQueueSize ).arg( mCursorName );
QgsDebugMsgLevel( QString( "fetching %1 features." ).arg( mFeatureQueueSize ), 4 );
if ( P->mConnectionRO->PQsendQuery( fetch ) == 0 ) // fetch features asynchronously
{
QgsMessageLog::logMessage( QObject::tr( "Fetching from cursor %1 failed\nDatabase error: %2" ).arg( mCursorName ).arg( P->mConnectionRO->PQerrorMessage() ), QObject::tr( "PostGIS" ) );
}
QgsPostgresResult queryResult;
for ( ;; )
{
queryResult = P->mConnectionRO->PQgetResult();
if ( !queryResult.result() )
break;
if ( queryResult.PQresultStatus() != PGRES_TUPLES_OK )
{
QgsMessageLog::logMessage( QObject::tr( "Fetching from cursor %1 failed\nDatabase error: %2" ).arg( mCursorName ).arg( P->mConnectionRO->PQerrorMessage() ), QObject::tr( "PostGIS" ) );
break;
}
int rows = queryResult.PQntuples();
if ( rows == 0 )
continue;
for ( int row = 0; row < rows; row++ )
{
mFeatureQueue.enqueue( QgsFeature() );
getFeature( queryResult, row, mFeatureQueue.back() );
} // for each row in queue
}
}
if ( mFeatureQueue.empty() )
{
QgsDebugMsg( QString( "Finished after %1 features" ).arg( mFetched ) );
close();
/* only updates the feature count if it was already once.
* Otherwise, this would lead to false feature count if
* an existing project is open at a restrictive extent.
*/
if ( P->mFeaturesCounted > 0 && P->mFeaturesCounted < mFetched )
{
QgsDebugMsg( QString( "feature count adjusted from %1 to %2" ).arg( P->mFeaturesCounted ).arg( mFetched ) );
P->mFeaturesCounted = mFetched;
}
return false;
}
// Now return the next feature from the queue
if ( mRequest.flags() & QgsFeatureRequest::NoGeometry )
{
feature.setGeometryAndOwnership( 0, 0 );
}
else
{
QgsGeometry* featureGeom = mFeatureQueue.front().geometryAndOwnership();
feature.setGeometry( featureGeom );
}
feature.setFeatureId( mFeatureQueue.front().id() );
feature.setAttributes( mFeatureQueue.front().attributes() );
mFeatureQueue.dequeue();
mFetched++;
feature.setValid( true );
feature.setFields( &P->mAttributeFields ); // allow name-based attribute lookups
return true;
}
bool QgsPostgresFeatureIterator::rewind()
{
if ( mClosed )
return false;
// move cursor to first record
P->mConnectionRO->PQexecNR( QString( "move absolute 0 in %1" ).arg( mCursorName ) );
mFeatureQueue.empty();
mFetched = 0;
return true;
}
bool QgsPostgresFeatureIterator::close()
{
if ( mClosed )
return false;
P->mConnectionRO->closeCursor( mCursorName );
while ( !mFeatureQueue.empty() )
{
mFeatureQueue.dequeue();
}
P->mActiveIterators.remove( this );
mClosed = true;
return true;
}
///////////////
QString QgsPostgresFeatureIterator::whereClauseRect()
{
QgsRectangle rect = mRequest.filterRect();
if ( P->mSpatialColType == sctGeography )
{
rect = QgsRectangle( -180.0, -90.0, 180.0, 90.0 ).intersect( &rect );
if ( !rect.isFinite() )
return "false";
}
QString qBox;
if ( P->mConnectionRO->majorVersion() < 2 )
{
qBox = QString( "setsrid('BOX3D(%1)'::box3d,%2)" )
.arg( rect.asWktCoordinates() )
.arg( P->mRequestedSrid.isEmpty() ? P->mDetectedSrid : P->mRequestedSrid );
}
else
{
qBox = QString( "st_makeenvelope(%1,%2,%3,%4,%5)" )
.arg( qgsDoubleToString( rect.xMinimum() ) )
.arg( qgsDoubleToString( rect.yMinimum() ) )
.arg( qgsDoubleToString( rect.xMaximum() ) )
.arg( qgsDoubleToString( rect.yMaximum() ) )
.arg( P->mRequestedSrid.isEmpty() ? P->mDetectedSrid : P->mRequestedSrid );
}
QString whereClause = QString( "%1 && %2" )
.arg( P->quotedIdentifier( P->mGeometryColumn ) )
.arg( qBox );
if ( mRequest.flags() & QgsFeatureRequest::ExactIntersect )
{
whereClause += QString( " AND %1(%2%3,%4)" )
.arg( P->mConnectionRO->majorVersion() < 2 ? "intersects" : "st_intersects" )
.arg( P->quotedIdentifier( P->mGeometryColumn ) )
.arg( P->mSpatialColType == sctGeography ? "::geometry" : "" )
.arg( qBox );
}
if ( !P->mRequestedSrid.isEmpty() && ( P->mRequestedSrid != P->mDetectedSrid || P->mRequestedSrid.toInt() == 0 ) )
{
whereClause += QString( " AND %1(%2%3)=%4" )
.arg( P->mConnectionRO->majorVersion() < 2 ? "srid" : "st_srid" )
.arg( P->quotedIdentifier( P->mGeometryColumn ) )
.arg( P->mSpatialColType == sctGeography ? "::geography" : "" )
.arg( P->mRequestedSrid );
}
if ( P->mRequestedGeomType != QGis::WKBUnknown && P->mRequestedGeomType != P->mDetectedGeomType )
{
whereClause += QString( " AND %1" ).arg( QgsPostgresConn::postgisTypeFilter( P->mGeometryColumn, P->mRequestedGeomType, P->mSpatialColType == sctGeography ) );
}
return whereClause;
}
bool QgsPostgresFeatureIterator::declareCursor( const QString& whereClause )
{
bool fetchGeometry = !( mRequest.flags() & QgsFeatureRequest::NoGeometry );
if ( fetchGeometry && P->mGeometryColumn.isNull() )
{
QgsMessageLog::logMessage( QObject::tr( "Trying to fetch geometry on a layer without geometry." ), QObject::tr( "PostgreSQL" ) );
return false;
}
try
{
QString query = "SELECT ", delim = "";
if ( fetchGeometry )
{
query += QString( "%1(%2%3,'%4')" )
.arg( P->mConnectionRO->majorVersion() < 2 ? "asbinary" : "st_asbinary" )
.arg( P->quotedIdentifier( P->mGeometryColumn ) )
.arg( P->mSpatialColType == sctGeography ? "::geometry" : "" )
.arg( P->endianString() );
delim = ",";
}
switch ( P->mPrimaryKeyType )
{
case QgsPostgresProvider::pktOid:
query += delim + "oid";
delim = ",";
break;
case QgsPostgresProvider::pktTid:
query += delim + "ctid";
delim = ",";
break;
case QgsPostgresProvider::pktInt:
query += delim + P->quotedIdentifier( P->field( P->mPrimaryKeyAttrs[0] ).name() );
delim = ",";
break;
case QgsPostgresProvider::pktFidMap:
foreach ( int idx, P->mPrimaryKeyAttrs )
{
query += delim + P->mConnectionRO->fieldExpression( P->field( idx ) );
delim = ",";
}
break;
case QgsPostgresProvider::pktUnknown:
QgsDebugMsg( "Cannot declare cursor without primary key." );
return false;
break;
}
bool subsetOfAttributes = mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes;
foreach ( int idx, subsetOfAttributes ? mRequest.subsetOfAttributes() : P->attributeIndexes() )
{
if ( P->mPrimaryKeyAttrs.contains( idx ) )
continue;
query += delim + P->mConnectionRO->fieldExpression( P->field( idx ) );
}
query += " FROM " + P->mQuery;
if ( !whereClause.isEmpty() )
query += QString( " WHERE %1" ).arg( whereClause );
if ( !P->mConnectionRO->openCursor( mCursorName, query ) )
{
// reloading the fields might help next time around
rewind();
P->loadFields();
return false;
}
}
catch ( QgsPostgresProvider::PGFieldNotFound )
{
rewind();
return false;
}
return true;
}
bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int row, QgsFeature &feature )
{
try
{
feature.initAttributes( P->fields().count() );
int col = 0;
if ( !( mRequest.flags() & QgsFeatureRequest::NoGeometry ) )
{
int returnedLength = ::PQgetlength( queryResult.result(), row, col );
if ( returnedLength > 0 )
{
unsigned char *featureGeom = new unsigned char[returnedLength + 1];
memset( featureGeom, 0, returnedLength + 1 );
memcpy( featureGeom, PQgetvalue( queryResult.result(), row, col ), returnedLength );
// modify 2.5D WKB types to make them compliant with OGR
unsigned int wkbType;
memcpy( &wkbType, featureGeom + 1, sizeof( wkbType) );
// convert unsupported types to supported ones
switch ( wkbType )
{
case 15:
// 2D polyhedral => multipolygon
wkbType = 6;
break;
case 1015:
// 3D polyhedral => multipolygon
wkbType = 1006;
break;
case 17:
// 2D triangle => polygon
wkbType = 3;
break;
case 1017:
// 3D triangle => polygon
wkbType = 1003;
break;
case 16:
// 2D TIN => multipolygon
wkbType = 6;
break;
case 1016:
// TIN => multipolygon
wkbType = 1006;
break;
}
// convert from postgis types to qgis types
if ( wkbType >= 1000 )
{
wkbType = wkbType - 1000 + QGis::WKBPoint25D - 1;
}
memcpy( featureGeom + 1, &wkbType, sizeof( wkbType) );
// change wkb type of inner geometries
if ( wkbType == QGis::WKBMultiPoint25D ||
wkbType == QGis::WKBMultiLineString25D ||
wkbType == QGis::WKBMultiPolygon25D )
{
unsigned int numGeoms = *(( int* )( featureGeom + 5 ));
unsigned char* wkb = featureGeom + 9;
for ( unsigned int i = 0; i < numGeoms; ++i )
{
unsigned int localType;
memcpy( &localType, wkb + 1, sizeof( localType) );
switch ( localType )
{
case 15:
// 2D polyhedral => multipolygon
localType = 6;
break;
case 1015:
// 3D polyhedral => multipolygon
localType = 1006;
break;
case 17:
// 2D triangle => polygon
localType = 3;
break;
case 1017:
// 3D triangle => polygon
localType = 1003;
break;
case 16:
// 2D TIN => multipolygon
localType = 6;
break;
case 1016:
// TIN => multipolygon
localType = 1006;
break;
}
if ( localType >= 1000 )
{
localType = localType - 1000 + QGis::WKBPoint25D - 1;
}
memcpy( wkb + 1, &localType, sizeof( localType) );
// skip endian and type info
wkb += sizeof( unsigned int ) + 1;
// skip coordinates
switch ( wkbType )
{
case QGis::WKBMultiPoint25D:
wkb += sizeof( double ) * 3;
break;
case QGis::WKBMultiLineString25D:
{
unsigned int nPoints = *(( int* ) wkb );
wkb += sizeof( nPoints );
wkb += sizeof( double ) * 3 * nPoints;
}
break;
default:
case QGis::WKBMultiPolygon25D:
{
unsigned int nRings = *(( int* ) wkb );
wkb += sizeof( nRings );
for ( unsigned int j = 0; j < nRings; ++j )
{
unsigned int nPoints = *(( int* ) wkb );
wkb += sizeof( nPoints );
wkb += sizeof( double ) * 3 * nPoints;
}
}
break;
}
}
}
feature.setGeometryAndOwnership( featureGeom, returnedLength + 1 );
}
else
{
feature.setGeometryAndOwnership( 0, 0 );
}
col++;
}
QgsFeatureId fid = 0;
bool subsetOfAttributes = mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes;
const QgsAttributeList& fetchAttributes = mRequest.subsetOfAttributes();
switch ( P->mPrimaryKeyType )
{
case QgsPostgresProvider::pktOid:
case QgsPostgresProvider::pktTid:
case QgsPostgresProvider::pktInt:
fid = P->mConnectionRO->getBinaryInt( queryResult, row, col++ );
if ( P->mPrimaryKeyType == QgsPostgresProvider::pktInt &&
( !subsetOfAttributes || fetchAttributes.contains( P->mPrimaryKeyAttrs[0] ) ) )
feature.setAttribute( P->mPrimaryKeyAttrs[0], fid );
break;
case QgsPostgresProvider::pktFidMap:
{
QList<QVariant> primaryKeyVals;
foreach ( int idx, P->mPrimaryKeyAttrs )
{
const QgsField &fld = P->field( idx );
QVariant v = P->convertValue( fld.type(), queryResult.PQgetvalue( row, col ) );
primaryKeyVals << v;
if ( !subsetOfAttributes || fetchAttributes.contains( idx ) )
feature.setAttribute( idx, v );
col++;
}
fid = P->lookupFid( QVariant( primaryKeyVals ) );
}
break;
case QgsPostgresProvider::pktUnknown:
Q_ASSERT( !"FAILURE: cannot get feature with unknown primary key" );
return false;
}
feature.setFeatureId( fid );
QgsDebugMsgLevel( QString( "fid=%1" ).arg( fid ), 4 );
// iterate attributes
if ( subsetOfAttributes )
{
foreach ( int idx, fetchAttributes )
getFeatureAttribute( idx, queryResult, row, col, feature );
}
else
{
for ( int idx = 0; idx < P->mAttributeFields.count(); ++idx )
getFeatureAttribute( idx, queryResult, row, col, feature );
}
return true;
}
catch ( QgsPostgresProvider::PGFieldNotFound )
{
return false;
}
}
void QgsPostgresFeatureIterator::getFeatureAttribute( int idx, QgsPostgresResult& queryResult, int row, int& col, QgsFeature& feature )
{
if ( P->mPrimaryKeyAttrs.contains( idx ) )
return;
QVariant v = P->convertValue( P->mAttributeFields[idx].type(), queryResult.PQgetvalue( row, col ) );
feature.setAttribute( idx, v );
col++;
}