Skip to content

Commit 9615ac4

Browse files
committed
Handle division operator during expression compilation
1 parent 56d5a37 commit 9615ac4

10 files changed

+116
-32
lines changed

src/core/qgssqlexpressioncompiler.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const Qg
215215
break;
216216

217217
case QgsExpression::boDiv:
218-
return Fail; // handle cast to real
218+
op = QStringLiteral( "/" );
219+
break;
219220

220221
case QgsExpression::boMod:
221222
op = QStringLiteral( "%" );
@@ -226,7 +227,8 @@ QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const Qg
226227
break;
227228

228229
case QgsExpression::boIntDiv:
229-
return Fail; // handle cast to int
230+
op = QStringLiteral( "/" );
231+
break;
230232

231233
case QgsExpression::boPow:
232234
op = QStringLiteral( "^" );
@@ -249,7 +251,27 @@ QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const Qg
249251
if ( failOnPartialNode && ( lr == Partial || rr == Partial ) )
250252
return Fail;
251253

254+
if ( n->op() == QgsExpression::boDiv && mFlags.testFlag( IntegerDivisionResultsInInteger ) )
255+
{
256+
right = castToReal( right );
257+
if ( right.isEmpty() )
258+
{
259+
// not supported
260+
return Fail;
261+
}
262+
}
263+
252264
result = '(' + left + ' ' + op + ' ' + right + ')';
265+
if ( n->op() == QgsExpression::boIntDiv )
266+
{
267+
result = castToInt( result );
268+
if ( result.isEmpty() )
269+
{
270+
// not supported
271+
return Fail;
272+
}
273+
}
274+
253275
if ( lr == Complete && rr == Complete )
254276
return ( partialCompilation ? Partial : Complete );
255277
else if (( lr == Partial && rr == Complete ) || ( lr == Complete && rr == Partial ) || ( lr == Partial && rr == Partial ) )
@@ -373,6 +395,18 @@ QStringList QgsSqlExpressionCompiler::sqlArgumentsFromFunctionName( const QStrin
373395
return QStringList( fnArgs );
374396
}
375397

