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