Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Failing tests for unique
  • Loading branch information
elpaso committed May 29, 2020
1 parent da21127 commit 582adf5
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 20 deletions.
78 changes: 58 additions & 20 deletions tests/src/python/providertestbase.py
Expand Up @@ -29,6 +29,7 @@
QgsFeatureSink,
QgsTestUtils,
QgsFeatureSource,
QgsFieldConstraints,
NULL
)
from qgis.PyQt.QtCore import QDate, QTime, QDateTime
Expand All @@ -48,6 +49,11 @@ class ProviderTestCase(FeatureSourceTestCase):
To test expression compilation, add the methods `enableCompiler()` and `disableCompiler()` to your subclass.
If these methods are present, the tests will ensure that the result of server side and client side expression
evaluation are equal.
To enable constraints checks for a data provider, please see the comment to the specific tests:
- testChangeAttributesConstraintViolation
- testUniqueNotNullConstraints
'''

def uncompiledFilters(self):
Expand All @@ -65,6 +71,11 @@ def partiallyCompiledFilters(self):
should be partially compiled """
return set()

@property
def pk_name(self):
"""Return the primary key name, override if different than the default 'pk'"""
return 'pk'

def assert_query(self, source, expression, expected):
FeatureSourceTestCase.assert_query(self, source, expression, expected)

Expand Down Expand Up @@ -94,7 +105,7 @@ def runGetFeatureTests(self, source):
request.setFilterExpression('"pk" = attribute(@parent, \'pk\')')
request.setLimit(1)

values = [f['pk'] for f in self.vl.getFeatures(request)]
values = [f[self.pk_name] for f in self.vl.getFeatures(request)]
self.assertEqual(values, [4])

def runPolyGetFeatureTests(self, provider):
Expand Down Expand Up @@ -170,7 +181,7 @@ def testSubsetString(self):
self.source.setSubsetString(subset)
self.assertEqual(len(changed_spy), 1)

result = set([f['pk'] for f in self.source.getFeatures()])
result = set([f[self.pk_name] for f in self.source.getFeatures()])
all_valid = (all(f.isValid() for f in self.source.getFeatures()))
self.source.setSubsetString(None)

Expand All @@ -183,7 +194,7 @@ def testSubsetString(self):
self.source.setSubsetString(subset)
extent = QgsRectangle(-70, 70, -60, 75)
request = QgsFeatureRequest().setFilterRect(extent)
result = set([f['pk'] for f in self.source.getFeatures(request)])
result = set([f[self.pk_name] for f in self.source.getFeatures(request)])
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
self.source.setSubsetString(None)
expected = set([2])
Expand All @@ -194,7 +205,7 @@ def testSubsetString(self):
# Subset string AND filter rect, version 2
self.source.setSubsetString(subset)
extent = QgsRectangle(-71, 65, -60, 80)
result = set([f['pk'] for f in self.source.getFeatures(QgsFeatureRequest().setFilterRect(extent))])
result = set([f[self.pk_name] for f in self.source.getFeatures(QgsFeatureRequest().setFilterRect(extent))])
self.source.setSubsetString(None)
expected = set([2, 4])
assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected),
Expand All @@ -203,7 +214,7 @@ def testSubsetString(self):
# Subset string AND expression
self.source.setSubsetString(subset)
request = QgsFeatureRequest().setFilterExpression('length("name")=5')
result = set([f['pk'] for f in self.source.getFeatures(request)])
result = set([f[self.pk_name] for f in self.source.getFeatures(request)])
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
self.source.setSubsetString(None)
expected = set([2, 4])
Expand All @@ -212,7 +223,7 @@ def testSubsetString(self):
self.assertTrue(all_valid)

# Subset string AND filter fid
ids = {f['pk']: f.id() for f in self.source.getFeatures()}
ids = {f[self.pk_name]: f.id() for f in self.source.getFeatures()}
self.source.setSubsetString(subset)
request = QgsFeatureRequest().setFilterFid(4)
result = set([f.id() for f in self.source.getFeatures(request)])
Expand Down Expand Up @@ -274,8 +285,8 @@ def runOrderByTests(self):
FeatureSourceTestCase.runOrderByTests(self)

# Combination with subset of attributes
request = QgsFeatureRequest().addOrderBy('num_char', False).setSubsetOfAttributes(['pk'], self.vl.fields())
values = [f['pk'] for f in self.vl.getFeatures(request)]
request = QgsFeatureRequest().addOrderBy('num_char', False).setSubsetOfAttributes([self.pk_name], self.vl.fields())
values = [f[self.pk_name] for f in self.vl.getFeatures(request)]
self.assertEqual(values, [5, 4, 3, 2, 1])

def testOpenIteratorAfterLayerRemoval(self):
Expand All @@ -299,7 +310,7 @@ def testOpenIteratorAfterLayerRemoval(self):
# get the features
pks = []
for f in source.getFeatures():
pks.append(f['pk'])
pks.append(f[self.pk_name])
self.assertEqual(set(pks), {1, 2, 3, 4, 5})

def testCloneLayer(self):
Expand All @@ -310,7 +321,7 @@ def testCloneLayer(self):

pks = []
for f in l.getFeatures():
pks.append(f['pk'])
pks.append(f[self.pk_name])
self.assertEqual(set(pks), {1, 2, 3, 4, 5})

def testGetFeaturesPolyFilterRectTests(self):
Expand All @@ -323,22 +334,22 @@ def testGetFeaturesPolyFilterRectTests(self):

extent = QgsRectangle(-73, 70, -63, 80)
request = QgsFeatureRequest().setFilterRect(extent)
features = [f['pk'] for f in self.poly_provider.getFeatures(request)]
features = [f[self.pk_name] for f in self.poly_provider.getFeatures(request)]
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
# Some providers may return the exact intersection matches (2, 3) even without the ExactIntersect flag, so we accept that too
assert set(features) == set([2, 3]) or set(features) == set([1, 2, 3]), 'Got {} instead'.format(features)
self.assertTrue(all_valid)

# Test with exact intersection
request = QgsFeatureRequest().setFilterRect(extent).setFlags(QgsFeatureRequest.ExactIntersect)
features = [f['pk'] for f in self.poly_provider.getFeatures(request)]
features = [f[self.pk_name] for f in self.poly_provider.getFeatures(request)]
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
assert set(features) == set([2, 3]), 'Got {} instead'.format(features)
self.assertTrue(all_valid)

# test with an empty rectangle
extent = QgsRectangle()
features = [f['pk'] for f in self.source.getFeatures(QgsFeatureRequest().setFilterRect(extent))]
features = [f[self.pk_name] for f in self.source.getFeatures(QgsFeatureRequest().setFilterRect(extent))]
assert set(features) == set([1, 2, 3, 4, 5]), 'Got {} instead'.format(features)

def testMinValue(self):
Expand Down Expand Up @@ -715,7 +726,7 @@ def testAddFeatureExtraAttributes(self):
'Provider returned False to addFeatures with extra attributes. Providers should accept these features but truncate the extra attributes.')

# make sure feature was added correctly
added = [f for f in l.dataProvider().getFeatures() if f['pk'] == 7][0]
added = [f for f in l.dataProvider().getFeatures() if f[self.pk_name] == 7][0]
self.assertEqual(added.attributes(), [7, -230, NULL, 'String', '15',
'2019-01-02 03:04:05' if self.treat_datetime_as_string() else QDateTime(
2019, 1, 2, 3, 4, 5),
Expand Down Expand Up @@ -746,7 +757,7 @@ def testAddFeatureWrongGeomType(self):
'Provider returned True to addFeatures with incorrect geometry type. Providers should reject these features.')

# make sure feature was not added
added = [f for f in l.dataProvider().getFeatures() if f['pk'] == 7]
added = [f for f in l.dataProvider().getFeatures() if f[self.pk_name] == 7]
self.assertFalse(added)

# yet providers MUST always accept null geometries
Expand All @@ -757,7 +768,7 @@ def testAddFeatureWrongGeomType(self):
'Provider returned False to addFeatures with null geometry. Providers should always accept these features.')

# make sure feature was added correctly
added = [f for f in l.dataProvider().getFeatures() if f['pk'] == 9][0]
added = [f for f in l.dataProvider().getFeatures() if f[self.pk_name] == 9][0]
self.assertFalse(added.hasGeometry())

def testAddFeaturesUpdateExtent(self):
Expand Down Expand Up @@ -829,7 +840,7 @@ def testTruncate(self):
l = self.getEditableLayer()
self.assertTrue(l.isValid())

features = [f['pk'] for f in l.dataProvider().getFeatures()]
features = [f[self.pk_name] for f in l.dataProvider().getFeatures()]

if l.dataProvider().capabilities() & QgsVectorDataProvider.FastTruncate or l.dataProvider().capabilities() & QgsVectorDataProvider.DeleteFeatures:
# expect success
Expand All @@ -838,7 +849,7 @@ def testTruncate(self):
'Provider reported FastTruncate or DeleteFeatures capability, but returned False to truncate()')

# check result
features = [f['pk'] for f in l.dataProvider().getFeatures()]
features = [f[self.pk_name] for f in l.dataProvider().getFeatures()]
self.assertEqual(len(features), 0)
else:
# expect fail
Expand Down Expand Up @@ -909,6 +920,33 @@ def testChangeAttributesConstraintViolation(self):
self.assertFalse(
result, 'Provider reported success when changing an attribute value that violates a DB level CHECK constraint')

def testUniqueNotNullConstraints(self):
"""Test provider-level NOT NULL and UNIQUE constraints, to enable
this test, implement getEditableLayerWithUniqueNotNullConstraints
to return an editable POINT layer with the following fields:
"unique" TEXT UNIQUE,
"not_null" TEXT NOT NULL
"""

if not getattr(self, 'getEditableLayerWithUniqueNotNullConstraints', None):
return

vl = self.getEditableLayerWithUniqueNotNullConstraints()

self.assertTrue(vl.isValid())
unique_field_idx = vl.fields().indexFromName('unique')
not_null_field_idx = vl.fields().indexFromName('not_null')
self.assertTrue(unique_field_idx > 0)
self.assertTrue(not_null_field_idx > 0)
# Not null
self.assertFalse(bool(vl.fieldConstraints(unique_field_idx) & QgsFieldConstraints.ConstraintNotNull))
self.assertTrue(bool(vl.fieldConstraints(not_null_field_idx) & QgsFieldConstraints.ConstraintNotNull))
# Unique
self.assertTrue(bool(vl.fieldConstraints(unique_field_idx) & QgsFieldConstraints.ConstraintUnique))
self.assertFalse(bool(vl.fieldConstraints(not_null_field_idx) & QgsFieldConstraints.ConstraintUnique))

def testChangeGeometries(self):
if not getattr(self, 'getEditableLayer', None):
return
Expand Down Expand Up @@ -1029,8 +1067,8 @@ def testMinMaxAfterChanges(self):
self.assertEqual(vl.dataProvider().maximumValue(1), 400)

# delete features
f1 = [f for f in vl.getFeatures() if f['pk'] == 5][0]
f3 = [f for f in vl.getFeatures() if f['pk'] == 3][0]
f1 = [f for f in vl.getFeatures() if f[self.pk_name] == 5][0]
f3 = [f for f in vl.getFeatures() if f[self.pk_name] == 3][0]
self.assertTrue(vl.dataProvider().deleteFeatures([f6.id(), f7.id()]))
self.assertEqual(vl.dataProvider().minimumValue(0), 1)
self.assertEqual(vl.dataProvider().minimumValue(1), -200)
Expand Down
10 changes: 10 additions & 0 deletions tests/src/python/test_provider_ogr_gpkg.py
Expand Up @@ -85,6 +85,11 @@ def setUpClass(cls):
cls.check_constraint = QgsVectorLayer(
cls.basetestfile + '|layername=check_constraint', 'check_constraint', 'ogr')

# Create the other layer for unique and not null constraints check
cls.unique_not_null_constraints = QgsVectorLayer(
cls.basetestfile + '|layername=unique_not_null_constraints', 'unique_not_null_constraints', 'ogr')
assert cls.unique_not_null_constraints.isValid()

@classmethod
def tearDownClass(cls):
"""Run after all tests"""
Expand All @@ -107,6 +112,11 @@ def getEditableLayerWithCheckConstraint(self):

return self.check_constraint

def getEditableLayerWithUniqueNotNullConstraints(self):
"""Returns the layer for UNIQUE and NOT NULL constraints detection"""

return self.unique_not_null_constraints

def enableCompiler(self):
QgsSettings().setValue('/qgis/compileExpressions', True)
return True
Expand Down
Binary file modified tests/testdata/provider/geopackage.gpkg
Binary file not shown.

0 comments on commit 582adf5

Please sign in to comment.