Skip to content

Commit 2608760

Browse files
authored
Merge pull request #88 from devenbansod/fix_pma_12100
Add parsing of CASE Expressions
2 parents c66456f + f151567 commit 2608760

22 files changed

+408
-3
lines changed

src/Components/CaseExpression.php

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
<?php
2+
3+
/**
4+
* Parses a reference to a CASE expression
5+
*
6+
* @package SqlParser
7+
* @subpackage Components
8+
*/
9+
namespace SqlParser\Components;
10+
11+
use SqlParser\Context;
12+
use SqlParser\Component;
13+
use SqlParser\Parser;
14+
use SqlParser\Token;
15+
use SqlParser\TokensList;
16+
17+
18+
/**
19+
* Parses a reference to a CASE expression
20+
*
21+
* @category Components
22+
* @package SqlParser
23+
* @subpackage Components
24+
* @license http://opensource.org/licenses/GPL-2.0 GNU Public License
25+
*/
26+
class CaseExpression extends Component
27+
{
28+
29+
/**
30+
* The value to be compared
31+
*
32+
* @var Expression
33+
*/
34+
public $value;
35+
36+
/**
37+
* The conditions in WHEN clauses
38+
*
39+
* @var array
40+
*/
41+
public $conditions;
42+
43+
/**
44+
* The results matching with the WHEN clauses
45+
*
46+
* @var array
47+
*/
48+
public $results;
49+
50+
/**
51+
* The values to be compared against
52+
*
53+
* @var array
54+
*/
55+
public $compare_values;
56+
57+
/**
58+
* The result in ELSE section of expr
59+
*
60+
* @var array
61+
*/
62+
public $else_result;
63+
64+
/**
65+
* Constructor.
66+
*
67+
*/
68+
public function __construct()
69+
{
70+
}
71+
72+
/**
73+
*
74+
* @param Parser $parser The parser that serves as context.
75+
* @param TokensList $list The list of tokens that are being parsed.
76+
*
77+
* @return Expression
78+
*/
79+
public static function parse(Parser $parser, TokensList $list, array $options = array())
80+
{
81+
$ret = new CaseExpression();
82+
83+
/**
84+
* State of parser
85+
*
86+
* @var int $parser
87+
*/
88+
$state = 0;
89+
90+
/**
91+
* Syntax type (type 0 or type 1)
92+
*
93+
* @var int $type
94+
*/
95+
$type = 0;
96+
97+
++$list->idx; // Skip 'CASE'
98+
99+
for (; $list->idx < $list->count; ++$list->idx) {
100+
101+
/**
102+
* Token parsed at this moment.
103+
*
104+
* @var Token $token
105+
*/
106+
$token = $list->tokens[$list->idx];
107+
108+
// Skipping whitespaces and comments.
109+
if (($token->type === Token::TYPE_WHITESPACE)
110+
|| ($token->type === Token::TYPE_COMMENT)
111+
) {
112+
continue;
113+
}
114+
115+
if ($state === 0) {
116+
if ($token->type === Token::TYPE_KEYWORD
117+
&& $token->value === 'WHEN'
118+
) {
119+
++$list->idx; // Skip 'WHEN'
120+
$new_condition = Condition::parse($parser, $list);
121+
$type = 1;
122+
$state = 1;
123+
$ret->conditions[] = $new_condition;
124+
} elseif ($token->type === Token::TYPE_KEYWORD
125+
&& $token->value === 'ELSE'
126+
) {
127+
++$list->idx; // Skip 'ELSE'
128+
$ret->else_result = Expression::parse($parser, $list);
129+
$state = 0; // last clause of CASE expression
130+
} elseif ($token->type === Token::TYPE_KEYWORD
131+
&& ($token->value === 'END'
132+
|| $token->value === 'end')
133+
) {
134+
$state = 3; // end of CASE expression
135+
++$list->idx;
136+
break;
137+
} elseif ($token->type === Token::TYPE_KEYWORD) {
138+
$parser->error(__('Unexpected keyword.'), $token);
139+
break;
140+
} else {
141+
$ret->value = Expression::parse($parser, $list);
142+
$type = 0;
143+
$state = 1;
144+
}
145+
} elseif ($state === 1) {
146+
if ($type === 0) {
147+
if ($token->type === Token::TYPE_KEYWORD
148+
&& $token->value === 'WHEN'
149+
) {
150+
++$list->idx; // Skip 'WHEN'
151+
$new_value = Expression::parse($parser, $list);
152+
$state = 2;
153+
$ret->compare_values[] = $new_value;
154+
} elseif ($token->type === Token::TYPE_KEYWORD
155+
&& $token->value === 'ELSE'
156+
) {
157+
++$list->idx; // Skip 'ELSE'
158+
$ret->else_result = Expression::parse($parser, $list);
159+
$state = 0; // last clause of CASE expression
160+
} elseif ($token->type === Token::TYPE_KEYWORD
161+
&& ($token->value === 'END'
162+
|| $token->value === 'end')
163+
) {
164+
$state = 3; // end of CASE expression
165+
++$list->idx;
166+
break;
167+
} elseif ($token->type === Token::TYPE_KEYWORD) {
168+
$parser->error(__('Unexpected keyword.'), $token);
169+
break;
170+
}
171+
} else {
172+
if ($token->type === Token::TYPE_KEYWORD
173+
&& $token->value === 'THEN'
174+
) {
175+
++$list->idx; // Skip 'THEN'
176+
$new_result = Expression::parse($parser, $list);
177+
$state = 0;
178+
$ret->results[] = $new_result;
179+
} elseif ($token->type === Token::TYPE_KEYWORD) {
180+
$parser->error(__('Unexpected keyword.'), $token);
181+
break;
182+
}
183+
}
184+
} elseif ($state === 2) {
185+
if ($type === 0) {
186+
if ($token->type === Token::TYPE_KEYWORD
187+
&& $token->value === 'THEN'
188+
) {
189+
++$list->idx; // Skip 'THEN'
190+
$new_result = Expression::parse($parser, $list);
191+
$ret->results[] = $new_result;
192+
$state = 1;
193+
} elseif ($token->type === Token::TYPE_KEYWORD) {
194+
$parser->error(__('Unexpected keyword.'), $token);
195+
break;
196+
}
197+
}
198+
}
199+
}
200+
201+
if ($state !== 3) {
202+
$parser->error(
203+
__('Unexpected end of CASE expression'),
204+
$list->tokens[$list->idx - 1]
205+
);
206+
}
207+
208+
--$list->idx;
209+
return $ret;
210+
}
211+
212+
/**
213+
* @param Expression $component The component to be built.
214+
* @param array $options Parameters for building.
215+
*
216+
* @return string
217+
*/
218+
public static function build($component, array $options = array())
219+
{
220+
$ret = 'CASE ';
221+
if (isset($component->value)) {
222+
// Syntax type 0
223+
$ret .= $component->value . ' ';
224+
for (
225+
$i = 0;
226+
$i < count($component->compare_values) && $i < count($component->results);
227+
++$i
228+
) {
229+
$ret .= 'WHEN ' . $component->compare_values[$i] . ' ';
230+
$ret .= 'THEN ' . $component->results[$i] . ' ';
231+
}
232+
} else {
233+
// Syntax type 1
234+
for (
235+
$i = 0;
236+
$i < count($component->conditions) && $i < count($component->results);
237+
++$i
238+
) {
239+
$ret .= 'WHEN ' . Condition::build($component->conditions[$i]) . ' ';
240+
$ret .= 'THEN ' . $component->results[$i] . ' ';
241+
}
242+
}
243+
if (isset($component->else_result)) {
244+
$ret .= 'ELSE ' . $component->else_result . ' ';
245+
}
246+
$ret .= 'END';
247+
248+
return $ret;
249+
}
250+
}

