Skip to content

Commit

Permalink
Create QgsSqlExpressionCompiler as base class for expression compilers
Browse files Browse the repository at this point in the history
Switch Postgres and OGR compilers to use the new base class. New class
should make it easier to add additional expression compilers (eg
spatialite, MS SQL, etc)
  • Loading branch information
nyalldawson committed Nov 18, 2015
1 parent f7ed42b commit 5f65f87
Show file tree
Hide file tree
Showing 8 changed files with 427 additions and 397 deletions.
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
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
280 changes: 280 additions & 0 deletions src/core/qgssqlexpressioncompiler.cpp
@@ -0,0 +1,280 @@
/***************************************************************************
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 compile( 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::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;
bool partialCompilation = 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";
break;

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

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

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

case QgsExpression::boNotILike:
op = "NOT ILIKE";
break;

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

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

case QgsExpression::boNE:
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( compile( n->opLeft(), left ) );

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

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 = 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;
}
102 changes: 102 additions & 0 deletions src/core/qgssqlexpressioncompiler.h
@@ -0,0 +1,102 @@
/***************************************************************************
qgssqlexpressioncompiler.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 QGSSQLEXPRESSIONCOMPILER_H
#define QGSSQLEXPRESSIONCOMPILER_H

#include "qgsexpression.h"
#include "qgsfield.h"

/** \ingroup core
* \class QgsSqlExpressionCompiler
* \brief Generic expression compiler for translation to provider specific SQL WHERE clauses.
*
* This class is designed to be overriden by providers to take advantage of expression compilation,
* so that feature requests can take advantage of the provider's native filtering support.
* \note Added in version 2.14
* \note Not part of stable API, may change in future versions of QGIS
* \note Not available in Python bindings
*/

class CORE_EXPORT QgsSqlExpressionCompiler
{
public:

/** Possible results from expression compilation */
enum Result
{
None, /*!< No expression */
Complete, /*!< Expression was successfully compiled and can be completely delegated to provider */
Partial, /*!< Expression was partially compiled, but provider will return extra records and results must be double-checked using QGIS' expression engine*/
Fail /*!< Provider cannot handle expression */
};

/** Enumeration of flags for how provider handles SQL clauses
*/
enum Flag
{
CaseInsensitiveStringMatch = 0x01, //!< Provider performs case-insensitive string matching
};
Q_DECLARE_FLAGS( Flags, Flag )

/** Constructor for expression compiler.
* @param fields fields from provider
* @param flags flags which control how expression is compiled
*/
explicit QgsSqlExpressionCompiler( const QgsFields& fields, const Flags& flags = ( Flags )0 );
virtual ~QgsSqlExpressionCompiler();

/** Compiles an expression and returns the result of the compilation.
*/
virtual Result compile( const QgsExpression* exp );

/** Returns the compiled expression string for use by the provider.
*/
virtual QString result() { return mResult; }

protected:

/** Returns a quoted column identifier, in the format expected by the provider.
* Derived classes should override this if special handling of column identifiers
* is required.
* @see quotedValue()
*/
virtual QString quotedIdentifier( const QString& identifier );

/** Returns a quoted attribute value, in the format expected by the provider.
* Derived classes should override this if special handling of attribute values is required.
* @see quotedIdentifier()
*/
virtual QString quotedValue( const QVariant& value );

/** Compiles an expression node and returns the result of the compilation.
* @param node expression node to compile
* @param str string representing compiled node should be stored in this parameter
* @returns result of node compilation
*/
virtual Result compile( const QgsExpression::Node* node, QString& str );

QString mResult;
QgsFields mFields;

private:

Flags mFlags;

};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsSqlExpressionCompiler::Flags )

#endif // QGSSQLEXPRESSIONCOMPILER_H

0 comments on commit 5f65f87

Please sign in to comment.