Skip to content

Commit c8bb12f

Browse files
committedJan 7, 2012
Expressions - conditional expressions (CASE)
Right now only without "base" expression: CASE WHEN cond1 THEN exp1 [WHEN cond2 THEN exp2]* [ELSE exp3] END
1 parent aa713ae commit c8bb12f

File tree

7 files changed

+193
-2
lines changed

7 files changed

+193
-2
lines changed
 

‎python/core/qgsexpression.sip

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,32 @@ public:
259259
virtual void accept( QgsExpression::Visitor& v );
260260
};
261261

262+
class WhenThen
263+
{
264+
public:
265+
WhenThen( QgsExpression::Node* whenExp, QgsExpression::Node* thenExp );
266+
~WhenThen();
267+
268+
//protected:
269+
QgsExpression::Node* mWhenExp;
270+
QgsExpression::Node* mThenExp;
271+
};
272+
typedef QList<QgsExpression::WhenThen*> WhenThenList;
273+
274+
class NodeCondition : QgsExpression::Node
275+
{
276+
public:
277+
NodeCondition( QgsExpression::WhenThenList* conditions, QgsExpression::Node* elseExp = NULL );
278+
~NodeCondition();
279+
280+
virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
281+
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
282+
virtual QString dump() const;
283+
virtual QStringList referencedColumns() const;
284+
virtual bool needsGeometry() const;
285+
virtual void accept( QgsExpression::Visitor& v );
286+
};
287+
262288
//////
263289

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

278305
/** entry function for the visitor pattern */

‎src/core/qgsexpression.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,3 +975,88 @@ QString QgsExpression::NodeColumnRef::dump() const
975975
{
976976
return mName;
977977
}
978+
979+
//
980+
981+
QVariant QgsExpression::NodeCondition::eval( QgsExpression* parent, QgsFeature* f )
982+
{
983+
foreach( WhenThen* cond, mConditions )
984+
{
985+
QVariant vWhen = cond->mWhenExp->eval( parent, f );
986+
TVL tvl = getTVLValue( vWhen, parent );
987+
ENSURE_NO_EVAL_ERROR;
988+
if ( tvl == True )
989+
{
990+
QVariant vRes = cond->mThenExp->eval( parent, f );
991+
ENSURE_NO_EVAL_ERROR;
992+
return vRes;
993+
}
994+
}
995+
996+
if ( mElseExp )
997+
{
998+
QVariant vElse = mElseExp->eval( parent, f );
999+
ENSURE_NO_EVAL_ERROR;
1000+
return vElse;
1001+
}
1002+
1003+
// return NULL if no condition is matching
1004+
return QVariant();
1005+
}
1006+
1007+
bool QgsExpression::NodeCondition::prepare( QgsExpression* parent, const QgsFieldMap& fields )
1008+
{
1009+
bool res;
1010+
foreach( WhenThen* cond, mConditions )
1011+
{
1012+
res = cond->mWhenExp->prepare( parent, fields )
1013+
& cond->mThenExp->prepare( parent, fields );
1014+
if ( !res ) return false;
1015+
}
1016+
1017+
if ( mElseExp )
1018+
return mElseExp->prepare( parent, fields );
1019+
1020+
return true;
1021+
}
1022+
1023+
QString QgsExpression::NodeCondition::dump() const
1024+
{
1025+
QString msg = "CONDITION:\n";
1026+
foreach( WhenThen* cond, mConditions )
1027+
{
1028+
msg += QString( "- WHEN %1 THEN %2\n" ).arg( cond->mWhenExp->dump() ).arg( cond->mThenExp->dump() );
1029+
}
1030+
if ( mElseExp )
1031+
msg += QString( "- ELSE %1" ).arg( mElseExp->dump() );
1032+
return msg;
1033+
}
1034+
1035+
QStringList QgsExpression::NodeCondition::referencedColumns() const
1036+
{
1037+
QStringList lst;
1038+
foreach( WhenThen* cond, mConditions )
1039+
{
1040+
lst += cond->mWhenExp->referencedColumns() + cond->mThenExp->referencedColumns();
1041+
}
1042+
1043+
if ( mElseExp )
1044+
lst += mElseExp->referencedColumns();
1045+
1046+
return lst;
1047+
}
1048+
1049+
bool QgsExpression::NodeCondition::needsGeometry() const
1050+
{
1051+
foreach( WhenThen* cond, mConditions )
1052+
{
1053+
if ( cond->mWhenExp->needsGeometry() ||
1054+
cond->mThenExp->needsGeometry() )
1055+
return true;
1056+
}
1057+
1058+
if ( mElseExp && mElseExp->needsGeometry() )
1059+
return true;
1060+
1061+
return false;
1062+
}