src/Components/ExpressionArray.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22

33
/**
4-
* Parses a a list of expressions delimited by a comma.
4+
* Parses a list of expressions delimited by a comma.
55
*
66
* @package SqlParser
77
* @subpackage Components
@@ -14,7 +14,7 @@
1414
use SqlParser\TokensList;
1515

1616
/**
17-
* Parses a a list of expressions delimited by a comma.
17+
* Parses a list of expressions delimited by a comma.
1818
*
1919
* @category Keywords
2020
* @package SqlParser
@@ -72,13 +72,21 @@ public static function parse(Parser $parser, TokensList $list, array $options =
7272
&& ((~$token->flags & Token::FLAG_KEYWORD_FUNCTION))
7373
&& ($token->value !== 'DUAL')
7474
&& ($token->value !== 'NULL')
75+
&& ($token->value !== 'CASE')
7576
) {
7677
// No keyword is expected.
7778
break;
7879
}
7980

8081
if ($state === 0) {
81-
$expr = Expression::parse($parser, $list, $options);
82+
if ($token->type === Token::TYPE_KEYWORD
83+
&& $token->value === 'CASE'
84+
) {
85+
$expr = CaseExpression::parse($parser, $list, $options);
86+
} else {
87+
$expr = Expression::parse($parser, $list, $options);
88+
}
89+
8290
if ($expr === null) {
8391
break;
8492
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace SqlParser\Tests\Components;
4+
5+
use SqlParser\Parser;
6+
use SqlParser\Components\CaseExpression;
7+
8+
use SqlParser\Tests\TestCase;
9+
10+
class CaseExpressionTest extends TestCase
11+
{
12+
13+
public function testParseBuild()
14+
{
15+
$caseExprQuery = 'case 1 when 1 then "Some" else "Other" end';
16+
$component = CaseExpression::parse(
17+
new Parser(),
18+
$this->getTokensList($caseExprQuery));
19+
$this->assertEquals(
20+
CaseExpression::build($component),
21+
'CASE 1 WHEN 1 THEN "Some" ELSE "Other" END'
22+
);
23+
}
24+
25+
public function testParseBuild2()
26+
{
27+
$caseExprQuery = 'case when 1=1 then "India" else "Other" end';
28+
$component = CaseExpression::parse(
29+
new Parser(),
30+
$this->getTokensList($caseExprQuery));
31+
$this->assertEquals(
32+
CaseExpression::build($component),
33+
'CASE WHEN 1=1 THEN "India" ELSE "Other" END'
34+
);
35+
}
36+
37+
public function testParseBuild3()
38+
{
39+
$caseExprQuery = 'case 1 when 1 then "Some" '
40+
. 'when 2 then "SomeOther" else "Other" end';
41+
$component = CaseExpression::parse(
42+
new Parser(),
43+
$this->getTokensList($caseExprQuery));
44+
$this->assertEquals(
45+
CaseExpression::build($component),
46+
'CASE 1 WHEN 1 THEN "Some" WHEN 2 THEN "SomeOther" ELSE "Other" END'
47+
);
48+
}
49+
50+
public function testParseBuild4()
51+
{
52+
$caseExprQuery = 'case 1 when 1 then "Some" '
53+
. 'when 2 then "SomeOther" end';
54+
$component = CaseExpression::parse(
55+
new Parser(),
56+
$this->getTokensList($caseExprQuery));
57+
$this->assertEquals(
58+
CaseExpression::build($component),
59+
'CASE 1 WHEN 1 THEN "Some" WHEN 2 THEN "SomeOther" END'
60+
);
61+
}
62+
63+
public function testParseBuild5()
64+
{
65+
$caseExprQuery = 'case when 1=1 then "Some" '
66+
. 'when 1=2 then "SomeOther" else "Other" end';
67+
$component = CaseExpression::parse(
68+
new Parser(),
69+
$this->getTokensList($caseExprQuery));
70+
$this->assertEquals(
71+
CaseExpression::build($component),
72+
'CASE WHEN 1=1 THEN "Some" WHEN 1=2 THEN "SomeOther" ELSE "Other" END'
73+
);
74+
}
75+
76+
public function testParseBuild6()
77+
{
78+
$caseExprQuery = 'case when 1=1 then "Some" '
79+
. 'when 1=2 then "SomeOther" end';
80+
$component = CaseExpression::parse(
81+
new Parser(),
82+
$this->getTokensList($caseExprQuery));
83+
$this->assertEquals(
84+
CaseExpression::build($component),
85+
'CASE WHEN 1=1 THEN "Some" WHEN 1=2 THEN "SomeOther" END'
86+
);
87+
}
88+
}

tests/Parser/SelectStatementTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ public function testSelectProvider()
3333
array('parser/parseSelect4'),
3434
array('parser/parseSelectErr1'),
3535
array('parser/parseSelectNested'),
36+
array('parser/parseSelectCase1'),
37+
array('parser/parseSelectCase2'),
38+
array('parser/parseSelectCase3'),
39+
array('parser/parseSelectCase4'),
40+
array('parser/parseSelectCaseErr1'),
41+
array('parser/parseSelectCaseErr2'),
42+
array('parser/parseSelectCaseErr3'),
43+
array('parser/parseSelectCaseErr4'),
44+
array('parser/parseSelectCaseErr5'),
3645
);
3746
}
3847
}

0 commit comments

Comments
 (0)