Skip to content

Commit a6cf547

Browse files
committed
[FEATURE] Expression compiler for mssql provider
Also make mssql provider handle requests which cannot be executed gracefully (was crashing before)
1 parent 8602a91 commit a6cf547

16 files changed

+251
-22
lines changed

src/core/qgssqlexpressioncompiler.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ QString QgsSqlExpressionCompiler::quotedIdentifier( const QString& identifier )
4343
return quoted;
4444
}
4545

46-
QString QgsSqlExpressionCompiler::quotedValue( const QVariant& value )
46+
QString QgsSqlExpressionCompiler::quotedValue( const QVariant& value, bool& ok )
4747
{
48+
ok = true;
49+
4850
if ( value.isNull() )
4951
return "NULL";
5052

@@ -231,17 +233,18 @@ QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const Qg
231233
case QgsExpression::ntLiteral:
232234
{
233235
const QgsExpression::NodeLiteral* n = static_cast<const QgsExpression::NodeLiteral*>( node );
236+
bool ok = false;
234237
if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->value().type() == QVariant::String )
235238
{
236239
// provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to
237240
// double check results using QGIS' expression engine
238-
result = quotedValue( n->value() );
239-
return Partial;
241+
result = quotedValue( n->value(), ok );
242+
return ok ? Partial : Fail;
240243
}
241244
else
242245
{
243-
result = quotedValue( n->value() );
244-
return Complete;
246+
result = quotedValue( n->value(), ok );
247+
return ok ? Complete : Fail;
245248
}
246249
}
247250

src/core/qgssqlexpressioncompiler.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,11 @@ class CORE_EXPORT QgsSqlExpressionCompiler
7878

7979
/** Returns a quoted attribute value, in the format expected by the provider.
8080
* Derived classes should override this if special handling of attribute values is required.
81+
* @param value value to quote
82+
* @param ok wil be set to true if value can be compiled
8183
* @see quotedIdentifier()
8284
*/
83-
virtual QString quotedValue( const QVariant& value );
85+
virtual QString quotedValue( const QVariant& value, bool &ok );
8486