398+
QString QgsSqlExpressionCompiler::castToReal( const QString& value ) const
399+
{
400+
Q_UNUSED( value );
401+
return QString();
402+
}
403+
404+
QString QgsSqlExpressionCompiler::castToInt( const QString& value ) const
405+
{
406+
Q_UNUSED( value );
407+
return QString();
408+
}
409+
376410
bool QgsSqlExpressionCompiler::nodeIsNullLiteral( const QgsExpression::Node* node ) const
377411
{
378412
if ( node->nodeType() != QgsExpression::ntLiteral )

src/core/qgssqlexpressioncompiler.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@ class CORE_EXPORT QgsSqlExpressionCompiler
4747
*/
4848
enum Flag
4949
{
50-
CaseInsensitiveStringMatch = 0x01, //!< Provider performs case-insensitive string matching for all strings
51-
LikeIsCaseInsensitive = 0x02, //!< Provider treats LIKE as case-insensitive
52-
NoNullInBooleanLogic = 0x04, //!< Provider does not support using NULL with boolean logic, e.g., "(...) OR NULL"
53-
NoUnaryMinus = 0x08, //!< Provider does not unary minus, e.g., " -( 100 * 2 ) = ..."
50+
CaseInsensitiveStringMatch = 1, //!< Provider performs case-insensitive string matching for all strings
51+
LikeIsCaseInsensitive = 1 << 1, //!< Provider treats LIKE as case-insensitive
52+
NoNullInBooleanLogic = 1 << 2, //!< Provider does not support using NULL with boolean logic, e.g., "(...) OR NULL"
53+
NoUnaryMinus = 1 << 3, //!< Provider does not unary minus, e.g., " -( 100 * 2 ) = ..."
54+
IntegerDivisionResultsInInteger = 1 << 4, //!< Dividing int by int results in int on provider. Subclass must implement the castToReal() function to allow compilation of division.
5455
};
5556
Q_DECLARE_FLAGS( Flags, Flag )
5657

@@ -108,6 +109,21 @@ class CORE_EXPORT QgsSqlExpressionCompiler
108109
*/
109110
virtual QStringList sqlArgumentsFromFunctionName( const QString& fnName, const QStringList& fnArgs ) const;
110111

112+
/**
113+
* Casts a value to a real result. Subclasses which indicate the IntegerDivisionResultsInInteger
114+
* flag must reimplement this to cast a numeric value to a real type value so that division results
115+
* in a real value result instead of integer.
116+
* @note added in QGIS 3.0
117+
*/
118+
virtual QString castToReal( const QString& value ) const;
119+
120+
/**
121+
* Casts a value to a integer result. Subclasses must reimplement this to cast a numeric value to a integer
122+
* type value so that integer division results in a integer value result instead of real.
123+
* @note added in QGIS 3.0
124+
*/
125+
virtual QString castToInt( const QString& value ) const;
126+
111127
QString mResult;
112128
QgsFields mFields;
113129

src/core/qgssqliteexpressioncompiler.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#include "qgssqlexpressioncompiler.h"
2020

2121
QgsSQLiteExpressionCompiler::QgsSQLiteExpressionCompiler( const QgsFields& fields )
22-
: QgsSqlExpressionCompiler( fields, QgsSqlExpressionCompiler::LikeIsCaseInsensitive )
22+
: QgsSqlExpressionCompiler( fields, QgsSqlExpressionCompiler::LikeIsCaseInsensitive | QgsSqlExpressionCompiler::IntegerDivisionResultsInInteger )
2323
{
2424
}
2525

@@ -84,4 +84,14 @@ QString QgsSQLiteExpressionCompiler::quotedValue( const QVariant& value, bool& o
8484
}
8585
}
8686

87+
QString QgsSQLiteExpressionCompiler::castToReal( const QString& value ) const
88+
{
89+
return QStringLiteral( "CAST((%1) AS REAL)" ).arg( value );
90+
}
91+
92+
QString QgsSQLiteExpressionCompiler::castToInt( const QString& value ) const
93+
{
94+
return QStringLiteral( "CAST((%1) AS INTEGER)" ).arg( value );
95+
}
96+
8797
///@endcond

src/core/qgssqliteexpressioncompiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class CORE_EXPORT QgsSQLiteExpressionCompiler : public QgsSqlExpressionCompiler
4444
virtual Result compileNode( const QgsExpression::Node* node, QString& str ) override;
4545
virtual QString quotedIdentifier( const QString& identifier ) override;
4646
virtual QString quotedValue( const QVariant& value, bool& ok ) override;
47+
virtual QString castToReal( const QString& value ) const override;
48+
virtual QString castToInt( const QString& value ) const override;
4749

4850
};
4951

src/providers/ogr/qgsogrexpressioncompiler.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
QgsOgrExpressionCompiler::QgsOgrExpressionCompiler( QgsOgrFeatureSource* source )
2020
: QgsSqlExpressionCompiler( source->mFields, QgsSqlExpressionCompiler::CaseInsensitiveStringMatch | QgsSqlExpressionCompiler::NoNullInBooleanLogic
21-
| QgsSqlExpressionCompiler::NoUnaryMinus )
21+
| QgsSqlExpressionCompiler::NoUnaryMinus | QgsSqlExpressionCompiler::IntegerDivisionResultsInInteger )
2222
, mSource( source )
2323
{
2424
}
@@ -57,10 +57,8 @@ QgsSqlExpressionCompiler::Result QgsOgrExpressionCompiler::compileNode( const Qg
5757
case QgsExpression::boNotILike:
5858
return Fail; //disabled until https://trac.osgeo.org/gdal/ticket/5132 is fixed
5959

60-
case QgsExpression::boDiv:
6160
case QgsExpression::boMod:
6261
case QgsExpression::boConcat:
63-
case QgsExpression::boIntDiv:
6462
case QgsExpression::boPow:
6563
case QgsExpression::boRegexp:
6664
return Fail; //not supported by OGR
@@ -103,3 +101,13 @@ QString QgsOgrExpressionCompiler::quotedValue( const QVariant& value, bool& ok )
103101

104102
return QgsOgrProviderUtils::quotedValue( value );
105103
}
104+
105+
QString QgsOgrExpressionCompiler::castToReal( const QString& value ) const
106+
{
107+
return QStringLiteral( "CAST((%1) AS float)" ).arg( value );
108+
}
109+
110+
QString QgsOgrExpressionCompiler::castToInt( const QString& value ) const
111+
{
112+
return QStringLiteral( "CAST((%1) AS integer)" ).arg( value );
113+
}

