Skip to content

Commit ada9348

Browse files
committedOct 14, 2016
Fix PostgreSQL import of layers with multi-column or quoted-column keys
Fixes #15226 (drag & drop of postgresql views) Includes test
1 parent 8207167 commit ada9348

File tree

3 files changed

+171
-85
lines changed

3 files changed

+171
-85
lines changed
 

‎src/providers/postgres/qgspostgresprovider.cpp

Lines changed: 128 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,56 +1360,73 @@ bool QgsPostgresProvider::determinePrimaryKey()
13601360
return mValid;
13611361
}
13621362

1363-
void QgsPostgresProvider::determinePrimaryKeyFromUriKeyColumn()
1363+
/* static */
1364+
QStringList QgsPostgresProvider::parseUriKey( const QString& key )
13641365
{
1365-
QString primaryKey = mUri.keyColumn();
1366-
mPrimaryKeyType = pktUnknown;
1366+
if ( key.isEmpty() ) return QStringList();
13671367

1368-
if ( !primaryKey.isEmpty() )
1369-
{
1370-
QStringList cols;
1368+
QStringList cols;
13711369

1372-
// remove quotes from key list
1373-
if ( primaryKey.startsWith( '"' ) && primaryKey.endsWith( '"' ) )
1370+
// remove quotes from key list
1371+
if ( key.startsWith( '"' ) && key.endsWith( '"' ) )
1372+
{
1373+
int i = 1;
1374+
QString col;
1375+
while ( i < key.size() )
13741376
{
1375-
int i = 1;
1376-
QString col;
1377-
while ( i < primaryKey.size() )
1377+
if ( key[i] == '"' )
13781378
{
1379-
if ( primaryKey[i] == '"' )
1379+
if ( i + 1 < key.size() && key[i+1] == '"' )
13801380
{
1381-
if ( i + 1 < primaryKey.size() && primaryKey[i+1] == '"' )
1382-
{
1383-
i++;
1384-
}
1385-
else
1386-
{
1387-
cols << col;
1388-
col = "";
1381+
i++;
1382+
}
1383+
else
1384+
{
1385+
cols << col;
1386+
col = "";
13891387

1390-
if ( ++i == primaryKey.size() )
1391-
break;
1388+
if ( ++i == key.size() )
1389+
break;
13921390

1393-
Q_ASSERT( primaryKey[i] == ',' );
1394-
i++;
1395-
Q_ASSERT( primaryKey[i] == '"' );
1396-
i++;
1397-
col = "";
1398-
continue;
1399-
}
1391+
Q_ASSERT( key[i] == ',' );
1392+
i++;
1393+
Q_ASSERT( key[i] == '"' );
1394+
i++;
1395+
col = "";
1396+
continue;
14001397
}
1401-
1402-
col += primaryKey[i++];
14031398
}
1399+
1400+
col += key[i++];
14041401
}
1405-
else if ( primaryKey.contains( ',' ) )
1406-
{
1407-
cols = primaryKey.split( ',' );
1408-
}
1409-
else
1402+
}
1403+
else if ( key.contains( ',' ) )
1404+
{
1405+
cols = key.split( ',' );
1406+
}
1407+
else
1408+
{
1409+
cols << key;
1410+
}
1411+
1412+
return cols;
1413+
}
1414+
1415+
void QgsPostgresProvider::determinePrimaryKeyFromUriKeyColumn()
1416+
{
1417+
QString primaryKey = mUri.keyColumn();
1418+
mPrimaryKeyType = pktUnknown;
1419+
1420+
if ( !primaryKey.isEmpty() )
1421+
{
1422+
QStringList cols = parseUriKey( primaryKey );
1423+
1424+
primaryKey = "";
1425+
QString del = "";
1426+
Q_FOREACH ( const QString& col, cols )
14101427
{
1411-
cols << primaryKey;
1412-
primaryKey = quotedIdentifier( primaryKey );
1428+
primaryKey += del + quotedIdentifier( col );
1429+
del = ",";
14131430
}
14141431

14151432
Q_FOREACH ( const QString& col, cols )
@@ -3463,6 +3480,9 @@ QgsVectorLayerImport::ImportError QgsPostgresProvider::createEmptyLayer( const Q
34633480
QString primaryKey = dsUri.keyColumn();
34643481
QString primaryKeyType;
34653482

3483+
QStringList pkList;
3484+
QStringList pkType;
3485+
34663486
QString schemaTableName = "";
34673487
if ( !schemaName.isEmpty() )
34683488
{
@@ -3503,45 +3523,39 @@ QgsVectorLayerImport::ImportError QgsPostgresProvider::createEmptyLayer( const Q
35033523
}
35043524
else
35053525
{
3506-
// search for the passed field
3507-
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx )
3526+
pkList = parseUriKey( primaryKey );
3527+
Q_FOREACH ( const QString& col, pkList )
35083528
{
3509-
if ( fields.at( fldIdx ).name() == primaryKey )
3529+
// search for the passed field
3530+
QString type;
3531+
for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx )
35103532
{
3511-
// found, get the field type
3512-
QgsField fld = fields.at( fldIdx );
3513-
if ( convertField( fld, options ) )
3533+
if ( fields[fldIdx].name() == col )
35143534
{
3515-
primaryKeyType = fld.typeName();
3535+
// found, get the field type
3536+
QgsField fld = fields[fldIdx];
3537+
if ( convertField( fld, options ) )
3538+
{
3539+
type = fld.typeName();
3540+
break;
3541+
}
35163542
}
35173543
}
3518-
}
3519-
}
3520-
3521-
// if the pk field doesn't exist yet, create a serial pk field
3522-
// as it's autoincremental
3523-
if ( primaryKeyType.isEmpty() )
3524-
{
3525-
primaryKeyType = "serial";
3526-
#if 0
3527-
// TODO: check the feature count to choose if create a serial8 pk field
3528-
if ( layer->featureCount() > 0xffffffff )
3529-
{
3530-
primaryKeyType = "serial8";
3531-
}
3532-
#endif
3533-
}
3534-
else
3535-
{
3536-
// if the pk field's type is one of the postgres integer types,
3537-
// use the equivalent autoincremental type (serialN)
3538-
if ( primaryKeyType == "int2" || primaryKeyType == "int4" )
3539-
{
3540-
primaryKeyType = "serial";
3541-
}
3542-
else if ( primaryKeyType == "int8" )
3543-
{
3544-
primaryKeyType = "serial8";
3544+
if ( type.isEmpty() ) type = "serial";
3545+
else
3546+
{
3547+
// if the pk field's type is one of the postgres integer types,
3548+
// use the equivalent autoincremental type (serialN)
3549+
if ( primaryKeyType == "int2" || primaryKeyType == "int4" )
3550+
{
3551+
primaryKeyType = "serial";
3552+
}
3553+
else if ( primaryKeyType == "int8" )
3554+
{
3555+
primaryKeyType = "serial8";
3556+
}
3557+
}
3558+
pkType << type;
35453559
}
35463560
}
35473561

@@ -3577,17 +3591,32 @@ QgsVectorLayerImport::ImportError QgsPostgresProvider::createEmptyLayer( const Q
35773591
throw PGException( result );
35783592
}
35793593

3580-
if ( options && options->value( "lowercaseFieldNames", false ).toBool() )
3594+
sql = QString( "CREATE TABLE %1(" ) .arg( schemaTableName );
3595+
QString pk;
3596+
for ( int i = 0; i < pkList.size(); ++i )
35813597
{
3582-
//convert primary key name to lowercase
3583-
//this must happen after determining the field type of the primary key
3584-
primaryKey = primaryKey.toLower();
3585-
}
3598+
QString col = pkList[i];
3599+
const QString& type = pkType[i];
35863600

3587-
sql = QString( "CREATE TABLE %1(%2 %3 PRIMARY KEY)" )
3588-
.arg( schemaTableName,
3589-
quotedIdentifier( primaryKey ),
3590-
primaryKeyType );
3601+
if ( options && options->value( "lowercaseFieldNames", false ).toBool() )
3602+
{
3603+
col = col.toLower();
3604+
}
3605+
else
3606+
{
3607+
col = quotedIdentifier( col ); // no need to quote lowercase field
3608+
}
3609+
3610+
if ( i )
3611+
{
3612+
pk += ",";
3613+
sql += ",";
3614+
}
3615+
3616+
pk += col;
3617+
sql += col + " " + type;
3618+
}
3619+
sql += QString( ", PRIMARY KEY (%1) )" ) .arg( pk );
35913620

35923621
result = conn->PQexec( sql );
35933622
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
@@ -3678,9 +3707,25 @@ QgsVectorLayerImport::ImportError QgsPostgresProvider::createEmptyLayer( const Q
36783707
fld.setName( fld.name().toLower() );
36793708
}
36803709

3681-
if ( fld.name() == primaryKey )
3710+
int pkIdx = -1;
3711+
for ( int i = 0; i < pkList.size(); ++i )
3712+
{
3713+
QString col = pkList[i];
3714+
if ( options && options->value( "lowercaseFieldNames", false ).toBool() )
3715+
{
3716+
//convert field name to lowercase (TODO: avoid doing this
3717+
//over and over)
3718+
col = col.toLower();
3719+
}
3720+
if ( fld.name() == col )
3721+
{
3722+
pkIdx = i;
3723+
break;
3724+
}
3725+
}
3726+
if ( pkIdx >= 0 )
36823727
{
3683-
oldToNewAttrIdxMap->insert( fldIdx, 0 );
3728+
oldToNewAttrIdxMap->insert( fldIdx, pkIdx );
36843729
continue;
36853730
}
36863731

‎src/providers/postgres/qgspostgresprovider.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ class QgsPostgresProvider : public QgsVectorDataProvider
114114
*/
115115
static QString endianString();
116116

117+
/**
118+
* Returns a list of unquoted column names from an uri key
119+
*/
120+
static QStringList parseUriKey( const QString& key );
121+
117122
/**
118123
* Changes the stored extent for this layer to the supplied extent.
119124
* For example, this is called when the extent worker thread has a result.
@@ -126,11 +131,16 @@ class QgsPostgresProvider : public QgsVectorDataProvider
126131
*/
127132
virtual void updateExtents() override;
128133

129-
/** Determine the fields making up the primary key
134+
/**
135+
* Determine the fields making up the primary key
130136
*/
131137
bool determinePrimaryKey();
132138

133-
/** Determine the fields making up the primary key from the uri attribute keyColumn
139+
/**
140+
* Determine the fields making up the primary key from the uri attribute keyColumn
141+
*
142+
* Fills mPrimaryKeyType and mPrimaryKeyAttrs
143+
* from mUri
134144
*/
135145
void determinePrimaryKeyFromUriKeyColumn();
136146

‎tests/src/python/test_provider_postgres.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,37 @@ def testNumericPrecision(self):
461461
self.assertEqual(f['f2'], 123.456)
462462
self.assertEqual(f['f3'], '12345678.90123456789')
463463

464+
# See http://hub.qgis.org/issues/15226
465+
def testImportKey(self):
466+
uri = 'point?field=f1:int'
467+
uri += '&field=F2:double(6,4)'
468+
uri += '&field=f3:string(20)'
469+
lyr = QgsVectorLayer(uri, "x", "memory")
470+
self.assertTrue(lyr.isValid())
471+
472+
def testKey(lyr, key, kfnames):
473+
self.execSQLCommand('DROP TABLE IF EXISTS qgis_test.import_test')
474+
uri = '%s table="qgis_test"."import_test" (g) key=\'%s\'' % (self.dbconn, key)
475+
err = QgsVectorLayerImport.importLayer(lyr, uri, "postgres", lyr.crs())
476+
self.assertEqual(err[0], QgsVectorLayerImport.NoError,
477+
'unexpected import error {0}'.format(err))
478+
olyr = QgsVectorLayer(uri, "y", "postgres")
479+
self.assertTrue(olyr.isValid())
480+
flds = lyr.fields()
481+
oflds = olyr.fields()
482+
self.assertEquals(oflds.size(), flds.size())
483+
for i in range(0, oflds.size()):
484+
self.assertEqual(oflds[i].name(), flds[i].name())
485+
pks = olyr.pkAttributeList()
486+
self.assertEquals(len(pks), len(kfnames))
487+
for i in range(0, len(kfnames)):
488+
self.assertEqual(oflds[pks[i]].name(), kfnames[i])
489+
490+
testKey(lyr, 'f1', ['f1'])
491+
testKey(lyr, '"f1"', ['f1'])
492+
testKey(lyr, '"f1","F2"', ['f1', 'F2'])
493+
testKey(lyr, '"f1","F2","f3"', ['f1', 'F2', 'f3'])
494+
464495

465496
class TestPyQgsPostgresProviderCompoundKey(unittest.TestCase, ProviderTestCase):
466497

0 commit comments

Comments
 (0)
Please sign in to comment.