Skip to content

Commit

Permalink
[FEATURE] Compiler for OGR expressions
Browse files Browse the repository at this point in the history
Allows much faster retrieval of features when a FilterExpression
feature request is used.

OGR SQL supports only basic expressions, so this is really only
enabled for "attribute"='value' type expressions.
  • Loading branch information
nyalldawson committed Nov 17, 2015
1 parent f184ec9 commit d19adfe
Show file tree
Hide file tree
Showing 8 changed files with 340 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/providers/ogr/CMakeLists.txt
@@ -1,5 +1,5 @@

SET (OGR_SRCS qgsogrprovider.cpp qgsogrdataitems.cpp qgsogrfeatureiterator.cpp qgsogrgeometrysimplifier.cpp qgsogrconnpool.cpp)
SET (OGR_SRCS qgsogrprovider.cpp qgsogrdataitems.cpp qgsogrfeatureiterator.cpp qgsogrgeometrysimplifier.cpp qgsogrconnpool.cpp qgsogrexpressioncompiler.cpp)

SET(OGR_MOC_HDRS qgsogrprovider.h qgsogrdataitems.h qgsogrconnpool.h)

Expand Down
212 changes: 212 additions & 0 deletions src/providers/ogr/qgsogrexpressioncompiler.cpp
@@ -0,0 +1,212 @@
/***************************************************************************
qgsogrexpressioncompiler.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 "qgsogrexpressioncompiler.h"
#include "qgsogrprovider.h"

QgsOgrExpressionCompiler::QgsOgrExpressionCompiler( QgsOgrFeatureSource* source )
: mResult( None )
, mSource( source )
{
}

QgsOgrExpressionCompiler::~QgsOgrExpressionCompiler()
{

}

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

QgsOgrExpressionCompiler::Result QgsOgrExpressionCompiler::compile( 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;
switch ( n->op() )
{
case QgsExpression::boEQ:
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";
break;

case QgsExpression::boLike:
op = "LIKE";
break;

case QgsExpression::boILike:
op = "ILIKE";
return Fail; //disabled until https://trac.osgeo.org/gdal/ticket/5132 is fixed

case QgsExpression::boNotLike:
op = "NOT LIKE";
break;

case QgsExpression::boNotILike:
op = "NOT ILIKE";
return Fail; //disabled until https://trac.osgeo.org/gdal/ticket/5132 is fixed

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

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

case QgsExpression::boNE:
op = "<>";
break;

case QgsExpression::boMul:
case QgsExpression::boPlus:
case QgsExpression::boMinus:
case QgsExpression::boDiv:
case QgsExpression::boMod:
case QgsExpression::boConcat:
case QgsExpression::boIntDiv:
case QgsExpression::boPow:
case QgsExpression::boRegexp:
return Fail; //not supported
}

if ( op.isNull() )
return Fail;

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

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

result = left + ' ' + op + ' ' + right;
if ( lr == Complete && rr == Complete )
return 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 );
result = QgsOgrUtils::quotedValue( n->value() );

// OGR SQL is case insensitive, so if literal was a string then we only have a Partial compilation and need to
// double check results using QGIS' expression engine

if ( n->value().type() == QVariant::String )
return Partial;
else
return Complete;
}

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

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

result = mSource->mProvider->quotedIdentifier( n->name().toUtf8() );

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 = compile( ln, s );
if ( r == Complete || r == Partial )
{
list << s;
if ( r == Partial )
inResult = Partial;
}
else
return r;
}

QString nd;
Result rn = compile( 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;
}
48 changes: 48 additions & 0 deletions src/providers/ogr/qgsogrexpressioncompiler.h
@@ -0,0 +1,48 @@
/***************************************************************************
qgsogrexpressioncompiler.h
--------------------------
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. *
* *
***************************************************************************/

#ifndef QGSOGREXPRESSIONCOMPILER_H
#define QGSOGREXPRESSIONCOMPILER_H

#include "qgsexpression.h"
#include "qgsogrfeatureiterator.h"

class QgsOgrExpressionCompiler
{
public:
enum Result
{
None,
Complete,
Partial,
Fail
};

explicit QgsOgrExpressionCompiler( QgsOgrFeatureSource* source );
~QgsOgrExpressionCompiler();

Result compile( const QgsExpression* exp );

const QString& result() { return mResult; }

private:
Result compile( const QgsExpression::Node* node, QString& str );

private:
QString mResult;
QgsOgrFeatureSource* mSource;
};