8587
/** Compiles an expression node and returns the result of the compilation.
8688
* @param node expression node to compile

src/providers/mssql/CMakeLists.txt

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
1-
SET (MSSQL_SRCS qgsmssqlprovider.cpp qgsmssqlgeometryparser.cpp qgsmssqlsourceselect.cpp qgsmssqltablemodel.cpp qgsmssqlnewconnection.cpp qgsmssqldataitems.cpp qgsmssqlfeatureiterator.cpp)
2-
SET (MSSQL_MOC_HDRS qgsmssqlprovider.h qgsmssqlsourceselect.h qgsmssqltablemodel.h qgsmssqlnewconnection.h qgsmssqldataitems.h)
1+
SET(MSSQL_SRCS
2+
qgsmssqlprovider.cpp
3+
qgsmssqlgeometryparser.cpp
4+
qgsmssqlsourceselect.cpp
5+
qgsmssqltablemodel.cpp
6+
qgsmssqlnewconnection.cpp
7+
qgsmssqldataitems.cpp
8+
qgsmssqlexpressioncompiler.cpp
9+
qgsmssqlfeatureiterator.cpp
10+
)
11+
12+
SET(MSSQL_MOC_HDRS
13+
qgsmssqlprovider.h
14+
qgsmssqlsourceselect.h
15+
qgsmssqltablemodel.h
16+
qgsmssqlnewconnection.h
17+
qgsmssqldataitems.h
18+
)
19+
20+
SET(MSSQL_HDRS
21+
qgsmssqlexpressioncompiler.h
22+
)
323

424
########################################################
525
# Build
@@ -23,7 +43,7 @@ INCLUDE_DIRECTORIES(
2343
)
2444

2545

26-
ADD_LIBRARY(mssqlprovider MODULE ${MSSQL_SRCS} ${MSSQL_MOC_SRCS})
46+
ADD_LIBRARY(mssqlprovider MODULE ${MSSQL_SRCS} ${MSSQL_MOC_SRCS} ${MSSQL_HDRS})
2747

2848
TARGET_LINK_LIBRARIES(mssqlprovider
2949
qgis_core
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/***************************************************************************
2+
qgsmssqlexpressioncompiler.cpp
3+
------------------------------
4+
begin : 9.12.2015
5+
copyright : (C) 2015 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsmssqlexpressioncompiler.h"
17+
18+
QgsMssqlExpressionCompiler::QgsMssqlExpressionCompiler( QgsMssqlFeatureSource* source )
19+
: QgsSqlExpressionCompiler( source->mFields,
20+
QgsSqlExpressionCompiler::LikeIsCaseInsensitive | QgsSqlExpressionCompiler::CaseInsensitiveStringMatch )
21+
{
22+
23+
}
24+
25+
QgsSqlExpressionCompiler::Result QgsMssqlExpressionCompiler::compileNode( const QgsExpression::Node* node, QString& result )
26+
{
27+
if ( node->nodeType() == QgsExpression::ntBinaryOperator )
28+
{
29+
const QgsExpression::NodeBinaryOperator *bin( static_cast<const QgsExpression::NodeBinaryOperator*>( node ) );
30+
QString op1, op2;
31+
32+
Result result1 = compileNode( bin->opLeft(), op1 );
33+
Result result2 = compileNode( bin->opRight(), op2 );
34+
if ( result1 == Fail || result2 == Fail )
35+
return Fail;
36+
37+
switch ( bin->op() )
38+
{
39+
case QgsExpression::boPow:
40+
result = QString( "power(%1,%2)" ).arg( op1, op2 );
41+
return result1 == Partial || result2 == Partial ? Partial : Complete;
42+
43+
case QgsExpression::boRegexp:
44+
return Fail; //not supported, regexp syntax is too different to Qt
45+
46+
case QgsExpression::boConcat:
47+
result = QString( "%1 + %2" ).arg( op1, op2 );
48+
return result1 == Partial || result2 == Partial ? Partial : Complete;
49+
50+
default:
51+
break;
52+
}
53+
54+
}
55+
56+
//fallback to default handling
57+
return QgsSqlExpressionCompiler::compileNode( node, result );
58+
}
59+
60+
QString QgsMssqlExpressionCompiler::quotedValue( const QVariant& value, bool& ok )
61+
{
62+
ok = true;
63+
if ( value.isNull() )
64+
{
65+
//no NULL literal support
66+
ok = false;
67+
return QString();
68+
}
69+
70+
switch ( value.type() )
71+
{
72+
case QVariant::Bool:
73+
//no boolean literal support in mssql, so fake it
74+
return value.toBool() ? "(1=1)" : "(1=0)";
75+
76+
default:
77+
return QgsSqlExpressionCompiler::quotedValue( value, ok );
78+
}
79+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/***************************************************************************
2+
qgsmssqlexpressioncompiler.h
3+
----------------------------
4+
begin : 9.12.2015
5+
copyright : (C) 2015 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSMSSQLEXPRESSIONCOMPILER_H
17+
#define QGSMSSQLEXPRESSIONCOMPILER_H
18+
19+
#include "qgssqlexpressioncompiler.h"
20+
#include "qgsexpression.h"
21+
#include "qgsmssqlfeatureiterator.h"
22+
23+
class QgsMssqlExpressionCompiler : public QgsSqlExpressionCompiler
24+
{
25+
public:
26+
27+
explicit QgsMssqlExpressionCompiler( QgsMssqlFeatureSource* source );
28+
29+
protected:
30+
virtual Result compileNode( const QgsExpression::Node* node, QString& result ) override;
31+
virtual QString quotedValue( const QVariant& value, bool& ok ) override;
32+
33+
};
34+
35+
#endif // QGSMSSQLEXPRESSIONCOMPILER_H

src/providers/mssql/qgsmssqlfeatureiterator.cpp

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@
1616
***************************************************************************/
1717

