Skip to content

Commit 79e4871

Browse files
author
jef
committed
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/qgis@10474 c8812cc2-4d05-0410-92ff-de0c093fc19c
1 parent 92266b9 commit 79e4871

File tree

2 files changed

+124
-33
lines changed

2 files changed

+124
-33
lines changed

src/providers/postgres/qgspostgresprovider.cpp

Lines changed: 122 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -450,9 +450,39 @@ bool QgsPostgresProvider::getFeature( PGresult *queryResult, int row, bool fetch
450450
{
451451
try
452452
{
453-
int oid = *( int * )PQgetvalue( queryResult, row, 0 );
454-
if ( swapEndian )
455-
oid = ntohl( oid ); // convert oid to opposite endian
453+
int oid;
454+
455+
if ( primaryKeyType != "tid" )
456+
{
457+
oid = *( int * )PQgetvalue( queryResult, row, 0 );
458+
if ( swapEndian )
459+
oid = ntohl( oid ); // convert oid to opposite endian
460+
}
461+
else if ( PQgetlength( queryResult, row, 0 ) == 6 )
462+
{
463+
char *data = PQgetvalue( queryResult, row, 0 );
464+
int block = *( int * )data;
465+
int offset = *( short * )( data + sizeof( int ) );
466+
467+
if ( swapEndian )
468+
{
469+
block = ntohl( block );
470+
offset = ntohs( offset );
471+
}
472+
473+
if ( block > 0xffff )
474+
{
475+
qWarning( "block number %x exceed 16 bit", block );
476+
return false;
477+
}
478+
479+
oid = ( block << 16 ) + offset;
480+
}
481+
else
482+
{
483+
qWarning( "expecting 6 bytes for tid (found %d bytes)", PQgetlength( queryResult, row, 0 ) );
484+
return false;
485+
}
456486

457487
feature.setFeatureId( oid );
458488

@@ -627,10 +657,23 @@ bool QgsPostgresProvider::nextFeature( QgsFeature& feature )
627657
return true;
628658
}
629659

660+
QString QgsPostgresProvider::whereClause( int featureId ) const
661+
{
662+
if ( primaryKeyType != "tid" )
663+
{
664+
return QString( "%1=%2" ).arg( quotedIdentifier( primaryKey ) ).arg( featureId );
665+
}
666+
else
667+
{
668+
return QString( "%1='(%2,%3)'" ).arg( quotedIdentifier( primaryKey ) ).arg( featureId >> 16 ).arg( featureId & 0xffff );
669+
}
670+
}
671+
630672
bool QgsPostgresProvider::featureAtId( int featureId, QgsFeature& feature, bool fetchGeometry, QgsAttributeList fetchAttributes )
631673
{
632674
QString cursorName = QString( "qgisfid%1" ).arg( providerId );
633-
if ( !declareCursor( cursorName, fetchAttributes, fetchGeometry, QString( "%2=%3" ).arg( quotedIdentifier( primaryKey ) ).arg( featureId ) ) )
675+
676+
if ( !declareCursor( cursorName, fetchAttributes, fetchGeometry, whereClause( featureId ) ) )
634677
return false;
635678

636679
Result queryResult = connectionRO->PQexec( QString( "fetch forward 1 from %1" ).arg( cursorName ) );
@@ -640,7 +683,7 @@ bool QgsPostgresProvider::featureAtId( int featureId, QgsFeature& feature, bool
640683
int rows = PQntuples( queryResult );
641684
if ( rows == 0 )
642685
{
643-
QgsDebugMsg( "feature " + QString::number( featureId ) + " not found" );
686+
QgsDebugMsg( QString( "feature %1 not found" ).arg( featureId ) );
644687
connectionRO->closeCursor( cursorName );
645688
return false;
646689
}
@@ -916,12 +959,40 @@ QString QgsPostgresProvider::getPrimaryKey()
916959
primaryKeyType = "int4";
917960
}
918961
else
962+
{
963+
sql = QString( "SELECT attname FROM pg_attribute WHERE attname='ctid' AND attrelid=regclass(%1)" )
964+
.arg( quotedValue( mSchemaTableName ) );
965+
966+
Result ctidCheck = connectionRO->PQexec( sql );
967+
968+
if ( PQntuples( ctidCheck ) == 1 )
969+
{
970+
sql = QString( "SELECT max(substring(ctid::text from E'\\\\((\\\\d+),\\\\d+\\\\)')::integer) from %1" )
971+
.arg( mSchemaTableName );
972+
973+
Result ctidCheck = connectionRO->PQexec( sql );
974+
if ( PQntuples( ctidCheck ) == 1 )
975+
{
976+
int id = QString( PQgetvalue( ctidCheck, 0, 0 ) ).toInt();
977+
978+
if ( id < 0x10000 )
979+
{
980+
// fallback to ctid
981+
primaryKey = "ctid";
982+
primaryKeyType = "tid";
983+
}
984+
}
985+
}
986+
}
987+
988+
if ( primaryKey.isEmpty() )
919989
{
920990
showMessageBox( tr( "No suitable key column in table" ),
921991
tr( "The table has no column suitable for use as a key.\n\n"
922992
"Qgis requires that the table either has a column of type\n"
923993
"int4 with a unique constraint on it (which includes the\n"
924-
"primary key) or has a PostgreSQL oid column.\n" ) );
994+
"primary key), has a PostgreSQL oid column or has a ctid\n"
995+
"column with a 16bit block number.\n" ) );
925996
}
926997
}
927998
else if ( type == "v" ) // the relation is a view
@@ -1739,14 +1810,25 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist )
17391810
connectionRW->PQexecNR( "BEGIN" );
17401811

