Skip to content

Commit

Permalink
Expressions - conditional expressions (CASE)
Browse files Browse the repository at this point in the history
Right now only without "base" expression:
CASE WHEN cond1 THEN exp1 [WHEN cond2 THEN exp2]* [ELSE exp3] END
  • Loading branch information
wonder-sk committed Jan 7, 2012
1 parent aa713ae commit c8bb12f
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 2 deletions.
27 changes: 27 additions & 0 deletions python/core/qgsexpression.sip
Expand Up @@ -259,6 +259,32 @@ public:
virtual void accept( QgsExpression::Visitor& v );
};

class WhenThen
{
public:
WhenThen( QgsExpression::Node* whenExp, QgsExpression::Node* thenExp );
~WhenThen();

//protected:
QgsExpression::Node* mWhenExp;
QgsExpression::Node* mThenExp;
};
typedef QList<QgsExpression::WhenThen*> WhenThenList;

class NodeCondition : QgsExpression::Node
{
public:
NodeCondition( QgsExpression::WhenThenList* conditions, QgsExpression::Node* elseExp = NULL );
~NodeCondition();

virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
virtual QString dump() const;
virtual QStringList referencedColumns() const;
virtual bool needsGeometry() const;
virtual void accept( QgsExpression::Visitor& v );
};

//////

/** support for visitor pattern - algorithms dealing with the expressions
Expand All @@ -273,6 +299,7 @@ public:
virtual void visit( QgsExpression::NodeFunction* n ) = 0;
virtual void visit( QgsExpression::NodeLiteral* n ) = 0;
virtual void visit( QgsExpression::NodeColumnRef* n ) = 0;
virtual void visit( QgsExpression::NodeCondition* n ) = 0;
};

/** entry function for the visitor pattern */
Expand Down
85 changes: 85 additions & 0 deletions src/core/qgsexpression.cpp
Expand Up @@ -975,3 +975,88 @@ QString QgsExpression::NodeColumnRef::dump() const
{
return mName;
}

//

QVariant QgsExpression::NodeCondition::eval( QgsExpression* parent, QgsFeature* f )
{
foreach( WhenThen* cond, mConditions )
{
QVariant vWhen = cond->mWhenExp->eval( parent, f );
TVL tvl = getTVLValue( vWhen, parent );
ENSURE_NO_EVAL_ERROR;
if ( tvl == True )
{
QVariant vRes = cond->mThenExp->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
return vRes;
}
}

if ( mElseExp )
{
QVariant vElse = mElseExp->eval( parent, f );
ENSURE_NO_EVAL_ERROR;
return vElse;
}

// return NULL if no condition is matching
return QVariant();
}

bool QgsExpression::NodeCondition::prepare( QgsExpression* parent, const QgsFieldMap& fields )
{
bool res;
foreach( WhenThen* cond, mConditions )
{
res = cond->mWhenExp->prepare( parent, fields )
& cond->mThenExp->prepare( parent, fields );
if ( !res ) return false;
}

if ( mElseExp )
return mElseExp->prepare( parent, fields );

return true;
}

QString QgsExpression::NodeCondition::dump() const
{
QString msg = "CONDITION:\n";
foreach( WhenThen* cond, mConditions )
{
msg += QString( "- WHEN %1 THEN %2\n" ).arg( cond->mWhenExp->dump() ).arg( cond->mThenExp->dump() );
}
if ( mElseExp )
msg += QString( "- ELSE %1" ).arg( mElseExp->dump() );
return msg;
}

QStringList QgsExpression::NodeCondition::referencedColumns() const
{
QStringList lst;
foreach( WhenThen* cond, mConditions )
{
lst += cond->mWhenExp->referencedColumns() + cond->mThenExp->referencedColumns();
}

if ( mElseExp )
lst += mElseExp->referencedColumns();

return lst;
}

bool QgsExpression::NodeCondition::needsGeometry() const
{
foreach( WhenThen* cond, mConditions )
{
if ( cond->mWhenExp->needsGeometry() ||
cond->mThenExp->needsGeometry() )
return true;
}

if ( mElseExp && mElseExp->needsGeometry() )
return true;

return false;
}
31 changes: 31 additions & 0 deletions src/core/qgsexpression.h
Expand Up @@ -376,6 +376,36 @@ class CORE_EXPORT QgsExpression
int mIndex;
};

