mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-10 00:04:23 -04:00
985 lines
48 KiB
Python
985 lines
48 KiB
Python
"""QGIS Unit tests for the MS SQL provider.
|
|
|
|
.. note:: This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
"""
|
|
__author__ = 'Nyall Dawson'
|
|
__date__ = '2015-12-07'
|
|
__copyright__ = 'Copyright 2015, The QGIS Project'
|
|
|
|
import os
|
|
|
|
import qgis # NOQA
|
|
from qgis.PyQt.QtCore import QDate, QDateTime, QDir, QTime, QVariant
|
|
from qgis.core import (
|
|
NULL,
|
|
QgsCoordinateReferenceSystem,
|
|
QgsDataProvider,
|
|
QgsDataSourceUri,
|
|
QgsFeature,
|
|
QgsFeatureRequest,
|
|
QgsField,
|
|
QgsFieldConstraints,
|
|
QgsGeometry,
|
|
QgsPointXY,
|
|
QgsProviderRegistry,
|
|
QgsRectangle,
|
|
QgsSettings,
|
|
QgsVectorLayer,
|
|
QgsVectorLayerExporter,
|
|
QgsWkbTypes,
|
|
)
|
|
import unittest
|
|
from qgis.testing import start_app, QgisTestCase
|
|
|
|
from providertestbase import ProviderTestCase
|
|
from utilities import unitTestDataPath
|
|
|
|
start_app()
|
|
TEST_DATA_DIR = unitTestDataPath()
|
|
|
|
|
|
class TestPyQgsMssqlProvider(QgisTestCase, ProviderTestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
"""Run before all tests"""
|
|
super(TestPyQgsMssqlProvider, cls).setUpClass()
|
|
# These are the connection details for the SQL Server instance running on Travis
|
|
cls.dbconn = "service='testsqlserver' user=sa password='<YourStrong!Passw0rd>' "
|
|
if 'QGIS_MSSQLTEST_DB' in os.environ:
|
|
cls.dbconn = os.environ['QGIS_MSSQLTEST_DB']
|
|
# Create test layers
|
|
cls.vl = QgsVectorLayer(
|
|
cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POINT table="qgis_test"."someData" (geom) sql=',
|
|
'test', 'mssql')
|
|
assert cls.vl.dataProvider() is not None, f"No data provider for {cls.vl.source()}"
|
|
assert cls.vl.isValid(), cls.vl.dataProvider().error().message()
|
|
cls.source = cls.vl.dataProvider()
|
|
cls.poly_vl = QgsVectorLayer(
|
|
cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POLYGON table="qgis_test"."some_poly_data" (geom) sql=',
|
|
'test', 'mssql')
|
|
assert cls.poly_vl.isValid(), cls.poly_vl.dataProvider().error().message()
|
|
cls.poly_provider = cls.poly_vl.dataProvider()
|
|
|
|
# Triggers a segfault in the sql server odbc driver on Travis - TODO test with more recent Ubuntu base image
|
|
if os.environ.get('QGIS_CONTINUOUS_INTEGRATION_RUN', 'true'):
|
|
del cls.getEditableLayer
|
|
|
|
# Use connections API
|
|
md = QgsProviderRegistry.instance().providerMetadata('mssql')
|
|
cls.conn_api = md.createConnection(cls.dbconn, {})
|
|
|
|
def setUp(self):
|
|
for t in ['new_table', 'new_table_multipoint', 'new_table_multipolygon']:
|
|
self.execSQLCommand(f'DROP TABLE IF EXISTS qgis_test.[{t}]')
|
|
|
|
def execSQLCommand(self, sql):
|
|
self.assertTrue(self.conn_api)
|
|
self.conn_api.executeSql(sql)
|
|
|
|
def getSource(self):
|
|
# create temporary table for edit tests
|
|
self.execSQLCommand('DROP TABLE IF EXISTS qgis_test.edit_data')
|
|
self.execSQLCommand(
|
|
"""CREATE TABLE qgis_test.edit_data (pk INTEGER PRIMARY KEY,cnt integer, name nvarchar(max), name2 nvarchar(max), num_char nvarchar(max), dt datetime, [date] date, [time] time, geom geometry)""")
|
|
self.execSQLCommand("INSERT INTO [qgis_test].[edit_data] (pk, cnt, name, name2, num_char, dt, [date], [time], geom) VALUES "
|
|
"(5, -200, NULL, 'NuLl', '5', '2020-05-04T12:13:14', '2020-05-02', '12:13:01', geometry::STGeomFromText('POINT(-71.123 78.23)', 4326)),"
|
|
"(3, 300, 'Pear', 'PEaR', '3', NULL, NULL, NULL, NULL),"
|
|
"(1, 100, 'Orange', 'oranGe', '1', '2020-05-03T12:13:14', '2020-05-03', '12:13:14', geometry::STGeomFromText('POINT(-70.332 66.33)', 4326)),"
|
|
"(2, 200, 'Apple', 'Apple', '2', '2020-05-04T12:14:14', '2020-05-04', '12:14:14', geometry::STGeomFromText('POINT(-68.2 70.8)', 4326)),"
|
|
"(4, 400, 'Honey', 'Honey', '4', '2021-05-04T13:13:14', '2021-05-04', '13:13:14', geometry::STGeomFromText('POINT(-65.32 78.3)', 4326))")
|
|
|
|
vl = QgsVectorLayer(
|
|
self.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POINT table="qgis_test"."edit_data" (geom) sql=',
|
|
'test', 'mssql')
|
|
return vl
|
|
|
|
def testDeleteFeaturesPktInt(self):
|
|
vl = self.getSource()
|
|
dp = vl.dataProvider()
|
|
|
|
self.assertEqual(dp.featureCount(), 5)
|
|
|
|
self.assertTrue(dp.deleteFeatures([1, 3, 4]))
|
|
self.assertEqual(dp.featureCount(), 2)
|
|
|
|
self.assertFalse(dp.deleteFeatures([3]))
|
|
self.assertFalse(dp.deleteFeatures([10]))
|
|
self.assertFalse(dp.deleteFeatures([3, 10]))
|
|
|
|
self.assertTrue(dp.deleteFeatures([5]))
|
|
self.assertEqual(dp.featureCount(), 1)
|
|
|
|
self.assertTrue(dp.deleteFeatures([2]))
|
|
self.assertEqual(dp.featureCount(), 0)
|
|
|
|
def getEditableLayer(self):
|
|
return self.getSource()
|
|
|
|
def enableCompiler(self):
|
|
QgsSettings().setValue('/qgis/compileExpressions', True)
|
|
return True
|
|
|
|
def disableCompiler(self):
|
|
QgsSettings().setValue('/qgis/compileExpressions', False)
|
|
|
|
def partiallyCompiledFilters(self):
|
|
filters = {
|
|
'name ILIKE \'QGIS\'',
|
|
'name = \'Apple\'',
|
|
'\"NaMe\" = \'Apple\'',
|
|
'name = \'apple\'',
|
|
'name LIKE \'Apple\'',
|
|
'name LIKE \'aPple\'',
|
|
'name ILIKE \'aPple\'',
|
|
'name LIKE \'Ap_le\'',
|
|
'name LIKE \'Ap\\_le\'',
|
|
'name ILIKE \'%pp%\'',
|
|
'"name" || \' \' || "name" = \'Orange Orange\'',
|
|
'"name" || \' \' || "cnt" = \'Orange 100\'',
|
|
'"name"="name2"',
|
|
'lower(name) = \'apple\'',
|
|
'upper(name) = \'APPLE\'',
|
|
'name = trim(\' Apple \')'
|
|
}
|
|
return filters
|
|
|
|
def uncompiledFilters(self):
|
|
filters = {
|
|
'"name" IS NULL',
|
|
'"name" IS NOT NULL',
|
|
'"name" NOT LIKE \'Ap%\'',
|
|
'"name" NOT ILIKE \'QGIS\'',
|
|
'"name" NOT ILIKE \'pEAR\'',
|
|
'name <> \'Apple\'',
|
|
'"name" <> \'apple\'',
|
|
'(name = \'Apple\') is not null',
|
|
'\'x\' || "name" IS NOT NULL',
|
|
'\'x\' || "name" IS NULL',
|
|
'"name" ~ \'[OP]ra[gne]+\'',
|
|
'false and NULL',
|
|
'true and NULL',
|
|
'NULL and false',
|
|
'NULL and true',
|
|
'NULL and NULL',
|
|
'false or NULL',
|
|
'true or NULL',
|
|
'NULL or false',
|
|
'NULL or true',
|
|
'NULL or NULL',
|
|
'not null',
|
|
'not name = \'Apple\'',
|
|
'not name IS NULL',
|
|
'not name = \'Apple\' or name = \'Apple\'',
|
|
'not name = \'Apple\' or not name = \'Apple\'',
|
|
'not name = \'Apple\' and pk = 4',
|
|
'not name = \'Apple\' and not pk = 4',
|
|
'pk = coalesce(NULL,3,4)',
|
|
'x($geometry) < -70',
|
|
'y($geometry) > 70',
|
|
'xmin($geometry) < -70',
|
|
'ymin($geometry) > 70',
|
|
'xmax($geometry) < -70',
|
|
'ymax($geometry) > 70',
|
|
'disjoint($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))',
|
|
'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))',
|
|
'contains(geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'),$geometry)',
|
|
'distance($geometry,geom_from_wkt( \'Point (-70 70)\')) > 7',
|
|
'intersects($geometry,geom_from_gml( \'<gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-72.2,66.1 -65.2,66.1 -65.2,72.0 -72.2,72.0 -72.2,66.1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>\'))',
|
|
'x($geometry) < -70',
|
|
'y($geometry) > 79',
|
|
'xmin($geometry) < -70',
|
|
'ymin($geometry) < 76',
|
|
'xmax($geometry) > -68',
|
|
'ymax($geometry) > 80',
|
|
'area($geometry) > 10',
|
|
'perimeter($geometry) < 12',
|
|
'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\'',
|
|
'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****\')',
|
|
'crosses($geometry,geom_from_wkt( \'Linestring (-68.2 82.1, -66.95 82.1, -66.95 79.05)\'))',
|
|
'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))\'))',
|
|
'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))\'))',
|
|
'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))\'))',
|
|
'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))\'))',
|
|
'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))\'))',
|
|
'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))\'))',
|
|
'"dt" = to_datetime(\'000www14ww13ww12www4ww5ww2020\',\'zzzwwwsswwmmwwhhwwwdwwMwwyyyy\')',
|
|
'"date" = to_date(\'www4ww5ww2020\',\'wwwdwwMwwyyyy\')',
|
|
'"time" = to_time(\'000www14ww13ww12www\',\'zzzwwwsswwmmwwhhwww\')'
|
|
}
|
|
return filters
|
|
|
|
def testGetFeaturesUncompiled(self):
|
|
if os.environ.get('QGIS_CONTINUOUS_INTEGRATION_RUN', 'true'):
|
|
return
|
|
super().testGetFeaturesUncompiled()
|
|
|
|
def testGetFeaturesExp(self):
|
|
if os.environ.get('QGIS_CONTINUOUS_INTEGRATION_RUN', 'true'):
|
|
return
|
|
super().testGetFeaturesExp()
|
|
|
|
def testOrderBy(self):
|
|
if os.environ.get('QGIS_CONTINUOUS_INTEGRATION_RUN', 'true'):
|
|
return
|
|
super().testOrderBy()
|
|
|
|
def testOrderByCompiled(self):
|
|
if os.environ.get('QGIS_CONTINUOUS_INTEGRATION_RUN', 'true'):
|
|
return
|
|
super().testOrderByCompiled()
|
|
|
|
# HERE GO THE PROVIDER SPECIFIC TESTS
|
|
def testDateTimeTypes(self):
|
|
vl = QgsVectorLayer('%s table="qgis_test"."date_times" sql=' %
|
|
(self.dbconn), "testdatetimes", "mssql")
|
|
assert (vl.isValid())
|
|
|
|
fields = vl.dataProvider().fields()
|
|
self.assertEqual(fields.at(fields.indexFromName(
|
|
'date_field')).type(), QVariant.Date)
|
|
self.assertEqual(fields.at(fields.indexFromName(
|
|
'time_field')).type(), QVariant.Time)
|
|
self.assertEqual(fields.at(fields.indexFromName(
|
|
'datetime_field')).type(), QVariant.DateTime)
|
|
|
|
f = next(vl.getFeatures(QgsFeatureRequest()))
|
|
|
|
date_idx = vl.fields().lookupField('date_field')
|
|
self.assertIsInstance(f.attributes()[date_idx], QDate)
|
|
self.assertEqual(f.attributes()[date_idx], QDate(2004, 3, 4))
|
|
time_idx = vl.fields().lookupField('time_field')
|
|
self.assertIsInstance(f.attributes()[time_idx], QTime)
|
|
self.assertEqual(f.attributes()[time_idx], QTime(13, 41, 52))
|
|
datetime_idx = vl.fields().lookupField('datetime_field')
|
|
self.assertIsInstance(f.attributes()[datetime_idx], QDateTime)
|
|
self.assertEqual(f.attributes()[datetime_idx], QDateTime(
|
|
QDate(2004, 3, 4), QTime(13, 41, 52)))
|
|
|
|
def testFloatDecimalFields(self):
|
|
vl = QgsVectorLayer('%s table="qgis_test"."float_dec" sql=' %
|
|
(self.dbconn), "testprec", "mssql")
|
|
self.assertTrue(vl.isValid())
|
|
|
|
fields = vl.dataProvider().fields()
|
|
self.assertEqual(fields.at(fields.indexFromName(
|
|
'float_field')).type(), QVariant.Double)
|
|
self.assertEqual(fields.at(fields.indexFromName(
|
|
'float_field')).length(), 15)
|
|
self.assertEqual(fields.at(fields.indexFromName(
|
|
'float_field')).precision(), -1)
|
|
|
|
self.assertEqual(fields.at(fields.indexFromName(
|
|
'dec_field')).type(), QVariant.Double)
|
|
self.assertEqual(fields.at(fields.indexFromName(
|
|
'dec_field')).length(), 7)
|
|
self.assertEqual(fields.at(fields.indexFromName(
|
|
'dec_field')).precision(), 3)
|
|
|
|
f = next(vl.getFeatures(QgsFeatureRequest()))
|
|
|
|
float_idx = vl.fields().lookupField('float_field')
|
|
self.assertIsInstance(f.attributes()[float_idx], float)
|
|
self.assertAlmostEqual(f.attributes()[float_idx], 1.1111111111, 5)
|
|
dec_idx = vl.fields().lookupField('dec_field')
|
|
self.assertIsInstance(f.attributes()[dec_idx], float)
|
|
self.assertEqual(f.attributes()[dec_idx], 1.123)
|
|
|
|
@unittest.skipIf(os.environ.get('QGIS_CONTINUOUS_INTEGRATION_RUN', 'true'), 'Failing on Travis')
|
|
def testCreateLayer(self):
|
|
layer = QgsVectorLayer("Point?field=id:integer&field=fldtxt:string&field=fldint:integer",
|
|
"addfeat", "memory")
|
|
pr = layer.dataProvider()
|
|
f = QgsFeature()
|
|
f.setAttributes([1, "test", 1])
|
|
f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(1, 2)))
|
|
f2 = QgsFeature()
|
|
f2.setAttributes([2, "test2", 3])
|
|
f3 = QgsFeature()
|
|
f3.setAttributes([3, "test2", NULL])
|
|
f3.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(3, 2)))
|
|
f4 = QgsFeature()
|
|
f4.setAttributes([4, NULL, 3])
|
|
f4.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(4, 3)))
|
|
pr.addFeatures([f, f2, f3, f4])
|
|
|
|
uri = f'{self.dbconn} table="qgis_test"."new_table" sql='
|
|
error, message = QgsVectorLayerExporter.exportLayer(layer, uri, 'mssql',
|
|
QgsCoordinateReferenceSystem('EPSG:4326'))
|
|
self.assertEqual(error, QgsVectorLayerExporter.NoError)
|
|
|
|
new_layer = QgsVectorLayer(uri, 'new', 'mssql')
|
|
self.assertTrue(new_layer.isValid())
|
|
self.assertEqual(new_layer.wkbType(), QgsWkbTypes.Point)
|
|
self.assertEqual([f.name() for f in new_layer.fields()], ['qgs_fid', 'id', 'fldtxt', 'fldint'])
|
|
|
|
features = [f.attributes() for f in new_layer.getFeatures()]
|
|
self.assertEqual(features, [[1, 1, 'test', 1],
|
|
[2, 2, 'test2', 3],
|
|
[3, 3, 'test2', NULL],
|
|
[4, 4, NULL, 3]])
|
|
geom = [f.geometry().asWkt() for f in new_layer.getFeatures()]
|
|
self.assertEqual(geom, ['Point (1 2)', '', 'Point (3 2)', 'Point (4 3)'])
|
|
|
|
@unittest.skipIf(os.environ.get('QGIS_CONTINUOUS_INTEGRATION_RUN', 'true'), 'Failing on Travis')
|
|
def testCreateLayerMultiPoint(self):
|
|
layer = QgsVectorLayer("MultiPoint?crs=epsg:3111&field=id:integer&field=fldtxt:string&field=fldint:integer",
|
|
"addfeat", "memory")
|
|
pr = layer.dataProvider()
|
|
f = QgsFeature()
|
|
f.setAttributes([1, "test", 1])
|
|
f.setGeometry(QgsGeometry.fromWkt('MultiPoint(1 2, 3 4)'))
|
|
f2 = QgsFeature()
|
|
f2.setAttributes([2, "test2", 3])
|
|
f3 = QgsFeature()
|
|
f3.setAttributes([3, "test2", NULL])
|
|
f3.setGeometry(QgsGeometry.fromWkt('MultiPoint(7 8)'))
|
|
pr.addFeatures([f, f2, f3])
|
|
|
|
uri = f'{self.dbconn} table="qgis_test"."new_table_multipoint" sql='
|
|
error, message = QgsVectorLayerExporter.exportLayer(layer, uri, 'mssql',
|
|
QgsCoordinateReferenceSystem('EPSG:3111'))
|
|
self.assertEqual(error, QgsVectorLayerExporter.NoError)
|
|
|
|
new_layer = QgsVectorLayer(uri, 'new', 'mssql')
|
|
self.assertTrue(new_layer.isValid())
|
|
self.assertEqual(new_layer.wkbType(), QgsWkbTypes.MultiPoint)
|
|
self.assertEqual(new_layer.crs().authid(), 'EPSG:3111')
|
|
self.assertEqual([f.name() for f in new_layer.fields()], ['qgs_fid', 'id', 'fldtxt', 'fldint'])
|
|
|
|
features = [f.attributes() for f in new_layer.getFeatures()]
|
|
self.assertEqual(features, [[1, 1, 'test', 1],
|
|
[2, 2, 'test2', 3],
|
|
[3, 3, 'test2', NULL]])
|
|
geom = [f.geometry().asWkt() for f in new_layer.getFeatures()]
|
|
self.assertEqual(geom, ['MultiPoint ((1 2),(3 4))', '', 'MultiPoint ((7 8))'])
|
|
|
|
@unittest.skipIf(os.environ.get('QGIS_CONTINUOUS_INTEGRATION_RUN', 'true'), 'Failing on Travis')
|
|
def testCurveGeometries(self):
|
|
geomtypes = ['CompoundCurveM', 'CurvePolygonM', 'CircularStringM', 'CompoundCurveZM', 'CurvePolygonZM',
|
|
'CircularStringZM', 'CompoundCurveZ', 'CurvePolygonZ', 'CircularStringZ', 'CompoundCurve',
|
|
'CurvePolygon', 'CircularString']
|
|
geoms = [
|
|
'CompoundCurveM ((0 -23.43778 10, 0 23.43778 10),CircularStringM (0 23.43778 10, -45 33.43778 10, -90 23.43778 10),(-90 23.43778 10, -90 -23.43778 10),CircularStringM (-90 -23.43778 10, -45 -23.43778 10, 0 -23.43778 10))',
|
|
'CurvePolygonM (CompoundCurveM ((0 -23.43778 10, 0 -15.43778 10, 0 23.43778 10),CircularStringM (0 23.43778 10, -45 100 10, -90 23.43778 10),(-90 23.43778 10, -90 -23.43778 10),CircularStringM (-90 -23.43778 10, -45 -16.43778 10, 0 -23.43778 10)),CompoundCurveM (CircularStringM (-30 0 10, -48 -12 10, -60 0 10, -48 -6 10, -30 0 10)))',
|
|
'CircularStringM (0 0 10, 0.14644660940672 0.35355339059327 10, 0.5 0.5 10, 0.85355339059327 0.35355339059327 10, 1 0 10, 0.85355339059327 -0.35355339059327 10, 0.5 -0.5 10, 0.14644660940672 -0.35355339059327 10, 0 0 10)',
|
|
'CompoundCurveZM ((0 -23.43778 2 10, 0 23.43778 2 10),CircularStringZM (0 23.43778 2 10, -45 33.43778 2 10, -90 23.43778 2 10),(-90 23.43778 2 10, -90 -23.43778 2 10),CircularStringZM (-90 -23.43778 2 10, -45 -23.43778 2 10, 0 -23.43778 2 10))',
|
|
'CurvePolygonZM (CompoundCurveZM ((0 -23.43778 5 10, 0 -15.43778 8 10, 0 23.43778 6 10),CircularStringZM (0 23.43778 6 10, -45 100 6 10, -90 23.43778 6 10),(-90 23.43778 6 10, -90 -23.43778 5 10),CircularStringZM (-90 -23.43778 5 10, -45 -16.43778 5 10, 0 -23.43778 5 10)),CompoundCurveZM (CircularStringZM (-30 0 10 10, -48 -12 10 10, -60 0 10 10, -48 -6 10 10, -30 0 10 10)))',
|
|
'CircularStringZM (0 0 1 10, 0.14644660940672 0.35355339059327 1 10, 0.5 0.5 1 10, 0.85355339059327 0.35355339059327 1 10, 1 0 1 10, 0.85355339059327 -0.35355339059327 1 10, 0.5 -0.5 1 10, 0.14644660940672 -0.35355339059327 1 10, 0 0 1 10)',
|
|
'CompoundCurveZ ((0 -23.43778 2, 0 23.43778 2),CircularStringZ (0 23.43778 2, -45 33.43778 2, -90 23.43778 2),(-90 23.43778 2, -90 -23.43778 2),CircularStringZ (-90 -23.43778 2, -45 -23.43778 2, 0 -23.43778 2))',
|
|
'CurvePolygonZ (CompoundCurveZ ((0 -23.43778 5, 0 -15.43778 8, 0 23.43778 6),CircularStringZ (0 23.43778 6, -45 100 6, -90 23.43778 6),(-90 23.43778 6, -90 -23.43778 5),CircularStringZ (-90 -23.43778 5, -45 -16.43778 5, 0 -23.43778 5)),CompoundCurveZ (CircularStringZ (-30 0 10, -48 -12 10, -60 0 10, -48 -6 10, -30 0 10)))',
|
|
'CircularStringZ (0 0 1, 0.14644660940672 0.35355339059327 1, 0.5 0.5 1, 0.85355339059327 0.35355339059327 1, 1 0 1, 0.85355339059327 -0.35355339059327 1, 0.5 -0.5 1, 0.14644660940672 -0.35355339059327 1, 0 0 1)',
|
|
'CompoundCurve ((0 -23.43778, 0 23.43778),CircularString (0 23.43778, -45 33.43778, -90 23.43778),(-90 23.43778, -90 -23.43778),CircularString (-90 -23.43778, -45 -23.43778, 0 -23.43778))',
|
|
'CurvePolygon (CompoundCurve ((0 -23.43778, 0 -15.43778, 0 23.43778),CircularString (0 23.43778, -45 100, -90 23.43778),(-90 23.43778, -90 -23.43778),CircularString (-90 -23.43778, -45 -16.43778, 0 -23.43778)),CompoundCurve (CircularString (-30 0, -48 -12, -60 0, -48 -6, -30 0)))',
|
|
'CircularString (0 0, 0.14644660940672 0.35355339059327, 0.5 0.5, 0.85355339059327 0.35355339059327, 1 0, 0.85355339059327 -0.35355339059327, 0.5 -0.5, 0.14644660940672 -0.35355339059327, 0 0)']
|
|
for idx, t in enumerate(geoms):
|
|
f = QgsFeature()
|
|
g = QgsGeometry.fromWkt(t)
|
|
f.setGeometry(g)
|
|
layer = QgsVectorLayer(geomtypes[idx] + "?crs=epsg:4326", "addfeat", "memory")
|
|
pr = layer.dataProvider()
|
|
pr.addFeatures([f])
|
|
uri = self.dbconn + ' table="qgis_test"."new_table_curvegeom_' + str(idx) + '" sql='
|
|
error, message = QgsVectorLayerExporter.exportLayer(layer, uri, 'mssql',
|
|
QgsCoordinateReferenceSystem('EPSG:4326'))
|
|
self.assertEqual(error, QgsVectorLayerExporter.NoError)
|
|
new_layer = QgsVectorLayer(uri, 'new', 'mssql')
|
|
self.assertTrue(new_layer.isValid())
|
|
self.assertEqual(new_layer.wkbType(), g.wkbType())
|
|
self.assertEqual(new_layer.crs().authid(), 'EPSG:4326')
|
|
result_geoms = [f.geometry().asWkt(14) for f in new_layer.getFeatures()]
|
|
self.assertEqual(result_geoms, [t])
|
|
self.execSQLCommand(f'DROP TABLE IF EXISTS [qgis_test].[new_table_curvegeom_{str(idx)}]')
|
|
|
|
def testStyle(self):
|
|
self.execSQLCommand('DROP TABLE IF EXISTS layer_styles')
|
|
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', 'not valid', '')
|
|
self.assertFalse(res)
|
|
self.assertTrue(err)
|
|
|
|
vl = self.getSource()
|
|
self.assertTrue(vl.isValid())
|
|
self.assertTrue(
|
|
vl.dataProvider().isSaveAndLoadStyleToDatabaseSupported())
|
|
|
|
# table layer_styles does not exist
|
|
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), '')
|
|
self.assertFalse(res)
|
|
self.assertFalse(err)
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), 'a style')
|
|
self.assertFalse(res)
|
|
self.assertFalse(err)
|
|
|
|
related_count, idlist, namelist, desclist, errmsg = vl.listStylesInDatabase()
|
|
self.assertEqual(related_count, -1)
|
|
self.assertEqual(idlist, [])
|
|
self.assertEqual(namelist, [])
|
|
self.assertEqual(desclist, [])
|
|
self.assertFalse(errmsg)
|
|
|
|
qml, errmsg = vl.getStyleFromDatabase("1")
|
|
self.assertFalse(qml)
|
|
self.assertTrue(errmsg)
|
|
|
|
mFilePath = QDir.toNativeSeparators(
|
|
f"{unitTestDataPath()}/symbol_layer/singleSymbol.qml")
|
|
status = vl.loadNamedStyle(mFilePath)
|
|
self.assertTrue(status)
|
|
|
|
# The style is saved as non-default
|
|
errorMsg = vl.saveStyleToDatabase(
|
|
"by day", "faded greens and elegant patterns", False, "")
|
|
self.assertFalse(errorMsg)
|
|
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), '')
|
|
self.assertFalse(res)
|
|
self.assertFalse(err)
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), 'a style')
|
|
self.assertFalse(res)
|
|
self.assertFalse(err)
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), 'by day')
|
|
self.assertTrue(res)
|
|
self.assertFalse(err)
|
|
|
|
# the style id should be "1", not "by day"
|
|
qml, errmsg = vl.getStyleFromDatabase("by day")
|
|
self.assertFalse(qml)
|
|
self.assertTrue(errmsg)
|
|
|
|
related_count, idlist, namelist, desclist, errmsg = vl.listStylesInDatabase()
|
|
self.assertEqual(related_count, 1)
|
|
self.assertFalse(errmsg)
|
|
self.assertEqual(idlist, ["1"])
|
|
self.assertEqual(namelist, ["by day"])
|
|
self.assertEqual(desclist, ["faded greens and elegant patterns"])
|
|
|
|
qml, errmsg = vl.getStyleFromDatabase("100")
|
|
self.assertFalse(qml)
|
|
self.assertTrue(errmsg)
|
|
|
|
qml, errmsg = vl.getStyleFromDatabase("1")
|
|
self.assertTrue(qml.startswith('<!DOCTYPE qgis'), qml)
|
|
self.assertFalse(errmsg)
|
|
|
|
# We save now the style again twice but with one as default
|
|
errorMsg = vl.saveStyleToDatabase(
|
|
"related style", "faded greens and elegant patterns", False, "")
|
|
self.assertFalse(errorMsg)
|
|
errorMsg = vl.saveStyleToDatabase(
|
|
"default style", "faded greens and elegant patterns", True, "")
|
|
self.assertFalse(errorMsg)
|
|
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), '')
|
|
self.assertFalse(res)
|
|
self.assertFalse(err)
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), 'a style')
|
|
self.assertFalse(res)
|
|
self.assertFalse(err)
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), 'default style')
|
|
self.assertTrue(res)
|
|
self.assertFalse(err)
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), 'related style')
|
|
self.assertTrue(res)
|
|
self.assertFalse(err)
|
|
res, err = QgsProviderRegistry.instance().styleExists('mssql', vl.source(), 'by day')
|
|
self.assertTrue(res)
|
|
self.assertFalse(err)
|
|
|
|
related_count, idlist, namelist, desclist, errmsg = vl.listStylesInDatabase()
|
|
self.assertEqual(related_count, 3)
|
|
self.assertFalse(errmsg)
|
|
self.assertCountEqual(idlist, ["1", "2", "3"])
|
|
self.assertCountEqual(namelist, ["default style", "related style", "by day"])
|
|
self.assertCountEqual(desclist, ["faded greens and elegant patterns"] * 3)
|
|
|
|
def testInsertPolygonInMultiPolygon(self):
|
|
layer = QgsVectorLayer("MultiPolygon?crs=epsg:4326&field=id:integer", "addfeat", "memory")
|
|
pr = layer.dataProvider()
|
|
f = QgsFeature()
|
|
f.setAttributes([1])
|
|
f.setGeometry(QgsGeometry.fromWkt('MultiPolygon(((0 0, 1 0, 1 1, 0 1, 0 0)),((10 0, 11 0, 11 1, 10 1, 10 0)))'))
|
|
pr.addFeatures([f])
|
|
|
|
uri = f'{self.dbconn} table="qgis_test"."new_table_multipolygon" sql='
|
|
error, message = QgsVectorLayerExporter.exportLayer(layer, uri, 'mssql',
|
|
QgsCoordinateReferenceSystem('EPSG:4326'))
|
|
self.assertEqual(error, QgsVectorLayerExporter.NoError)
|
|
|
|
new_layer = QgsVectorLayer(uri, 'new', 'mssql')
|
|
self.assertTrue(new_layer.isValid())
|
|
self.assertEqual(new_layer.wkbType(), QgsWkbTypes.MultiPolygon)
|
|
geom = [f.geometry().asWkt() for f in new_layer.getFeatures()]
|
|
self.assertEqual(geom, ['MultiPolygon (((0 0, 1 0, 1 1, 0 1, 0 0)),((10 0, 11 0, 11 1, 10 1, 10 0)))'])
|
|
|
|
# add single part
|
|
f2 = QgsFeature()
|
|
f2.setAttributes([2])
|
|
f2.setGeometry(QgsGeometry.fromWkt('Polygon((30 0, 31 0, 31 1, 30 1, 30 0))'))
|
|
self.assertTrue(new_layer.dataProvider().addFeatures([f2]))
|
|
|
|
# should become multipart
|
|
geom = [f.geometry().asWkt() for f in new_layer.getFeatures()]
|
|
self.assertEqual(geom, ['MultiPolygon (((0 0, 1 0, 1 1, 0 1, 0 0)),((10 0, 11 0, 11 1, 10 1, 10 0)))',
|
|
'MultiPolygon (((30 0, 31 0, 31 1, 30 1, 30 0)))'])
|
|
|
|
def testOverwriteExisting(self):
|
|
layer = QgsVectorLayer("NoGeometry?field=pk:integer", "addfeat", "memory")
|
|
pr = layer.dataProvider()
|
|
f = QgsFeature()
|
|
f.setAttributes([133])
|
|
pr.addFeatures([f])
|
|
|
|
uri = f'{self.dbconn} table="qgis_test"."sacrificialLamb" sql='
|
|
new_layer = QgsVectorLayer(uri, 'new', 'mssql')
|
|
self.assertTrue(new_layer.isValid())
|
|
self.assertEqual([f.attributes() for f in new_layer.getFeatures()], [[1]])
|
|
|
|
# try to overwrite
|
|
error, message = QgsVectorLayerExporter.exportLayer(layer, uri, 'mssql', QgsCoordinateReferenceSystem())
|
|
self.assertEqual(error, QgsVectorLayerExporter.ErrCreateLayer)
|
|
|
|
# should not have overwritten features
|
|
self.assertEqual([f.attributes() for f in new_layer.getFeatures()], [[1]])
|
|
|
|
def testMultiGeomColumns(self):
|
|
uri = f'{self.dbconn} table="qgis_test"."multiGeomColumns" (geom1) sql='
|
|
new_layer = QgsVectorLayer(uri, 'new', 'mssql')
|
|
self.assertTrue(new_layer.isValid())
|
|
|
|
geom = {f[0]: f.geometry().asWkt() for f in new_layer.getFeatures()}
|
|
self.assertEqual(geom, {1: 'Point (2 3)', 2: 'Point (3 4)', 3: '', 4: 'Point (5 6)', 5: 'Point (1 2)'})
|
|
|
|
uri = f'{self.dbconn} table="qgis_test"."multiGeomColumns" (geom2) sql='
|
|
new_layer2 = QgsVectorLayer(uri, 'new', 'mssql')
|
|
self.assertTrue(new_layer2.isValid())
|
|
|
|
geom = {f[0]: f.geometry().asWkt() for f in new_layer2.getFeatures()}
|
|
self.assertEqual(geom, {1: 'LineString (2 3, 4 5)', 2: 'LineString (3 4, 5 6)', 3: 'LineString (1 2, 3 4)',
|
|
4: 'LineString (5 6, 7 8)', 5: ''})
|
|
|
|
def testInvalidGeometries(self):
|
|
""" Test what happens when SQL Server is a POS and throws an exception on encountering an invalid geometry """
|
|
vl = QgsVectorLayer('%s srid=4167 type=POLYGON table="qgis_test"."invalid_polys" (ogr_geometry) sql=' %
|
|
(self.dbconn), "testinvalid", "mssql")
|
|
self.assertTrue(vl.isValid())
|
|
|
|
self.assertEqual(vl.dataProvider().extent().toString(1),
|
|
QgsRectangle(173.953, -41.513, 173.967, -41.502).toString(1))
|
|
|
|
# burn through features - don't want SQL server to trip up on the invalid ones
|
|
count = 0
|
|
for f in vl.dataProvider().getFeatures():
|
|
count += 1
|
|
self.assertEqual(count, 39)
|
|
|
|
count = 0
|
|
|
|
for f in vl.dataProvider().getFeatures(QgsFeatureRequest(QgsRectangle(173, -42, 174, -41))):
|
|
count += 1
|
|
# two invalid geometry features
|
|
self.assertEqual(count, 37)
|
|
# sorry... you get NO chance to see these features exist and repair them... because SQL server. Use PostGIS instead and live a happier life!
|
|
|
|
# with estimated metadata
|
|
vl = QgsVectorLayer(
|
|
'%s srid=4167 type=POLYGON estimatedmetadata=true table="qgis_test"."invalid_polys" (ogr_geometry) sql=' %
|
|
(self.dbconn), "testinvalid", "mssql")
|
|
self.assertTrue(vl.isValid())
|
|
self.assertEqual(vl.dataProvider().extent().toString(1),
|
|
QgsRectangle(173.954, -41.513, 173.967, -41.502).toString(1))
|
|
|
|
# Now, play on the edge! Let's disable invalid geometry handling and watch things crash and burn
|
|
vl = QgsVectorLayer(
|
|
'%s srid=4167 type=POLYGON table="qgis_test"."invalid_polys" (ogr_geometry) disableInvalidGeometryHandling="1" sql=' %
|
|
(self.dbconn), "testinvalid", "mssql")
|
|
self.assertTrue(vl.isValid())
|
|
|
|
self.assertEqual(vl.dataProvider().extent().toString(1), 'Empty') # HAHA - you asked for it
|
|
# burn through features - don't expect anything wrong here yet
|
|
count = 0
|
|
for f in vl.dataProvider().getFeatures():
|
|
count += 1
|
|
self.assertEqual(count, 39)
|
|
count = 0
|
|
|
|
for f in vl.dataProvider().getFeatures(QgsFeatureRequest(QgsRectangle(173, -42, 174, -41))):
|
|
count += 1
|
|
# now you only get 1 feature *sad trumpet*
|
|
self.assertEqual(count, 1)
|
|
count = 0
|
|
|
|
# same, with estimated metadata
|
|
vl = QgsVectorLayer(
|
|
'%s srid=4167 type=POLYGON estimatedmetadata=true table="qgis_test"."invalid_polys" (ogr_geometry) disableInvalidGeometryHandling="1" sql=' %
|
|
(self.dbconn), "testinvalid", "mssql")
|
|
self.assertTrue(vl.isValid())
|
|
self.assertEqual(vl.dataProvider().extent().toString(1), 'Empty')
|
|
|
|
def testEvaluateDefaultValueClause(self):
|
|
|
|
vl = QgsVectorLayer(
|
|
'%s table="qgis_test"."someData" sql=' %
|
|
(self.dbconn), "testdatetimes", "mssql")
|
|
|
|
# Activate EvaluateDefaultValues
|
|
vl.dataProvider().setProviderProperty(QgsDataProvider.EvaluateDefaultValues, True)
|
|
|
|
name_index = vl.fields().lookupField('name')
|
|
defaultValue = vl.dataProvider().defaultValue(name_index)
|
|
self.assertEqual(defaultValue, 'qgis')
|
|
|
|
def testPktComposite(self):
|
|
"""
|
|
Check that tables with PKs composed of many fields of different types are correctly read and written to
|
|
"""
|
|
vl = QgsVectorLayer(f'{self.dbconn} type=POINT estimatedmetadata=true key=\'"pk1","pk2"\' table="qgis_test"."tb_test_compound_pk" (geom)', "test_compound", "mssql")
|
|
self.assertTrue(vl.isValid())
|
|
|
|
fields = vl.fields()
|
|
|
|
f = next(vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk1 = 1 AND pk2 = 2')))
|
|
# first of all: we must be able to fetch a valid feature
|
|
self.assertTrue(f.isValid())
|
|
self.assertEqual(f['pk1'], 1)
|
|
self.assertEqual(f['pk2'], 2)
|
|
self.assertEqual(f['value'], 'test 2')
|
|
|
|
# can we edit a field?
|
|
vl.startEditing()
|
|
vl.changeAttributeValue(f.id(), fields.indexOf('value'), 'Edited Test 2')
|
|
self.assertTrue(vl.commitChanges())
|
|
|
|
# Did we get it right? Let's create a new QgsVectorLayer and try to read back our changes:
|
|
vl2 = QgsVectorLayer(f'{self.dbconn} type=POINT estimatedmetadata=true table="qgis_test"."tb_test_compound_pk" (geom) key=\'"pk1","pk2"\' ', "test_compound2", "mssql")
|
|
self.assertTrue(vl2.isValid())
|
|
f2 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk1 = 1 AND pk2 = 2')))
|
|
self.assertTrue(f2.isValid())
|
|
|
|
# Then, making sure we really did change our value.
|
|
self.assertEqual(f2['value'], 'Edited Test 2')
|
|
|
|
# How about inserting a new field?
|
|
f3 = QgsFeature(vl2.fields())
|
|
f3['pk1'] = 4
|
|
f3['pk2'] = -9223372036854775800
|
|
f3['value'] = 'other test'
|
|
vl.startEditing()
|
|
res, f3 = vl.dataProvider().addFeatures([f3])
|
|
self.assertTrue(res)
|
|
self.assertTrue(vl.commitChanges())
|
|
|
|
# can we catch it on another layer?
|
|
f4 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk2 = -9223372036854775800')))
|
|
|
|
self.assertTrue(f4.isValid())
|
|
expected_attrs = [4, -9223372036854775800, 'other test']
|
|
self.assertEqual(f4.attributes(), expected_attrs)
|
|
|
|
# Finally, let's delete one of the features.
|
|
f5 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk1 = 2 AND pk2 = 1')))
|
|
vl2.startEditing()
|
|
vl2.deleteFeatures([f5.id()])
|
|
self.assertTrue(vl2.commitChanges())
|
|
|
|
# did we really delete? Let's try to get the deleted feature from the first layer.
|
|
f_iterator = vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk1 = 2 AND pk2 = 1'))
|
|
got_feature = True
|
|
|
|
try:
|
|
f6 = next(f_iterator)
|
|
got_feature = f6.isValid()
|
|
except StopIteration:
|
|
got_feature = False
|
|
|
|
self.assertFalse(got_feature)
|
|
|
|
def testPktCompositeFloat(self):
|
|
"""
|
|
Check that tables with PKs composed of many fields of different types are correctly read and written to
|
|
"""
|
|
vl = QgsVectorLayer(f'{self.dbconn} type=POINT key=\'"pk1","pk2","pk3"\' table="qgis_test"."tb_test_composite_float_pk" (geom)', "test_composite_float", "mssql")
|
|
self.assertTrue(vl.isValid())
|
|
|
|
fields = vl.fields()
|
|
|
|
f = next(vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk3 = 3.14159274')))
|
|
# first of all: we must be able to fetch a valid feature
|
|
self.assertTrue(f.isValid())
|
|
self.assertEqual(f['pk1'], 1)
|
|
self.assertEqual(f['pk2'], 2)
|
|
|
|
self.assertEqual(round(f['pk3'], 6), round(3.14159274, 6))
|
|
self.assertEqual(f['value'], 'test 2')
|
|
|
|
# can we edit a field?
|
|
vl.startEditing()
|
|
vl.changeAttributeValue(f.id(), fields.indexOf('value'), 'Edited Test 2')
|
|
self.assertTrue(vl.commitChanges())
|
|
|
|
# Did we get it right? Let's create a new QgsVectorLayer and try to read back our changes:
|
|
vl2 = QgsVectorLayer(f'{self.dbconn} sslmode=disable srid=4326 key=\'"pk1","pk2","pk3"\' table="qgis_test"."tb_test_composite_float_pk" (geom)', "test_composite_float2", "mssql")
|
|
self.assertTrue(vl2.isValid())
|
|
f2 = next(vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk3 = 3.14159274')))
|
|
self.assertTrue(f2.isValid())
|
|
|
|
# just making sure we have the correct feature
|
|
# Only 6 decimals for PostgreSQL 11.
|
|
self.assertEqual(round(f2['pk3'], 6), round(3.14159274, 6))
|
|
|
|
# Then, making sure we really did change our value.
|
|
self.assertEqual(f2['value'], 'Edited Test 2')
|
|
|
|
# How about inserting a new field?
|
|
f3 = QgsFeature(vl2.fields())
|
|
f3['pk1'] = 4
|
|
f3['pk2'] = -9223372036854775800
|
|
f3['pk3'] = 7.29154
|
|
f3['value'] = 'other test'
|
|
vl.startEditing()
|
|
res, f3 = vl.dataProvider().addFeatures([f3])
|
|
self.assertTrue(res)
|
|
self.assertTrue(vl.commitChanges())
|
|
|
|
# can we catch it on another layer?
|
|
f4 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk2 = -9223372036854775800')))
|
|
|
|
self.assertTrue(f4.isValid())
|
|
expected_attrs = [4, -9223372036854775800, 7.29154, 'other test']
|
|
gotten_attrs = [f4['pk1'], f4['pk2'], round(f4['pk3'], 5), f4['value']]
|
|
self.assertEqual(gotten_attrs, expected_attrs)
|
|
|
|
# Finally, let's delete one of the features.
|
|
f5 = next(vl2.getFeatures(QgsFeatureRequest().setFilterExpression('pk3 = 7.29154')))
|
|
vl2.startEditing()
|
|
vl2.deleteFeatures([f5.id()])
|
|
self.assertTrue(vl2.commitChanges())
|
|
|
|
# did we really delete?
|
|
f_iterator = vl.getFeatures(QgsFeatureRequest().setFilterExpression('pk3 = 7.29154'))
|
|
got_feature = True
|
|
|
|
try:
|
|
f6 = next(f_iterator)
|
|
got_feature = f6.isValid()
|
|
except StopIteration:
|
|
got_feature = False
|
|
|
|
self.assertFalse(got_feature)
|
|
|
|
def testNotNullConstraint(self):
|
|
vl = QgsVectorLayer('%s table="qgis_test"."constraints" sql=' %
|
|
(self.dbconn), "testdatetimes", "mssql")
|
|
self.assertTrue(vl.isValid())
|
|
self.assertEqual(len(vl.fields()), 4)
|
|
|
|
# test some bad field indexes
|
|
self.assertEqual(vl.dataProvider().fieldConstraints(-1),
|
|
QgsFieldConstraints.Constraints())
|
|
self.assertEqual(vl.dataProvider().fieldConstraints(
|
|
1001), QgsFieldConstraints.Constraints())
|
|
|
|
self.assertTrue(vl.dataProvider().fieldConstraints(0) &
|
|
QgsFieldConstraints.ConstraintNotNull)
|
|
self.assertFalse(vl.dataProvider().fieldConstraints(1)
|
|
& QgsFieldConstraints.ConstraintNotNull)
|
|
self.assertTrue(vl.dataProvider().fieldConstraints(2) &
|
|
QgsFieldConstraints.ConstraintNotNull)
|
|
self.assertFalse(vl.dataProvider().fieldConstraints(3)
|
|
& QgsFieldConstraints.ConstraintNotNull)
|
|
|
|
# test that constraints have been saved to fields correctly
|
|
fields = vl.fields()
|
|
self.assertTrue(fields.at(0).constraints().constraints()
|
|
& QgsFieldConstraints.ConstraintNotNull)
|
|
self.assertEqual(fields.at(0).constraints().constraintOrigin(QgsFieldConstraints.ConstraintNotNull),
|
|
QgsFieldConstraints.ConstraintOriginProvider)
|
|
self.assertFalse(fields.at(1).constraints().constraints()
|
|
& QgsFieldConstraints.ConstraintNotNull)
|
|
self.assertTrue(fields.at(2).constraints().constraints()
|
|
& QgsFieldConstraints.ConstraintNotNull)
|
|
self.assertEqual(fields.at(2).constraints().constraintOrigin(QgsFieldConstraints.ConstraintNotNull),
|
|
QgsFieldConstraints.ConstraintOriginProvider)
|
|
self.assertFalse(fields.at(3).constraints().constraints()
|
|
& QgsFieldConstraints.ConstraintNotNull)
|
|
|
|
def testUniqueConstraint(self):
|
|
vl = QgsVectorLayer('%s table="qgis_test"."constraints" sql=' %
|
|
(self.dbconn), "testdatetimes", "mssql")
|
|
self.assertTrue(vl.isValid())
|
|
self.assertEqual(len(vl.fields()), 4)
|
|
|
|
# test some bad field indexes
|
|
self.assertEqual(vl.dataProvider().fieldConstraints(-1),
|
|
QgsFieldConstraints.Constraints())
|
|
self.assertEqual(vl.dataProvider().fieldConstraints(
|
|
1001), QgsFieldConstraints.Constraints())
|
|
|
|
self.assertTrue(vl.dataProvider().fieldConstraints(0)
|
|
& QgsFieldConstraints.ConstraintUnique)
|
|
self.assertTrue(vl.dataProvider().fieldConstraints(1)
|
|
& QgsFieldConstraints.ConstraintUnique)
|
|
self.assertFalse(vl.dataProvider().fieldConstraints(2)
|
|
& QgsFieldConstraints.ConstraintUnique)
|
|
self.assertFalse(vl.dataProvider().fieldConstraints(3)
|
|
& QgsFieldConstraints.ConstraintUnique)
|
|
|
|
# test that constraints have been saved to fields correctly
|
|
fields = vl.fields()
|
|
self.assertTrue(fields.at(0).constraints().constraints()
|
|
& QgsFieldConstraints.ConstraintUnique)
|
|
self.assertEqual(fields.at(0).constraints().constraintOrigin(QgsFieldConstraints.ConstraintUnique),
|
|
QgsFieldConstraints.ConstraintOriginProvider)
|
|
self.assertTrue(fields.at(1).constraints().constraints()
|
|
& QgsFieldConstraints.ConstraintUnique)
|
|
self.assertEqual(fields.at(1).constraints().constraintOrigin(QgsFieldConstraints.ConstraintUnique),
|
|
QgsFieldConstraints.ConstraintOriginProvider)
|
|
self.assertFalse(fields.at(2).constraints().constraints()
|
|
& QgsFieldConstraints.ConstraintUnique)
|
|
self.assertFalse(fields.at(3).constraints().constraints()
|
|
& QgsFieldConstraints.ConstraintUnique)
|
|
|
|
def testIdentityFieldHandling(self):
|
|
"""
|
|
Test identity field handling
|
|
"""
|
|
md = QgsProviderRegistry.instance().providerMetadata('mssql')
|
|
conn = md.createConnection(self.dbconn, {})
|
|
|
|
conn.execSql('DROP TABLE IF EXISTS qgis_test.test_identity')
|
|
conn.execSql("""CREATE TABLE [qgis_test].[test_identity](
|
|
[pk] [int] IDENTITY(1,1) NOT NULL,
|
|
[name] [nchar](10) NULL,
|
|
[geom] [geometry] NULL,
|
|
CONSTRAINT [PK_test_table] PRIMARY KEY CLUSTERED
|
|
(
|
|
[pk] ASC
|
|
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
|
|
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]""")
|
|
|
|
uri = f'{self.dbconn} table="qgis_test"."test_identity" sql='
|
|
vl = QgsVectorLayer(uri, '', 'mssql')
|
|
self.assertTrue(vl.isValid())
|
|
|
|
self.assertEqual(vl.dataProvider().pkAttributeIndexes(), [0])
|
|
self.assertEqual(vl.dataProvider().defaultValueClause(0), 'Autogenerate')
|
|
identity_field = vl.dataProvider().fields().at(0)
|
|
self.assertEqual(identity_field.name(), 'pk')
|
|
self.assertEqual(identity_field.constraints().constraints(), QgsFieldConstraints.ConstraintNotNull)
|
|
self.assertEqual(identity_field.constraints().constraintOrigin(QgsFieldConstraints.ConstraintNotNull), QgsFieldConstraints.ConstraintOriginProvider)
|
|
self.assertEqual(identity_field.constraints().constraintStrength(QgsFieldConstraints.ConstraintNotNull),
|
|
QgsFieldConstraints.ConstraintStrengthHard)
|
|
self.assertTrue(identity_field.isReadOnly())
|
|
|
|
def getSubsetString(self):
|
|
return '[cnt] > 100 and [cnt] < 410'
|
|
|
|
def getSubsetString2(self):
|
|
return '[cnt] > 100 and [cnt] < 400'
|
|
|
|
def getSubsetString3(self):
|
|
return '[name]=\'Apple\''
|
|
|
|
def getSubsetStringNoMatching(self):
|
|
return '[name]=\'AppleBearOrangePear\''
|
|
|
|
def testExtentFromGeometryTable(self):
|
|
"""
|
|
Check if the behavior of the mssql provider if extent is defined in the geometry_column table
|
|
"""
|
|
# Create a layer
|
|
layer = QgsVectorLayer("Point?field=id:integer&field=fldtxt:string&field=fldint:integer",
|
|
"layer", "memory")
|
|
pr = layer.dataProvider()
|
|
f1 = QgsFeature()
|
|
f1.setAttributes([1, "test", 1])
|
|
f1.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(1, 2)))
|
|
f2 = QgsFeature()
|
|
f2.setAttributes([2, "test2", 3])
|
|
f3 = QgsFeature()
|
|
f3.setAttributes([3, "test2", NULL])
|
|
f3.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(3, 2)))
|
|
f4 = QgsFeature()
|
|
f4.setAttributes([4, NULL, 3])
|
|
f4.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(4, 3)))
|
|
pr.addFeatures([f1, f2, f3, f4])
|
|
uri = f'{self.dbconn} table="qgis_test"."layer_extent_in_geometry_table" sql='
|
|
QgsVectorLayerExporter.exportLayer(layer, uri, 'mssql', QgsCoordinateReferenceSystem('EPSG:4326'))
|
|
|
|
layerUri = QgsDataSourceUri(uri)
|
|
# Load and check if the layer is valid
|
|
loadedLayer = QgsVectorLayer(layerUri.uri(), "valid", "mssql")
|
|
self.assertTrue(loadedLayer.isValid())
|
|
extent = loadedLayer.extent()
|
|
self.assertEqual(extent.toString(1),
|
|
QgsRectangle(1.0, 2.0, 4.0, 3.0).toString(1))
|
|
|
|
# Load with flag extent in geometry_columns table and check if the layer is still valid and extent doesn't change
|
|
layerUri.setParam('extentInGeometryColumns', '1')
|
|
loadedLayer = QgsVectorLayer(layerUri.uri(), "invalid", "mssql")
|
|
self.assertTrue(loadedLayer.isValid())
|
|
extent = loadedLayer.extent()
|
|
self.assertEqual(extent.toString(1),
|
|
QgsRectangle(1.0, 2.0, 4.0, 3.0).toString(1))
|
|
|
|
md = QgsProviderRegistry.instance().providerMetadata('mssql')
|
|
conn = md.createConnection(self.dbconn, {})
|
|
conn.addField(QgsField('qgis_xmin', QVariant.Double, 'FLOAT(24)'), 'dbo', 'geometry_columns')
|
|
conn.addField(QgsField('qgis_xmax', QVariant.Double, 'FLOAT(24)'), 'dbo', 'geometry_columns')
|
|
conn.addField(QgsField('qgis_ymin', QVariant.Double, 'FLOAT(24)'), 'dbo', 'geometry_columns')
|
|
conn.addField(QgsField('qgis_ymax', QVariant.Double, 'FLOAT(24)'), 'dbo', 'geometry_columns')
|
|
|
|
# try with empty attribute
|
|
layerUri.setParam('extentInGeometryColumns', '1')
|
|
loadedLayer = QgsVectorLayer(layerUri.uri(), "invalid", "mssql")
|
|
self.assertTrue(loadedLayer.isValid())
|
|
self.assertTrue(loadedLayer.isValid())
|
|
extent = loadedLayer.extent()
|
|
self.assertEqual(extent.toString(1),
|
|
QgsRectangle(1.0, 2.0, 4.0, 3.0).toString(1))
|
|
|
|
conn.execSql('UPDATE dbo.geometry_columns SET qgis_xmin=0, qgis_xmax=5.5, qgis_ymin=0.5, qgis_ymax=6 WHERE f_table_name=\'layer_extent_in_geometry_table\'')
|
|
|
|
# try with valid attribute
|
|
layerUri.setParam('extentInGeometryColumns', '1')
|
|
loadedLayer = QgsVectorLayer(layerUri.uri(), "valid", "mssql")
|
|
self.assertTrue(loadedLayer.isValid())
|
|
extent = loadedLayer.extent()
|
|
self.assertEqual(extent.toString(1),
|
|
QgsRectangle(0.0, 0.5, 5.5, 6.0).toString(1))
|
|
|
|
def test_insert_pk_escaping(self):
|
|
"""
|
|
Test that inserting features works with complex pk name
|
|
see https://github.com/qgis/QGIS/issues/42290
|
|
"""
|
|
md = QgsProviderRegistry.instance().providerMetadata('mssql')
|
|
conn = md.createConnection(self.dbconn, {})
|
|
|
|
conn.execSql('DROP TABLE IF EXISTS qgis_test.test_complex_pk_name')
|
|
conn.execSql('CREATE TABLE qgis_test.test_complex_pk_name ([test-field] int)')
|
|
|
|
uri = f'{self.dbconn} table="qgis_test"."test_complex_pk_name" sql='
|
|
vl = QgsVectorLayer(uri, '', 'mssql')
|
|
self.assertTrue(vl.isValid())
|
|
|
|
self.assertEqual(vl.primaryKeyAttributes(), [0])
|
|
|
|
vl.startEditing()
|
|
f = QgsFeature(vl.fields())
|
|
f.setAttributes([1])
|
|
self.assertTrue(vl.addFeature(f))
|
|
self.assertTrue(vl.commitChanges())
|
|
|
|
vl = QgsVectorLayer(uri, '', 'mssql')
|
|
features = list(vl.getFeatures())
|
|
self.assertEqual([f['test-field'] for f in features], [1])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|