From f8277d91541e587e8e77dfdd4818d78801ec7832 Mon Sep 17 00:00:00 2001 From: jef Date: Sun, 5 Apr 2009 10:51:35 +0000 Subject: [PATCH] use ctid as temporary feature id in postgres provider if there is no other primary key available git-svn-id: http://svn.osgeo.org/qgis/trunk@10474 c8812cc2-4d05-0410-92ff-de0c093fc19c --- .../postgres/qgspostgresprovider.cpp | 155 ++++++++++++++---- src/providers/postgres/qgspostgresprovider.h | 2 + 2 files changed, 124 insertions(+), 33 deletions(-) diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 319ace917644..5ad296fc706b 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -450,9 +450,39 @@ bool QgsPostgresProvider::getFeature( PGresult *queryResult, int row, bool fetch { try { - int oid = *( int * )PQgetvalue( queryResult, row, 0 ); - if ( swapEndian ) - oid = ntohl( oid ); // convert oid to opposite endian + int oid; + + if ( primaryKeyType != "tid" ) + { + oid = *( int * )PQgetvalue( queryResult, row, 0 ); + if ( swapEndian ) + oid = ntohl( oid ); // convert oid to opposite endian + } + else if ( PQgetlength( queryResult, row, 0 ) == 6 ) + { + char *data = PQgetvalue( queryResult, row, 0 ); + int block = *( int * )data; + int offset = *( short * )( data + sizeof( int ) ); + + if ( swapEndian ) + { + block = ntohl( block ); + offset = ntohs( offset ); + } + + if ( block > 0xffff ) + { + qWarning( "block number %x exceed 16 bit", block ); + return false; + } + + oid = ( block << 16 ) + offset; + } + else + { + qWarning( "expecting 6 bytes for tid (found %d bytes)", PQgetlength( queryResult, row, 0 ) ); + return false; + } feature.setFeatureId( oid ); @@ -627,10 +657,23 @@ bool QgsPostgresProvider::nextFeature( QgsFeature& feature ) return true; } +QString QgsPostgresProvider::whereClause( int featureId ) const +{ + if ( primaryKeyType != "tid" ) + { + return QString( "%1=%2" ).arg( quotedIdentifier( primaryKey ) ).arg( featureId ); + } + else + { + return QString( "%1='(%2,%3)'" ).arg( quotedIdentifier( primaryKey ) ).arg( featureId >> 16 ).arg( featureId & 0xffff ); + } +} + bool QgsPostgresProvider::featureAtId( int featureId, QgsFeature& feature, bool fetchGeometry, QgsAttributeList fetchAttributes ) { QString cursorName = QString( "qgisfid%1" ).arg( providerId ); - if ( !declareCursor( cursorName, fetchAttributes, fetchGeometry, QString( "%2=%3" ).arg( quotedIdentifier( primaryKey ) ).arg( featureId ) ) ) + + if ( !declareCursor( cursorName, fetchAttributes, fetchGeometry, whereClause( featureId ) ) ) return false; Result queryResult = connectionRO->PQexec( QString( "fetch forward 1 from %1" ).arg( cursorName ) ); @@ -640,7 +683,7 @@ bool QgsPostgresProvider::featureAtId( int featureId, QgsFeature& feature, bool int rows = PQntuples( queryResult ); if ( rows == 0 ) { - QgsDebugMsg( "feature " + QString::number( featureId ) + " not found" ); + QgsDebugMsg( QString( "feature %1 not found" ).arg( featureId ) ); connectionRO->closeCursor( cursorName ); return false; } @@ -916,12 +959,40 @@ QString QgsPostgresProvider::getPrimaryKey() primaryKeyType = "int4"; } else + { + sql = QString( "SELECT attname FROM pg_attribute WHERE attname='ctid' AND attrelid=regclass(%1)" ) + .arg( quotedValue( mSchemaTableName ) ); + + Result ctidCheck = connectionRO->PQexec( sql ); + + if ( PQntuples( ctidCheck ) == 1 ) + { + sql = QString( "SELECT max(substring(ctid::text from E'\\\\((\\\\d+),\\\\d+\\\\)')::integer) from %1" ) + .arg( mSchemaTableName ); + + Result ctidCheck = connectionRO->PQexec( sql ); + if ( PQntuples( ctidCheck ) == 1 ) + { + int id = QString( PQgetvalue( ctidCheck, 0, 0 ) ).toInt(); + + if ( id < 0x10000 ) + { + // fallback to ctid + primaryKey = "ctid"; + primaryKeyType = "tid"; + } + } + } + } + + if ( primaryKey.isEmpty() ) { showMessageBox( tr( "No suitable key column in table" ), tr( "The table has no column suitable for use as a key.\n\n" "Qgis requires that the table either has a column of type\n" "int4 with a unique constraint on it (which includes the\n" - "primary key) or has a PostgreSQL oid column.\n" ) ); + "primary key), has a PostgreSQL oid column or has a ctid\n" + "column with a 16bit block number.\n" ) ); } } else if ( type == "v" ) // the relation is a view @@ -1739,14 +1810,25 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist ) connectionRW->PQexecNR( "BEGIN" ); // Prepare the INSERT statement - QString insert = QString( "INSERT INTO %1(%2,%3" ) + QString insert = QString( "INSERT INTO %1(%2" ) .arg( mSchemaTableName ) - .arg( quotedIdentifier( geometryColumn ) ) - .arg( quotedIdentifier( primaryKey ) ), - values = QString( ") VALUES (GeomFromWKB($1%1,%2),$2" ) + .arg( quotedIdentifier( geometryColumn ) ), + values = QString( ") VALUES (GeomFromWKB($1%1,%2)" ) .arg( connectionRW->useWkbHex() ? "" : "::bytea" ) .arg( srid ); + int offset; + if ( primaryKeyType != "tid" ) + { + insert += "," + quotedIdentifier( primaryKey ); + values += ",$2"; + offset = 3; + } + else + { + offset = 2; + } + const QgsAttributeMap &attributevec = flist[0].attributeMap(); QStringList defaultValues; @@ -1811,11 +1893,11 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist ) // value is not unique => add parameter if ( fit->typeName() == "geometry" ) { - values += QString( ",geomfromewkt($%1)" ).arg( defaultValues.size() + 3 ); + values += QString( ",geomfromewkt($%1)" ).arg( defaultValues.size() + offset ); } else { - values += QString( ",$%1" ).arg( defaultValues.size() + 3 ); + values += QString( ",$%1" ).arg( defaultValues.size() + offset ); } defaultValues.append( defVal ); fieldId.append( it.key() ); @@ -1825,7 +1907,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist ) insert += values + ")"; QgsDebugMsg( QString( "prepare addfeatures: %1" ).arg( insert ) ); - PGresult *stmt = connectionRW->PQprepare( "addfeatures", insert, fieldId.size() + 2, NULL ); + PGresult *stmt = connectionRW->PQprepare( "addfeatures", insert, fieldId.size() + offset - 1, NULL ); if ( stmt == 0 || PQresultStatus( stmt ) == PGRES_FATAL_ERROR ) throw PGException( stmt ); PQclear( stmt ); @@ -1846,17 +1928,20 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist ) QStringList params; params << geomParam; - if ( keyDefault.isNull() ) + if ( primaryKeyType != "tid" ) { - ++primaryKeyHighWater; - params << QString::number( primaryKeyHighWater ); - newIds << primaryKeyHighWater; - } - else - { - QByteArray key = paramValue( keyDefault, keyDefault ); - params << key; - newIds << key.toInt(); + if ( keyDefault.isNull() ) + { + ++primaryKeyHighWater; + params << QString::number( primaryKeyHighWater ); + newIds << primaryKeyHighWater; + } + else + { + QByteArray key = paramValue( keyDefault, keyDefault ); + params << key; + newIds << key.toInt(); + } } for ( int i = 0; i < fieldId.size(); i++ ) @@ -1868,8 +1953,9 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist ) PQclear( result ); } - for ( int i = 0; i < flist.size(); i++ ) - flist[i].setFeatureId( newIds[i] ); + if ( flist.size() == newIds.size() ) + for ( int i = 0; i < flist.size(); i++ ) + flist[i].setFeatureId( newIds[i] ); connectionRW->PQexecNR( "DEALLOCATE addfeatures" ); connectionRW->PQexecNR( "COMMIT" ); @@ -1901,10 +1987,8 @@ bool QgsPostgresProvider::deleteFeatures( const QgsFeatureIds & id ) for ( QgsFeatureIds::const_iterator it = id.begin();it != id.end();++it ) { - QString sql = QString( "DELETE FROM %1 WHERE %2=%3" ) - .arg( mSchemaTableName ) - .arg( quotedIdentifier( primaryKey ) ) - .arg( *it ); + QString sql = QString( "DELETE FROM %1 WHERE %2" ) + .arg( mSchemaTableName ).arg( whereClause( *it ) ); QgsDebugMsg( "delete sql: " + sql ); //send DELETE statement and do error handling @@ -2059,9 +2143,7 @@ bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap & } } - sql += QString( " WHERE %1=%2" ) - .arg( quotedIdentifier( primaryKey ) ) - .arg( fid ); + sql += QString( " WHERE %1" ).arg( whereClause( fid ) ); PGresult *result = connectionRW->PQexec( sql ); if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR ) @@ -2137,7 +2219,14 @@ bool QgsPostgresProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) QStringList params; params << geomParam; - params << QString( "%1" ).arg( iter.key() ); + if ( primaryKeyType != "tid" ) + { + params << QString( "%1" ).arg( iter.key() ); + } + else + { + params << QString( "(%1,%2)" ).arg( iter.key() >> 16 ).arg( iter.key() & 0xffff ); + } PGresult *result = connectionRW->PQexecPrepared( "updatefeatures", params ); if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR ) diff --git a/src/providers/postgres/qgspostgresprovider.h b/src/providers/postgres/qgspostgresprovider.h index 681dfc5e311f..98005980ef11 100644 --- a/src/providers/postgres/qgspostgresprovider.h +++ b/src/providers/postgres/qgspostgresprovider.h @@ -322,6 +322,8 @@ class QgsPostgresProvider : public QgsVectorDataProvider QgsFeature &feature, const QgsAttributeList &fetchAttributes ); + QString whereClause( int featureId ) const; + const QgsField &field( int index ) const; /** Double quote a PostgreSQL identifier for placement in a SQL string.