Skip to content

Commit 60bbd09

Browse files
nyalldawsonm-kuhn
authored andcommitted
Avoid double-evaluating postgres default values
1 parent 249c8dc commit 60bbd09

File tree

7 files changed

+100
-11
lines changed

7 files changed

+100
-11
lines changed

python/core/qgsvectordataprovider.sip

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,13 @@ class QgsVectorDataProvider : QgsDataProvider
239239

240240
/**
241241
* Returns any literal default values which are present at the provider for a specified
242-
* field index.
242+
* field index. Important - this should ONLY be called when creating an attribute to insert
243+
* directly into the database. Do not call this method for non-feature creation or modification,
244+
* eg when validating an attribute or to compare it against an existing value, as calling it
245+
* can cause changes to the underlying data source (eg Postgres provider where the default value
246+
* is calculated as a result of a sequence). It is recommended that you instead use the methods
247+
* in QgsVectorLayerUtils such as QgsVectorLayerUtils::createFeature()
248+
* so that default value handling and validation is automatically carried out.
243249
* @see defaultValueClause()
244250
*/
245251
virtual QVariant defaultValue( int fieldIndex ) const;
@@ -257,9 +263,19 @@ class QgsVectorDataProvider : QgsDataProvider
257263
* Returns any constraints which are present at the provider for a specified
258264
* field index.
259265
* @note added in QGIS 3.0
266+
* @see skipConstraintCheck()
260267
*/
261268
QgsFieldConstraints::Constraints fieldConstraints( int fieldIndex ) const;
262269

270+
/**
271+
* Returns true if a constraint check should be skipped for a specified field (eg if
272+
* the value returned by defaultValue() is trusted implicitly. An optional attribute value can be
273+
* passed which can help refine the skip constraint check.
274+
* @note added in QGIS 3.0
275+
* @see fieldConstraints()
276+
*/
277+
virtual bool skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant& value = QVariant() ) const;
278+
263279
/**
264280
* Changes geometries of existing features
265281
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs

src/core/qgsvectordataprovider.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ QgsFieldConstraints::Constraints QgsVectorDataProvider::fieldConstraints( int fi
114114
return f.at( fieldIndex ).constraints().constraints();
115115
}
116116

117+
bool QgsVectorDataProvider::skipConstraintCheck( int, QgsFieldConstraints::Constraint, const QVariant& ) const
118+
{
119+
return false;
120+
}
121+
117122
bool QgsVectorDataProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
118123
{
119124
Q_UNUSED( geometry_map );

src/core/qgsvectordataprovider.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,13 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
293293

294294
/**
295295
* Returns any literal default values which are present at the provider for a specified
296-
* field index.
296+
* field index. Important - this should ONLY be called when creating an attribute to insert
297+
* directly into the database. Do not call this method for non-feature creation or modification,
298+
* eg when validating an attribute or to compare it against an existing value, as calling it
299+
* can cause changes to the underlying data source (eg Postgres provider where the default value
300+
* is calculated as a result of a sequence). It is recommended that you instead use the methods
301+
* in QgsVectorLayerUtils such as QgsVectorLayerUtils::createFeature()
302+
* so that default value handling and validation is automatically carried out.
297303
* @see defaultValueClause()
298304
*/
299305
virtual QVariant defaultValue( int fieldIndex ) const;
@@ -311,9 +317,19 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
311317
* Returns any constraints which are present at the provider for a specified
312318
* field index.
313319
* @note added in QGIS 3.0
320+
* @see skipConstraintCheck()
314321
*/
315322
QgsFieldConstraints::Constraints fieldConstraints( int fieldIndex ) const;
316323

