Skip to content
Permalink
Browse files

PostgreSQL composite PK with bigint component

Bigint PostgreSQL fields are handled correctly if they are part of a
composite primary key. As a bonus, we always pass double values as
quoted to the DBMS (but not cast to text). With this one can work with
tables with real/double primary keys without the penalty of casting
these values to text. Fixes #37126.
  • Loading branch information
espinafre authored and nyalldawson committed Jun 17, 2020
1 parent e73074b commit 617e0dbe8fbd2b6040f09a3990a7f5911dca3612
@@ -1212,7 +1212,6 @@ QString QgsPostgresConn::quotedValue( const QVariant &value )
{
case QVariant::Int:
case QVariant::LongLong:
case QVariant::Double:
return value.toString();

case QVariant::DateTime:
@@ -1228,6 +1227,8 @@ QString QgsPostgresConn::quotedValue( const QVariant &value )
case QVariant::List:
return quotedList( value.toList() );

// we need to pass floating point values quoted to the DBMS
case QVariant::Double:
case QVariant::String:
default:
return quotedString( value.toString() );
@@ -783,8 +783,16 @@ bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int
Q_FOREACH ( int idx, mSource->mPrimaryKeyAttrs )
{
QgsField fld = mSource->mFields.at( idx );
QVariant v;

QVariant v = QgsPostgresProvider::convertValue( fld.type(), fld.subType(), queryResult.PQgetvalue( row, col ), fld.typeName() );
if ( fld.type() == QVariant::LongLong )
{
v = QgsPostgresProvider::convertValue( fld.type(), fld.subType(), QString::number( mConn->getBinaryInt( queryResult, row, col ) ), fld.typeName() );
}
else
{
v = QgsPostgresProvider::convertValue( fld.type(), fld.subType(), queryResult.PQgetvalue( row, col ), fld.typeName() );
}
primaryKeyVals << v;

if ( !subsetOfAttributes || fetchAttributes.contains( idx ) )
@@ -252,7 +252,7 @@ void TestQgsPostgresProvider::testQuotedValueBigInt()
sdata->clear();
sdata->insertFid( 1LL, vlst );

QCOMPARE( QgsPostgresUtils::whereClause( 1LL, fields, NULL, QgsPostgresPrimaryKeyType::PktFidMap, pkAttrs, std::shared_ptr<QgsPostgresSharedData>( sdata ) ), QString( "\"fld_double\"=3.141592741" ) );
QCOMPARE( QgsPostgresUtils::whereClause( 1LL, fields, NULL, QgsPostgresPrimaryKeyType::PktFidMap, pkAttrs, std::shared_ptr<QgsPostgresSharedData>( sdata ) ), QString( "\"fld_double\"='3.141592741'" ) );

// text
f3.setName( "fld_text" );
@@ -772,6 +772,133 @@ def testPktUpdateBigintPkNonFirst(self):
statuses[3] = 1
self.assertTrue(all(x == 1 for x in statuses))

def testPktComposite(self):
"""
Check that tables with PKs composed of many fields of different types are correctly read and written to
"""
vl = QgsVectorLayer('{} sslmode=disable srid=4326 key=\'"pk1","pk2","pk3"\' table="qgis_test"."tb_test_compound_pk" (geom)'.format(self.dbconn), "test_compound", "postgres")
self.assertTrue(vl.isValid())

fields = vl.fields()

f = next(vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk3 = 3.1415927')))
# first of all: we must be able to fetch a valid feature
self.assertTrue(f.isValid())
self.assertEqual(f['pk1'], 1)
self.assertEqual(f['pk2'], 2)
self.assertEqual(f['pk3'], 3.1415927)
self.assertEqual(f['value'], 'test 2')

# can we edit a field?
vl.startEditing()
vl.changeAttributeValue(f.id(), fields.indexOf('value'), 'Edited Test 2')
self.assertTrue(vl.commitChanges())

# Did we get it right? Let's create a new QgsVectorLayer and try to read back our changes:
vl2 = QgsVectorLayer('{} sslmode=disable srid=4326 table="qgis_test"."tb_test_compound_pk" (geom) key=\'"pk1","pk2","pk3"\' '.format(self.dbconn), "test_compound2", "postgres")
self.assertTrue(vl2.isValid())
f2 = next(vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk3 = 3.1415927')))
self.assertTrue(f2.isValid())

# just making sure we have the correct feature
self.assertEqual(f2['pk3'], 3.1415927)

# Then, making sure we really did change our value.
self.assertEqual(f2['value'], 'Edited Test 2')

# How about inserting a new field?
f3 = QgsFeature(vl2.fields())
f3['pk1'] = 4
f3['pk2'] = -9223372036854775800
f3['pk3'] = 7.29154
f3['value'] = 'other test'
vl.startEditing()
res, f3 = vl.dataProvider().addFeatures([f3])
self.assertTrue(res)
self.assertTrue(vl.commitChanges())

# can we catch it on another layer?
f4 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk2 = -9223372036854775800')))

self.assertTrue(f4.isValid())
expected_attrs = [4, -9223372036854775800, 7.29154, 'other test']
self.assertEqual(f4.attributes(), expected_attrs)

# Finally, let's delete one of the features.
f5 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk3 = 7.29154')))
vl2.startEditing()
vl2.deleteFeatures([f5.id()])
self.assertTrue(vl2.commitChanges())

# did we really delete?
f_iterator = vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk3 = 7.29154'))
got_feature = True

try:
f6 = next(f_iterator)
got_feature = f6.isValid()
except StopIteration:
got_feature = False

self.assertFalse(got_feature)

def testPktFloat(self):
"""
Verify that we handle tables with floating point PKs well.
"""
vl = QgsVectorLayer('{} sslmode=disable srid=4326 key="pk" table="qgis_test"."tb_test_float_pk" (geom)'.format(self.dbconn), "test_floatpk", "postgres")
self.assertTrue(vl.isValid())
fields = vl.fields()
f = next(vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk = 3.141592741')))
self.assertTrue(f.isValid())
self.assertEqual(f['pk'], 3.1415927)
self.assertEqual(f['value'], 'first test')

# editing
vl.startEditing()
vl.changeAttributeValue(f.id(), fields.indexOf('value'), 'first check')
self.assertTrue(vl.commitChanges())

# checking out if we really wrote to the table
vl2 = QgsVectorLayer('{} sslmode=disable srid=4326 key="pk" table="qgis_test"."tb_test_float_pk" (geom)'.format(self.dbconn), "test_floatpk2", "postgres")
self.assertTrue(vl2.isValid())
f2 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk = 3.141592741')))

self.assertEqual(f2['value'], 'first check')

# inserting new...
f3 = QgsFeature(vl2.fields())
f3['pk'] = 7.29154
f3['value'] = 'newly inserted'
vl2.startEditing()
res, f3 = vl.dataProvider().addFeatures([f3])
self.assertTrue(res)
self.assertTrue(vl2.commitChanges())

# checking if correctly inserted...
f4 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk = 7.29154')))
self.assertTrue(f4.isValid())
self.assertEqual(f4['value'], 'newly inserted')

# Checking deletion
f5 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk = 2.7182817')))
self.assertTrue(f5.isValid())
vl2.startEditing()
vl2.deleteFeatures([f5.id()])
self.assertTrue(vl2.commitChanges())

# did we really delete?
f_iterator = vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk = 2.7182817'))
got_feature = True

try:
f6 = next(f_iterator)
got_feature = f6.isValid()
except StopIteration:
got_feature = False

self.assertFalse(got_feature)

def testPktMapInsert(self):
vl = QgsVectorLayer('{} table="qgis_test"."{}" key="obj_id" sql='.format(self.dbconn, 'oid_serial_table'),
"oid_serial", "postgres")
@@ -104,20 +104,29 @@ INSERT INTO qgis_test.provider_bigint_nonfirst_pk (zeroth_field, key1, key2, pr
(1, 2, 3, 4, 400, 'Honey', 'Honey', '4', TIMESTAMP '2021-05-04 13:13:14', '2021-05-04', '13:13:14', '0101000020E610000014AE47E17A5450C03333333333935340')
;

/* -- PostgreSQL 12 or later
DROP TABLE IF EXISTS qgis_test.bigint_partitioned;
CREATE TABLE qgis_test.bigint_partitioned (
pk BIGSERIAL NOT NULL,
value varchar(8),
geom geometry(Point, 4326)
) PARTITION BY RANGE(pk);
CREATE TABLE qgis_test.tb_test_compound_pk
(
pk1 INTEGER,
pk2 BIGINT,
pk3 REAL,
value VARCHAR(16),
geom geometry(Point, 4326),
PRIMARY KEY (pk1, pk2, pk3)
);

CREATE TABLE qgis_test.bigint_partitioned_positive PARTITION OF qgis_test.bigint_partitioned
FOR VALUES FROM 1 TO 1000;
CREATE TABLE qgis_test.bigint_partitioned_nonpositive PARTITION OF qgis_test.bigint_partitioned
FOR VALUES FROM -1 TO 0;
INSERT INTO qgis_test.tb_test_compound_pk (pk1, pk2, pk3, value, geom) VALUES
(1, 1, 1.0, 'test 1', ST_SetSRID(ST_Point(-47.930, -15.818), 4326)),
(1, 2, 3.141592741, 'test 2', ST_SetSRID(ST_Point(-47.887, -15.864), 4326)),
(2, 2, 2.718281828, 'test 3', ST_SetSRID(ST_Point(-47.902, -15.763), 4326)),
(2, 2, 1.0, 'test 4', ST_SetSRID(ST_Point(-47.952, -15.781), 4326));

CREATE TABLE qgis_test.tb_test_float_pk
(
pk REAL PRIMARY KEY,
value VARCHAR(16),
geom geometry(Point, 4326)
);

ALTER TABLE qgis_test.bigint_partitioned ADD PRIMARY KEY(pk);
*/
INSERT INTO qgis_test.tb_test_float_pk (pk, value, geom) VALUES
(3.141592741, 'first test', ST_SetSRID(ST_Point(-47.887, -15.864), 4326)),
(2.718281828, 'second test', ST_SetSRID(ST_Point(-47.902, -15.763), 4326));

0 comments on commit 617e0db

Please sign in to comment.
You can’t perform that action at this time.