Skip to content

Commit 146b15a

Browse files
committed
Merge pull request #2460 from nyalldawson/ogr_compiler
[FEATURE] Compiler for OGR and Spatialite expressions
2 parents 86c1ffa + 26b3685 commit 146b15a

30 files changed

+923
-505
lines changed

src/app/qgsoptions.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl ) :
575575
cbxSnappingOptionsDocked->setChecked( settings.value( "/qgis/dockSnapping", false ).toBool() );
576576
cbxAddPostgisDC->setChecked( settings.value( "/qgis/addPostgisDC", false ).toBool() );
577577
cbxAddOracleDC->setChecked( settings.value( "/qgis/addOracleDC", false ).toBool() );
578-
cbxCompileExpressions->setChecked( settings.value( "/qgis/postgres/compileExpressions", false ).toBool() );
578+
cbxCompileExpressions->setChecked( settings.value( "/qgis/compileExpressions", true ).toBool() );
579579
cbxCreateRasterLegendIcons->setChecked( settings.value( "/qgis/createRasterLegendIcons", false ).toBool() );
580580
cbxCopyWKTGeomFromTable->setChecked( settings.value( "/qgis/copyGeometryAsWKT", true ).toBool() );
581581
leNullValue->setText( settings.value( "qgis/nullValue", "NULL" ).toString() );
@@ -1102,7 +1102,7 @@ void QgsOptions::saveOptions()
11021102
settings.setValue( "/qgis/dockSnapping", cbxSnappingOptionsDocked->isChecked() );
11031103
settings.setValue( "/qgis/addPostgisDC", cbxAddPostgisDC->isChecked() );
11041104
settings.setValue( "/qgis/addOracleDC", cbxAddOracleDC->isChecked() );
1105-
settings.setValue( "/qgis/postgres/compileExpressions", cbxCompileExpressions->isChecked() );
1105+
settings.setValue( "/qgis/compileExpressions", cbxCompileExpressions->isChecked() );
11061106
settings.setValue( "/qgis/defaultLegendGraphicResolution", mLegendGraphicResolutionSpinBox->value() );
11071107
bool createRasterLegendIcons = settings.value( "/qgis/createRasterLegendIcons", false ).toBool();
11081108
settings.setValue( "/qgis/createRasterLegendIcons", cbxCreateRasterLegendIcons->isChecked() );

