bool QgsPostgresFeatureIterator::fetchFeature( 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 ( mConn->PQsendQuery( fetch ) == 0 ) // fetch features asynchronously { QgsMessageLog::logMessage( QObject::tr( "Fetching from cursor %1 failed\nDatabase error: %2" ).arg( mCursorName, mConn->PQerrorMessage() ), QObject::tr( "PostGIS" ) ); } QgsPostgresResult queryResult; for ( ;; ) { queryResult = mConn->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, mConn->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(); mSource->mShared->ensureFeaturesCountedAtLeast( mFetched ); return false; } feature = mFeatureQueue.dequeue(); mFetched++; feature.setValid( true ); feature.setFields( mSource->mFields ); // allow name-based attribute lookups return true; }
bool QgsPostgresFeatureIterator::nextFeature( QgsFeature& feature ) { feature.setValid( false ); if ( mClosed ) return false; #if 0 // featureAtId used to have some special checks - necessary? if ( !mUseQueue ) { QgsPostgresResult queryResult = P->mConnectionRO->PQexec( QString( "FETCH FORWARD 1 FROM %1" ).arg( mCursorName ) ); int rows = queryResult.PQntuples(); if ( rows == 0 ) { QgsMessageLog::logMessage( tr( "feature %1 not found" ).arg( featureId ), tr( "PostGIS" ) ); P->mConnectionRO->closeCursor( cursorName ); return false; } else if ( rows != 1 ) { QgsMessageLog::logMessage( tr( "found %1 features instead of just one." ).arg( rows ), tr( "PostGIS" ) ); } bool gotit = getFeature( queryResult, 0, feature ); feature.setValid( gotit ); feature.setFields( &P->mAttributeFields ); // allow name-based attribute lookups return gotit; } #endif 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(); if ( 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::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 ); 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; }
qint64 QgsPostgresConn::getBinaryInt( QgsPostgresResult &queryResult, int row, int col ) { quint64 oid; char *p = PQgetvalue( queryResult.result(), row, col ); size_t s = PQgetlength( queryResult.result(), row, col ); #ifdef QGISDEBUG if ( QgsLogger::debugLevel() >= 4 ) { QString buf; for ( size_t i = 0; i < s; i++ ) { buf += QString( "%1 " ).arg( *( unsigned char * )( p + i ), 0, 16, QLatin1Char( ' ' ) ); } QgsDebugMsg( QString( "int in hex:%1" ).arg( buf ) ); } #endif switch ( s ) { case 2: oid = *( quint16 * )p; if ( mSwapEndian ) oid = ntohs( oid ); break; case 6: { quint64 block = *( quint32 * ) p; quint64 offset = *( quint16 * )( p + sizeof( quint32 ) ); if ( mSwapEndian ) { block = ntohl( block ); offset = ntohs( offset ); } oid = ( block << 16 ) + offset; } break; case 8: { quint32 oid0 = *( quint32 * ) p; quint32 oid1 = *( quint32 * )( p + sizeof( quint32 ) ); if ( mSwapEndian ) { QgsDebugMsgLevel( QString( "swap oid0:%1 oid1:%2" ).arg( oid0 ).arg( oid1 ), 4 ); oid0 = ntohl( oid0 ); oid1 = ntohl( oid1 ); } QgsDebugMsgLevel( QString( "oid0:%1 oid1:%2" ).arg( oid0 ).arg( oid1 ), 4 ); oid = oid0; QgsDebugMsgLevel( QString( "oid:%1" ).arg( oid ), 4 ); oid <<= 32; QgsDebugMsgLevel( QString( "oid:%1" ).arg( oid ), 4 ); oid |= oid1; QgsDebugMsgLevel( QString( "oid:%1" ).arg( oid ), 4 ); } break; default: QgsDebugMsg( QString( "unexpected size %1" ).arg( s ) ); case 4: oid = *( quint32 * )p; if ( mSwapEndian ) oid = ntohl( oid ); break; } return oid; }
bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int row, QgsFeature &feature ) { feature.initAttributes( mSource->mFields.count() ); int col = 0; if ( mFetchGeometry ) { int returnedLength = ::PQgetlength( queryResult.result(), row, col ); if ( returnedLength > 0 ) { unsigned char *featureGeom = new unsigned char[returnedLength + 1]; memcpy( featureGeom, PQgetvalue( queryResult.result(), row, col ), returnedLength ); memset( featureGeom + returnedLength, 0, 1 ); unsigned int wkbType; memcpy( &wkbType, featureGeom + 1, sizeof( wkbType ) ); QgsWKBTypes::Type newType = QgsPostgresConn::wkbTypeFromOgcWkbType( wkbType ); if (( unsigned int )newType != wkbType ) { // overwrite type unsigned int n = newType; memcpy( featureGeom + 1, &n, sizeof( n ) ); } // PostGIS stores TIN as a collection of Triangles. // Since Triangles are not supported, they have to be converted to Polygons const int nDims = 2 + ( QgsWKBTypes::hasZ( newType ) ? 1 : 0 ) + ( QgsWKBTypes::hasM( newType ) ? 1 : 0 ); if ( wkbType % 1000 == 16 ) { unsigned int numGeoms; memcpy( &numGeoms, featureGeom + 5, sizeof( unsigned int ) ); unsigned char *wkb = featureGeom + 9; for ( unsigned int i = 0; i < numGeoms; ++i ) { const unsigned int localType = QgsWKBTypes::singleType( newType ); // polygon(Z|M) memcpy( wkb + 1, &localType, sizeof( localType ) ); // skip endian and type info wkb += sizeof( unsigned int ) + 1; // skip coordinates unsigned int nRings; memcpy( &nRings, wkb, sizeof( int ) ); wkb += sizeof( int ); for ( unsigned int j = 0; j < nRings; ++j ) { unsigned int nPoints; memcpy( &nPoints, wkb, sizeof( int ) ); wkb += sizeof( nPoints ) + sizeof( double ) * nDims * nPoints; } } } 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 ( mSource->mPrimaryKeyType ) { case pktOid: case pktTid: case pktInt: fid = mConn->getBinaryInt( queryResult, row, col++ ); if ( mSource->mPrimaryKeyType == pktInt && ( !subsetOfAttributes || fetchAttributes.contains( mSource->mPrimaryKeyAttrs[0] ) ) ) feature.setAttribute( mSource->mPrimaryKeyAttrs[0], fid ); break; case pktFidMap: { QList<QVariant> primaryKeyVals; Q_FOREACH ( int idx, mSource->mPrimaryKeyAttrs ) { const QgsField &fld = mSource->mFields.at( idx ); QVariant v = QgsPostgresProvider::convertValue( fld.type(), queryResult.PQgetvalue( row, col ) ); primaryKeyVals << v; if ( !subsetOfAttributes || fetchAttributes.contains( idx ) ) feature.setAttribute( idx, v ); col++; } fid = mSource->mShared->lookupFid( QVariant( primaryKeyVals ) ); } break; case 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 ) { Q_FOREACH ( int idx, fetchAttributes ) getFeatureAttribute( idx, queryResult, row, col, feature ); } else { for ( int idx = 0; idx < mSource->mFields.count(); ++idx )
bool QgsPostgresFeatureIterator::fetchFeature( 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::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; }
bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int row, QgsFeature &feature ) { feature.initAttributes( mSource->mFields.count() ); int col = 0; if ( mFetchGeometry ) { int returnedLength = ::PQgetlength( queryResult.result(), row, col ); if ( returnedLength > 0 ) { unsigned char *featureGeom = new unsigned char[returnedLength + 1]; memcpy( featureGeom, PQgetvalue( queryResult.result(), row, col ), returnedLength ); memset( featureGeom + returnedLength, 0, 1 ); // modify 2.5D WKB types to make them compliant with OGR unsigned int wkbType; memcpy( &wkbType, featureGeom + 1, sizeof( wkbType ) ); wkbType = QgsPostgresConn::wkbTypeFromOgcWkbType( wkbType ); 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; memcpy( &numGeoms, featureGeom + 5, sizeof( unsigned int ) ); unsigned char *wkb = featureGeom + 9; for ( unsigned int i = 0; i < numGeoms; ++i ) { unsigned int localType; memcpy( &localType, wkb + 1, sizeof( localType ) ); localType = QgsPostgresConn::wkbTypeFromOgcWkbType( localType ); 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; memcpy( &nPoints, wkb, sizeof( int ) ); wkb += sizeof( int ) + sizeof( double ) * 3 * nPoints; } break; default: case QGis::WKBMultiPolygon25D: { unsigned int nRings; memcpy( &nRings, wkb, sizeof( int ) ); wkb += sizeof( int ); for ( unsigned int j = 0; j < nRings; ++j ) { unsigned int nPoints; memcpy( &nPoints, wkb, sizeof( int ) ); wkb += sizeof( nPoints ) + 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 ( mSource->mPrimaryKeyType ) { case pktOid: case pktTid: case pktInt: fid = mConn->getBinaryInt( queryResult, row, col++ ); if ( mSource->mPrimaryKeyType == pktInt && ( !subsetOfAttributes || fetchAttributes.contains( mSource->mPrimaryKeyAttrs[0] ) ) ) feature.setAttribute( mSource->mPrimaryKeyAttrs[0], fid ); break; case pktFidMap: { QList<QVariant> primaryKeyVals; Q_FOREACH ( int idx, mSource->mPrimaryKeyAttrs ) { const QgsField &fld = mSource->mFields[idx]; QVariant v = QgsPostgresProvider::convertValue( fld.type(), queryResult.PQgetvalue( row, col ) ); primaryKeyVals << v; if ( !subsetOfAttributes || fetchAttributes.contains( idx ) ) feature.setAttribute( idx, v ); col++; } fid = mSource->mShared->lookupFid( QVariant( primaryKeyVals ) ); } break; case 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 ) { Q_FOREACH ( int idx, fetchAttributes ) getFeatureAttribute( idx, queryResult, row, col, feature ); } else { for ( int idx = 0; idx < mSource->mFields.count(); ++idx )