1818
#include "qgsmssqlfeatureiterator.h"
19+
#include "qgsmssqlexpressioncompiler.h"
1920
#include "qgsmssqlprovider.h"
2021
#include "qgslogger.h"
2122

2223
#include <QObject>
2324
#include <QTextStream>
2425
#include <QSqlRecord>
26+
#include <QSettings>
2527

2628

2729
QgsMssqlFeatureIterator::QgsMssqlFeatureIterator( QgsMssqlFeatureSource* source, bool ownSource, const QgsFeatureRequest& request )
2830
: QgsAbstractFeatureIteratorFromSource<QgsMssqlFeatureSource>( source, ownSource, request )
31+
, mExpressionCompiled( false )
2932
{
3033
mClosed = false;
3134
mQuery = NULL;
@@ -60,12 +63,14 @@ QgsMssqlFeatureIterator::~QgsMssqlFeatureIterator()
6063

6164
void QgsMssqlFeatureIterator::BuildStatement( const QgsFeatureRequest& request )
6265
{
63-
// build sql statement
64-
mStatement = QString( "SELECT " );
66+
mFallbackStatement.clear();
67+
mStatement.clear();
68+
69+
bool limitAtProvider = ( mRequest.limit() >= 0 );
6570

66-
if ( request.limit() >= 0 && request.filterType() != QgsFeatureRequest::FilterExpression )
67-
mStatement += QString( "TOP %1 " ).arg( mRequest.limit() );
71+
// build sql statement
6872

73+
// note: 'SELECT ' is added later, to account for 'SELECT TOP...' type queries
6974
mStatement += QString( "[%1]" ).arg( mSource->mFidColName );
7075
mFidCol = mSource->mFields.indexFromName( mSource->mFidColName );
7176
mAttributesToFetch.append( mFidCol );
@@ -131,6 +136,52 @@ void QgsMssqlFeatureIterator::BuildStatement( const QgsFeatureRequest& request )
131136
mStatement += " WHERE (" + mSource->mSqlWhereClause + ')';
132137
else
133138
mStatement += " AND (" + mSource->mSqlWhereClause + ')';
139+
filterAdded = true;
140+
}
141+
142+
//NOTE - must be last added!
143+
mExpressionCompiled = false;
144+
if ( request.filterType() == QgsFeatureRequest::FilterExpression )
145+
{
146+
if ( QSettings().value( "/qgis/compileExpressions", true ).toBool() )
147+
{
148+
QgsMssqlExpressionCompiler compiler = QgsMssqlExpressionCompiler( mSource );
149+
QgsSqlExpressionCompiler::Result result = compiler.compile( request.filterExpression() );
150+
if ( result == QgsSqlExpressionCompiler::Complete || result == QgsSqlExpressionCompiler::Partial )
151+
{
152+
mFallbackStatement = mStatement;
153+
if ( !filterAdded )
154+
mStatement += " WHERE (" + compiler.result() + ')';
155+
else
156+
mStatement += " AND (" + compiler.result() + ')';
157+
filterAdded = true;
158+
159+
//if only partial success when compiling expression, we need to double-check results using QGIS' expressions
160+
mExpressionCompiled = ( result == QgsSqlExpressionCompiler::Complete );
161+
limitAtProvider = mExpressionCompiled;
162+
}
163+
else
164+
{
165+
limitAtProvider = false;
166+
}
167+
}
168+
else
169+
{
170+
limitAtProvider = false;
171+
}
172+
}
173+
174+
if ( request.limit() >= 0 && limitAtProvider )
175+
{
176+
mStatement.prepend( QString( "SELECT TOP %1 " ).arg( mRequest.limit() ) );
177+
if ( !mFallbackStatement.isEmpty() )
178+
mFallbackStatement.prepend( QString( "SELECT TOP %1 " ).arg( mRequest.limit() ) );
179+
}
180+
else
181+
{
182+
mStatement.prepend( "SELECT " );
183+
if ( !mFallbackStatement.isEmpty() )
184+
mFallbackStatement.prepend( "SELECT " );
134185
}
135186

136187
QgsDebugMsg( mStatement );
@@ -188,6 +239,13 @@ bool QgsMssqlFeatureIterator::fetchFeature( QgsFeature& feature )
188239
return false;
189240
}
190241

