Skip to content

Commit

Permalink
Handle division operator during expression compilation
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jan 3, 2017
1 parent 56d5a37 commit 9615ac4
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 32 deletions.
38 changes: 36 additions & 2 deletions src/core/qgssqlexpressioncompiler.cpp
Expand Up @@ -215,7 +215,8 @@ QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const Qg
break;

case QgsExpression::boDiv:
return Fail; // handle cast to real
op = QStringLiteral( "/" );
break;

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

case QgsExpression::boIntDiv:
return Fail; // handle cast to int
op = QStringLiteral( "/" );
break;

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

if ( n->op() == QgsExpression::boDiv && mFlags.testFlag( IntegerDivisionResultsInInteger ) )
{
right = castToReal( right );
if ( right.isEmpty() )
{
// not supported
return Fail;
}
}

result = '(' + left + ' ' + op + ' ' + right + ')';
if ( n->op() == QgsExpression::boIntDiv )
{
result = castToInt( result );
if ( result.isEmpty() )
{
// not supported
return Fail;
}
}

if ( lr == Complete && rr == Complete )
return ( partialCompilation ? Partial : Complete );
else if (( lr == Partial && rr == Complete ) || ( lr == Complete && rr == Partial ) || ( lr == Partial && rr == Partial ) )
Expand Down Expand Up @@ -373,6 +395,18 @@ QStringList QgsSqlExpressionCompiler::sqlArgumentsFromFunctionName( const QStrin
return QStringList( fnArgs );
}

QString QgsSqlExpressionCompiler::castToReal( const QString& value ) const
{
Q_UNUSED( value );
return QString();
}

QString QgsSqlExpressionCompiler::castToInt( const QString& value ) const
{
Q_UNUSED( value );
return QString();
}

bool QgsSqlExpressionCompiler::nodeIsNullLiteral( const QgsExpression::Node* node ) const
{
if ( node->nodeType() != QgsExpression::ntLiteral )
Expand Down
24 changes: 20 additions & 4 deletions src/core/qgssqlexpressioncompiler.h
Expand Up @@ -47,10 +47,11 @@ class CORE_EXPORT QgsSqlExpressionCompiler
*/
enum Flag
{
CaseInsensitiveStringMatch = 0x01, //!< Provider performs case-insensitive string matching for all strings
LikeIsCaseInsensitive = 0x02, //!< Provider treats LIKE as case-insensitive
NoNullInBooleanLogic = 0x04, //!< Provider does not support using NULL with boolean logic, e.g., "(...) OR NULL"
NoUnaryMinus = 0x08, //!< Provider does not unary minus, e.g., " -( 100 * 2 ) = ..."
CaseInsensitiveStringMatch = 1, //!< Provider performs case-insensitive string matching for all strings
LikeIsCaseInsensitive = 1 << 1, //!< Provider treats LIKE as case-insensitive
NoNullInBooleanLogic = 1 << 2, //!< Provider does not support using NULL with boolean logic, e.g., "(...) OR NULL"
NoUnaryMinus = 1 << 3, //!< Provider does not unary minus, e.g., " -( 100 * 2 ) = ..."
IntegerDivisionResultsInInteger = 1 << 4, //!< Dividing int by int results in int on provider. Subclass must implement the castToReal() function to allow compilation of division.
};
Q_DECLARE_FLAGS( Flags, Flag )

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

/**
* Casts a value to a real result. Subclasses which indicate the IntegerDivisionResultsInInteger
* flag must reimplement this to cast a numeric value to a real type value so that division results
* in a real value result instead of integer.
* @note added in QGIS 3.0
*/
virtual QString castToReal( const QString& value ) const;

/**
* Casts a value to a integer result. Subclasses must reimplement this to cast a numeric value to a integer
* type value so that integer division results in a integer value result instead of real.
* @note added in QGIS 3.0
*/
virtual QString castToInt( const QString& value ) const;

QString mResult;
QgsFields mFields;

Expand Down
12 changes: 11 additions & 1 deletion src/core/qgssqliteexpressioncompiler.cpp
Expand Up @@ -19,7 +19,7 @@
#include "qgssqlexpressioncompiler.h"

QgsSQLiteExpressionCompiler::QgsSQLiteExpressionCompiler( const QgsFields& fields )
: QgsSqlExpressionCompiler( fields, QgsSqlExpressionCompiler::LikeIsCaseInsensitive )
: QgsSqlExpressionCompiler( fields, QgsSqlExpressionCompiler::LikeIsCaseInsensitive | QgsSqlExpressionCompiler::IntegerDivisionResultsInInteger )
{
}

Expand Down Expand Up @@ -84,4 +84,14 @@ QString QgsSQLiteExpressionCompiler::quotedValue( const QVariant& value, bool& o
}
}

