Skip to content
Permalink
Browse files

Merge pull request #2460 from nyalldawson/ogr_compiler

[FEATURE] Compiler for OGR and Spatialite expressions
  • Loading branch information
m-kuhn committed Nov 18, 2015
2 parents 86c1ffa + 26b3685 commit 146b15a1f82cc8db9cbb682be49c1205d73587d9
Showing with 923 additions and 505 deletions.
  1. +2 −2 src/app/qgsoptions.cpp
  2. +2 −0 src/core/CMakeLists.txt
  3. +296 −0 src/core/qgssqlexpressioncompiler.cpp
  4. +103 −0 src/core/qgssqlexpressioncompiler.h
  5. +1 −1 src/providers/ogr/CMakeLists.txt
  6. +95 −0 src/providers/ogr/qgsogrexpressioncompiler.cpp
  7. +42 −0 src/providers/ogr/qgsogrexpressioncompiler.h
  8. +37 −0 src/providers/ogr/qgsogrfeatureiterator.cpp
  9. +6 −0 src/providers/ogr/qgsogrfeatureiterator.h
  10. +32 −5 src/providers/ogr/qgsogrprovider.cpp
  11. +5 −1 src/providers/ogr/qgsogrprovider.h
  12. +6 −190 src/providers/postgres/qgspostgresexpressioncompiler.cpp
  13. +5 −18 src/providers/postgres/qgspostgresexpressioncompiler.h
  14. +2 −2 src/providers/postgres/qgspostgresfeatureiterator.cpp
  15. +1 −0 src/providers/spatialite/CMakeLists.txt
  16. +80 −0 src/providers/spatialite/qgsspatialiteexpressioncompiler.cpp
  17. +37 −0 src/providers/spatialite/qgsspatialiteexpressioncompiler.h
  18. +29 −2 src/providers/spatialite/qgsspatialitefeatureiterator.cpp
  19. +8 −0 src/providers/spatialite/qgsspatialitefeatureiterator.h
  20. +50 −257 src/ui/qgsoptionsbase.ui
  21. +43 −0 tests/src/python/providertestbase.py
  22. +6 −6 tests/src/python/test_provider_memory.py
  23. +2 −2 tests/src/python/test_provider_postgres.py
  24. +7 −1 tests/src/python/test_provider_shapefile.py
  25. +7 −0 tests/src/python/test_provider_spatialite.py
  26. +6 −6 tests/testdata/provider/delimited_wkt.csv
  27. +6 −6 tests/testdata/provider/delimited_xy.csv
  28. BIN tests/testdata/provider/shapefile.dbf
  29. BIN tests/testdata/provider/spatialite.db
  30. +7 −6 tests/testdata/provider/testdata.sql