src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
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

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
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 compileNode( 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::compileNode( 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+
bool failOnPartialNode = false;
98+
switch ( n->op() )
99+
{
100+
case QgsExpression::boEQ:
101+
if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->opLeft()->nodeType() == QgsExpression::ntColumnRef && n->opRight()->nodeType() == QgsExpression::ntColumnRef )
102+
{
103+
// equality between column refs results in a partial compilation, since provider is performing
104+
// case-insensitive matches between strings
105+
partialCompilation = true;
106+
}
107+
108+
op = "=";
109+
break;
110+
111+
case QgsExpression::boGE:
112+
op = ">=";
113+
break;
114+
115+
case QgsExpression::boGT:
116+
op = ">";
117+
break;
118+
119+
case QgsExpression::boLE:
120+
op = "<=";
121+
break;
122+
123+
case QgsExpression::boLT:
124+
op = "<";
125+
break;
126+
127+
case QgsExpression::boIs:
128+
op = "IS";
129+
break;
130+
131+
case QgsExpression::boIsNot:
132+
op = "IS NOT";
133+
failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
134+
break;
135+
136+
case QgsExpression::boLike:
137+
op = "LIKE";
138+
partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive );
139+
break;
140+
141+
case QgsExpression::boILike:
142+
if ( mFlags.testFlag( LikeIsCaseInsensitive ) )
143+
op = "LIKE";
144+
else
145+
op = "ILIKE";
146+
break;
147+
148+
case QgsExpression::boNotLike:
149+
op = "NOT LIKE";
150+
partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive );
151+
failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
152+
break;
153+
154+
case QgsExpression::boNotILike:
155+
failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
156+
if ( mFlags.testFlag( LikeIsCaseInsensitive ) )
157+
op = "NOT LIKE";
158+
else
159+
op = "NOT ILIKE";
160+
break;
161+
162+
case QgsExpression::boOr:
163+
op = "OR";
164+
break;
165+
166+
case QgsExpression::boAnd:
167+
op = "AND";
168+
break;
169+
170+
case QgsExpression::boNE:
171+
failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
172+
op = "<>";
173+
break;
174+
175+
case QgsExpression::boMul:
176+
op = "*";
177+
break;
178+
179+
case QgsExpression::boPlus:
180+
op = "+";
181+
break;
182+
183+
case QgsExpression::boMinus:
184+
op = "-";
185+
break;
186+
187+
case QgsExpression::boDiv:
188+
return Fail; // handle cast to real
189+
190+
case QgsExpression::boMod:
191+
op = "%";
192+
break;
193+
194+
case QgsExpression::boConcat:
195+
op = "||";
196+
break;
197+
198+
case QgsExpression::boIntDiv:
199+
return Fail; // handle cast to int
200+
201+
case QgsExpression::boPow:
202+
op = "^";
203+
break;
204+
205+
case QgsExpression::boRegexp:
206+
op = "~";
207+
break;
208+
}
209+
210+
if ( op.isNull() )
211+
return Fail;
212+
213+
QString left;
214+
Result lr( compileNode( n->opLeft(), left ) );
215+
216+
QString right;
217+
Result rr( compileNode( n->opRight(), right ) );
218+
219+
if ( failOnPartialNode && ( lr == Partial || rr == Partial ) )
220+
return Fail;
221+
222+
result = '(' + left + ' ' + op + ' ' + right + ')';
223+
if ( lr == Complete && rr == Complete )
224+
return ( partialCompilation ? Partial : Complete );
225+
else if (( lr == Partial && rr == Complete ) || ( lr == Complete && rr == Partial ) || ( lr == Partial && rr == Partial ) )
226+
return Partial;
227+
else
228+
return Fail;
229+
}
230+
231+
case QgsExpression::ntLiteral:
232+
{
233+
const QgsExpression::NodeLiteral* n = static_cast<const QgsExpression::NodeLiteral*>( node );
234+
if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->value().type() == QVariant::String )
235+
{
236+
// provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to
237+
// double check results using QGIS' expression engine
238+
result = quotedValue( n->value() );
239+
return Partial;
240+
}
241+
else
242+
{
243+
result = quotedValue( n->value() );
244+
return Complete;
245+
}
246+
}
247+
248+
case QgsExpression::ntColumnRef:
249+
{
250+
const QgsExpression::NodeColumnRef* n = static_cast<const QgsExpression::NodeColumnRef*>( node );
251+
252+
if ( mFields.indexFromName( n->name() ) == -1 )
253+
// Not a provider field
254+
return Fail;
255+
256+
result = quotedIdentifier( n->name() );
257+
258+
return Complete;
259+
}
260+
261+
case QgsExpression::ntInOperator:
262+
{
263+
const QgsExpression::NodeInOperator* n = static_cast<const QgsExpression::NodeInOperator*>( node );
264+
QStringList list;
265+
266+
Result inResult = Complete;
267+
Q_FOREACH ( const QgsExpression::Node* ln, n->list()->list() )
268+
{
269+
QString s;
270+
Result r = compileNode( ln, s );
271+
if ( r == Complete || r == Partial )
272+
{
273+
list << s;
274+
if ( r == Partial )
275+
inResult = Partial;
276+
}
277+
else
278+
return r;
279+
}
280+
281+
QString nd;
282+
Result rn = compileNode( n->node(), nd );
283+
if ( rn != Complete && rn != Partial )
284+
return rn;
285+
286+
result = QString( "%1 %2IN(%3)" ).arg( nd, n->isNotIn() ? "NOT " : "", list.join( "," ) );
287+
return ( inResult == Partial || rn == Partial ) ? Partial : Complete;
288+
}
289+
290+
case QgsExpression::ntFunction:
291+
case QgsExpression::ntCondition:
292+
break;
293+
}
294+
295+
return Fail;
296+
}

0 commit comments

Comments
 (0)