Skip to content

Commit

Permalink
Merge pull request #2460 from nyalldawson/ogr_compiler
Browse files Browse the repository at this point in the history
[FEATURE] Compiler for OGR and Spatialite expressions
  • Loading branch information
m-kuhn committed Nov 18, 2015
2 parents 86c1ffa + 26b3685 commit 146b15a
Show file tree
Hide file tree
Showing 30 changed files with 923 additions and 505 deletions.
4 changes: 2 additions & 2 deletions src/app/qgsoptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
Expand Down Expand Up @@ -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() );
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ SET(QGIS_CORE_SRCS
qgssnapper.cpp
qgssnappingutils.cpp
qgsspatialindex.cpp
qgssqlexpressioncompiler.cpp
qgsstatisticalsummary.cpp
qgsstringutils.cpp
qgstransaction.cpp
Expand Down Expand Up @@ -624,6 +625,7 @@ SET(QGIS_CORE_HDRS
qgssnapper.h
qgssnappingutils.h
qgsspatialindex.h
qgssqlexpressioncompiler.h
qgsstatisticalsummary.h
qgsstringutils.h
qgstolerance.h
Expand Down
296 changes: 296 additions & 0 deletions src/core/qgssqlexpressioncompiler.cpp
Original file line number Diff line number Diff line change
@@ -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.