src/providers/ogr/qgsogrexpressioncompiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class QgsOgrExpressionCompiler : public QgsSqlExpressionCompiler
3333
virtual Result compileNode( const QgsExpression::Node* node, QString& str ) override;
3434
virtual QString quotedIdentifier( const QString& identifier ) override;
3535
virtual QString quotedValue( const QVariant& value, bool& ok ) override;
36+
virtual QString castToReal( const QString& value ) const override;
37+
virtual QString castToInt( const QString& value ) const override;
3638

3739
private:
3840

src/providers/postgres/qgspostgresexpressioncompiler.cpp

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#include "qgssqlexpressioncompiler.h"
1818

1919
QgsPostgresExpressionCompiler::QgsPostgresExpressionCompiler( QgsPostgresFeatureSource* source )
20-
: QgsSqlExpressionCompiler( source->mFields )
20+
: QgsSqlExpressionCompiler( source->mFields, QgsSqlExpressionCompiler::IntegerDivisionResultsInInteger )
2121
, mGeometryColumn( source->mGeometryColumn )
2222
, mSpatialColType( source->mSpatialColType )
2323
, mDetectedGeomType( source->mDetectedGeomType )
@@ -88,15 +88,19 @@ static const QMap<QString, QString>& functionNamesSqlFunctionsMap()
8888
{ "buffer", "ST_Buffer" },
8989
{ "centroid", "ST_Centroid" },
9090
{ "point_on_surface", "ST_PointOnSurface" },
91-
//{ "reverse", "ST_Reverse" },
92-
//{ "is_closed", "ST_IsClosed" },
93-
//{ "convex_hull", "ST_ConvexHull" },
94-
//{ "difference", "ST_Difference" },
91+
#if 0
92+
{ "reverse", "ST_Reverse" },
93+
{ "is_closed", "ST_IsClosed" },
94+
{ "convex_hull", "ST_ConvexHull" },
95+
{ "difference", "ST_Difference" },
96+
#endif
9597
{ "distance", "ST_Distance" },
96-
//{ "intersection", "ST_Intersection" },
97-
//{ "sym_difference", "ST_SymDifference" },
98-
//{ "combine", "ST_Union" },
99-
//{ "union", "ST_Union" },
98+
#if 0
99+
{ "intersection", "ST_Intersection" },
100+
{ "sym_difference", "ST_SymDifference" },
101+
{ "combine", "ST_Union" },
102+
{ "union", "ST_Union" },
103+
#endif
100104
{ "geom_from_wkt", "ST_GeomFromText" },
101105
{ "geom_from_gml", "ST_GeomFromGML" }
102106
};
@@ -132,6 +136,16 @@ QStringList QgsPostgresExpressionCompiler::sqlArgumentsFromFunctionName( const Q
132136
return args;
133137
}
134138

