Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
MSSQL: fix Z and ZM geometry type detection
Fixes #52660
  • Loading branch information
elpaso committed May 10, 2023
1 parent 11d4b51 commit 712f374
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 14 deletions.
6 changes: 3 additions & 3 deletions src/providers/mssql/qgsmssqlconnection.cpp
Expand Up @@ -434,13 +434,13 @@ QString QgsMssqlConnection::buildQueryForTables( bool allowTablesWithNoGeometry,
QString query( QStringLiteral( "SELECT " ) );
if ( geometryColumnOnly )
{
query += QLatin1String( "f_table_schema, f_table_name, f_geometry_column, srid, geometry_type, 0 FROM geometry_columns" );
query += QLatin1String( "f_table_schema, f_table_name, f_geometry_column, srid, geometry_type, 0, coord_dimension FROM geometry_columns" );
if ( !notSelectedSchemas.isEmpty() )
query += QStringLiteral( " WHERE f_table_schema NOT IN %1" ).arg( notSelectedSchemas );
}
else
{
query += QStringLiteral( "sys.schemas.name, sys.objects.name, sys.columns.name, null, 'GEOMETRY', CASE when sys.objects.type = 'V' THEN 1 ELSE 0 END \n"
query += QStringLiteral( "sys.schemas.name, sys.objects.name, sys.columns.name, null, 'GEOMETRY', CASE when sys.objects.type = 'V' THEN 1 ELSE 0 END \n, 0"
"FROM sys.columns JOIN sys.types ON sys.columns.system_type_id = sys.types.system_type_id AND sys.columns.user_type_id = sys.types.user_type_id JOIN sys.objects ON sys.objects.object_id = sys.columns.object_id JOIN sys.schemas ON sys.objects.schema_id = sys.schemas.schema_id \n"
"WHERE (sys.types.name = 'geometry' OR sys.types.name = 'geography') AND (sys.objects.type = 'U' OR sys.objects.type = 'V')" );
if ( !notSelectedSchemas.isEmpty() )
Expand All @@ -450,7 +450,7 @@ QString QgsMssqlConnection::buildQueryForTables( bool allowTablesWithNoGeometry,
if ( allowTablesWithNoGeometry )
{
query += QStringLiteral( " UNION ALL \n"
"SELECT sys.schemas.name, sys.objects.name, null, null, 'NONE', case when sys.objects.type = 'V' THEN 1 ELSE 0 END \n"
"SELECT sys.schemas.name, sys.objects.name, null, null, 'NONE', CASE when sys.objects.type = 'V' THEN 1 ELSE 0 END \n, 0"
"FROM sys.objects JOIN sys.schemas ON sys.objects.schema_id = sys.schemas.schema_id "
"WHERE NOT EXISTS (SELECT * FROM sys.columns sc1 JOIN sys.types ON sc1.system_type_id = sys.types.system_type_id WHERE (sys.types.name = 'geometry' OR sys.types.name = 'geography') AND sys.objects.object_id = sc1.object_id) AND (sys.objects.type = 'U' or sys.objects.type = 'V')" );
if ( !notSelectedSchemas.isEmpty() )
Expand Down
9 changes: 9 additions & 0 deletions src/providers/mssql/qgsmssqldataitems.cpp
Expand Up @@ -169,6 +169,15 @@ QVector<QgsDataItem *> QgsMssqlConnectionItem::createChildren()
layer.srid = q.value( 3 ).toString();
layer.type = q.value( 4 ).toString();
layer.isView = q.value( 5 ).toBool();
const int dimensions { q.value( 6 ).toInt( ) };
if ( dimensions >= 3 )
{
layer.type = layer.type.append( 'Z' );
}
if ( dimensions == 4 )
{
layer.type = layer.type.append( 'M' );
}
layer.pkCols = QStringList(); //TODO
layer.isGeography = false;

Expand Down
18 changes: 16 additions & 2 deletions src/providers/mssql/qgsmssqlgeomcolumntypethread.cpp
Expand Up @@ -61,7 +61,9 @@ void QgsMssqlGeomColumnTypeThread::run()

const QString query = QStringLiteral( "SELECT %3"
" UPPER([%1].STGeometryType()),"
" [%1].STSrid"
" [%1].STSrid,"
" [%1].HasZ,"
" [%1].HasM"
" FROM %2"
" WHERE [%1] IS NOT NULL %4"
" GROUP BY [%1].STGeometryType(), [%1].STSrid" )
Expand Down Expand Up @@ -95,12 +97,24 @@ void QgsMssqlGeomColumnTypeThread::run()

while ( q.next() )
{
const QString type = q.value( 0 ).toString().toUpper();
QString type = q.value( 0 ).toString().toUpper();
const QString srid = q.value( 1 ).toString();
const bool hasZ { q.value( 2 ).toString() == '1' };
const bool hasM { q.value( 3 ).toString() == '1' };

if ( type.isEmpty() )
continue;

if ( hasZ )
{
type.append( 'Z' );
}

if ( hasM )
{
type.append( 'M' );
}

types << type;
srids << srid;
}
Expand Down
16 changes: 13 additions & 3 deletions src/providers/mssql/qgsmssqlproviderconnection.cpp
Expand Up @@ -445,10 +445,11 @@ QList<QgsMssqlProviderConnection::TableProperty> QgsMssqlProviderConnection::tab
const QString geomColSql
{
QStringLiteral( R"raw(
SELECT %4 UPPER( %1.STGeometryType()), %1.STSrid
SELECT %4 UPPER( %1.STGeometryType()), %1.STSrid,
%1.HasZ, %1.HasM
FROM %2.%3
WHERE %1 IS NOT NULL
GROUP BY %1.STGeometryType(), %1.STSrid
GROUP BY %1.STGeometryType(), %1.STSrid, %1.HasZ, %1.HasM
)raw" )
.arg( QgsMssqlProvider::quotedIdentifier( table.geometryColumn() ),
QgsMssqlProvider::quotedIdentifier( table.schema() ),
Expand All @@ -461,7 +462,16 @@ QList<QgsMssqlProviderConnection::TableProperty> QgsMssqlProviderConnection::tab
const auto geomColResults { executeSqlPrivate( geomColSql ).rows() };
for ( const auto &row : geomColResults )
{
table.addGeometryColumnType( QgsWkbTypes::parseType( row[0].toString() ),
Qgis::WkbType geometryType { QgsWkbTypes::parseType( row[0].toString() ) };
if ( row[2].toString() == '1' )
{
geometryType = QgsWkbTypes::addZ( geometryType );
}
if ( row[3].toString() == '1' )
{
geometryType = QgsWkbTypes::addM( geometryType );
}
table.addGeometryColumnType( geometryType,
QgsCoordinateReferenceSystem::fromEpsgId( row[1].toLongLong( ) ) );
++geomColCount;
}
Expand Down
10 changes: 10 additions & 0 deletions src/providers/mssql/qgsmssqlsourceselect.cpp
Expand Up @@ -431,6 +431,16 @@ void QgsMssqlSourceSelect::btnConnect_clicked()
layer.isView = q.value( 5 ).toBool();
layer.pkCols = QStringList(); //TODO
layer.isGeography = false;
const int dimensions { q.value( 6 ).toInt( ) };

if ( dimensions >= 3 )
{
layer.type = layer.type.append( 'Z' );
}
if ( dimensions == 4 )
{
layer.type = layer.type.append( 'M' );
}

QString type = layer.type;
QString srid = layer.srid;
Expand Down
50 changes: 44 additions & 6 deletions tests/src/python/test_qgsproviderconnection_mssql.py
Expand Up @@ -18,6 +18,9 @@
QgsDataSourceUri,
QgsProviderRegistry,
QgsVectorLayer,
Qgis,
QgsCoordinateReferenceSystem,
QgsFields,
)
from qgis.testing import unittest

Expand Down Expand Up @@ -111,22 +114,22 @@ def test_schemas_filtering(self):

conn = md.createConnection(self.uri, {})
schemas = conn.schemas()
self.assertEqual(len(schemas), 2)
self.assertEqual(schemas, ['dbo', 'qgis_test'])
self.assertTrue('dbo' in schemas)
self.assertTrue('qgis_test' in schemas)
filterUri = QgsDataSourceUri(self.uri)
filterUri.setParam('excludedSchemas', 'dbo')
conn = md.createConnection(filterUri.uri(), {})
schemas = conn.schemas()
self.assertEqual(len(schemas), 1)
self.assertEqual(schemas, ['qgis_test'])
self.assertFalse('dbo' in schemas)
self.assertTrue('qgis_test' in schemas)

# Store the connection
conn.store('filteredConnection')

otherConn = md.createConnection('filteredConnection')
schemas = otherConn.schemas()
self.assertEqual(len(schemas), 1)
self.assertEqual(schemas, ['qgis_test'])
self.assertFalse('dbo' in schemas)
self.assertTrue('qgis_test' in schemas)

def test_exec_sql(self):

Expand All @@ -144,6 +147,41 @@ def test_exec_sql(self):
self.assertEqual(len(rows), 4)
self.assertEqual(rows, results)

def test_geometry_z(self):
"""Test for issue GH #52660: Z values are not correctly stored when using MSSQL"""

md = QgsProviderRegistry.instance().providerMetadata('mssql')
conn = md.createConnection(self.uri, {})
conn.dropVectorTable('qgis_test', 'test_z')

conn.createVectorTable('qgis_test', 'test_z', QgsFields(), Qgis.WkbType.PolygonZ, QgsCoordinateReferenceSystem(), True, {})
conn.executeSql("""INSERT INTO qgis_test.test_z (geom) values (geometry::STGeomFromText ('POLYGON ((523699.41 6231152.17 80.53, 523698.64 6231154.35 79.96, 523694.92 6231152.82 80.21, 523695.8 6231150.68 80.54, 523699.41 6231152.17 80.53))' , 25832))""")

tb = conn.table('qgis_test', 'test_z')
gct = tb.geometryColumnTypes()[0]
self.assertEqual(gct.wkbType, Qgis.WkbType.PolygonZ)
self.assertEqual(tb.maxCoordinateDimensions(), 3)

vl = QgsVectorLayer(conn.tableUri('qgis_test', 'test_z'), 'test_z', 'mssql')
self.assertEqual(vl.wkbType(), Qgis.WkbType.PolygonZ)

conn.dropVectorTable('qgis_test', 'test_z')

# Also test ZM
conn.dropVectorTable('qgis_test', 'test_zm')
conn.createVectorTable('qgis_test', 'test_zm', QgsFields(), Qgis.WkbType.PolygonZM, QgsCoordinateReferenceSystem(), True, {})
conn.executeSql("""INSERT INTO qgis_test.test_zm (geom) values (geometry::STGeomFromText ('POLYGON ((523699.41 6231152.17 80.53 123, 523698.64 6231154.35 79.96 456, 523694.92 6231152.82 80.21 789, 523695.8 6231150.68 80.54, 523699.41 6231152.17 80.53 123))' , 25832))""")

tb = conn.table('qgis_test', 'test_zm')
gct = tb.geometryColumnTypes()[0]
self.assertEqual(gct.wkbType, Qgis.WkbType.PolygonZM)
self.assertEqual(tb.maxCoordinateDimensions(), 4)

vl = QgsVectorLayer(conn.tableUri('qgis_test', 'test_zm'), 'test_zm', 'mssql')
self.assertEqual(vl.wkbType(), Qgis.WkbType.PolygonZM)

conn.dropVectorTable('qgis_test', 'test_zm')


if __name__ == '__main__':
unittest.main()

0 comments on commit 712f374

Please sign in to comment.