242+
bool QgsMssqlFeatureIterator::nextFeatureFilterExpression( QgsFeature& f )
243+
{
244+
if ( !mExpressionCompiled )
245+
return QgsAbstractFeatureIterator::nextFeatureFilterExpression( f );
246+
else
247+
return fetchFeature( f );
248+
}
191249

192250
bool QgsMssqlFeatureIterator::rewind()
193251
{
@@ -205,10 +263,28 @@ bool QgsMssqlFeatureIterator::rewind()
205263

206264
mQuery->clear();
207265
mQuery->setForwardOnly( true );
208-
if ( !mQuery->exec( mStatement ) )
266+
267+
bool result = mQuery->exec( mStatement );
268+
if ( !result && !mFallbackStatement.isEmpty() )
269+
{
270+
//try with fallback statement
271+
result = mQuery->exec( mFallbackStatement );
272+
mExpressionCompiled = false;
273+
}
274+
275+
if ( !result )
209276
{
210277
QString msg = mQuery->lastError().text();
211278
QgsDebugMsg( msg );
279+
delete mQuery;
280+
mQuery = 0;
281+
if ( mDatabase.isOpen() )
282+
mDatabase.close();
283+
284+
iteratorClosed();
285+
286+
mClosed = true;
287+
return false;
212288
}
213289

214290
return true;
@@ -231,7 +307,10 @@ bool QgsMssqlFeatureIterator::close()
231307
}
232308

233309
if ( mQuery )
310+
{
234311
delete mQuery;
312+
mQuery = 0;
313+
}
235314

236315
if ( mDatabase.isOpen() )
237316
mDatabase.close();

src/providers/mssql/qgsmssqlfeatureiterator.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class QgsMssqlFeatureSource : public QgsAbstractFeatureSource
6565
bool isSpatial() { return !mGeometryColName.isEmpty() || !mGeometryColType.isEmpty(); }
6666

6767
friend class QgsMssqlFeatureIterator;
68+
friend class QgsMssqlExpressionCompiler;
6869
};
6970

7071
class QgsMssqlFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsMssqlFeatureSource>
@@ -87,6 +88,9 @@ class QgsMssqlFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsM
8788
//! fetch next feature, return true on success
8889
virtual bool fetchFeature( QgsFeature& feature ) override;
8990

91+
//! fetch next feature filter expression
92+
bool nextFeatureFilterExpression( QgsFeature& f ) override;
93+
9094
// The current database
9195
QSqlDatabase mDatabase;
9296

@@ -96,6 +100,8 @@ class QgsMssqlFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsM
96100
// The current sql statement
97101
QString mStatement;
98102

103+
QString mFallbackStatement;
104+
99105
// Field index of FID column
100106
long mFidCol;
101107

@@ -104,6 +110,8 @@ class QgsMssqlFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsM
104110

105111
// for parsing sql geometries
106112
QgsMssqlGeometryParser mParser;
113+
114+
bool mExpressionCompiled;
107115
};
108116

109117
#endif // QGSMSSQLFEATUREITERATOR_H

src/providers/ogr/qgsogrexpressioncompiler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ QString QgsOgrExpressionCompiler::quotedIdentifier( const QString& identifier )
8989
return mSource->mProvider->quotedIdentifier( identifier.toUtf8() );
9090
}
9191

92-
QString QgsOgrExpressionCompiler::quotedValue( const QVariant& value )
92+
QString QgsOgrExpressionCompiler::quotedValue( const QVariant& value, bool& )
9393
{
9494
return QgsOgrUtils::quotedValue( value );
9595
}

0 commit comments

Comments
 (0)