class WhenThen
{
public:
WhenThen( Node* whenExp, Node* thenExp ) : mWhenExp( whenExp ), mThenExp( thenExp ) {}
~WhenThen() { delete mWhenExp; delete mThenExp; }

//protected:
Node* mWhenExp;
Node* mThenExp;
};
typedef QList<WhenThen*> WhenThenList;

class NodeCondition : public Node
{
public:
NodeCondition( WhenThenList* conditions, Node* elseExp = NULL ) : mConditions( *conditions ), mElseExp( elseExp ) { delete conditions; }
~NodeCondition() { delete mElseExp; foreach( WhenThen* cond, mConditions ) delete cond; }

virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
virtual QString dump() const;
virtual QStringList referencedColumns() const;
virtual bool needsGeometry() const;
virtual void accept( Visitor& v ) { v.visit( this ); }

protected:
WhenThenList mConditions;
Node* mElseExp;
};

//////

/** support for visitor pattern - algorithms dealing with the expressions
Expand All @@ -390,6 +420,7 @@ class CORE_EXPORT QgsExpression
virtual void visit( NodeFunction* n ) = 0;
virtual void visit( NodeLiteral* n ) = 0;
virtual void visit( NodeColumnRef* n ) = 0;
virtual void visit( NodeCondition* n ) = 0;
};

/** entry function for the visitor pattern */
Expand Down
5 changes: 5 additions & 0 deletions src/core/qgsexpressionlexer.ll
Expand Up @@ -140,6 +140,11 @@ string "'"{str_char}*"'"
"NULL" { return NULLVALUE; }
"CASE" { return CASE; }
"WHEN" { return WHEN; }
"THEN" { return THEN; }
"ELSE" { return ELSE; }
"END" { return END; }
[()] { return yytext[0]; }
Expand Down
19 changes: 19 additions & 0 deletions src/core/qgsexpressionparser.yy
Expand Up @@ -62,6 +62,8 @@ QgsExpression::Node* gExpParserRootNode;
QString* text;
QgsExpression::BinaryOperator b_op;
QgsExpression::UnaryOperator u_op;
QgsExpression::WhenThen* whenthen;
QgsExpression::WhenThenList* whenthenlist;
}

%start root
Expand All @@ -81,6 +83,9 @@ QgsExpression::Node* gExpParserRootNode;
%token <numberInt> NUMBER_INT
%token NULLVALUE

// tokens for conditional expressions
%token CASE WHEN THEN ELSE END

%token <text> STRING COLUMN_REF FUNCTION SPECIAL_COL

%token COMMA
Expand All @@ -93,6 +98,8 @@ QgsExpression::Node* gExpParserRootNode;

%type <node> expression
%type <nodelist> exp_list
%type <whenthen> when_then_clause
%type <whenthenlist> when_then_clauses

// debugging
%error-verbose
Expand Down Expand Up @@ -175,6 +182,9 @@ expression:
| PLUS expression %prec UMINUS { $$ = $2; }
| MINUS expression %prec UMINUS { $$ = new QgsExpression::NodeUnaryOperator( QgsExpression::uoMinus, $2); }

| CASE when_then_clauses END { $$ = new QgsExpression::NodeCondition($2); }
| CASE when_then_clauses ELSE expression END { $$ = new QgsExpression::NodeCondition($2,$4); }

// columns
| COLUMN_REF { $$ = new QgsExpression::NodeColumnRef( *$1 ); delete $1; }

Expand Down Expand Up @@ -203,6 +213,15 @@ exp_list:
| expression { $$ = new QgsExpression::NodeList(); $$->append($1); }
;

when_then_clauses:
when_then_clauses when_then_clause { $$ = $1; $1->append($2); }
| when_then_clause { $$ = new QgsExpression::WhenThenList(); $$->append($1); }
;

when_then_clause:
WHEN expression THEN expression { $$ = new QgsExpression::WhenThen($2,$4); }
;

%%