#endif // QGSOGREXPRESSIONCOMPILER_H
34 changes: 34 additions & 0 deletions src/providers/ogr/qgsogrfeatureiterator.cpp
Expand Up @@ -16,6 +16,7 @@

#include "qgsogrprovider.h"
#include "qgsogrgeometrysimplifier.h"
#include "qgsogrexpressioncompiler.h"

#include "qgsapplication.h"
#include "qgsgeometry.h"
Expand All @@ -38,6 +39,7 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource* source, bool
, ogrLayer( 0 )
, mSubsetStringSet( false )
, mGeometrySimplifier( NULL )
, mExpressionCompiled( false )
{
mFeatureFetched = false;

Expand Down Expand Up @@ -82,6 +84,29 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource* source, bool
OGR_L_SetSpatialFilter( ogrLayer, 0 );
}

if ( request.filterType() == QgsFeatureRequest::FilterExpression )
{
QgsOgrExpressionCompiler compiler = QgsOgrExpressionCompiler( source );

QgsOgrExpressionCompiler::Result result = compiler.compile( request.filterExpression() );

if ( result == QgsOgrExpressionCompiler::Complete || result == QgsOgrExpressionCompiler::Partial )
{
QString whereClause = compiler.result();
OGR_L_SetAttributeFilter( ogrLayer, whereClause.toLocal8Bit().data() );
//if only partial success when compiling expression, we need to double-check results using QGIS' expressions
mExpressionCompiled = ( result == QgsOgrExpressionCompiler::Complete );
}
else
{
OGR_L_SetAttributeFilter( ogrLayer, 0 );
}
}
else
{
OGR_L_SetAttributeFilter( ogrLayer, 0 );
}

//start with first feature
rewind();
}
Expand Down Expand Up @@ -128,6 +153,14 @@ bool QgsOgrFeatureIterator::prepareSimplification( const QgsSimplifyMethod& simp
return QgsAbstractFeatureIterator::prepareSimplification( simplifyMethod );
}

bool QgsOgrFeatureIterator::nextFeatureFilterExpression( QgsFeature& f )
{
if ( !mExpressionCompiled )
return QgsAbstractFeatureIterator::nextFeatureFilterExpression( f );
else
return fetchFeature( f );
}

bool QgsOgrFeatureIterator::providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const
{
#if defined(GDAL_VERSION_NUM) && defined(GDAL_COMPUTE_VERSION)
Expand Down Expand Up @@ -333,6 +366,7 @@ bool QgsOgrFeatureIterator::readFeature( OGRFeatureH fet, QgsFeature& feature )


QgsOgrFeatureSource::QgsOgrFeatureSource( const QgsOgrProvider* p )
: mProvider( p )
{
mFilePath = p->filePath();
mLayerName = p->layerName();
Expand Down
6 changes: 6 additions & 0 deletions src/providers/ogr/qgsogrfeatureiterator.h
Expand Up @@ -33,6 +33,7 @@ class QgsOgrFeatureSource : public QgsAbstractFeatureSource
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest& request ) override;

protected:
const QgsOgrProvider* mProvider;
QString mFilePath;
QString mLayerName;
int mLayerIndex;
Expand All @@ -43,6 +44,7 @@ class QgsOgrFeatureSource : public QgsAbstractFeatureSource
QString mDriverName;

friend class QgsOgrFeatureIterator;
friend class QgsOgrExpressionCompiler;
};

class QgsOgrFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsOgrFeatureSource>
Expand All @@ -65,6 +67,8 @@ class QgsOgrFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsOgr
//! Setup the simplification of geometries to fetch using the specified simplify method
virtual bool prepareSimplification( const QgsSimplifyMethod& simplifyMethod ) override;

//! fetch next feature filter expression
bool nextFeatureFilterExpression( QgsFeature& f ) override;

bool readFeature( OGRFeatureH fet, QgsFeature& feature );

Expand All @@ -85,6 +89,8 @@ class QgsOgrFeatureIterator : public QgsAbstractFeatureIteratorFromSource<QgsOgr
//! optional object to simplify OGR-geometries fecthed by this feature iterator
QgsOgrAbstractGeometrySimplifier* mGeometrySimplifier;

bool mExpressionCompiled;

//! returns whether the iterator supports simplify geometries on provider side
virtual bool providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const override;
};
Expand Down

0 comments on commit d19adfe

Please sign in to comment.