Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create QgsSqlExpressionCompiler as base class for expression compilers
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
1 parent
f7ed42b
commit 5f65f87
Showing
8 changed files
with
427 additions
and
397 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.