// returns parsed tree, otherwise returns NULL and sets parserErrorMsg
Expand Down
2 changes: 2 additions & 0 deletions src/providers/wfs/qgswfsutils.cpp
Expand Up @@ -91,6 +91,8 @@ class QgsExpressionOGCVisitor : public QgsExpression::Visitor
mResult = true;
}

void visit( QgsExpression::NodeCondition* n ) { mResult = false; }

protected:
QDomDocument mDoc;
QDomElement mParent;
Expand Down
26 changes: 24 additions & 2 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -66,6 +66,16 @@ class TestQgsExpression: public QObject
QTest::newRow( "arithmetics" ) << "1+2*3" << true;
QTest::newRow( "logic" ) << "be or not be" << true;

QTest::newRow( "conditions +1" ) << "case when x then y end" << true;
QTest::newRow( "conditions +2" ) << "case when x then y else z end" << true;
QTest::newRow( "conditions +3" ) << "case when x then y when a then b end" << true;
QTest::newRow( "conditions +4" ) << "case when x then y when a then b else z end" << true;

QTest::newRow( "conditions -1" ) << "case end" << false;
QTest::newRow( "conditions -2" ) << "when x then y" << false;
QTest::newRow( "conditions -3" ) << "case" << false;
QTest::newRow( "conditions -4" ) << "case when x y end" << false;
QTest::newRow( "conditions -5" ) << "case y end" << false;
}
void parsing()
{
Expand Down Expand Up @@ -235,6 +245,12 @@ class TestQgsExpression: public QObject
QTest::newRow( "implicit text->int" ) << "'5'+2" << false << QVariant( 7 );
QTest::newRow( "implicit text->double" ) << "'5.1'+2" << false << QVariant( 7.1 );
QTest::newRow( "implicit text->bool" ) << "'0.1' or 0" << false << QVariant( 1 );

// conditions (without base expression, i.e. CASE WHEN ... THEN ... END)
QTest::newRow( "condition when" ) << "case when 2>1 then 'good' end" << false << QVariant( "good" );
QTest::newRow( "condition else" ) << "case when 1=0 then 'bad' else 678 end" << false << QVariant( 678 );
QTest::newRow( "condition null" ) << "case when length(123)=0 then 111 end" << false << QVariant();
QTest::newRow( "condition 2 when" ) << "case when 2>3 then 23 when 3>2 then 32 else 0 end" << false << QVariant( 32 );
}

void evaluation()
Expand Down Expand Up @@ -323,8 +339,8 @@ class TestQgsExpression: public QObject
void referenced_columns()
{
QSet<QString> expectedCols;
expectedCols << "foo" << "bar";
QgsExpression exp( "length(Bar || FOO) = 4 or foo + sqrt(bar) > 0" );
expectedCols << "foo" << "bar" << "ppp" << "qqq" << "rrr";
QgsExpression exp( "length(Bar || FOO) = 4 or foo + sqrt(bar) > 0 or case when ppp then qqq else rrr end" );
QCOMPARE( exp.hasParserError(), false );
QStringList refCols = exp.referencedColumns();
// make sure we have lower case
Expand All @@ -351,6 +367,10 @@ class TestQgsExpression: public QObject
QTest::newRow( "$perimeter" ) << "$perimeter" << true;
QTest::newRow( "toint($perimeter)" ) << "toint($perimeter)" << true;
QTest::newRow( "toint(123)" ) << "toint(123)" << false;
QTest::newRow( "case 0" ) << "case when 1 then 0 end" << false;
QTest::newRow( "case 1" ) << "case when $area > 0 then 1 end" << true;
QTest::newRow( "case 2" ) << "case when 1 then $area end" << true;
QTest::newRow( "case 3" ) << "case when 1 then 0 else $area end" << true;
}

void needs_geometry()
Expand All @@ -359,6 +379,8 @@ class TestQgsExpression: public QObject
QFETCH( bool, needsGeom );

QgsExpression exp( string );
if ( exp.hasParserError() )
qDebug() << "parser error! " << exp.parserErrorString();
QCOMPARE( exp.hasParserError(), false );
QCOMPARE( exp.needsGeometry(), needsGeom );
}
Expand Down

0 comments on commit c8bb12f

Please sign in to comment.