[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 )
This commit is contained in:
Matthias Kuhn 2015-04-22 23:33:32 +02:00
parent 9b6e23d3af
commit a99deb8b88
5 changed files with 244 additions and 3 deletions

View File

@ -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
)
########################################################

View File

@ -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;
}

View File

@ -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

View File

@ -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();

View File

@ -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