Skip to content

Commit 7f4702a

Browse files
committed
Add parsing of CASE Expressions
Fix phpmyadmin/phpmyadmin#12100 Signed-off-by: Deven Bansod <devenbansod.bits@gmail.com>
1 parent 5988855 commit 7f4702a

File tree

2 files changed

+295
-3
lines changed

2 files changed

+295
-3
lines changed

src/Components/CaseExpression.php

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

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
@@ -73,13 +73,21 @@ public static function parse(Parser $parser, TokensList $list, array $options =
7373
&& ((~$token->flags & Token::FLAG_KEYWORD_FUNCTION))
7474
&& ($token->value !== 'DUAL')
7575
&& ($token->value !== 'NULL')
76+
&& ($token->value !== 'CASE')
7677
) {
7778
// No keyword is expected.
7879
break;
7980
}
8081

8182
if ($state === 0) {
82-
$expr = Expression::parse($parser, $list, $options);
83+
if ($token->type === Token::TYPE_KEYWORD
84+
&& $token->value === 'CASE'
85+
) {
86+
$expr = CaseExpression::parse($parser, $list, $options);
87+
} else {
88+
$expr = Expression::parse($parser, $list, $options);
89+
}
90+
8391
if ($expr === null) {
8492
break;
8593
}

0 commit comments

Comments
 (0)