139+
QString QgsPostgresExpressionCompiler::castToReal( const QString& value ) const
140+
{
141+
return QStringLiteral( "((%1)::real)" ).arg( value );
142+
}
143+
144+
QString QgsPostgresExpressionCompiler::castToInt( const QString& value ) const
145+
{
146+
return QStringLiteral( "((%1)::int)" ).arg( value );
147+
}
148+
135149
QgsSqlExpressionCompiler::Result QgsPostgresExpressionCompiler::compileNode( const QgsExpression::Node* node, QString& result )
136150
{
137151
switch ( node->nodeType() )
@@ -146,11 +160,13 @@ QgsSqlExpressionCompiler::Result QgsPostgresExpressionCompiler::compileNode( con
146160
result = quotedIdentifier( mGeometryColumn );
147161
return Complete;
148162
}
163+
#if 0
149164
/*
150165
* These methods are tricky
151166
* QGIS expression versions of these return ellipsoidal measurements
152167
* based on the project settings, and also convert the result to the
153168
* units specified in project properties.
169+
*/
154170
else if ( fd->name() == "$area" )
155171
{
156172
result = QStringLiteral( "ST_Area(%1)" ).arg( quotedIdentifier( mGeometryColumn ) );
@@ -176,7 +192,7 @@ QgsSqlExpressionCompiler::Result QgsPostgresExpressionCompiler::compileNode( con
176192
result = QStringLiteral( "ST_Y(%1)" ).arg( quotedIdentifier( mGeometryColumn ) );
177193
return Complete;
178194
}
179-
*/
195+
#endif
180196
}
181197

182198
default:

src/providers/postgres/qgspostgresexpressioncompiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class QgsPostgresExpressionCompiler : public QgsSqlExpressionCompiler
3434
virtual Result compileNode( const QgsExpression::Node* node, QString& str ) override;
3535
virtual QString sqlFunctionFromFunctionName( const QString& fnName ) const override;
3636
virtual QStringList sqlArgumentsFromFunctionName( const QString& fnName, const QStringList& fnArgs ) const override;
37+
virtual QString castToReal( const QString& value ) const override;
38+
virtual QString castToInt( const QString& value ) const override;
3739

3840
QString mGeometryColumn;
3941
QgsPostgresGeometryColumnType mSpatialColType;

tests/src/python/providertestbase.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ def runGetFeatureTests(self, provider):
168168
self.assert_query(provider, 'cnt <= 100', [1, 5])
169169
self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4])
170170
self.assert_query(provider, 'cnt = 50 * 2', [1])
171+
self.assert_query(provider, 'cnt = 150 / 1.5', [1])
172+
self.assert_query(provider, 'cnt = 1000 / 10', [1])
173+
self.assert_query(provider, 'cnt = 1000/11+10', []) # checks that provider isn't rounding int/int
174+
self.assert_query(provider, 'pk = 9 // 4', [2]) # int division
171175
self.assert_query(provider, 'cnt = 99 + 1', [1])
172176
self.assert_query(provider, 'cnt = 101 - 1', [1])
173177
self.assert_query(provider, 'cnt - 1 = 99', [1])

tests/src/python/test_provider_postgres.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,7 @@ def disableCompiler(self):
9494
QSettings().setValue('/qgis/compileExpressions', False)
9595

9696
def uncompiledFilters(self):
97-
return set([
98-
'round(cnt / 66.67) <= 2',
99-
'floor(cnt / 66.67) <= 2',
100-
'ceil(cnt / 66.67) <= 2',
101-
'pk < pi() / 2'
102-
])
97+
return set([])
10398

10499
def partiallyCompiledFilters(self):
105100
return set([])
@@ -669,12 +664,7 @@ def disableCompiler(self):
669664
QSettings().setValue('/qgis/compileExpressions', False)
670665

671666
def uncompiledFilters(self):
672-
return set([
673-
'round(cnt / 66.67) <= 2',
674-
'floor(cnt / 66.67) <= 2',
675-
'ceil(cnt / 66.67) <= 2',
676-
'pk < pi() / 2'
677-
])
667+
return set([])
678668

679669
def partiallyCompiledFilters(self):
680670
return set([])

0 commit comments

Comments
 (0)