Skip to content
Permalink
Browse files
[FEATURE] Send filter expressions to postgres provider
This commit allows to filter features already on server side. Only supported
expressions will be sent to the database. Expressions using unsupported
operators or functions will gracefully fallback to local evaluation.

To make use of this feature

 * Enable it in options: data sources
 * QgsFeatureRequest().setFilterExpression( expression )
 * or QgsVectorLayer::getFeatures( expression )
  • Loading branch information
m-kuhn committed May 22, 2015
1 parent 9b6e23d commit a99deb8b88dae24c669a0d7e696e1aeb35da87fb
@@ -13,6 +13,7 @@ SET(PG_SRCS
qgspgnewconnection.cpp
qgspgtablemodel.cpp
qgscolumntypethread.cpp
qgspostgresexpressioncompiler.cpp
)
SET(PG_MOC_HDRS
qgspostgresprovider.h
@@ -27,6 +28,7 @@ SET(PG_MOC_HDRS

SET(PG_HDRS
qgspostgrestransaction.h
qgspostgresexpressioncompiler.h
)

########################################################
@@ -0,0 +1,152 @@
/***************************************************************************
----------------------------------------------------
date : 22.4.2015
copyright : (C) 2015 by Matthias Kuhn
email : matthias (at) opengis.ch
***************************************************************************
* *
* 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 "qgspostgresexpressioncompiler.h"

QgsPostgresExpressionCompiler::QgsPostgresExpressionCompiler( QgsPostgresFeatureSource* source )
: mResult( None )
, mSource( source )
{
}

QgsPostgresExpressionCompiler::~QgsPostgresExpressionCompiler()
{

}

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

QgsPostgresExpressionCompiler::Result QgsPostgresExpressionCompiler::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:
{
QString op;
QString left;
QString right;
Result lr;
Result rr;

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

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::boLike:
op = "LIKE";
break;

case QgsExpression::boILike:
op = "ILIKE";
break;

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

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

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

if ( !op.isNull() )
{
lr = compile( n->opLeft(), left );
rr = compile( n->opRight(), right );
result = left + " " + op + " " + right;
return ( lr == Complete && rr == Complete ) ? Complete : Fail;
}
else
{
return Fail;
}

break;
}

case QgsExpression::ntFunction:
return Fail;
break;

case QgsExpression::ntLiteral:
{
const QgsExpression::NodeLiteral* n = static_cast<const QgsExpression::NodeLiteral*>( node );
result = QgsPostgresConn::quotedValue( n->value() );
return Complete;
}

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

if ( mSource->mFields.indexFromName( n->name() ) != -1 )
{
result = QgsPostgresConn::quotedIdentifier( n->name() );
return Complete;
}
else
{
// Not a provider field
return Fail;
}
}
}
return Fail;
}
@@ -0,0 +1,50 @@
/***************************************************************************
----------------------------------------------------
date : 22.4.2015
copyright : (C) 2015 by Matthias Kuhn
email : matthias (at) opengis.ch
***************************************************************************
* *
* 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 QGSPOSTGRESEXPRESSIONCOMPILER_H
#define QGSPOSTGRESEXPRESSIONCOMPILER_H

#include "qgsexpression.h"
#include "qgspostgresfeatureiterator.h"

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

QgsPostgresExpressionCompiler( QgsPostgresFeatureSource* source );
~QgsPostgresExpressionCompiler();

Result compile( const QgsExpression* exp );

inline Result success() { return mSuccess; }

const QString& result() { return mResult; }

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

private:
Result mSuccess;
QString mResult;
QgsPostgresFeatureSource* mSource;
};

#endif // QGSPOSTGRESEXPRESSIONCOMPILER_H
@@ -12,16 +12,18 @@
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsgeometry.h"
#include "qgspostgresconnpool.h"
#include "qgspostgresexpressioncompiler.h"
#include "qgspostgresfeatureiterator.h"
#include "qgspostgresprovider.h"
#include "qgspostgresconnpool.h"
#include "qgspostgrestransaction.h"
#include "qgsgeometry.h"

#include "qgslogger.h"
#include "qgsmessagelog.h"

#include <QObject>
#include <QSettings>


const int QgsPostgresFeatureIterator::sFeatureQueueSize = 2000;
@@ -32,6 +34,7 @@ QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource
, mFeatureQueueSize( sFeatureQueueSize )
, mFetched( 0 )
, mFetchGeometry( false )
, mExpressionCompiled( false)
{
if ( !source->mTransactionConnection )
{
@@ -67,6 +70,17 @@ QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource
{
whereClause = QgsPostgresUtils::whereClause( mRequest.filterFids(), mSource->mFields, mConn, mSource->mPrimaryKeyType, mSource->mPrimaryKeyAttrs, mSource->mShared );
}
else if ( request.filterType() == QgsFeatureRequest::FilterExpression
&& QSettings().value( "/qgis/postgres/compileExpressions", false ).toBool() )
{
QgsPostgresExpressionCompiler compiler = QgsPostgresExpressionCompiler( source );

if ( compiler.compile( request.filterExpression() ) == QgsPostgresExpressionCompiler::Complete )
{
whereClause = compiler.result();
mExpressionCompiled = true;
}
}

if ( !mSource->mSqlWhereClause.isEmpty() )
{
@@ -166,6 +180,14 @@ bool QgsPostgresFeatureIterator::fetchFeature( QgsFeature& feature )
return true;
}

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

bool QgsPostgresFeatureIterator::prepareSimplification( const QgsSimplifyMethod& simplifyMethod )
{
// setup simplification of geometries to fetch
@@ -233,6 +255,11 @@ bool QgsPostgresFeatureIterator::close()
return true;
}

int QgsPostgresFeatureIterator::count()
{

}

///////////////

QString QgsPostgresFeatureIterator::whereClauseRect()
@@ -407,6 +434,7 @@ bool QgsPostgresFeatureIterator::declareCursor( const QString& whereClause )

if ( !mConn->openCursor( mCursorName, query ) )
{

// reloading the fields might help next time around
// TODO how to cleanly force reload of fields? P->loadFields();
close();
@@ -55,13 +55,14 @@ class QgsPostgresFeatureSource : public QgsAbstractFeatureSource
QSharedPointer<QgsPostgresSharedData> mShared;

/* The transaction connection (if any) gets refed/unrefed when creating/
* destroying the QgsPostfresFeatureSource, to ensure that the transaction
* destroying the QgsPostgresFeatureSource, to ensure that the transaction
* connection remains valid during the life time of the feature source
* even if the QgsPostgresTransaction object which initially created the
* connection has since been destroyed. */
QgsPostgresConn* mTransactionConnection;

friend class QgsPostgresFeatureIterator;
friend class QgsPostgresExpressionCompiler;
};


@@ -80,10 +81,16 @@ class QgsPostgresFeatureIterator : public QgsAbstractFeatureIteratorFromSource<Q
//! end of iterating: free the resources / lock
virtual bool close() override;

//! the number of features
virtual int count() override;

protected:
//! fetch next feature, return true on success
virtual bool fetchFeature( QgsFeature& feature ) override;

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

//! Setup the simplification of geometries to fetch using the specified simplify method
virtual bool prepareSimplification( const QgsSimplifyMethod& simplifyMethod ) override;

@@ -119,6 +126,8 @@ class QgsPostgresFeatureIterator : public QgsAbstractFeatureIteratorFromSource<Q
private:
//! returns whether the iterator supports simplify geometries on provider side
virtual bool providerCanSimplify( QgsSimplifyMethod::MethodType methodType ) const override;

bool mExpressionCompiled;
};

#endif // QGSPOSTGRESFEATUREITERATOR_H

0 comments on commit a99deb8

Please sign in to comment.