Skip to content

Commit

Permalink
Add lazy evaluation support to expression functions
Browse files Browse the repository at this point in the history
Add if(cond, true, false) function using lazy evaluation feature
  • Loading branch information
NathanW2 committed Dec 13, 2014
1 parent fd15a16 commit d61bf1f
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 8 deletions.
46 changes: 42 additions & 4 deletions src/core/qgsexpression.cpp
Expand Up @@ -338,6 +338,15 @@ static QgsFeature getFeature( const QVariant& value, QgsExpression* parent )
return 0;
}

static QgsExpression::Node* getNode( const QVariant& value, QgsExpression* parent )
{
if ( value.canConvert<QgsExpression::Node*>() )
return value.value<QgsExpression::Node*>();

parent->setEvalErrorString( "Cannot convert to Node" );
return 0;
}

// this handles also NULL values
static TVL getTVLValue( const QVariant& value, QgsExpression* parent )
{
Expand Down Expand Up @@ -1355,6 +1364,27 @@ static QVariant fcnColorRgb( const QVariantList &values, const QgsFeature *, Qgs
return QString( "%1,%2,%3" ).arg( color.red() ).arg( color.green() ).arg( color.blue() );
}

static QVariant fcnIf( const QVariantList &values, const QgsFeature *f, QgsExpression *parent )
{
QgsExpression::Node* node = getNode( values.at( 0 ), parent );
ENSURE_NO_EVAL_ERROR;
QVariant value = node->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
if ( value.toBool() ) {
node = getNode( values.at( 1 ), parent );
ENSURE_NO_EVAL_ERROR;
value = node->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
}
else {
node = getNode( values.at( 2 ), parent );
ENSURE_NO_EVAL_ERROR;
value = node->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
}
return value;
}

static QVariant fncColorRgba( const QVariantList &values, const QgsFeature *, QgsExpression *parent )
{
int red = getIntValue( values.at( 0 ), parent );
Expand Down Expand Up @@ -1660,6 +1690,7 @@ const QList<QgsExpression::Function*> &QgsExpression::Functions()
<< new StaticFunction( "totime", 1, fcnToTime, "Conversions" )
<< new StaticFunction( "tointerval", 1, fcnToInterval, "Conversions" )
<< new StaticFunction( "coalesce", -1, fcnCoalesce, "Conditionals" )
<< new StaticFunction( "if", 3, fcnIf, "Conditionals", "", False, QStringList(), true )
<< new StaticFunction( "regexp_match", 2, fcnRegexpMatch, "Conditionals" )
<< new StaticFunction( "$now", 0, fcnNow, "Date and Time" )
<< new StaticFunction( "age", 2, fcnAge, "Date and Time" )
Expand Down Expand Up @@ -2533,10 +2564,17 @@ QVariant QgsExpression::NodeFunction::eval( QgsExpression* parent, const QgsFeat
{
foreach ( Node* n, mArgs->list() )
{
QVariant v = n->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
if ( isNull( v ) && fd->name() != "coalesce" )
return QVariant(); // all "normal" functions return NULL, when any parameter is NULL (so coalesce is abnormal)
QVariant v;
if ( fd->lazyEval() ) {
// Pass in the node for the function to eval as it needs.
v = QVariant::fromValue( n );
}
else {
v = n->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
if ( isNull( v ) && fd->name() != "coalesce" )
return QVariant(); // all "normal" functions return NULL, when any parameter is NULL (so coalesce is abnormal)
}
argValues.append( v );
}
}
Expand Down
15 changes: 11 additions & 4 deletions src/core/qgsexpression.h
Expand Up @@ -282,15 +282,20 @@ class CORE_EXPORT QgsExpression
class CORE_EXPORT Function
{
public:
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList() )
: mName( fnname ), mParams( params ), mUsesGeometry( usesGeometry ), mGroup( group ), mHelpText( helpText ), mReferencedColumns( referencedColumns ) {}
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList(), bool lazyEval = false )
: mName( fnname ), mParams( params ), mUsesGeometry( usesGeometry ), mGroup( group ), mHelpText( helpText ), mReferencedColumns( referencedColumns ), mLazyEval( lazyEval ) {}
/** The name of the function. */
QString name() { return mName; }
/** The number of parameters this function takes. */
int params() { return mParams; }
/** Does this function use a geometry object. */
bool usesgeometry() { return mUsesGeometry; }

/** True if this function should use lazy evaluation. Lazy evaluation functions take QgsExpression::Node objects
* rather than the node results when called. You can use node->eval(parent, feature) to evaluate the node and return the result
* Functions are non lazy default and will be given the node return value when called **/
bool lazyEval() { return mLazyEval; }

virtual QStringList referencedColumns() const { return mReferencedColumns; }

/** The group the function belongs to. */
Expand All @@ -315,13 +320,14 @@ class CORE_EXPORT QgsExpression
QString mGroup;
QString mHelpText;
QStringList mReferencedColumns;
bool mLazyEval;
};

class StaticFunction : public Function
{
public:
StaticFunction( QString fnname, int params, FcnEval fcn, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList() )
: Function( fnname, params, group, helpText, usesGeometry, referencedColumns ), mFnc( fcn ) {}
StaticFunction( QString fnname, int params, FcnEval fcn, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList(), bool lazyEval = false )
: Function( fnname, params, group, helpText, usesGeometry, referencedColumns, lazyEval ), mFnc( fcn ) {}

virtual QVariant func( const QVariantList& values, const QgsFeature* f, QgsExpression* parent )
{
Expand Down Expand Up @@ -680,5 +686,6 @@ class CORE_EXPORT QgsExpression
};

Q_DECLARE_METATYPE( QgsExpression::Interval );
Q_DECLARE_METATYPE( QgsExpression::Node* );

#endif // QGSEXPRESSION_H
2 changes: 2 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -377,6 +377,8 @@ class TestQgsExpression: public QObject
QTest::newRow( "regexp match invalid" ) << "regexp_match('abc DEF','[[[')" << true << QVariant();
QTest::newRow( "regexp match escaped" ) << "regexp_match('abc DEF','\\\\s[A-Z]+')" << false << QVariant( 1 );
QTest::newRow( "regexp match false" ) << "regexp_match('abc DEF','\\\\s[a-z]+')" << false << QVariant( 0 );
QTest::newRow( "if true" ) << "if(1=1, 1, 0)" << false << QVariant( 1 );
QTest::newRow( "if false" ) << "if(1=2, 1, 0)" << false << QVariant( 0 );

// Datetime functions
QTest::newRow( "to date" ) << "todate('2012-06-28')" << false << QVariant( QDate( 2012, 6, 28 ) );
Expand Down

0 comments on commit d61bf1f

Please sign in to comment.