1# -*- coding: utf-8 -*- 2"""QGIS Unit test utils for provider tests. 3 4.. note:: This program is free software; you can redistribute it and/or modify 5it under the terms of the GNU General Public License as published by 6the Free Software Foundation; either version 2 of the License, or 7(at your option) any later version. 8""" 9 10from builtins import str 11from builtins import object 12 13__author__ = 'Matthias Kuhn' 14__date__ = '2015-04-27' 15__copyright__ = 'Copyright 2015, The QGIS Project' 16 17from qgis.core import ( 18 QgsApplication, 19 QgsRectangle, 20 QgsFeatureRequest, 21 QgsFeature, 22 QgsGeometry, 23 QgsAbstractFeatureIterator, 24 QgsExpressionContextScope, 25 QgsExpressionContext, 26 QgsExpression, 27 QgsVectorDataProvider, 28 QgsVectorLayerFeatureSource, 29 QgsFeatureSink, 30 QgsTestUtils, 31 QgsFeatureSource, 32 QgsFieldConstraints, 33 QgsDataProvider, 34 QgsVectorLayerUtils, 35 NULL 36) 37from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant 38from qgis.PyQt.QtTest import QSignalSpy 39 40from utilities import compareWkt 41from featuresourcetestbase import FeatureSourceTestCase 42 43 44class ProviderTestCase(FeatureSourceTestCase): 45 ''' 46 This is a collection of tests for vector data providers and kept generic. 47 To make use of it, subclass it and set self.source to a provider you want to test. 48 Make sure that your provider uses the default dataset by converting one of the provided datasets from the folder 49 tests/testdata/provider to a dataset your provider is able to handle. 50 51 To test expression compilation, add the methods `enableCompiler()` and `disableCompiler()` to your subclass. 52 If these methods are present, the tests will ensure that the result of server side and client side expression 53 evaluation are equal. 54 55 To enable constraints checks for a data provider, please see the comment to the specific tests: 56 - testChangeAttributesConstraintViolation 57 - testUniqueNotNullConstraints 58 59 ''' 60 61 def uncompiledFilters(self): 62 """ Individual derived provider tests should override this to return a list of expressions which 63 cannot be compiled """ 64 return set() 65 66 def enableCompiler(self): 67 """By default there is no expression compiling available, needs to be overridden in subclass""" 68 print('Provider does not support compiling') 69 return False 70 71 def partiallyCompiledFilters(self): 72 """ Individual derived provider tests should override this to return a list of expressions which 73 should be partially compiled """ 74 return set() 75 76 @property 77 def pk_name(self): 78 """Return the primary key name, override if different than the default 'pk'""" 79 return 'pk' 80 81 def assert_query(self, source, expression, expected): 82 FeatureSourceTestCase.assert_query(self, source, expression, expected) 83 84 if self.compiled: 85 # Check compilation status 86 it = source.getFeatures(QgsFeatureRequest().setFilterExpression(expression).setFlags(QgsFeatureRequest.IgnoreStaticNodesDuringExpressionCompilation)) 87 88 if expression in self.uncompiledFilters(): 89 self.assertEqual(it.compileStatus(), QgsAbstractFeatureIterator.NoCompilation) 90 elif expression in self.partiallyCompiledFilters(): 91 self.assertEqual(it.compileStatus(), QgsAbstractFeatureIterator.PartiallyCompiled) 92 else: 93 self.assertEqual(it.compileStatus(), QgsAbstractFeatureIterator.Compiled, expression) 94 95 def runGetFeatureTests(self, source): 96 FeatureSourceTestCase.runGetFeatureTests(self, source) 97 98 # combination of an uncompilable expression and limit 99 feature = next(self.vl.getFeatures('pk=4')) 100 context = QgsExpressionContext() 101 scope = QgsExpressionContextScope() 102 scope.setVariable('parent', feature) 103 context.appendScope(scope) 104 105 request = QgsFeatureRequest() 106 request.setExpressionContext(context) 107 request.setFilterExpression('"pk" = attribute(@parent, \'pk\')').setFlags(QgsFeatureRequest.IgnoreStaticNodesDuringExpressionCompilation) 108 request.setLimit(1) 109 110 values = [f[self.pk_name] for f in self.vl.getFeatures(request)] 111 self.assertEqual(values, [4]) 112 113 def runPolyGetFeatureTests(self, provider): 114 assert len([f for f in provider.getFeatures()]) == 4 115 116 # geometry 117 self.assert_query(provider, 'x($geometry) < -70', [1]) 118 self.assert_query(provider, 'y($geometry) > 79', [1, 2]) 119 self.assert_query(provider, 'xmin($geometry) < -70', [1, 3]) 120 self.assert_query(provider, 'ymin($geometry) < 76', [3]) 121 self.assert_query(provider, 'xmax($geometry) > -68', [2, 3]) 122 self.assert_query(provider, 'ymax($geometry) > 80', [1, 2]) 123 self.assert_query(provider, 'area($geometry) > 10', [1]) 124 self.assert_query(provider, 'perimeter($geometry) < 12', [2, 3]) 125 self.assert_query(provider, 126 'relate($geometry,geom_from_wkt( \'Polygon ((-68.2 82.1, -66.95 82.1, -66.95 79.05, -68.2 79.05, -68.2 82.1))\')) = \'FF2FF1212\'', 127 [1, 3]) 128 self.assert_query(provider, 129 'relate($geometry,geom_from_wkt( \'Polygon ((-68.2 82.1, -66.95 82.1, -66.95 79.05, -68.2 79.05, -68.2 82.1))\'), \'****F****\')', 130 [1, 3]) 131 self.assert_query(provider, 132 'crosses($geometry,geom_from_wkt( \'Linestring (-68.2 82.1, -66.95 82.1, -66.95 79.05)\'))', 133 [2]) 134 self.assert_query(provider, 135 'overlaps($geometry,geom_from_wkt( \'Polygon ((-68.2 82.1, -66.95 82.1, -66.95 79.05, -68.2 79.05, -68.2 82.1))\'))', 136 [2]) 137 self.assert_query(provider, 138 'within($geometry,geom_from_wkt( \'Polygon ((-75.1 76.1, -75.1 81.6, -68.8 81.6, -68.8 76.1, -75.1 76.1))\'))', 139 [1]) 140 self.assert_query(provider, 141 'overlaps(translate($geometry,-1,-1),geom_from_wkt( \'Polygon ((-75.1 76.1, -75.1 81.6, -68.8 81.6, -68.8 76.1, -75.1 76.1))\'))', 142 [1]) 143 self.assert_query(provider, 144 'overlaps(buffer($geometry,1),geom_from_wkt( \'Polygon ((-75.1 76.1, -75.1 81.6, -68.8 81.6, -68.8 76.1, -75.1 76.1))\'))', 145 [1, 3]) 146 self.assert_query(provider, 147 'intersects(centroid($geometry),geom_from_wkt( \'Polygon ((-74.4 78.2, -74.4 79.1, -66.8 79.1, -66.8 78.2, -74.4 78.2))\'))', 148 [2]) 149 self.assert_query(provider, 150 'intersects(point_on_surface($geometry),geom_from_wkt( \'Polygon ((-74.4 78.2, -74.4 79.1, -66.8 79.1, -66.8 78.2, -74.4 78.2))\'))', 151 [1, 2]) 152 self.assert_query(provider, 'distance($geometry,geom_from_wkt( \'Point (-70 70)\')) > 7', [1, 2]) 153 154 def testGetFeaturesUncompiled(self): 155 self.compiled = False 156 try: 157 self.disableCompiler() 158 except AttributeError: 159 pass 160 self.runGetFeatureTests(self.source) 161 if hasattr(self, 'poly_provider'): 162 self.runPolyGetFeatureTests(self.poly_provider) 163 164 def testGetFeaturesExp(self): 165 if self.enableCompiler(): 166 self.compiled = True 167 self.runGetFeatureTests(self.source) 168 if hasattr(self, 'poly_provider'): 169 self.runPolyGetFeatureTests(self.poly_provider) 170 171 def testSubsetString(self): 172 if not self.source.supportsSubsetString(): 173 print('Provider does not support subset strings') 174 return 175 176 changed_spy = QSignalSpy(self.source.dataChanged) 177 subset = self.getSubsetString() 178 self.source.setSubsetString(subset) 179 self.assertEqual(self.source.subsetString(), subset) 180 self.assertEqual(len(changed_spy), 1) 181 182 # No signal should be emitted if the subset string is not modified 183 self.source.setSubsetString(subset) 184 self.assertEqual(len(changed_spy), 1) 185 186 result = set([f[self.pk_name] for f in self.source.getFeatures()]) 187 all_valid = (all(f.isValid() for f in self.source.getFeatures())) 188 self.source.setSubsetString(None) 189 190 expected = set([2, 3, 4]) 191 assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), 192 result, subset) 193 self.assertTrue(all_valid) 194 195 # Subset string AND filter rect 196 self.source.setSubsetString(subset) 197 extent = QgsRectangle(-70, 70, -60, 75) 198 request = QgsFeatureRequest().setFilterRect(extent) 199 result = set([f[self.pk_name] for f in self.source.getFeatures(request)]) 200 all_valid = (all(f.isValid() for f in self.source.getFeatures(request))) 201 self.source.setSubsetString(None) 202 expected = set([2]) 203 assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), 204 result, subset) 205 self.assertTrue(all_valid) 206 207 # Subset string AND filter rect, version 2 208 self.source.setSubsetString(subset) 209 extent = QgsRectangle(-71, 65, -60, 80) 210 result = set([f[self.pk_name] for f in self.source.getFeatures(QgsFeatureRequest().setFilterRect(extent))]) 211 self.source.setSubsetString(None) 212 expected = set([2, 4]) 213 assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), 214 result, subset) 215 216 # Subset string AND expression 217 self.source.setSubsetString(subset) 218 request = QgsFeatureRequest().setFilterExpression('length("name")=5') 219 result = set([f[self.pk_name] for f in self.source.getFeatures(request)]) 220 all_valid = (all(f.isValid() for f in self.source.getFeatures(request))) 221 self.source.setSubsetString(None) 222 expected = set([2, 4]) 223 assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), 224 result, subset) 225 self.assertTrue(all_valid) 226 227 # Subset string AND filter fid 228 ids = {f[self.pk_name]: f.id() for f in self.source.getFeatures()} 229 self.source.setSubsetString(subset) 230 request = QgsFeatureRequest().setFilterFid(4) 231 result = set([f.id() for f in self.source.getFeatures(request)]) 232 all_valid = (all(f.isValid() for f in self.source.getFeatures(request))) 233 self.source.setSubsetString(None) 234 expected = set([4]) 235 assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), 236 result, subset) 237 self.assertTrue(all_valid) 238 239 # Subset string AND filter fids 240 self.source.setSubsetString(subset) 241 request = QgsFeatureRequest().setFilterFids([ids[2], ids[4]]) 242 result = set([f.id() for f in self.source.getFeatures(request)]) 243 all_valid = (all(f.isValid() for f in self.source.getFeatures(request))) 244 self.source.setSubsetString(None) 245 expected = set([ids[2], ids[4]]) 246 assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), 247 result, subset) 248 self.assertTrue(all_valid) 249 250 def getSubsetString(self): 251 """Individual providers may need to override this depending on their subset string formats""" 252 return '"cnt" > 100 and "cnt" < 410' 253 254 def getSubsetString2(self): 255 """Individual providers may need to override this depending on their subset string formats""" 256 return '"cnt" > 100 and "cnt" < 400' 257 258 def getSubsetString3(self): 259 """Individual providers may need to override this depending on their subset string formats""" 260 return '"name"=\'Apple\'' 261 262 def getSubsetStringNoMatching(self): 263 """Individual providers may need to override this depending on their subset string formats""" 264 return '"name"=\'AppleBearOrangePear\'' 265 266 def testGetFeaturesThreadSafety(self): 267 # no request 268 self.assertTrue(QgsTestUtils.testProviderIteratorThreadSafety(self.source)) 269 270 # filter rect request 271 extent = QgsRectangle(-73, 70, -63, 80) 272 request = QgsFeatureRequest().setFilterRect(extent) 273 self.assertTrue(QgsTestUtils.testProviderIteratorThreadSafety(self.source, request)) 274 275 def testOrderBy(self): 276 try: 277 self.disableCompiler() 278 except AttributeError: 279 pass 280 self.runOrderByTests() 281 282 def testOrderByCompiled(self): 283 if self.enableCompiler(): 284 self.runOrderByTests() 285 286 def runOrderByTests(self): 287 FeatureSourceTestCase.runOrderByTests(self) 288 289 # Combination with subset of attributes 290 request = QgsFeatureRequest().addOrderBy('num_char', False).setSubsetOfAttributes([self.pk_name], self.vl.fields()) 291 values = [f[self.pk_name] for f in self.vl.getFeatures(request)] 292 self.assertEqual(values, [5, 4, 3, 2, 1]) 293 294 def testOpenIteratorAfterLayerRemoval(self): 295 """ 296 Test that removing layer after opening an iterator does not crash. All required 297 information should be captured in the iterator's source and there MUST be no 298 links between the iterators and the layer's data provider 299 """ 300 if not getattr(self, 'getEditableLayer', None): 301 return 302 303 l = self.getEditableLayer() 304 self.assertTrue(l.isValid()) 305 306 # store the source 307 source = QgsVectorLayerFeatureSource(l) 308 309 # delete the layer 310 del l 311 312 # get the features 313 pks = [] 314 for f in source.getFeatures(): 315 pks.append(f[self.pk_name]) 316 self.assertEqual(set(pks), {1, 2, 3, 4, 5}) 317 318 def testCloneLayer(self): 319 """ 320 Test that cloning layer works and has all expected features 321 """ 322 l = self.vl.clone() 323 324 pks = [] 325 for f in l.getFeatures(): 326 pks.append(f[self.pk_name]) 327 self.assertEqual(set(pks), {1, 2, 3, 4, 5}) 328 329 def testGetFeaturesPolyFilterRectTests(self): 330 """ Test fetching features from a polygon layer with filter rect""" 331 try: 332 if not self.poly_provider: 333 return 334 except: 335 return 336 337 extent = QgsRectangle(-73, 70, -63, 80) 338 request = QgsFeatureRequest().setFilterRect(extent) 339 features = [f[self.pk_name] for f in self.poly_provider.getFeatures(request)] 340 all_valid = (all(f.isValid() for f in self.source.getFeatures(request))) 341 # Some providers may return the exact intersection matches (2, 3) even without the ExactIntersect flag, so we accept that too 342 assert set(features) == set([2, 3]) or set(features) == set([1, 2, 3]), 'Got {} instead'.format(features) 343 self.assertTrue(all_valid) 344 345 # Test with exact intersection 346 request = QgsFeatureRequest().setFilterRect(extent).setFlags(QgsFeatureRequest.ExactIntersect) 347 features = [f[self.pk_name] for f in self.poly_provider.getFeatures(request)] 348 all_valid = (all(f.isValid() for f in self.source.getFeatures(request))) 349 assert set(features) == set([2, 3]), 'Got {} instead'.format(features) 350 self.assertTrue(all_valid) 351 352 # test with an empty rectangle 353 extent = QgsRectangle() 354 features = [f[self.pk_name] for f in self.source.getFeatures(QgsFeatureRequest().setFilterRect(extent))] 355 assert set(features) == set([1, 2, 3, 4, 5]), 'Got {} instead'.format(features) 356 357 def testMinValue(self): 358 self.assertFalse(self.source.minimumValue(-1)) 359 self.assertFalse(self.source.minimumValue(1000)) 360 361 self.assertEqual(self.source.minimumValue(self.source.fields().lookupField('cnt')), -200) 362 self.assertEqual(self.source.minimumValue(self.source.fields().lookupField('name')), 'Apple') 363 364 if self.treat_datetime_as_string(): 365 self.assertEqual(self.source.minimumValue(self.source.fields().lookupField('dt')), '2020-05-03 12:13:14') 366 else: 367 self.assertEqual(self.source.minimumValue(self.source.fields().lookupField('dt')), 368 QDateTime(QDate(2020, 5, 3), QTime(12, 13, 14))) 369 370 if self.treat_date_as_string(): 371 self.assertEqual(self.source.minimumValue(self.source.fields().lookupField('date')), '2020-05-02') 372 elif not self.treat_date_as_datetime(): 373 self.assertEqual(self.source.minimumValue(self.source.fields().lookupField('date')), QDate(2020, 5, 2)) 374 else: 375 self.assertEqual(self.source.minimumValue(self.source.fields().lookupField('date')), 376 QDateTime(2020, 5, 2, 0, 0, 0)) 377 378 if not self.treat_time_as_string(): 379 self.assertEqual(self.source.minimumValue(self.source.fields().lookupField('time')), QTime(12, 13, 1)) 380 else: 381 self.assertEqual(self.source.minimumValue(self.source.fields().lookupField('time')), '12:13:01') 382 383 if self.source.supportsSubsetString(): 384 subset = self.getSubsetString() 385 self.source.setSubsetString(subset) 386 min_value = self.source.minimumValue(self.source.fields().lookupField('cnt')) 387 self.source.setSubsetString(None) 388 self.assertEqual(min_value, 200) 389 390 def testMaxValue(self): 391 self.assertFalse(self.source.maximumValue(-1)) 392 self.assertFalse(self.source.maximumValue(1000)) 393 self.assertEqual(self.source.maximumValue(self.source.fields().lookupField('cnt')), 400) 394 self.assertEqual(self.source.maximumValue(self.source.fields().lookupField('name')), 'Pear') 395 396 if not self.treat_datetime_as_string(): 397 self.assertEqual(self.source.maximumValue(self.source.fields().lookupField('dt')), 398 QDateTime(QDate(2021, 5, 4), QTime(13, 13, 14))) 399 else: 400 self.assertEqual(self.source.maximumValue(self.source.fields().lookupField('dt')), '2021-05-04 13:13:14') 401 402 if self.treat_date_as_string(): 403 self.assertEqual(self.source.maximumValue(self.source.fields().lookupField('date')), '2021-05-04') 404 elif not self.treat_date_as_datetime(): 405 self.assertEqual(self.source.maximumValue(self.source.fields().lookupField('date')), QDate(2021, 5, 4)) 406 else: 407 self.assertEqual(self.source.maximumValue(self.source.fields().lookupField('date')), 408 QDateTime(2021, 5, 4, 0, 0, 0)) 409 410 if not self.treat_time_as_string(): 411 self.assertEqual(self.source.maximumValue(self.source.fields().lookupField('time')), QTime(13, 13, 14)) 412 else: 413 self.assertEqual(self.source.maximumValue(self.source.fields().lookupField('time')), '13:13:14') 414 415 if self.source.supportsSubsetString(): 416 subset = self.getSubsetString2() 417 self.source.setSubsetString(subset) 418 max_value = self.source.maximumValue(self.source.fields().lookupField('cnt')) 419 self.source.setSubsetString(None) 420 self.assertEqual(max_value, 300) 421 422 def testExtent(self): 423 reference = QgsGeometry.fromRect( 424 QgsRectangle(-71.123, 66.33, -65.32, 78.3)) 425 provider_extent = self.source.extent() 426 self.assertAlmostEqual(provider_extent.xMinimum(), -71.123, 3) 427 self.assertAlmostEqual(provider_extent.xMaximum(), -65.32, 3) 428 self.assertAlmostEqual(provider_extent.yMinimum(), 66.33, 3) 429 self.assertAlmostEqual(provider_extent.yMaximum(), 78.3, 3) 430 431 def testExtentSubsetString(self): 432 if self.source.supportsSubsetString(): 433 # with only one point 434 subset = self.getSubsetString3() 435 self.source.setSubsetString(subset) 436 count = self.source.featureCount() 437 provider_extent = self.source.extent() 438 self.source.setSubsetString(None) 439 self.assertEqual(count, 1) 440 self.assertAlmostEqual(provider_extent.xMinimum(), -68.2, 3) 441 self.assertAlmostEqual(provider_extent.xMaximum(), -68.2, 3) 442 self.assertAlmostEqual(provider_extent.yMinimum(), 70.8, 3) 443 self.assertAlmostEqual(provider_extent.yMaximum(), 70.8, 3) 444 445 # with no points 446 subset = self.getSubsetStringNoMatching() 447 self.source.setSubsetString(subset) 448 count = self.source.featureCount() 449 provider_extent = self.source.extent() 450 self.source.setSubsetString(None) 451 self.assertEqual(count, 0) 452 self.assertTrue(provider_extent.isNull()) 453 self.assertEqual(self.source.featureCount(), 5) 454 455 def testUnique(self): 456 self.assertEqual(self.source.uniqueValues(-1), set()) 457 self.assertEqual(self.source.uniqueValues(1000), set()) 458 459 self.assertEqual(set(self.source.uniqueValues(self.source.fields().lookupField('cnt'))), 460 set([-200, 100, 200, 300, 400])) 461 assert set(['Apple', 'Honey', 'Orange', 'Pear', NULL]) == set( 462 self.source.uniqueValues(self.source.fields().lookupField('name'))), 'Got {}'.format( 463 set(self.source.uniqueValues(self.source.fields().lookupField('name')))) 464 465 if self.treat_datetime_as_string(): 466 self.assertEqual(set(self.source.uniqueValues(self.source.fields().lookupField('dt'))), 467 set(['2021-05-04 13:13:14', '2020-05-04 12:14:14', '2020-05-04 12:13:14', 468 '2020-05-03 12:13:14', NULL])) 469 else: 470 self.assertEqual(set(self.source.uniqueValues(self.source.fields().lookupField('dt'))), 471 set([QDateTime(2021, 5, 4, 13, 13, 14), QDateTime(2020, 5, 4, 12, 14, 14), 472 QDateTime(2020, 5, 4, 12, 13, 14), QDateTime(2020, 5, 3, 12, 13, 14), NULL])) 473 474 if self.treat_date_as_string(): 475 self.assertEqual(set(self.source.uniqueValues(self.source.fields().lookupField('date'))), 476 set(['2020-05-03', '2020-05-04', '2021-05-04', '2020-05-02', NULL])) 477 elif self.treat_date_as_datetime(): 478 self.assertEqual(set(self.source.uniqueValues(self.source.fields().lookupField('date'))), 479 set([QDateTime(2020, 5, 3, 0, 0, 0), QDateTime(2020, 5, 4, 0, 0, 0), 480 QDateTime(2021, 5, 4, 0, 0, 0), QDateTime(2020, 5, 2, 0, 0, 0), NULL])) 481 else: 482 self.assertEqual(set(self.source.uniqueValues(self.source.fields().lookupField('date'))), 483 set([QDate(2020, 5, 3), QDate(2020, 5, 4), QDate(2021, 5, 4), QDate(2020, 5, 2), NULL])) 484 if self.treat_time_as_string(): 485 self.assertEqual(set(self.source.uniqueValues(self.source.fields().lookupField('time'))), 486 set(['12:14:14', '13:13:14', '12:13:14', '12:13:01', NULL])) 487 else: 488 self.assertEqual(set(self.source.uniqueValues(self.source.fields().lookupField('time'))), 489 set([QTime(12, 14, 14), QTime(13, 13, 14), QTime(12, 13, 14), QTime(12, 13, 1), NULL])) 490 491 if self.source.supportsSubsetString(): 492 subset = self.getSubsetString2() 493 self.source.setSubsetString(subset) 494 values = self.source.uniqueValues(self.source.fields().lookupField('cnt')) 495 self.source.setSubsetString(None) 496 self.assertEqual(set(values), set([200, 300])) 497 498 def testUniqueStringsMatching(self): 499 self.assertEqual(self.source.uniqueStringsMatching(-1, 'a'), []) 500 self.assertEqual(self.source.uniqueStringsMatching(100001, 'a'), []) 501 502 field_index = self.source.fields().lookupField('name') 503 self.assertEqual(set(self.source.uniqueStringsMatching(field_index, 'a')), set(['Pear', 'Orange', 'Apple'])) 504 # test case insensitive 505 self.assertEqual(set(self.source.uniqueStringsMatching(field_index, 'A')), set(['Pear', 'Orange', 'Apple'])) 506 # test string ending in substring 507 self.assertEqual(set(self.source.uniqueStringsMatching(field_index, 'ney')), set(['Honey'])) 508 # test limit 509 result = set(self.source.uniqueStringsMatching(field_index, 'a', 2)) 510 self.assertEqual(len(result), 2) 511 self.assertTrue(result.issubset(set(['Pear', 'Orange', 'Apple']))) 512 513 assert set([u'Apple', u'Honey', u'Orange', u'Pear', NULL]) == set( 514 self.source.uniqueValues(field_index)), 'Got {}'.format(set(self.source.uniqueValues(field_index))) 515 516 if self.source.supportsSubsetString(): 517 subset = self.getSubsetString2() 518 self.source.setSubsetString(subset) 519 values = self.source.uniqueStringsMatching(field_index, 'a') 520 self.source.setSubsetString(None) 521 self.assertEqual(set(values), set(['Pear', 'Apple'])) 522 523 def testFeatureCount(self): 524 self.assertEqual(self.source.featureCount(), 5) 525 526 if self.source.supportsSubsetString(): 527 # Add a subset string and test feature count 528 subset = self.getSubsetString() 529 self.source.setSubsetString(subset) 530 count = self.source.featureCount() 531 self.source.setSubsetString(None) 532 self.assertEqual(count, 3) 533 self.assertEqual(self.source.featureCount(), 5) 534 535 # one matching records 536 subset = self.getSubsetString3() 537 self.source.setSubsetString(subset) 538 count = self.source.featureCount() 539 self.source.setSubsetString(None) 540 self.assertEqual(count, 1) 541 self.assertEqual(self.source.featureCount(), 5) 542 543 # no matching records 544 subset = self.getSubsetStringNoMatching() 545 self.source.setSubsetString(subset) 546 count = self.source.featureCount() 547 self.source.setSubsetString(None) 548 self.assertEqual(count, 0) 549 self.assertEqual(self.source.featureCount(), 5) 550 551 def testEmpty(self): 552 self.assertFalse(self.source.empty()) 553 self.assertEqual(self.source.hasFeatures(), QgsFeatureSource.FeaturesAvailable) 554 555 if self.source.supportsSubsetString(): 556 try: 557 backup = self.source.subsetString() 558 # Add a subset string and test feature count 559 subset = self.getSubsetString() 560 self.source.setSubsetString(subset) 561 self.assertFalse(self.source.empty()) 562 self.assertEqual(self.source.hasFeatures(), QgsFeatureSource.FeaturesAvailable) 563 subsetNoMatching = self.getSubsetStringNoMatching() 564 self.source.setSubsetString(subsetNoMatching) 565 self.assertTrue(self.source.empty()) 566 self.assertEqual(self.source.hasFeatures(), QgsFeatureSource.NoFeaturesAvailable) 567 finally: 568 self.source.setSubsetString(None) 569 self.assertFalse(self.source.empty()) 570 571 # If the provider supports tests on editable layers 572 if getattr(self, 'getEditableLayer', None): 573 l = self.getEditableLayer() 574 self.assertTrue(l.isValid()) 575 576 self.assertEqual(l.hasFeatures(), QgsFeatureSource.FeaturesAvailable) 577 578 # Test that deleting some features in the edit buffer does not 579 # return empty, we accept FeaturesAvailable as well as 580 # MaybeAvailable 581 l.startEditing() 582 l.deleteFeature(next(l.getFeatures()).id()) 583 self.assertNotEqual(l.hasFeatures(), QgsFeatureSource.NoFeaturesAvailable) 584 l.rollBack() 585 586 # Call truncate(), we need an empty set now 587 l.dataProvider().truncate() 588 self.assertTrue(l.dataProvider().empty()) 589 self.assertEqual(l.dataProvider().hasFeatures(), QgsFeatureSource.NoFeaturesAvailable) 590 591 def testGetFeaturesNoGeometry(self): 592 """ Test that no geometry is present when fetching features without geometry""" 593 594 for f in self.source.getFeatures(QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)): 595 self.assertFalse(f.hasGeometry(), 'Expected no geometry, got one') 596 self.assertTrue(f.isValid()) 597 598 def testAddFeature(self): 599 if not getattr(self, 'getEditableLayer', None): 600 return 601 602 l = self.getEditableLayer() 603 self.assertTrue(l.isValid()) 604 605 f1 = QgsFeature() 606 f1.setAttributes([6, -220, NULL, 'String', '15', 607 '2019-01-02 03:04:05' if self.treat_datetime_as_string() else QDateTime(2019, 1, 2, 3, 4, 5), 608 '2019-01-02' if self.treat_date_as_string() else QDateTime(2019, 1, 2, 0, 0, 609 0) if self.treat_date_as_datetime() else QDate( 610 2019, 1, 2), 611 '03:04:05' if self.treat_time_as_string() else QTime(3, 4, 5)]) 612 f1.setGeometry(QgsGeometry.fromWkt('Point (-72.345 71.987)')) 613 614 f2 = QgsFeature() 615 f2.setAttributes([7, 330, 'Coconut', 'CoCoNut', '13', 616 '2018-05-06 07:08:09' if self.treat_datetime_as_string() else QDateTime(2018, 5, 6, 7, 8, 9), 617 '2018-05-06' if self.treat_date_as_string() else QDateTime(2018, 5, 6, 0, 0, 618 0) if self.treat_date_as_datetime() else QDate( 619 2018, 5, 6), 620 '07:08:09' if self.treat_time_as_string() else QTime(7, 8, 9)]) 621 622 if l.dataProvider().capabilities() & QgsVectorDataProvider.AddFeatures: 623 # expect success 624 result, added = l.dataProvider().addFeatures([f1, f2]) 625 self.assertTrue(result, 'Provider reported AddFeatures capability, but returned False to addFeatures') 626 f1.setId(added[0].id()) 627 f2.setId(added[1].id()) 628 629 # check result 630 self.testGetFeatures(l.dataProvider(), [f1, f2]) 631 632 # add empty list, should return true for consistency 633 self.assertTrue(l.dataProvider().addFeatures([])) 634 635 # ensure that returned features have been given the correct id 636 f = next(l.getFeatures(QgsFeatureRequest().setFilterFid(added[0].id()))) 637 self.assertTrue(f.isValid()) 638 self.assertEqual(f['cnt'], -220) 639 640 f = next(l.getFeatures(QgsFeatureRequest().setFilterFid(added[1].id()))) 641 self.assertTrue(f.isValid()) 642 self.assertEqual(f['cnt'], 330) 643 else: 644 # expect fail 645 self.assertFalse(l.dataProvider().addFeatures([f1, f2]), 646 'Provider reported no AddFeatures capability, but returned true to addFeatures') 647 648 def testAddFeatureFastInsert(self): 649 if not getattr(self, 'getEditableLayer', None): 650 return 651 652 l = self.getEditableLayer() 653 self.assertTrue(l.isValid()) 654 655 f1 = QgsFeature() 656 f1.setAttributes( 657 [6, -220, NULL, 'String', '15', 658 '2019-01-02 03:04:05' if self.treat_datetime_as_string() else QDateTime(2019, 1, 2, 3, 4, 5), 659 '2019-01-02' if self.treat_date_as_string() else QDateTime(2019, 1, 2, 0, 0, 0) if self.treat_date_as_datetime() else QDate(2019, 1, 2), 660 '03:04:05' if self.treat_time_as_string() else QTime(3, 4, 5)]) 661 f1.setGeometry(QgsGeometry.fromWkt('Point (-72.345 71.987)')) 662 663 f2 = QgsFeature() 664 f2.setAttributes([7, 330, 'Coconut', 'CoCoNut', '13', 665 '2019-01-02 03:04:05' if self.treat_datetime_as_string() else QDateTime(2019, 1, 2, 3, 4, 5), 666 '2019-01-02' if self.treat_date_as_string() else QDateTime(2019, 1, 2, 0, 0, 0) if self.treat_date_as_datetime() else QDate(2019, 1, 2), 667 '03:04:05' if self.treat_time_as_string() else QTime(3, 4, 5)]) 668 669 if l.dataProvider().capabilities() & QgsVectorDataProvider.AddFeatures: 670 # expect success 671 result, added = l.dataProvider().addFeatures([f1, f2], QgsFeatureSink.FastInsert) 672 self.assertTrue(result, 'Provider reported AddFeatures capability, but returned False to addFeatures') 673 self.assertEqual(l.dataProvider().featureCount(), 7) 674 675 def testAddFeatureMissingAttributes(self): 676 if not getattr(self, 'getEditableLayer', None): 677 return 678 679 l = self.getEditableLayer() 680 self.assertTrue(l.isValid()) 681 682 if not l.dataProvider().capabilities() & QgsVectorDataProvider.AddFeatures: 683 return 684 685 # test that adding features with missing attributes pads out these 686 # attributes with NULL values to the correct length 687 f1 = QgsFeature() 688 f1.setAttributes([6, -220, NULL, 'String']) 689 f2 = QgsFeature() 690 f2.setAttributes([7, 330]) 691 692 result, added = l.dataProvider().addFeatures([f1, f2]) 693 self.assertTrue(result, 694 'Provider returned False to addFeatures with missing attributes. Providers should accept these features but add NULL attributes to the end of the existing attributes to the required field length.') 695 f1.setId(added[0].id()) 696 f2.setId(added[1].id()) 697 698 # check result - feature attributes MUST be padded out to required number of fields 699 f1.setAttributes([6, -220, NULL, 'String', 'NULL', NULL, NULL, NULL]) 700 f2.setAttributes([7, 330, NULL, NULL, 'NULL', NULL, NULL, NULL]) 701 self.testGetFeatures(l.dataProvider(), [f1, f2]) 702 703 def testAddFeatureExtraAttributes(self): 704 if not getattr(self, 'getEditableLayer', None): 705 return 706 707 l = self.getEditableLayer() 708 self.assertTrue(l.isValid()) 709 710 if not l.dataProvider().capabilities() & QgsVectorDataProvider.AddFeatures: 711 return 712 713 # test that adding features with too many attributes drops these attributes 714 # we be more tricky and also add a valid feature to stress test the provider 715 f1 = QgsFeature() 716 f1.setAttributes([6, -220, NULL, 'String', '15', 717 '2019-01-02 03:04:05' if self.treat_datetime_as_string() else QDateTime(2019, 1, 2, 3, 4, 5), 718 '2019-01-02' if self.treat_date_as_string() else QDateTime(2019, 1, 2, 0, 0, 0) if self.treat_date_as_datetime() else QDate(2019, 1, 2), 719 '03:04:05' if self.treat_time_as_string() else QTime(3, 4, 5)]) 720 f2 = QgsFeature() 721 f2.setAttributes([7, -230, NULL, 'String', '15', 722 '2019-01-02 03:04:05' if self.treat_datetime_as_string() else QDateTime(2019, 1, 2, 3, 4, 5), 723 '2019-01-02' if self.treat_date_as_string() else QDateTime(2019, 1, 2, 0, 0, 0) if self.treat_date_as_datetime() else QDate(2019, 1, 2), 724 '03:04:05' if self.treat_time_as_string() else QTime(3, 4, 5), 15, 16, 17]) 725 726 result, added = l.dataProvider().addFeatures([f1, f2]) 727 self.assertTrue(result, 728 'Provider returned False to addFeatures with extra attributes. Providers should accept these features but truncate the extra attributes.') 729 730 # make sure feature was added correctly 731 added = [f for f in l.dataProvider().getFeatures() if f[self.pk_name] == 7][0] 732 self.assertEqual(added.attributes(), [7, -230, NULL, 'String', '15', 733 '2019-01-02 03:04:05' if self.treat_datetime_as_string() else QDateTime( 734 2019, 1, 2, 3, 4, 5), 735 '2019-01-02' if self.treat_date_as_string() else QDateTime(2019, 1, 2, 0, 0, 0) if self.treat_date_as_datetime() else QDate(2019, 1, 2), 736 '03:04:05' if self.treat_time_as_string() else QTime(3, 4, 5)]) 737 738 def testAddFeatureWrongGeomType(self): 739 if not getattr(self, 'getEditableLayer', None): 740 return 741 742 l = self.getEditableLayer() 743 self.assertTrue(l.isValid()) 744 745 if not l.dataProvider().capabilities() & QgsVectorDataProvider.AddFeatures: 746 return 747 748 # test that adding features with incorrect geometry type rejects the feature 749 # we be more tricky and also add a valid feature to stress test the provider 750 f1 = QgsFeature() 751 f1.setGeometry(QgsGeometry.fromWkt('LineString (-72.345 71.987, -80 80)')) 752 f1.setAttributes([7]) 753 f2 = QgsFeature() 754 f2.setGeometry(QgsGeometry.fromWkt('Point (-72.345 71.987)')) 755 f2.setAttributes([8]) 756 757 result, added = l.dataProvider().addFeatures([f1, f2]) 758 self.assertFalse(result, 759 'Provider returned True to addFeatures with incorrect geometry type. Providers should reject these features.') 760 761 # make sure feature was not added 762 added = [f for f in l.dataProvider().getFeatures() if f[self.pk_name] == 7] 763 self.assertFalse(added) 764 765 # yet providers MUST always accept null geometries 766 f3 = QgsFeature() 767 f3.setAttributes([9]) 768 result, added = l.dataProvider().addFeatures([f3]) 769 self.assertTrue(result, 770 'Provider returned False to addFeatures with null geometry. Providers should always accept these features.') 771 772 # make sure feature was added correctly 773 added = [f for f in l.dataProvider().getFeatures() if f[self.pk_name] == 9][0] 774 self.assertFalse(added.hasGeometry()) 775 776 def testAddFeaturesUpdateExtent(self): 777 if not getattr(self, 'getEditableLayer', None): 778 return 779 780 l = self.getEditableLayer() 781 self.assertTrue(l.isValid()) 782 783 self.assertEqual(l.dataProvider().extent().toString(1), '-71.1,66.3 : -65.3,78.3') 784 785 if l.dataProvider().capabilities() & QgsVectorDataProvider.AddFeatures: 786 f1 = QgsFeature() 787 f1.setAttributes([6, -220, NULL, 'String', '15']) 788 f1.setGeometry(QgsGeometry.fromWkt('Point (-50 90)')) 789 l.dataProvider().addFeatures([f1]) 790 791 l.dataProvider().updateExtents() 792 self.assertEqual(l.dataProvider().extent().toString(1), '-71.1,66.3 : -50.0,90.0') 793 794 def testDeleteFeatures(self): 795 if not getattr(self, 'getEditableLayer', None): 796 return 797 798 l = self.getEditableLayer() 799 self.assertTrue(l.isValid()) 800 801 # find 2 features to delete 802 features = [f for f in l.dataProvider().getFeatures()] 803 to_delete = [f.id() for f in features if f.attributes()[0] in [1, 3]] 804 805 if l.dataProvider().capabilities() & QgsVectorDataProvider.DeleteFeatures: 806 # expect success 807 result = l.dataProvider().deleteFeatures(to_delete) 808 self.assertTrue(result, 'Provider reported DeleteFeatures capability, but returned False to deleteFeatures') 809 810 # check result 811 self.testGetFeatures(l.dataProvider(), skip_features=[1, 3]) 812 813 # delete empty list, should return true for consistency 814 self.assertTrue(l.dataProvider().deleteFeatures([])) 815 816 else: 817 # expect fail 818 self.assertFalse(l.dataProvider().deleteFeatures(to_delete), 819 'Provider reported no DeleteFeatures capability, but returned true to deleteFeatures') 820 821 def testDeleteFeaturesUpdateExtent(self): 822 if not getattr(self, 'getEditableLayer', None): 823 return 824 825 l = self.getEditableLayer() 826 self.assertTrue(l.isValid()) 827 828 self.assertEqual(l.dataProvider().extent().toString(1), '-71.1,66.3 : -65.3,78.3') 829 830 to_delete = [f.id() for f in l.dataProvider().getFeatures() if f.attributes()[0] in [5, 4]] 831 832 if l.dataProvider().capabilities() & QgsVectorDataProvider.DeleteFeatures: 833 l.dataProvider().deleteFeatures(to_delete) 834 835 l.dataProvider().updateExtents() 836 self.assertEqual(l.dataProvider().extent().toString(1), '-70.3,66.3 : -68.2,70.8') 837 838 def testTruncate(self): 839 if not getattr(self, 'getEditableLayer', None): 840 return 841 842 l = self.getEditableLayer() 843 self.assertTrue(l.isValid()) 844 845 features = [f[self.pk_name] for f in l.dataProvider().getFeatures()] 846 847 if l.dataProvider().capabilities() & QgsVectorDataProvider.FastTruncate or l.dataProvider().capabilities() & QgsVectorDataProvider.DeleteFeatures: 848 # expect success 849 result = l.dataProvider().truncate() 850 self.assertTrue(result, 851 'Provider reported FastTruncate or DeleteFeatures capability, but returned False to truncate()') 852 853 # check result 854 features = [f[self.pk_name] for f in l.dataProvider().getFeatures()] 855 self.assertEqual(len(features), 0) 856 else: 857 # expect fail 858 self.assertFalse(l.dataProvider().truncate(), 859 'Provider reported no FastTruncate or DeleteFeatures capability, but returned true to truncate()') 860 861 def testChangeAttributes(self): 862 if not getattr(self, 'getEditableLayer', None): 863 return 864 865 l = self.getEditableLayer() 866 self.assertTrue(l.isValid()) 867 868 # find 2 features to change 869 features = [f for f in l.dataProvider().getFeatures()] 870 # need to keep order here 871 to_change = [f for f in features if f.attributes()[0] == 1] 872 to_change.extend([f for f in features if f.attributes()[0] == 3]) 873 # changes by feature id, for changeAttributeValues call 874 changes = {to_change[0].id(): {1: 501, 3: 'new string'}, to_change[1].id(): {1: 502, 4: 'NEW'}} 875 # changes by pk, for testing after retrieving changed features 876 new_attr_map = {1: {1: 501, 3: 'new string'}, 3: {1: 502, 4: 'NEW'}} 877 878 if l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeAttributeValues: 879 # expect success 880 result = l.dataProvider().changeAttributeValues(changes) 881 self.assertTrue(result, 882 'Provider reported ChangeAttributeValues capability, but returned False to changeAttributeValues') 883 884 # check result 885 self.testGetFeatures(l.dataProvider(), changed_attributes=new_attr_map) 886 887 # change empty list, should return true for consistency 888 self.assertTrue(l.dataProvider().changeAttributeValues({})) 889 890 else: 891 # expect fail 892 self.assertFalse(l.dataProvider().changeAttributeValues(changes), 893 'Provider reported no ChangeAttributeValues capability, but returned true to changeAttributeValues') 894 895 def testChangeAttributesConstraintViolation(self): 896 """Checks that changing attributes violating a DB-level CHECK constraint returns false 897 the provider test case must provide an editable layer with a text field 898 "i_will_fail_on_no_name" having a CHECK constraint that will fail when value is "no name". 899 The layer must contain at least 2 features, that will be used to test the attribute change. 900 """ 901 902 if not getattr(self, 'getEditableLayerWithCheckConstraint', None): 903 return 904 905 l = self.getEditableLayerWithCheckConstraint() 906 self.assertTrue(l.isValid()) 907 908 assert l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeAttributeValues 909 910 # find the featurea to change 911 feature0 = [f for f in l.dataProvider().getFeatures()][0] 912 feature1 = [f for f in l.dataProvider().getFeatures()][1] 913 field_idx = l.fields().indexFromName('i_will_fail_on_no_name') 914 self.assertTrue(field_idx >= 0) 915 # changes by feature id, for changeAttributeValues call 916 changes = { 917 feature0.id(): {field_idx: 'no name'}, 918 feature1.id(): {field_idx: 'I have a valid name'} 919 } 920 # expect failure 921 result = l.dataProvider().changeAttributeValues(changes) 922 self.assertFalse( 923 result, 'Provider reported success when changing an attribute value that violates a DB level CHECK constraint') 924 925 def testUniqueNotNullConstraints(self): 926 """Test provider-level NOT NULL and UNIQUE constraints, to enable 927 this test, implement getEditableLayerWithUniqueNotNullConstraints 928 to return an editable POINT layer with the following fields: 929 930 "unique" TEXT UNIQUE, 931 "not_null" TEXT NOT NULL 932 933 """ 934 935 if not getattr(self, 'getEditableLayerWithUniqueNotNullConstraints', None): 936 return 937 938 vl = self.getEditableLayerWithUniqueNotNullConstraints() 939 940 self.assertTrue(vl.isValid()) 941 unique_field_idx = vl.fields().indexFromName('unique') 942 not_null_field_idx = vl.fields().indexFromName('not_null') 943 self.assertTrue(unique_field_idx > 0) 944 self.assertTrue(not_null_field_idx > 0) 945 # Not null 946 self.assertFalse(bool(vl.fieldConstraints(unique_field_idx) & QgsFieldConstraints.ConstraintNotNull)) 947 self.assertTrue(bool(vl.fieldConstraints(not_null_field_idx) & QgsFieldConstraints.ConstraintNotNull)) 948 # Unique 949 self.assertTrue(bool(vl.fieldConstraints(unique_field_idx) & QgsFieldConstraints.ConstraintUnique)) 950 self.assertFalse(bool(vl.fieldConstraints(not_null_field_idx) & QgsFieldConstraints.ConstraintUnique)) 951 952 def testChangeGeometries(self): 953 if not getattr(self, 'getEditableLayer', None): 954 return 955 956 l = self.getEditableLayer() 957 self.assertTrue(l.isValid()) 958 959 # find 2 features to change 960 features = [f for f in l.dataProvider().getFeatures()] 961 to_change = [f for f in features if f.attributes()[0] == 1] 962 to_change.extend([f for f in features if f.attributes()[0] == 3]) 963 # changes by feature id, for changeGeometryValues call 964 changes = {to_change[0].id(): QgsGeometry.fromWkt('Point (10 20)'), to_change[1].id(): QgsGeometry()} 965 # changes by pk, for testing after retrieving changed features 966 new_geom_map = {1: QgsGeometry.fromWkt('Point ( 10 20 )'), 3: QgsGeometry()} 967 968 if l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeGeometries: 969 # expect success 970 result = l.dataProvider().changeGeometryValues(changes) 971 self.assertTrue(result, 972 'Provider reported ChangeGeometries capability, but returned False to changeGeometryValues') 973 974 # check result 975 self.testGetFeatures(l.dataProvider(), changed_geometries=new_geom_map) 976 977 # change empty list, should return true for consistency 978 self.assertTrue(l.dataProvider().changeGeometryValues({})) 979 980 else: 981 # expect fail 982 self.assertFalse(l.dataProvider().changeGeometryValues(changes), 983 'Provider reported no ChangeGeometries capability, but returned true to changeGeometryValues') 984 985 def testChangeFeatures(self): 986 if not getattr(self, 'getEditableLayer', None): 987 return 988 989 l = self.getEditableLayer() 990 self.assertTrue(l.isValid()) 991 992 features = [f for f in l.dataProvider().getFeatures()] 993 994 # find 2 features to change attributes for 995 features = [f for f in l.dataProvider().getFeatures()] 996 # need to keep order here 997 to_change = [f for f in features if f.attributes()[0] == 1] 998 to_change.extend([f for f in features if f.attributes()[0] == 2]) 999 # changes by feature id, for changeAttributeValues call 1000 attribute_changes = {to_change[0].id(): {1: 501, 3: 'new string'}, to_change[1].id(): {1: 502, 4: 'NEW'}} 1001 # changes by pk, for testing after retrieving changed features 1002 new_attr_map = {1: {1: 501, 3: 'new string'}, 2: {1: 502, 4: 'NEW'}} 1003 1004 # find 2 features to change geometries for 1005 to_change = [f for f in features if f.attributes()[0] == 1] 1006 to_change.extend([f for f in features if f.attributes()[0] == 3]) 1007 # changes by feature id, for changeGeometryValues call 1008 geometry_changes = {to_change[0].id(): QgsGeometry.fromWkt('Point (10 20)'), to_change[1].id(): QgsGeometry()} 1009 # changes by pk, for testing after retrieving changed features 1010 new_geom_map = {1: QgsGeometry.fromWkt('Point ( 10 20 )'), 3: QgsGeometry()} 1011 1012 if l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeGeometries and l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeAttributeValues: 1013 # expect success 1014 result = l.dataProvider().changeFeatures(attribute_changes, geometry_changes) 1015 self.assertTrue(result, 1016 'Provider reported ChangeGeometries and ChangeAttributeValues capability, but returned False to changeFeatures') 1017 1018 # check result 1019 self.testGetFeatures(l.dataProvider(), changed_attributes=new_attr_map, changed_geometries=new_geom_map) 1020 1021 # change empty list, should return true for consistency 1022 self.assertTrue(l.dataProvider().changeFeatures({}, {})) 1023 1024 elif not l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeGeometries: 1025 # expect fail 1026 self.assertFalse(l.dataProvider().changeFeatures(attribute_changes, geometry_changes), 1027 'Provider reported no ChangeGeometries capability, but returned true to changeFeatures') 1028 elif not l.dataProvider().capabilities() & QgsVectorDataProvider.ChangeAttributeValues: 1029 # expect fail 1030 self.assertFalse(l.dataProvider().changeFeatures(attribute_changes, geometry_changes), 1031 'Provider reported no ChangeAttributeValues capability, but returned true to changeFeatures') 1032 1033 def testMinMaxAfterChanges(self): 1034 """ 1035 Tests retrieving field min and max value after making changes to the provider's features 1036 """ 1037 if not getattr(self, 'getEditableLayer', None): 1038 return 1039 1040 vl = self.getEditableLayer() 1041 self.assertTrue(vl.isValid()) 1042 1043 self.assertEqual(vl.dataProvider().minimumValue(0), 1) 1044 self.assertEqual(vl.dataProvider().minimumValue(1), -200) 1045 self.assertEqual(vl.dataProvider().maximumValue(0), 5) 1046 self.assertEqual(vl.dataProvider().maximumValue(1), 400) 1047 1048 # add feature 1049 f6 = QgsFeature() 1050 f6.setAttributes([15, 1400]) 1051 res, [f6] = vl.dataProvider().addFeatures([f6]) 1052 self.assertTrue(res) 1053 self.assertEqual(vl.dataProvider().minimumValue(0), 1) 1054 self.assertEqual(vl.dataProvider().minimumValue(1), -200) 1055 self.assertEqual(vl.dataProvider().maximumValue(0), 15) 1056 self.assertEqual(vl.dataProvider().maximumValue(1), 1400) 1057 f7 = QgsFeature() 1058 f7.setAttributes([0, -1400]) 1059 res, [f7] = vl.dataProvider().addFeatures([f7]) 1060 self.assertTrue(res) 1061 self.assertEqual(vl.dataProvider().minimumValue(0), 0) 1062 self.assertEqual(vl.dataProvider().minimumValue(1), -1400) 1063 self.assertEqual(vl.dataProvider().maximumValue(0), 15) 1064 self.assertEqual(vl.dataProvider().maximumValue(1), 1400) 1065 1066 # change attribute values 1067 self.assertTrue(vl.dataProvider().changeAttributeValues({f6.id(): {1: 150}, f7.id(): {1: -100}})) 1068 self.assertEqual(vl.dataProvider().minimumValue(1), -200) 1069 self.assertEqual(vl.dataProvider().maximumValue(1), 400) 1070 1071 # delete features 1072 f1 = [f for f in vl.getFeatures() if f[self.pk_name] == 5][0] 1073 f3 = [f for f in vl.getFeatures() if f[self.pk_name] == 3][0] 1074 self.assertTrue(vl.dataProvider().deleteFeatures([f6.id(), f7.id()])) 1075 self.assertEqual(vl.dataProvider().minimumValue(0), 1) 1076 self.assertEqual(vl.dataProvider().minimumValue(1), -200) 1077 self.assertEqual(vl.dataProvider().maximumValue(0), 5) 1078 self.assertEqual(vl.dataProvider().maximumValue(1), 400) 1079 1080 if vl.dataProvider().capabilities() & QgsVectorDataProvider.DeleteAttributes: 1081 # delete attributes 1082 if vl.dataProvider().deleteAttributes([0]): 1083 # may not be possible, e.g. if it's a primary key 1084 self.assertEqual(vl.dataProvider().minimumValue(0), -200) 1085 self.assertEqual(vl.dataProvider().maximumValue(0), 400) 1086 1087 def testStringComparison(self): 1088 """ 1089 Test if string comparisons with numbers are cast by the expression 1090 compiler (or work fine without doing anything :P) 1091 """ 1092 for expression in ( 1093 '5 LIKE \'5\'', 1094 '5 ILIKE \'5\'', 1095 '15 NOT LIKE \'5\'', 1096 '15 NOT ILIKE \'5\'', 1097 '5 ~ \'5\''): 1098 iterator = self.source.getFeatures(QgsFeatureRequest().setFilterExpression('5 LIKE \'5\'').setFlags(QgsFeatureRequest.IgnoreStaticNodesDuringExpressionCompilation)) 1099 count = len([f for f in iterator]) 1100 self.assertEqual(count, 5) 1101 self.assertFalse(iterator.compileFailed()) 1102 if self.enableCompiler(): 1103 iterator = self.source.getFeatures(QgsFeatureRequest().setFilterExpression('5 LIKE \'5\'').setFlags(QgsFeatureRequest.IgnoreStaticNodesDuringExpressionCompilation)) 1104 self.assertEqual(count, 5) 1105 self.assertFalse(iterator.compileFailed()) 1106 self.disableCompiler() 1107 1108 def testConcurrency(self): 1109 """ 1110 The connection pool has a maximum of 4 connections defined (+2 spare connections) 1111 Make sure that if we exhaust those 4 connections and force another connection 1112 it is actually using the spare connections and does not freeze. 1113 This situation normally happens when (at least) 4 rendering threads are active 1114 in parallel and one requires an expression to be evaluated. 1115 """ 1116 # Acquire the maximum amount of concurrent connections 1117 iterators = list() 1118 for i in range(QgsApplication.instance().maxConcurrentConnectionsPerPool()): 1119 iterators.append(self.vl.getFeatures()) 1120 1121 # Run an expression that will also do a request and should use a spare 1122 # connection. It just should not deadlock here. 1123 1124 feat = next(iterators[0]) 1125 context = QgsExpressionContext() 1126 context.setFeature(feat) 1127 exp = QgsExpression('get_feature(\'{layer}\', \'pk\', 5)'.format(layer=self.vl.id())) 1128 exp.evaluate(context) 1129 1130 def testEmptySubsetOfAttributesWithSubsetString(self): 1131 1132 if self.source.supportsSubsetString(): 1133 try: 1134 # Add a subset string 1135 subset = self.getSubsetString() 1136 self.source.setSubsetString(subset) 1137 1138 # First test, in a regular way 1139 features = [f for f in self.source.getFeatures()] 1140 count = len(features) 1141 self.assertEqual(count, 3) 1142 has_geometry = features[0].hasGeometry() 1143 1144 # Ask for no attributes 1145 request = QgsFeatureRequest().setSubsetOfAttributes([]) 1146 # Make sure we still retrieve features ! 1147 features = [f for f in self.source.getFeatures(request)] 1148 count = len(features) 1149 self.assertEqual(count, 3) 1150 # Check that we still get a geometry if we add one before 1151 self.assertEqual(features[0].hasGeometry(), has_geometry) 1152 1153 finally: 1154 self.source.setSubsetString(None) 1155 1156 def testGeneratedColumns(self): 1157 1158 if not getattr(self, 'getGeneratedColumnsData', None): 1159 return 1160 1161 vl, generated_value = self.getGeneratedColumnsData() 1162 if vl is None: 1163 return 1164 1165 self.assertTrue(vl.isValid()) 1166 self.assertEqual(vl.fields().count(), 2) 1167 1168 field = vl.fields().at(1) 1169 self.assertEqual(field.name(), "generated_field") 1170 self.assertEqual(field.type(), QVariant.String) 1171 self.assertEqual(vl.dataProvider().defaultValueClause(1), generated_value) 1172 1173 vl.startEditing() 1174 1175 feature = next(vl.getFeatures()) 1176 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature)) 1177 self.assertTrue(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature)) 1178 1179 # same test on a new inserted feature 1180 feature = QgsFeature(vl.fields()) 1181 feature.setAttribute(0, 2) 1182 vl.addFeature(feature) 1183 self.assertTrue(feature.id() < 0) 1184 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature)) 1185 self.assertTrue(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature)) 1186 vl.commitChanges() 1187 1188 feature = vl.getFeature(2) 1189 self.assertTrue(feature.isValid()) 1190 self.assertEqual(feature.attribute(1), "test:2") 1191 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature)) 1192 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature)) 1193 1194 # test update id and commit 1195 vl.startEditing() 1196 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature)) 1197 self.assertTrue(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature)) 1198 self.assertTrue(vl.changeAttributeValue(2, 0, 10)) 1199 self.assertTrue(vl.commitChanges()) 1200 feature = vl.getFeature(10) 1201 self.assertTrue(feature.isValid()) 1202 self.assertEqual(feature.attribute(0), 10) 1203 self.assertEqual(feature.attribute(1), "test:10") 1204 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature)) 1205 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature)) 1206 1207 # test update the_field and commit (the value is not changed because the field is generated) 1208 vl.startEditing() 1209 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature)) 1210 self.assertTrue(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature)) 1211 self.assertTrue(vl.changeAttributeValue(10, 1, "new value")) 1212 self.assertTrue(vl.commitChanges()) 1213 feature = vl.getFeature(10) 1214 self.assertTrue(feature.isValid()) 1215 self.assertEqual(feature.attribute(1), "test:10") 1216 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature)) 1217 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature)) 1218 1219 # Test insertion with default value evaluation on provider side to be sure 1220 # it doesn't fail generated columns 1221 vl.dataProvider().setProviderProperty(QgsDataProvider.EvaluateDefaultValues, True) 1222 1223 vl.startEditing() 1224 feature = QgsVectorLayerUtils.createFeature(vl, QgsGeometry(), {0: 8}) 1225 vl.addFeature(feature) 1226 self.assertTrue(feature.id() < 0) 1227 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature)) 1228 self.assertTrue(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature)) 1229 self.assertTrue(vl.commitChanges()) 1230 1231 feature = vl.getFeature(8) 1232 self.assertTrue(feature.isValid()) 1233 self.assertEqual(feature.attribute(1), "test:8") 1234 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 1, feature)) 1235 self.assertFalse(QgsVectorLayerUtils.fieldIsEditable(vl, 0, feature)) 1236 1237 # CLEANUP: delete features added during test (cleanup) 1238 vl.startEditing() 1239 self.assertTrue(vl.deleteFeature(10)) 1240 self.assertTrue(vl.commitChanges()) 1241 # TODO: further cleanups in case attributes have been changed 1242