17411812
// Prepare the INSERT statement
1742-
QString insert = QString( "INSERT INTO %1(%2,%3" )
1813+
QString insert = QString( "INSERT INTO %1(%2" )
17431814
.arg( mSchemaTableName )
1744-
.arg( quotedIdentifier( geometryColumn ) )
1745-
.arg( quotedIdentifier( primaryKey ) ),
1746-
values = QString( ") VALUES (GeomFromWKB($1%1,%2),$2" )
1815+
.arg( quotedIdentifier( geometryColumn ) ),
1816+
values = QString( ") VALUES (GeomFromWKB($1%1,%2)" )
17471817
.arg( connectionRW->useWkbHex() ? "" : "::bytea" )
17481818
.arg( srid );
17491819

1820+
int offset;
1821+
if ( primaryKeyType != "tid" )
1822+
{
1823+
insert += "," + quotedIdentifier( primaryKey );
1824+
values += ",$2";
1825+
offset = 3;
1826+
}
1827+
else
1828+
{
1829+
offset = 2;
1830+
}
1831+
17501832
const QgsAttributeMap &attributevec = flist[0].attributeMap();
17511833

17521834
QStringList defaultValues;
@@ -1811,11 +1893,11 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist )
18111893
// value is not unique => add parameter
18121894
if ( fit->typeName() == "geometry" )
18131895
{
1814-
values += QString( ",geomfromewkt($%1)" ).arg( defaultValues.size() + 3 );
1896+
values += QString( ",geomfromewkt($%1)" ).arg( defaultValues.size() + offset );
18151897
}
18161898
else
18171899
{
1818-
values += QString( ",$%1" ).arg( defaultValues.size() + 3 );
1900+
values += QString( ",$%1" ).arg( defaultValues.size() + offset );
18191901
}
18201902
defaultValues.append( defVal );
18211903
fieldId.append( it.key() );
@@ -1825,7 +1907,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist )
18251907
insert += values + ")";
18261908

18271909
QgsDebugMsg( QString( "prepare addfeatures: %1" ).arg( insert ) );
1828-
PGresult *stmt = connectionRW->PQprepare( "addfeatures", insert, fieldId.size() + 2, NULL );
1910+
PGresult *stmt = connectionRW->PQprepare( "addfeatures", insert, fieldId.size() + offset - 1, NULL );
18291911
if ( stmt == 0 || PQresultStatus( stmt ) == PGRES_FATAL_ERROR )
18301912
throw PGException( stmt );
18311913
PQclear( stmt );
@@ -1846,17 +1928,20 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist )
18461928
QStringList params;
18471929
params << geomParam;
18481930