‎src/core/qgsexpression.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,36 @@ class CORE_EXPORT QgsExpression
376376
int mIndex;
377377
};
378378

379+
class WhenThen
380+
{
381+
public:
382+
WhenThen( Node* whenExp, Node* thenExp ) : mWhenExp( whenExp ), mThenExp( thenExp ) {}
383+
~WhenThen() { delete mWhenExp; delete mThenExp; }
384+
385+
//protected:
386+
Node* mWhenExp;
387+
Node* mThenExp;
388+
};
389+
typedef QList<WhenThen*> WhenThenList;
390+
391+
class NodeCondition : public Node
392+
{
393+
public:
394+
NodeCondition( WhenThenList* conditions, Node* elseExp = NULL ) : mConditions( *conditions ), mElseExp( elseExp ) { delete conditions; }
395+
~NodeCondition() { delete mElseExp; foreach( WhenThen* cond, mConditions ) delete cond; }
396+
397+
virtual QVariant eval( QgsExpression* parent, QgsFeature* f );
398+
virtual bool prepare( QgsExpression* parent, const QgsFieldMap& fields );
399+
virtual QString dump() const;
400+
virtual QStringList referencedColumns() const;
401+
virtual bool needsGeometry() const;
402+
virtual void accept( Visitor& v ) { v.visit( this ); }
403+
404+
protected:
405+
WhenThenList mConditions;
406+
Node* mElseExp;
407+
};
408+
379409
//////
380410

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

395426
/** entry function for the visitor pattern */

‎src/core/qgsexpressionlexer.ll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ string "'"{str_char}*"'"
140140
141141
"NULL" { return NULLVALUE; }
142142
143+
"CASE" { return CASE; }
144+
"WHEN" { return WHEN; }
145+
"THEN" { return THEN; }
146+
"ELSE" { return ELSE; }
147+
"END" { return END; }
143148
144149
[()] { return yytext[0]; }
145150

‎src/core/qgsexpressionparser.yy

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ QgsExpression::Node* gExpParserRootNode;
6262
QString* text;
6363
QgsExpression::BinaryOperator b_op;
6464
QgsExpression::UnaryOperator u_op;
65+
QgsExpression::WhenThen* whenthen;
66+
QgsExpression::WhenThenList* whenthenlist;
6567
}
6668

6769
%start root
@@ -81,6 +83,9 @@ QgsExpression::Node* gExpParserRootNode;
8183
%token <numberInt> NUMBER_INT
8284
%token NULLVALUE
8385

86+
// tokens for conditional expressions
87+
%token CASE WHEN THEN ELSE END
88+
8489
%token <text> STRING COLUMN_REF FUNCTION SPECIAL_COL
8590

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

9499
%type <node> expression
95100
%type <nodelist> exp_list
101+
%type <whenthen> when_then_clause
102+
%type <whenthenlist> when_then_clauses
96103

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

185+
| CASE when_then_clauses END { $$ = new QgsExpression::NodeCondition($2); }
186+
| CASE when_then_clauses ELSE expression END { $$ = new QgsExpression::NodeCondition($2,$4); }
187+
178188
// columns
179189
| COLUMN_REF { $$ = new QgsExpression::NodeColumnRef( *$1 ); delete $1; }
180190

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

216+
when_then_clauses:
217+
when_then_clauses when_then_clause { $$ = $1; $1->append($2); }
218+
| when_then_clause { $$ = new QgsExpression::WhenThenList(); $$->append($1); }
219+
;
220+
221+
when_then_clause:
222+
WHEN expression THEN expression { $$ = new QgsExpression::WhenThen($2,$4); }
223+
;
224+
206225
%%
207226

208227
// returns parsed tree, otherwise returns NULL and sets parserErrorMsg

