mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-17 00:04:02 -04:00
Avoid double-evaluating postgres default values
This commit is contained in:
parent
249c8dca20
commit
60bbd09339
@ -239,7 +239,13 @@ class QgsVectorDataProvider : QgsDataProvider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns any literal default values which are present at the provider for a specified
|
* Returns any literal default values which are present at the provider for a specified
|
||||||
* field index.
|
* field index. Important - this should ONLY be called when creating an attribute to insert
|
||||||
|
* directly into the database. Do not call this method for non-feature creation or modification,
|
||||||
|
* eg when validating an attribute or to compare it against an existing value, as calling it
|
||||||
|
* can cause changes to the underlying data source (eg Postgres provider where the default value
|
||||||
|
* is calculated as a result of a sequence). It is recommended that you instead use the methods
|
||||||
|
* in QgsVectorLayerUtils such as QgsVectorLayerUtils::createFeature()
|
||||||
|
* so that default value handling and validation is automatically carried out.
|
||||||
* @see defaultValueClause()
|
* @see defaultValueClause()
|
||||||
*/
|
*/
|
||||||
virtual QVariant defaultValue( int fieldIndex ) const;
|
virtual QVariant defaultValue( int fieldIndex ) const;
|
||||||
@ -257,9 +263,19 @@ class QgsVectorDataProvider : QgsDataProvider
|
|||||||
* Returns any constraints which are present at the provider for a specified
|
* Returns any constraints which are present at the provider for a specified
|
||||||
* field index.
|
* field index.
|
||||||
* @note added in QGIS 3.0
|
* @note added in QGIS 3.0
|
||||||
|
* @see skipConstraintCheck()
|
||||||
*/
|
*/
|
||||||
QgsFieldConstraints::Constraints fieldConstraints( int fieldIndex ) const;
|
QgsFieldConstraints::Constraints fieldConstraints( int fieldIndex ) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a constraint check should be skipped for a specified field (eg if
|
||||||
|
* the value returned by defaultValue() is trusted implicitly. An optional attribute value can be
|
||||||
|
* passed which can help refine the skip constraint check.
|
||||||
|
* @note added in QGIS 3.0
|
||||||
|
* @see fieldConstraints()
|
||||||
|
*/
|
||||||
|
virtual bool skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant& value = QVariant() ) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes geometries of existing features
|
* Changes geometries of existing features
|
||||||
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs
|
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs
|
||||||
|
@ -114,6 +114,11 @@ QgsFieldConstraints::Constraints QgsVectorDataProvider::fieldConstraints( int fi
|
|||||||
return f.at( fieldIndex ).constraints().constraints();
|
return f.at( fieldIndex ).constraints().constraints();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QgsVectorDataProvider::skipConstraintCheck( int, QgsFieldConstraints::Constraint, const QVariant& ) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool QgsVectorDataProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
|
bool QgsVectorDataProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
|
||||||
{
|
{
|
||||||
Q_UNUSED( geometry_map );
|
Q_UNUSED( geometry_map );
|
||||||
|
@ -293,7 +293,13 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns any literal default values which are present at the provider for a specified
|
* Returns any literal default values which are present at the provider for a specified
|
||||||
* field index.
|
* field index. Important - this should ONLY be called when creating an attribute to insert
|
||||||
|
* directly into the database. Do not call this method for non-feature creation or modification,
|
||||||
|
* eg when validating an attribute or to compare it against an existing value, as calling it
|
||||||
|
* can cause changes to the underlying data source (eg Postgres provider where the default value
|
||||||
|
* is calculated as a result of a sequence). It is recommended that you instead use the methods
|
||||||
|
* in QgsVectorLayerUtils such as QgsVectorLayerUtils::createFeature()
|
||||||
|
* so that default value handling and validation is automatically carried out.
|
||||||
* @see defaultValueClause()
|
* @see defaultValueClause()
|
||||||
*/
|
*/
|
||||||
virtual QVariant defaultValue( int fieldIndex ) const;
|
virtual QVariant defaultValue( int fieldIndex ) const;
|
||||||
@ -311,9 +317,19 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
|
|||||||
* Returns any constraints which are present at the provider for a specified
|
* Returns any constraints which are present at the provider for a specified
|
||||||
* field index.
|
* field index.
|
||||||
* @note added in QGIS 3.0
|
* @note added in QGIS 3.0
|
||||||
|
* @see skipConstraintCheck()
|
||||||
*/
|
*/
|
||||||
QgsFieldConstraints::Constraints fieldConstraints( int fieldIndex ) const;
|
QgsFieldConstraints::Constraints fieldConstraints( int fieldIndex ) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a constraint check should be skipped for a specified field (eg if
|
||||||
|
* the value returned by defaultValue() is trusted implicitly. An optional attribute value can be
|
||||||
|
* passed which can help refine the skip constraint check.
|
||||||
|
* @note added in QGIS 3.0
|
||||||
|
* @see fieldConstraints()
|
||||||
|
*/
|
||||||
|
virtual bool skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant& value = QVariant() ) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes geometries of existing features
|
* Changes geometries of existing features
|
||||||
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs
|
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs
|
||||||
|
@ -143,7 +143,8 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
|
|||||||
if ( attributeIndex < 0 || attributeIndex >= layer->fields().count() )
|
if ( attributeIndex < 0 || attributeIndex >= layer->fields().count() )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QgsField field = layer->fields().at( attributeIndex );
|
QgsFields fields = layer->fields();
|
||||||
|
QgsField field = fields.at( attributeIndex );
|
||||||
QVariant value = feature.attribute( attributeIndex );
|
QVariant value = feature.attribute( attributeIndex );
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
errors.clear();
|
errors.clear();
|
||||||
@ -179,11 +180,22 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
|
|||||||
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintNotNull ) )
|
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintNotNull ) )
|
||||||
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) ) )
|
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) ) )
|
||||||
{
|
{
|
||||||
valid = valid && !value.isNull();
|
bool exempt = false;
|
||||||
|
if ( fields.fieldOrigin( attributeIndex ) == QgsFields::OriginProvider
|
||||||
if ( value.isNull() )
|
&& constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) == QgsFieldConstraints::ConstraintOriginProvider )
|
||||||
{
|
{
|
||||||
errors << QObject::tr( "value is NULL" );
|
int providerIdx = fields.fieldOriginIndex( attributeIndex );
|
||||||
|
exempt = layer->dataProvider()->skipConstraintCheck( providerIdx, QgsFieldConstraints::ConstraintNotNull, value );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !exempt )
|
||||||
|
{
|
||||||
|
valid = valid && !value.isNull();
|
||||||
|
|
||||||
|
if ( value.isNull() )
|
||||||
|
{
|
||||||
|
errors << QObject::tr( "value is NULL" );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,12 +203,23 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
|
|||||||
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) )
|
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) )
|
||||||
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintUnique ) ) )
|
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintUnique ) ) )
|
||||||
{
|
{
|
||||||
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer, attributeIndex, value, QgsFeatureIds() << feature.id() );
|
bool exempt = false;
|
||||||
valid = valid && !alreadyExists;
|
if ( fields.fieldOrigin( attributeIndex ) == QgsFields::OriginProvider
|
||||||
|
&& constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) == QgsFieldConstraints::ConstraintOriginProvider )
|
||||||
if ( alreadyExists )
|
|
||||||
{
|
{
|
||||||
errors << QObject::tr( "value is not unique" );
|
int providerIdx = fields.fieldOriginIndex( attributeIndex );
|
||||||
|
exempt = layer->dataProvider()->skipConstraintCheck( providerIdx, QgsFieldConstraints::ConstraintUnique, value );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !exempt )
|
||||||
|
{
|
||||||
|
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer, attributeIndex, value, QgsFeatureIds() << feature.id() );
|
||||||
|
valid = valid && !alreadyExists;
|
||||||
|
|
||||||
|
if ( alreadyExists )
|
||||||
|
{
|
||||||
|
errors << QObject::tr( "value is not unique" );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1806,6 +1806,20 @@ QVariant QgsPostgresProvider::defaultValue( int fieldId ) const
|
|||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QgsPostgresProvider::skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint, const QVariant& value ) const
|
||||||
|
{
|
||||||
|
if ( providerProperty( EvaluateDefaultValues, false ).toBool() )
|
||||||
|
{
|
||||||
|
return mDefaultValues.contains( fieldIndex );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// stricter check - if we are evaluating default values only on commit then we can only bypass the check
|
||||||
|
// if the attribute values matches the original default clause
|
||||||
|
return mDefaultValues.contains( fieldIndex ) && mDefaultValues.value( fieldIndex ) == value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString QgsPostgresProvider::paramValue( const QString& fieldValue, const QString &defaultValue ) const
|
QString QgsPostgresProvider::paramValue( const QString& fieldValue, const QString &defaultValue ) const
|
||||||
{
|
{
|
||||||
if ( fieldValue.isNull() )
|
if ( fieldValue.isNull() )
|
||||||
|
@ -164,6 +164,7 @@ class QgsPostgresProvider : public QgsVectorDataProvider
|
|||||||
QgsAttributeList pkAttributeIndexes() const override { return mPrimaryKeyAttrs; }
|
QgsAttributeList pkAttributeIndexes() const override { return mPrimaryKeyAttrs; }
|
||||||
QString defaultValueClause( int fieldId ) const override;
|
QString defaultValueClause( int fieldId ) const override;
|
||||||
QVariant defaultValue( int fieldId ) const override;
|
QVariant defaultValue( int fieldId ) const override;
|
||||||
|
bool skipConstraintCheck( int fieldIndex, QgsFieldConstraints::Constraint constraint, const QVariant& value = QVariant() ) const override;
|
||||||
|
|
||||||
/** Adds a list of features
|
/** Adds a list of features
|
||||||
@return true in case of success and false in case of failure*/
|
@return true in case of success and false in case of failure*/
|
||||||
|
@ -520,14 +520,28 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
|
|||||||
def testVectorLayerUtilsUniqueWithProviderDefault(self):
|
def testVectorLayerUtilsUniqueWithProviderDefault(self):
|
||||||
vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")
|
vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")
|
||||||
default_clause = 'nextval(\'qgis_test."someData_pk_seq"\'::regclass)'
|
default_clause = 'nextval(\'qgis_test."someData_pk_seq"\'::regclass)'
|
||||||
|
vl.dataProvider().setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False)
|
||||||
self.assertEqual(vl.dataProvider().defaultValueClause(0), default_clause)
|
self.assertEqual(vl.dataProvider().defaultValueClause(0), default_clause)
|
||||||
self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, 4))
|
self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, 4))
|
||||||
|
|
||||||
vl.startEditing()
|
vl.startEditing()
|
||||||
f = QgsFeature(vl.fields())
|
f = QgsFeature(vl.fields())
|
||||||
f.setAttribute(0, default_clause)
|
f.setAttribute(0, default_clause)
|
||||||
self.assertTrue(vl.addFeatures([f]))
|
|
||||||
self.assertFalse(QgsVectorLayerUtils.valueExists(vl, 0, default_clause))
|
self.assertFalse(QgsVectorLayerUtils.valueExists(vl, 0, default_clause))
|
||||||
|
self.assertTrue(vl.addFeatures([f]))
|
||||||
|
|
||||||
|
# the default value clause should exist...
|
||||||
|
self.assertTrue(QgsVectorLayerUtils.valueExists(vl, 0, default_clause))
|
||||||
|
# but it should not prevent the attribute being validated
|
||||||
|
self.assertTrue(QgsVectorLayerUtils.validateAttribute(vl, f, 0))
|
||||||
|
vl.rollBack()
|
||||||
|
|
||||||
|
def testSkipConstraintCheck(self):
|
||||||
|
vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")
|
||||||
|
default_clause = 'nextval(\'qgis_test."someData_pk_seq"\'::regclass)'
|
||||||
|
vl.dataProvider().setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False)
|
||||||
|
self.assertTrue(vl.dataProvider().skipConstraintCheck(0, QgsFieldConstraints.ConstraintUnique, default_clause))
|
||||||
|
self.assertFalse(vl.dataProvider().skipConstraintCheck(0, QgsFieldConstraints.ConstraintUnique, 59))
|
||||||
|
|
||||||
def testVectorLayerUtilsCreateFeatureWithProviderDefault(self):
|
def testVectorLayerUtilsCreateFeatureWithProviderDefault(self):
|
||||||
vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")
|
vl = QgsVectorLayer('%s table="qgis_test"."someData" sql=' % (self.dbconn), "someData", "postgres")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user