QString QgsSQLiteExpressionCompiler::castToReal( const QString& value ) const
{
return QStringLiteral( "CAST((%1) AS REAL)" ).arg( value );
}

QString QgsSQLiteExpressionCompiler::castToInt( const QString& value ) const
{
return QStringLiteral( "CAST((%1) AS INTEGER)" ).arg( value );
}

///@endcond
2 changes: 2 additions & 0 deletions src/core/qgssqliteexpressioncompiler.h
Expand Up @@ -44,6 +44,8 @@ class CORE_EXPORT QgsSQLiteExpressionCompiler : public QgsSqlExpressionCompiler
virtual Result compileNode( const QgsExpression::Node* node, QString& str ) override;
virtual QString quotedIdentifier( const QString& identifier ) override;
virtual QString quotedValue( const QVariant& value, bool& ok ) override;
virtual QString castToReal( const QString& value ) const override;
virtual QString castToInt( const QString& value ) const override;

};

Expand Down
14 changes: 11 additions & 3 deletions src/providers/ogr/qgsogrexpressioncompiler.cpp
Expand Up @@ -18,7 +18,7 @@

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

case QgsExpression::boDiv:
case QgsExpression::boMod:
case QgsExpression::boConcat:
case QgsExpression::boIntDiv:
case QgsExpression::boPow:
case QgsExpression::boRegexp:
return Fail; //not supported by OGR
Expand Down Expand Up @@ -103,3 +101,13 @@ QString QgsOgrExpressionCompiler::quotedValue( const QVariant& value, bool& ok )

return QgsOgrProviderUtils::quotedValue( value );
}

QString QgsOgrExpressionCompiler::castToReal( const QString& value ) const
{
return QStringLiteral( "CAST((%1) AS float)" ).arg( value );
}

QString QgsOgrExpressionCompiler::castToInt( const QString& value ) const
{
return QStringLiteral( "CAST((%1) AS integer)" ).arg( value );
}
2 changes: 2 additions & 0 deletions src/providers/ogr/qgsogrexpressioncompiler.h
Expand Up @@ -33,6 +33,8 @@ class QgsOgrExpressionCompiler : public QgsSqlExpressionCompiler
virtual Result compileNode( const QgsExpression::Node* node, QString& str ) override;
virtual QString quotedIdentifier( const QString& identifier ) override;
virtual QString quotedValue( const QVariant& value, bool& ok ) override;
virtual QString castToReal( const QString& value ) const override;
virtual QString castToInt( const QString& value ) const override;

private:

Expand Down
36 changes: 26 additions & 10 deletions src/providers/postgres/qgspostgresexpressioncompiler.cpp
Expand Up @@ -17,7 +17,7 @@
#include "qgssqlexpressioncompiler.h"

QgsPostgresExpressionCompiler::QgsPostgresExpressionCompiler( QgsPostgresFeatureSource* source )
: QgsSqlExpressionCompiler( source->mFields )
: QgsSqlExpressionCompiler( source->mFields, QgsSqlExpressionCompiler::IntegerDivisionResultsInInteger )
, mGeometryColumn( source->mGeometryColumn )
, mSpatialColType( source->mSpatialColType )
, mDetectedGeomType( source->mDetectedGeomType )
Expand Down Expand Up @@ -88,15 +88,19 @@ static const QMap<QString, QString>& functionNamesSqlFunctionsMap()
{ "buffer", "ST_Buffer" },
{ "centroid", "ST_Centroid" },
{ "point_on_surface", "ST_PointOnSurface" },
//{ "reverse", "ST_Reverse" },
//{ "is_closed", "ST_IsClosed" },
//{ "convex_hull", "ST_ConvexHull" },
//{ "difference", "ST_Difference" },
#if 0
{ "reverse", "ST_Reverse" },
{ "is_closed", "ST_IsClosed" },
{ "convex_hull", "ST_ConvexHull" },
{ "difference", "ST_Difference" },
#endif
{ "distance", "ST_Distance" },
//{ "intersection", "ST_Intersection" },
//{ "sym_difference", "ST_SymDifference" },
//{ "combine", "ST_Union" },
//{ "union", "ST_Union" },
#if 0
{ "intersection", "ST_Intersection" },
{ "sym_difference", "ST_SymDifference" },
{ "combine", "ST_Union" },
{ "union", "ST_Union" },
#endif
{ "geom_from_wkt", "ST_GeomFromText" },
{ "geom_from_gml", "ST_GeomFromGML" }
};
Expand Down Expand Up @@ -132,6 +136,16 @@ QStringList QgsPostgresExpressionCompiler::sqlArgumentsFromFunctionName( const Q
return args;
}