‎src/providers/wfs/qgswfsutils.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ class QgsExpressionOGCVisitor : public QgsExpression::Visitor
9191
mResult = true;
9292
}
9393

94+
void visit( QgsExpression::NodeCondition* n ) { mResult = false; }
95+
9496
protected:
9597
QDomDocument mDoc;
9698
QDomElement mParent;

‎tests/src/core/testqgsexpression.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ class TestQgsExpression: public QObject
6666
QTest::newRow( "arithmetics" ) << "1+2*3" << true;
6767
QTest::newRow( "logic" ) << "be or not be" << true;
6868

69+
QTest::newRow( "conditions +1" ) << "case when x then y end" << true;
70+
QTest::newRow( "conditions +2" ) << "case when x then y else z end" << true;
71+
QTest::newRow( "conditions +3" ) << "case when x then y when a then b end" << true;
72+
QTest::newRow( "conditions +4" ) << "case when x then y when a then b else z end" << true;
73+
74+
QTest::newRow( "conditions -1" ) << "case end" << false;
75+
QTest::newRow( "conditions -2" ) << "when x then y" << false;
76+
QTest::newRow( "conditions -3" ) << "case" << false;
77+
QTest::newRow( "conditions -4" ) << "case when x y end" << false;
78+
QTest::newRow( "conditions -5" ) << "case y end" << false;
6979
}
7080
void parsing()
7181
{
@@ -235,6 +245,12 @@ class TestQgsExpression: public QObject
235245
QTest::newRow( "implicit text->int" ) << "'5'+2" << false << QVariant( 7 );
236246
QTest::newRow( "implicit text->double" ) << "'5.1'+2" << false << QVariant( 7.1 );
237247
QTest::newRow( "implicit text->bool" ) << "'0.1' or 0" << false << QVariant( 1 );
248+
249+
// conditions (without base expression, i.e. CASE WHEN ... THEN ... END)
250+
QTest::newRow( "condition when" ) << "case when 2>1 then 'good' end" << false << QVariant( "good" );
251+
QTest::newRow( "condition else" ) << "case when 1=0 then 'bad' else 678 end" << false << QVariant( 678 );
252+
QTest::newRow( "condition null" ) << "case when length(123)=0 then 111 end" << false << QVariant();
253+
QTest::newRow( "condition 2 when" ) << "case when 2>3 then 23 when 3>2 then 32 else 0 end" << false << QVariant( 32 );
238254
}
239255

240256
void evaluation()
@@ -323,8 +339,8 @@ class TestQgsExpression: public QObject
323339
void referenced_columns()
324340
{
325341
QSet<QString> expectedCols;
326-
expectedCols << "foo" << "bar";
327-
QgsExpression exp( "length(Bar || FOO) = 4 or foo + sqrt(bar) > 0" );
342+
expectedCols << "foo" << "bar" << "ppp" << "qqq" << "rrr";
343+
QgsExpression exp( "length(Bar || FOO) = 4 or foo + sqrt(bar) > 0 or case when ppp then qqq else rrr end" );
328344
QCOMPARE( exp.hasParserError(), false );
329345
QStringList refCols = exp.referencedColumns();
330346
// make sure we have lower case
@@ -351,6 +367,10 @@ class TestQgsExpression: public QObject
351367
QTest::newRow( "$perimeter" ) << "$perimeter" << true;
352368
QTest::newRow( "toint($perimeter)" ) << "toint($perimeter)" << true;
353369
QTest::newRow( "toint(123)" ) << "toint(123)" << false;
370+
QTest::newRow( "case 0" ) << "case when 1 then 0 end" << false;
371+
QTest::newRow( "case 1" ) << "case when $area > 0 then 1 end" << true;
372+
QTest::newRow( "case 2" ) << "case when 1 then $area end" << true;
373+
QTest::newRow( "case 3" ) << "case when 1 then 0 else $area end" << true;
354374
}
355375

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

361381
QgsExpression exp( string );
382+
if ( exp.hasParserError() )
383+
qDebug() << "parser error! " << exp.parserErrorString();
362384
QCOMPARE( exp.hasParserError(), false );
363385
QCOMPARE( exp.needsGeometry(), needsGeom );
364386
}

0 commit comments

Comments
 (0)
Please sign in to comment.