324+
/**
325+
* Returns true if a constraint check should be skipped for a specified field (eg if
326+
* the value returned by defaultValue() is trusted implicitly. An optional attribute value can be
327+
* passed which can help refine the skip constraint check.
328+
* @note added in QGIS 3.0
329+
* @see fieldConstraints()
330+
*/
331+
virtual bool skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant& value = QVariant() ) const;
332+
317333
/**
318334
* Changes geometries of existing features
319335
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs

src/core/qgsvectorlayerutils.cpp

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
143143
if ( attributeIndex < 0 || attributeIndex >= layer->fields().count() )
144144
return false;
145145

146-
QgsField field = layer->fields().at( attributeIndex );
146+
QgsFields fields = layer->fields();
147+
QgsField field = fields.at( attributeIndex );
147148
QVariant value = feature.attribute( attributeIndex );
148149
bool valid = true;
149150
errors.clear();
@@ -179,24 +180,46 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
179180
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintNotNull ) )
180181
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) ) )
181182
{
182-
valid = valid && !value.isNull();
183+
bool exempt = false;
184+
if ( fields.fieldOrigin( attributeIndex ) == QgsFields::OriginProvider
185+
&& constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) == QgsFieldConstraints::ConstraintOriginProvider )
186+
{
187+
int providerIdx = fields.fieldOriginIndex( attributeIndex );
188+
exempt = layer->dataProvider()->skipConstraintCheck( providerIdx, QgsFieldConstraints::ConstraintNotNull, value );
189+
}
183190

184-
if ( value.isNull() )
191+
if ( !exempt )
185192
{
186-
errors << QObject::tr( "value is NULL" );
193+
valid = valid && !value.isNull();
194+
195+
if ( value.isNull() )
196+
{
197+
errors << QObject::tr( "value is NULL" );
198+
}
187199
}
188200
}
189201

190202
if ( constraints.constraints() & QgsFieldConstraints::ConstraintUnique
191203
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) )
192204
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintUnique ) ) )
193205
{
194-
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer, attributeIndex, value, QgsFeatureIds() << feature.id() );
195-
valid = valid && !alreadyExists;
206+
bool exempt = false;
207+
if ( fields.fieldOrigin( attributeIndex ) == QgsFields::OriginProvider
208+
&& constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) == QgsFieldConstraints::ConstraintOriginProvider )
209+
{
210+
int providerIdx = fields.fieldOriginIndex( attributeIndex );
211+
exempt = layer->dataProvider()->skipConstraintCheck( providerIdx, QgsFieldConstraints::ConstraintUnique, value );
212+
}
196213

197-
if ( alreadyExists )
214+
if ( !exempt )
198215
{
199-
errors << QObject::tr( "value is not unique" );
216+
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer, attributeIndex, value, QgsFeatureIds() << feature.id() );
217+
valid = valid && !alreadyExists;
218+
219+
if ( alreadyExists )
220+
{
221+
errors << QObject::tr( "value is not unique" );
222+
}
200223
}
201224
}
202225

src/providers/postgres/qgspostgresprovider.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,6 +1806,20 @@ QVariant QgsPostgresProvider::defaultValue( int fieldId ) const
18061806
return QVariant();
18071807
}
18081808

1809+
bool QgsPostgresProvider::skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint, const QVariant& value ) const
1810+
{
1811+
if ( providerProperty( EvaluateDefaultValues, false ).toBool() )
1812+
{
1813+
return mDefaultValues.contains( fieldIndex );
1814+
}
1815+
else
1816+
{
1817+
// stricter check - if we are evaluating default values only on commit then we can only bypass the check
1818+
// if the attribute values matches the original default clause
1819+
return mDefaultValues.contains( fieldIndex ) && mDefaultValues.value( fieldIndex ) == value.toString();
1820+
}
1821+
}
1822+
18091823
QString QgsPostgresProvider::paramValue( const QString& fieldValue, const QString &defaultValue ) const
18101824
{
18111825
if ( fieldValue.isNull() )

src/providers/postgres/qgspostgresprovider.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ class QgsPostgresProvider : public QgsVectorDataProvider
164164
QgsAttributeList pkAttributeIndexes() const override { return mPrimaryKeyAttrs; }
165165
QString defaultValueClause( int fieldId ) const override;
166166
QVariant defaultValue( int fieldId ) const override;
167+
bool skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant& value = QVariant() ) const override;
167168

168169
/** Adds a list of features
169170
@return true in case of success and false in case of failure*/

tests/src/python/test_provider_postgres.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,14 +520,28 @@ def testConstraintOverwrite(self):
520520
def testVectorLayerUtilsUniqueWithProviderDefault(self):
521521
vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")
522522
default_clause = 'nextval(\'qgis_test."someData_pk_seq"\'::regclass)'
523+
vl.dataProvider().setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False)
523524
self.assertEqual(vl.dataProvider().defaultValueClause(0), default_clause)
524525
self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, 4))
525526

526527
vl.startEditing()
527528
f = QgsFeature(vl.fields())
528529
f.setAttribute(0, default_clause)
529-
self.assertTrue(vl.addFeatures([f]))
530530
self.assertFalse(QgsVectorLayerUtils.valueExists(vl, 0, default_clause))
531+
self.assertTrue(vl.addFeatures([f]))
532+
533+
# the default value clause should exist...
534+
self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, default_clause))
535+
# but it should not prevent the attribute being validated
536+
self.assertTrue(QgsVectorLayerUtils.validateAttribute(vl, f, 0))
537+
vl.rollBack()
538+
539+
def testSkipConstraintCheck(self):
540+
vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")
541+
default_clause = 'nextval(\'qgis_test."someData_pk_seq"\'::regclass)'
542+
vl.dataProvider().setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False)
543+
self.assertTrue(vl.dataProvider().skipConstraintCheck(0, QgsFieldConstraints.ConstraintUnique, default_clause))
544+
self.assertFalse(vl.dataProvider().skipConstraintCheck(0, QgsFieldConstraints.ConstraintUnique, 59))
531545

532546
def testVectorLayerUtilsCreateFeatureWithProviderDefault(self):
533547
vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")

0 commit comments

Comments
 (0)