Skip to content

Commit 5f65f87

Browse files
committed
Create QgsSqlExpressionCompiler as base class for expression compilers
Switch Postgres and OGR compilers to use the new base class. New class should make it easier to add additional expression compilers (eg spatialite, MS SQL, etc)
1 parent f7ed42b commit 5f65f87

8 files changed

+427
-397
lines changed

src/core/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ SET(QGIS_CORE_SRCS
178178
qgssnapper.cpp
179179
qgssnappingutils.cpp
180180
qgsspatialindex.cpp
181+
qgssqlexpressioncompiler.cpp
181182
qgsstatisticalsummary.cpp
182183
qgsstringutils.cpp
183184
qgstransaction.cpp
@@ -624,6 +625,7 @@ SET(QGIS_CORE_HDRS
624625
qgssnapper.h
625626
qgssnappingutils.h
626627
qgsspatialindex.h
628+
qgssqlexpressioncompiler.h
627629
qgsstatisticalsummary.h
628630
qgsstringutils.h
629631
qgstolerance.h

src/core/qgssqlexpressioncompiler.cpp

+280
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
/***************************************************************************
2+
qgssqlexpressioncompiler.cpp
3+
----------------------------
4+
begin : November 2015
5+
copyright : (C) 2015 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 "qgssqlexpressioncompiler.h"
17+
18+
QgsSqlExpressionCompiler::QgsSqlExpressionCompiler( const QgsFields& fields, const Flags& flags )
19+
: mResult( None )
20+
, mFields( fields )
21+
, mFlags( flags )
22+
{
23+
}
24+
25+
QgsSqlExpressionCompiler::~QgsSqlExpressionCompiler()
26+
{
27+
28+
}
29+
30+
QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compile( const QgsExpression* exp )
31+
{
32+
if ( exp->rootNode() )
33+
return compile( exp->rootNode(), mResult );
34+
else
35+
return Fail;
36+
}
37+
38+
QString QgsSqlExpressionCompiler::quotedIdentifier( const QString& identifier )
39+
{
40+
QString quoted = identifier;
41+
quoted.replace( '"', "\"\"" );
42+
quoted = quoted.prepend( '\"' ).append( '\"' );
43+
return quoted;
44+
}
45+
46+
QString QgsSqlExpressionCompiler::quotedValue( const QVariant& value )
47+
{
48+
if ( value.isNull() )
49+
return "NULL";
50+
51+
switch ( value.type() )
52+
{
53+
case QVariant::Int:
54+
case QVariant::LongLong:
55+
case QVariant::Double:
56+
return value.toString();
57+
58+
case QVariant::Bool:
59+
return value.toBool() ? "TRUE" : "FALSE";
60+
61+
default:
62+
case QVariant::String:
63+
QString v = value.toString();
64+
v.replace( '\'', "''" );
65+
if ( v.contains( '\\' ) )
66+
return v.replace( '\\', "\\\\" ).prepend( "E'" ).append( '\'' );
67+
else
68+
return v.prepend( '\'' ).append( '\'' );
69+
}
70+
}
71+
72+
QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compile( const QgsExpression::Node* node, QString& result )
73+
{
74+
switch ( node->nodeType() )
75+
{
76+
case QgsExpression::ntUnaryOperator:
77+
{
78+
const QgsExpression::NodeUnaryOperator* n = static_cast<const QgsExpression::NodeUnaryOperator*>( node );
79+
switch ( n->op() )
80+
{
81+
case QgsExpression::uoNot:
82+
break;
83+
84+
case QgsExpression::uoMinus:
85+
break;
86+
}
87+
88+
break;
89+
}
90+
91+
case QgsExpression::ntBinaryOperator:
92+
{
93+
const QgsExpression::NodeBinaryOperator* n = static_cast<const QgsExpression::NodeBinaryOperator*>( node );
94+
95+
QString op;
96+
bool partialCompilation = false;
97+
switch ( n->op() )
98+
{
99+
case QgsExpression::boEQ:
100+
if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->opLeft()->nodeType() == QgsExpression::ntColumnRef && n->opRight()->nodeType() == QgsExpression::ntColumnRef )
101+
{
102+
// equality between column refs results in a partial compilation, since provider is performing
103+
// case-insensitive matches between strings
104+
partialCompilation = true;
105+
}
106+
107+
op = "=";
108+
break;
109+
110+
case QgsExpression::boGE:
111+
op = ">=";
112+
break;
113+
114+
case QgsExpression::boGT:
115+
op = ">";
116+
break;
117+
118+
case QgsExpression::boLE:
119+
op = "<=";
120+
break;
121+
122+
case QgsExpression::boLT:
123+
op = "<";
124+
break;
125+
126+
case QgsExpression::boIs:
127+
op = "IS";
128+
break;
129+
130+
case QgsExpression::boIsNot:
131+
op = "IS NOT";
132+
break;
133+
134+
case QgsExpression::boLike:
135+
op = "LIKE";
136+
break;
137+
138+
case QgsExpression::boILike:
139+
op = "ILIKE";
140+
break;
141+
142+
case QgsExpression::boNotLike:
143+
op = "NOT LIKE";
144+
break;
145+
146+
case QgsExpression::boNotILike:
147+
op = "NOT ILIKE";
148+
break;
149+
150+
case QgsExpression::boOr:
151+
op = "OR";
152+
break;
153+
154+
case QgsExpression::boAnd:
155+
op = "AND";
156+
break;
157+
158+
case QgsExpression::boNE:
159+
op = "<>";
160+
break;
161+
162+
case QgsExpression::boMul:
163+
op = "*";
164+
break;
165+
166+
case QgsExpression::boPlus:
167+
op = "+";
168+
break;
169+
170+
case QgsExpression::boMinus:
171+
op = "-";
172+
break;
173+
174+
case QgsExpression::boDiv:
175+
return Fail; // handle cast to real
176+
177+
case QgsExpression::boMod:
178+
op = "%";
179+
break;
180+
181+
case QgsExpression::boConcat:
182+
op = "||";
183+
break;
184+
185+
case QgsExpression::boIntDiv:
186+
return Fail; // handle cast to int
187+
188+
case QgsExpression::boPow:
189+
op = "^";
190+
break;
191+
192+
case QgsExpression::boRegexp:
193+
op = "~";
194+
break;
195+
}
196+
197+
if ( op.isNull() )
198+
return Fail;
199+
200+
QString left;
201+
Result lr( compile( n->opLeft(), left ) );
202+
203+
QString right;
204+
Result rr( compile( n->opRight(), right ) );
205+
206+
result = left + ' ' + op + ' ' + right;
207+
if ( lr == Complete && rr == Complete )
208+
return ( partialCompilation ? Partial : Complete );
209+
else if (( lr == Partial && rr == Complete ) || ( lr == Complete && rr == Partial ) || ( lr == Partial && rr == Partial ) )
210+
return Partial;
211+
else
212+
return Fail;
213+
}
214+
215+
case QgsExpression::ntLiteral:
216+
{
217+
const QgsExpression::NodeLiteral* n = static_cast<const QgsExpression::NodeLiteral*>( node );
218+
if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->value().type() == QVariant::String )
219+
{
220+
// provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to
221+
// double check results using QGIS' expression engine
222+
result = quotedValue( n->value() );
223+
return Partial;
224+
}
225+
else
226+
{
227+
result = quotedValue( n->value() );
228+
return Complete;
229+
}
230+
}
231+
232+
case QgsExpression::ntColumnRef:
233+
{
234+
const QgsExpression::NodeColumnRef* n = static_cast<const QgsExpression::NodeColumnRef*>( node );
235+
236+
if ( mFields.indexFromName( n->name() ) == -1 )
237+
// Not a provider field
238+
return Fail;
239+
240+
result = quotedIdentifier( n->name() );
241+
242+
return Complete;
243+
}
244+
245+
case QgsExpression::ntInOperator:
246+
{
247+
const QgsExpression::NodeInOperator* n = static_cast<const QgsExpression::NodeInOperator*>( node );
248+
QStringList list;
249+
250+
Result inResult = Complete;
251+
Q_FOREACH ( const QgsExpression::Node* ln, n->list()->list() )
252+
{
253+
QString s;
254+
Result r = compile( ln, s );
255+
if ( r == Complete || r == Partial )
256+
{
257+
list << s;
258+
if ( r == Partial )
259+
inResult = Partial;
260+
}
261+
else
262+
return r;
263+
}
264+
265+
QString nd;
266+
Result rn = compile( n->node(), nd );
267+
if ( rn != Complete && rn != Partial )
268+
return rn;
269+
270+
result = QString( "%1 %2IN(%3)" ).arg( nd, n->isNotIn() ? "NOT " : "", list.join( "," ) );
271+
return ( inResult == Partial || rn == Partial ) ? Partial : Complete;
272+
}
273+
274+
case QgsExpression::ntFunction:
275+
case QgsExpression::ntCondition:
276+
break;
277+
}
278+
279+
return Fail;
280+
}

src/core/qgssqlexpressioncompiler.h

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/***************************************************************************
2+
qgssqlexpressioncompiler.h
3+
--------------------------
4+
begin : November 2015
5+
copyright : (C) 2015 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 QGSSQLEXPRESSIONCOMPILER_H
17+
#define QGSSQLEXPRESSIONCOMPILER_H
18+
19+
#include "qgsexpression.h"
20+
#include "qgsfield.h"
21+
22+
/** \ingroup core
23+
* \class QgsSqlExpressionCompiler
24+
* \brief Generic expression compiler for translation to provider specific SQL WHERE clauses.
25+
*
26+
* This class is designed to be overriden by providers to take advantage of expression compilation,
27+
* so that feature requests can take advantage of the provider's native filtering support.
28+
* \note Added in version 2.14
29+
* \note Not part of stable API, may change in future versions of QGIS
30+
* \note Not available in Python bindings
31+
*/
32+
33+
class CORE_EXPORT QgsSqlExpressionCompiler
34+
{
35+
public:
36+
37+
/** Possible results from expression compilation */
38+
enum Result
39+
{
40+
None, /*!< No expression */
41+
Complete, /*!< Expression was successfully compiled and can be completely delegated to provider */
42+
Partial, /*!< Expression was partially compiled, but provider will return extra records and results must be double-checked using QGIS' expression engine*/
43+
Fail /*!< Provider cannot handle expression */
44+
};
45+
46+
/** Enumeration of flags for how provider handles SQL clauses
47+
*/
48+
enum Flag
49+
{
50+
CaseInsensitiveStringMatch = 0x01, //!< Provider performs case-insensitive string matching
51+
};
52+
Q_DECLARE_FLAGS( Flags, Flag )
53+
54+
/** Constructor for expression compiler.
55+
* @param fields fields from provider
56+
* @param flags flags which control how expression is compiled
57+
*/
58+
explicit QgsSqlExpressionCompiler( const QgsFields& fields, const Flags& flags = ( Flags )0 );
59+
virtual ~QgsSqlExpressionCompiler();
60+
61+
/** Compiles an expression and returns the result of the compilation.
62+
*/
63+
virtual Result compile( const QgsExpression* exp );
64+
65+
/** Returns the compiled expression string for use by the provider.
66+
*/
67+
virtual QString result() { return mResult; }
68+
69+
protected:
70+
71+
/** Returns a quoted column identifier, in the format expected by the provider.
72+
* Derived classes should override this if special handling of column identifiers
73+
* is required.
74+
* @see quotedValue()
75+
*/
76+
virtual QString quotedIdentifier( const QString& identifier );
77+
78+
/** Returns a quoted attribute value, in the format expected by the provider.
79+
* Derived classes should override this if special handling of attribute values is required.
80+
* @see quotedIdentifier()
81+
*/
82+
virtual QString quotedValue( const QVariant& value );
83+
84+
/** Compiles an expression node and returns the result of the compilation.
85+
* @param node expression node to compile
86+
* @param str string representing compiled node should be stored in this parameter
87+
* @returns result of node compilation
88+
*/
89+
virtual Result compile( const QgsExpression::Node* node, QString& str );
90+
91+
QString mResult;
92+
QgsFields mFields;
93+
94+
private:
95+
96+
Flags mFlags;
97+
98+
};
99+
100+
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsSqlExpressionCompiler::Flags )
101+
102+
#endif // QGSSQLEXPRESSIONCOMPILER_H

0 commit comments

Comments
 (0)