QString QgsPostgresExpressionCompiler::castToReal( const QString& value ) const
{
return QStringLiteral( "((%1)::real)" ).arg( value );
}

QString QgsPostgresExpressionCompiler::castToInt( const QString& value ) const
{
return QStringLiteral( "((%1)::int)" ).arg( value );
}

QgsSqlExpressionCompiler::Result QgsPostgresExpressionCompiler::compileNode( const QgsExpression::Node* node, QString& result )
{
switch ( node->nodeType() )
Expand All @@ -146,11 +160,13 @@ QgsSqlExpressionCompiler::Result QgsPostgresExpressionCompiler::compileNode( con
result = quotedIdentifier( mGeometryColumn );
return Complete;
}
#if 0
/*
* These methods are tricky
* QGIS expression versions of these return ellipsoidal measurements
* based on the project settings, and also convert the result to the
* units specified in project properties.
*/
else if ( fd->name() == "$area" )
{
result = QStringLiteral( "ST_Area(%1)" ).arg( quotedIdentifier( mGeometryColumn ) );
Expand All @@ -176,7 +192,7 @@ QgsSqlExpressionCompiler::Result QgsPostgresExpressionCompiler::compileNode( con
result = QStringLiteral( "ST_Y(%1)" ).arg( quotedIdentifier( mGeometryColumn ) );
return Complete;
}
*/
#endif
}

default:
Expand Down
2 changes: 2 additions & 0 deletions src/providers/postgres/qgspostgresexpressioncompiler.h
Expand Up @@ -34,6 +34,8 @@ class QgsPostgresExpressionCompiler : public QgsSqlExpressionCompiler
virtual Result compileNode( const QgsExpression::Node* node, QString& str ) override;
virtual QString sqlFunctionFromFunctionName( const QString& fnName ) const override;
virtual QStringList sqlArgumentsFromFunctionName( const QString& fnName, const QStringList& fnArgs ) const override;
virtual QString castToReal( const QString& value ) const override;
virtual QString castToInt( const QString& value ) const override;

QString mGeometryColumn;
QgsPostgresGeometryColumnType mSpatialColType;
Expand Down
4 changes: 4 additions & 0 deletions tests/src/python/providertestbase.py
Expand Up @@ -168,6 +168,10 @@ def runGetFeatureTests(self, provider):
self.assert_query(provider, 'cnt <= 100', [1, 5])
self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4])
self.assert_query(provider, 'cnt = 50 * 2', [1])
self.assert_query(provider, 'cnt = 150 / 1.5', [1])
self.assert_query(provider, 'cnt = 1000 / 10', [1])
self.assert_query(provider, 'cnt = 1000/11+10', []) # checks that provider isn't rounding int/int
self.assert_query(provider, 'pk = 9 // 4', [2]) # int division
self.assert_query(provider, 'cnt = 99 + 1', [1])
self.assert_query(provider, 'cnt = 101 - 1', [1])
self.assert_query(provider, 'cnt - 1 = 99', [1])
Expand Down
14 changes: 2 additions & 12 deletions tests/src/python/test_provider_postgres.py
Expand Up @@ -94,12 +94,7 @@ def disableCompiler(self):
QSettings().setValue('/qgis/compileExpressions', False)

def uncompiledFilters(self):
return set([
'round(cnt / 66.67) <= 2',
'floor(cnt / 66.67) <= 2',
'ceil(cnt / 66.67) <= 2',
'pk < pi() / 2'
])
return set([])

def partiallyCompiledFilters(self):
return set([])
Expand Down Expand Up @@ -669,12 +664,7 @@ def disableCompiler(self):
QSettings().setValue('/qgis/compileExpressions', False)

def uncompiledFilters(self):
return set([
'round(cnt / 66.67) <= 2',
'floor(cnt / 66.67) <= 2',
'ceil(cnt / 66.67) <= 2',
'pk < pi() / 2'
])
return set([])

def partiallyCompiledFilters(self):
return set([])
Expand Down

0 comments on commit 9615ac4

Please sign in to comment.