@@ -575,7 +575,7 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl ) :
cbxSnappingOptionsDocked->setChecked( settings.value( "/qgis/dockSnapping", false ).toBool() );
cbxAddPostgisDC->setChecked( settings.value( "/qgis/addPostgisDC", false ).toBool() );
cbxAddOracleDC->setChecked( settings.value( "/qgis/addOracleDC", false ).toBool() );
cbxCompileExpressions->setChecked( settings.value( "/qgis/postgres/compileExpressions", false ).toBool() );
cbxCompileExpressions->setChecked( settings.value( "/qgis/compileExpressions", true ).toBool() );
cbxCreateRasterLegendIcons->setChecked( settings.value( "/qgis/createRasterLegendIcons", false ).toBool() );
cbxCopyWKTGeomFromTable->setChecked( settings.value( "/qgis/copyGeometryAsWKT", true ).toBool() );
leNullValue->setText( settings.value( "qgis/nullValue", "NULL" ).toString() );
@@ -1102,7 +1102,7 @@ void QgsOptions::saveOptions()
settings.setValue( "/qgis/dockSnapping", cbxSnappingOptionsDocked->isChecked() );
settings.setValue( "/qgis/addPostgisDC", cbxAddPostgisDC->isChecked() );
settings.setValue( "/qgis/addOracleDC", cbxAddOracleDC->isChecked() );
settings.setValue( "/qgis/postgres/compileExpressions", cbxCompileExpressions->isChecked() );
settings.setValue( "/qgis/compileExpressions", cbxCompileExpressions->isChecked() );
settings.setValue( "/qgis/defaultLegendGraphicResolution", mLegendGraphicResolutionSpinBox->value() );
bool createRasterLegendIcons = settings.value( "/qgis/createRasterLegendIcons", false ).toBool();
settings.setValue( "/qgis/createRasterLegendIcons", cbxCreateRasterLegendIcons->isChecked() );
@@ -178,6 +178,7 @@ SET(QGIS_CORE_SRCS
qgssnapper.cpp
qgssnappingutils.cpp
qgsspatialindex.cpp
qgssqlexpressioncompiler.cpp
qgsstatisticalsummary.cpp
qgsstringutils.cpp
qgstransaction.cpp
@@ -624,6 +625,7 @@ SET(QGIS_CORE_HDRS
qgssnapper.h
qgssnappingutils.h
qgsspatialindex.h
qgssqlexpressioncompiler.h
qgsstatisticalsummary.h
qgsstringutils.h
qgstolerance.h
@@ -0,0 +1,296 @@
/***************************************************************************
qgssqlexpressioncompiler.cpp
----------------------------
begin : November 2015
copyright : (C) 2015 Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgssqlexpressioncompiler.h"

QgsSqlExpressionCompiler::QgsSqlExpressionCompiler( const QgsFields& fields, const Flags& flags )
: mResult( None )
, mFields( fields )
, mFlags( flags )
{
}

QgsSqlExpressionCompiler::~QgsSqlExpressionCompiler()
{

}

QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compile( const QgsExpression* exp )
{
if ( exp->rootNode() )
return compileNode( exp->rootNode(), mResult );
else
return Fail;
}

QString QgsSqlExpressionCompiler::quotedIdentifier( const QString& identifier )
{
QString quoted = identifier;
quoted.replace( '"', "\"\"" );
quoted = quoted.prepend( '\"' ).append( '\"' );
return quoted;
}

QString QgsSqlExpressionCompiler::quotedValue( const QVariant& value )
{
if ( value.isNull() )
return "NULL";

switch ( value.type() )
{
case QVariant::Int:
case QVariant::LongLong:
case QVariant::Double:
return value.toString();

case QVariant::Bool:
return value.toBool() ? "TRUE" : "FALSE";

default:
case QVariant::String:
QString v = value.toString();
v.replace( '\'', "''" );
if ( v.contains( '\\' ) )
return v.replace( '\\', "\\\\" ).prepend( "E'" ).append( '\'' );
else
return v.prepend( '\'' ).append( '\'' );
}
}

QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const QgsExpression::Node* node, QString& result )
{
switch ( node->nodeType() )
{
case QgsExpression::ntUnaryOperator:
{
const QgsExpression::NodeUnaryOperator* n = static_cast<const QgsExpression::NodeUnaryOperator*>( node );
switch ( n->op() )
{
case QgsExpression::uoNot:
break;

case QgsExpression::uoMinus:
break;
}

break;
}

case QgsExpression::ntBinaryOperator:
{
const QgsExpression::NodeBinaryOperator* n = static_cast<const QgsExpression::NodeBinaryOperator*>( node );

QString op;
bool partialCompilation = false;
bool failOnPartialNode = false;
switch ( n->op() )
{
case QgsExpression::boEQ:
if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->opLeft()->nodeType() == QgsExpression::ntColumnRef && n->opRight()->nodeType() == QgsExpression::ntColumnRef )
{
// equality between column refs results in a partial compilation, since provider is performing
// case-insensitive matches between strings
partialCompilation = true;
}

op = "=";
break;

case QgsExpression::boGE:
op = ">=";
break;

case QgsExpression::boGT:
op = ">";
break;

case QgsExpression::boLE:
op = "<=";
break;

case QgsExpression::boLT:
op = "<";
break;

case QgsExpression::boIs:
op = "IS";
break;

case QgsExpression::boIsNot:
op = "IS NOT";
failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
break;

case QgsExpression::boLike:
op = "LIKE";
partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive );
break;

case QgsExpression::boILike:
if ( mFlags.testFlag( LikeIsCaseInsensitive ) )
op = "LIKE";
else
op = "ILIKE";
break;

case QgsExpression::boNotLike:
op = "NOT LIKE";
partialCompilation = mFlags.testFlag( LikeIsCaseInsensitive );
failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
break;

case QgsExpression::boNotILike:
failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
if ( mFlags.testFlag( LikeIsCaseInsensitive ) )
op = "NOT LIKE";
else
op = "NOT ILIKE";
break;

case QgsExpression::boOr:
op = "OR";
break;

case QgsExpression::boAnd:
op = "AND";
break;

case QgsExpression::boNE:
failOnPartialNode = mFlags.testFlag( CaseInsensitiveStringMatch );
op = "<>";
break;

case QgsExpression::boMul:
op = "*";
break;

case QgsExpression::boPlus:
op = "+";
break;

case QgsExpression::boMinus:
op = "-";
break;

case QgsExpression::boDiv:
return Fail; // handle cast to real

case QgsExpression::boMod:
op = "%";
break;

case QgsExpression::boConcat:
op = "||";
break;

case QgsExpression::boIntDiv:
return Fail; // handle cast to int

case QgsExpression::boPow:
op = "^";
break;

case QgsExpression::boRegexp:
op = "~";
break;
}

if ( op.isNull() )
return Fail;

QString left;
Result lr( compileNode( n->opLeft(), left ) );

QString right;
Result rr( compileNode( n->opRight(), right ) );

if ( failOnPartialNode && ( lr == Partial || rr == Partial ) )
return Fail;

result = '(' + left + ' ' + op + ' ' + right + ')';
if ( lr == Complete && rr == Complete )
return ( partialCompilation ? Partial : Complete );
else if (( lr == Partial && rr == Complete ) || ( lr == Complete && rr == Partial ) || ( lr == Partial && rr == Partial ) )
return Partial;
else
return Fail;
}

case QgsExpression::ntLiteral:
{
const QgsExpression::NodeLiteral* n = static_cast<const QgsExpression::NodeLiteral*>( node );
if ( mFlags.testFlag( CaseInsensitiveStringMatch ) && n->value().type() == QVariant::String )
{
// provider uses case insensitive matching, so if literal was a string then we only have a Partial compilation and need to
// double check results using QGIS' expression engine
result = quotedValue( n->value() );
return Partial;
}
else
{
result = quotedValue( n->value() );
return Complete;
}
}

case QgsExpression::ntColumnRef:
{
const QgsExpression::NodeColumnRef* n = static_cast<const QgsExpression::NodeColumnRef*>( node );

if ( mFields.indexFromName( n->name() ) == -1 )
// Not a provider field
return Fail;

result = quotedIdentifier( n->name() );

return Complete;
}

case QgsExpression::ntInOperator:
{
const QgsExpression::NodeInOperator* n = static_cast<const QgsExpression::NodeInOperator*>( node );
QStringList list;

Result inResult = Complete;
Q_FOREACH ( const QgsExpression::Node* ln, n->list()->list() )
{
QString s;
Result r = compileNode( ln, s );
if ( r == Complete || r == Partial )
{
list << s;
if ( r == Partial )
inResult = Partial;
}
else
return r;
}

QString nd;
Result rn = compileNode( n->node(), nd );
if ( rn != Complete && rn != Partial )
return rn;

result = QString( "%1 %2IN(%3)" ).arg( nd, n->isNotIn() ? "NOT " : "", list.join( "," ) );
return ( inResult == Partial || rn == Partial ) ? Partial : Complete;
}

case QgsExpression::ntFunction:
case QgsExpression::ntCondition:
break;
}

return Fail;
}

0 comments on commit 146b15a

Please sign in to comment.
You can’t perform that action at this time.