1849-
if ( keyDefault.isNull() )
1931+
if ( primaryKeyType != "tid" )
18501932
{
1851-
++primaryKeyHighWater;
1852-
params << QString::number( primaryKeyHighWater );
1853-
newIds << primaryKeyHighWater;
1854-
}
1855-
else
1856-
{
1857-
QByteArray key = paramValue( keyDefault, keyDefault );
1858-
params << key;
1859-
newIds << key.toInt();
1933+
if ( keyDefault.isNull() )
1934+
{
1935+
++primaryKeyHighWater;
1936+
params << QString::number( primaryKeyHighWater );
1937+
newIds << primaryKeyHighWater;
1938+
}
1939+
else
1940+
{
1941+
QByteArray key = paramValue( keyDefault, keyDefault );
1942+
params << key;
1943+
newIds << key.toInt();
1944+
}
18601945
}
18611946

18621947
for ( int i = 0; i < fieldId.size(); i++ )
@@ -1868,8 +1953,9 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList & flist )
18681953
PQclear( result );
18691954
}
18701955

1871-
for ( int i = 0; i < flist.size(); i++ )
1872-
flist[i].setFeatureId( newIds[i] );
1956+
if ( flist.size() == newIds.size() )
1957+
for ( int i = 0; i < flist.size(); i++ )
1958+
flist[i].setFeatureId( newIds[i] );
18731959

18741960
connectionRW->PQexecNR( "DEALLOCATE addfeatures" );
18751961
connectionRW->PQexecNR( "COMMIT" );
@@ -1901,10 +1987,8 @@ bool QgsPostgresProvider::deleteFeatures( const QgsFeatureIds & id )
19011987

19021988
for ( QgsFeatureIds::const_iterator it = id.begin();it != id.end();++it )
19031989
{
1904-
QString sql = QString( "DELETE FROM %1 WHERE %2=%3" )
1905-
.arg( mSchemaTableName )
1906-
.arg( quotedIdentifier( primaryKey ) )
1907-
.arg( *it );
1990+
QString sql = QString( "DELETE FROM %1 WHERE %2" )
1991+
.arg( mSchemaTableName ).arg( whereClause( *it ) );
19081992
QgsDebugMsg( "delete sql: " + sql );
19091993

19101994
//send DELETE statement and do error handling
@@ -2059,9 +2143,7 @@ bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap &
20592143
}
20602144
}
20612145

2062-
sql += QString( " WHERE %1=%2" )
2063-
.arg( quotedIdentifier( primaryKey ) )
2064-
.arg( fid );
2146+
sql += QString( " WHERE %1" ).arg( whereClause( fid ) );
20652147

20662148
PGresult *result = connectionRW->PQexec( sql );
20672149
if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR )
@@ -2137,7 +2219,14 @@ bool QgsPostgresProvider::changeGeometryValues( QgsGeometryMap & geometry_map )
21372219

21382220
QStringList params;
21392221
params << geomParam;
2140-
params << QString( "%1" ).arg( iter.key() );
2222+
if ( primaryKeyType != "tid" )
2223+
{
2224+
params << QString( "%1" ).arg( iter.key() );
2225+
}
2226+
else
2227+
{
2228+
params << QString( "(%1,%2)" ).arg( iter.key() >> 16 ).arg( iter.key() & 0xffff );
2229+
}
21412230

21422231
PGresult *result = connectionRW->PQexecPrepared( "updatefeatures", params );
21432232
if ( result == 0 || PQresultStatus( result ) == PGRES_FATAL_ERROR )

src/providers/postgres/qgspostgresprovider.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ class QgsPostgresProvider : public QgsVectorDataProvider
322322
QgsFeature &feature,
323323
const QgsAttributeList &fetchAttributes );
324324

325+
QString whereClause( int featureId ) const;
326+
325327
const QgsField &field( int index ) const;
326328

327329
/** Double quote a PostgreSQL identifier for placement in a SQL string.

0 commit comments

Comments
 (0)