Completed
Pull Request — master (#203)
by
unknown
03:42
created

CaseExpression::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/**
4
 * Parses a reference to a CASE expression.
5
 */
6
7
namespace PhpMyAdmin\SqlParser\Components;
8
9
use PhpMyAdmin\SqlParser\Component;
10
use PhpMyAdmin\SqlParser\Context;
11
use PhpMyAdmin\SqlParser\Parser;
12
use PhpMyAdmin\SqlParser\Token;
13
use PhpMyAdmin\SqlParser\TokensList;
14
15
/**
16
 * Parses a reference to a CASE expression.
17
 *
18
 * @category   Components
19
 *
20
 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
21
 */
22
class CaseExpression extends Component
23
{
24
    /**
25
     * The value to be compared.
26
     *
27
     * @var Expression
28
     */
29
    public $value;
30
31
    /**
32
     * The conditions in WHEN clauses.
33
     *
34
     * @var array
35
     */
36
    public $conditions;
37
38
    /**
39
     * The results matching with the WHEN clauses.
40
     *
41
     * @var array
42
     */
43
    public $results;
44
45
    /**
46
     * The values to be compared against.
47
     *
48
     * @var array
49
     */
50
    public $compare_values;
51
52
    /**
53
     * The result in ELSE section of expr.
54
     *
55
     * @var Expression
56
     */
57
    public $else_result;
58
59
    /**
60
     * The alias of this CASE statement.
61
     *
62
     * @var string
63
     */
64
    public $alias;
65
66
    /**
67
     * The sub-expression.
68
     *
69
     * @var string
70
     */
71
    public $expr = '';
72
73
    /**
74
     * Constructor.
75
     */
76 29
    public function __construct()
77
    {
78 29
    }
79
80
    /**
81
     * @param Parser     $parser  the parser that serves as context
82
     * @param TokensList $list    the list of tokens that are being parsed
83
     * @param array      $options parameters for parsing
84
     *
85
     * @return CaseExpression
86
     */
87 29
    public static function parse(Parser $parser, TokensList $list, array $options = array())
88
    {
89 29
        $ret = new self();
90
91
        /**
92
         * State of parser.
93
         *
94
         * @var int
95
         */
96 29
        $state = 0;
97
98
        /**
99
         * Syntax type (type 0 or type 1).
100
         *
101
         * @var int
102
         */
103 29
        $type = 0;
104
105 29
        ++$list->idx; // Skip 'CASE'
106
107 29
        for (; $list->idx < $list->count; ++$list->idx) {
108
            /**
109
             * Token parsed at this moment.
110
             *
111
             * @var Token
112
             */
113 29
            $token = $list->tokens[$list->idx];
114
115
            // Skipping whitespaces and comments.
116 29
            if (($token->type === Token::TYPE_WHITESPACE)
117 29
                || ($token->type === Token::TYPE_COMMENT)
118
            ) {
119 29
                continue;
120
            }
121
122 29
            if ($state === 0) {
123 29
                if ($token->type === Token::TYPE_KEYWORD
124 21
                    && $token->keyword === 'WHEN'
125
                ) {
126 12
                    ++$list->idx; // Skip 'WHEN'
127 12
                    $new_condition = Condition::parse($parser, $list);
128 12
                    $type = 1;
129 12
                    $state = 1;
130 12
                    $ret->conditions[] = $new_condition;
131 28
                } elseif ($token->type === Token::TYPE_KEYWORD
132 20
                    && $token->keyword === 'ELSE'
133
                ) {
134 5
                    ++$list->idx; // Skip 'ELSE'
135 5
                    $ret->else_result = Expression::parse($parser, $list);
136 5
                    $state = 0; // last clause of CASE expression
137 28
                } elseif ($token->type === Token::TYPE_KEYWORD
138 20
                    && $token->keyword === 'END'
139
                ) {
140 19
                    $state = 3; // end of CASE expression
141 19
                    ++$list->idx;
142 19
                    break;
143 17
                } elseif ($token->type === Token::TYPE_KEYWORD) {
144 1
                    $parser->error('Unexpected keyword.', $token);
145 1
                    break;
146
                } else {
147 16
                    $ret->value = Expression::parse($parser, $list);
148 16
                    $type = 0;
149 16
                    $state = 1;
150
                }
151 28
            } elseif ($state === 1) {
152 28
                if ($type === 0) {
153 16
                    if ($token->type === Token::TYPE_KEYWORD
154 16
                        && $token->keyword === 'WHEN'
155
                    ) {
156 14
                        ++$list->idx; // Skip 'WHEN'
157 14
                        $new_value = Expression::parse($parser, $list);
158 14
                        $state = 2;
159 14
                        $ret->compare_values[] = $new_value;
160 15
                    } elseif ($token->type === Token::TYPE_KEYWORD
161 15
                        && $token->keyword === 'ELSE'
162
                    ) {
163 8
                        ++$list->idx; // Skip 'ELSE'
164 8
                        $ret->else_result = Expression::parse($parser, $list);
165 8
                        $state = 0; // last clause of CASE expression
166 7
                    } elseif ($token->type === Token::TYPE_KEYWORD
167 7
                        && $token->keyword === 'END'
168
                    ) {
169 5
                        $state = 3; // end of CASE expression
170 5
                        ++$list->idx;
171 5
                        break;
172 2
                    } elseif ($token->type === Token::TYPE_KEYWORD) {
173 2
                        $parser->error('Unexpected keyword.', $token);
174 2
                        break;
175
                    }
176
                } else {
177 12
                    if ($token->type === Token::TYPE_KEYWORD
178 12
                        && $token->keyword === 'THEN'
179
                    ) {
180 11
                        ++$list->idx; // Skip 'THEN'
181 11
                        $new_result = Expression::parse($parser, $list);
182 11
                        $state = 0;
183 11
                        $ret->results[] = $new_result;
184 1
                    } elseif ($token->type === Token::TYPE_KEYWORD) {
185 1
                        $parser->error('Unexpected keyword.', $token);
186 1
                        break;
187
                    }
188
                }
189 14
            } elseif ($state === 2) {
190 14
                if ($type === 0) {
191 14
                    if ($token->type === Token::TYPE_KEYWORD
192 14
                        && $token->keyword === 'THEN'
193
                    ) {
194 14
                        ++$list->idx; // Skip 'THEN'
195 14
                        $new_result = Expression::parse($parser, $list);
196 14
                        $ret->results[] = $new_result;
197 14
                        $state = 1;
198 1
                    } elseif ($token->type === Token::TYPE_KEYWORD) {
199 1
                        $parser->error('Unexpected keyword.', $token);
200 1
                        break;
201
                    }
202
                }
203
            }
204
        }
205
206 29
        if ($state !== 3) {
207 5
            $parser->error(
208 5
                'Unexpected end of CASE expression',
209 5
                $list->tokens[$list->idx - 1]
210
            );
211
        } else {
212
            // Parse for alias of CASE expression
213 24
            $asFound = false;
214 24
            for (; $list->idx < $list->count; ++$list->idx) {
215 24
                $token = $list->tokens[$list->idx];
216
217
                // End of statement.
218 24
                if ($token->type === Token::TYPE_DELIMITER) {
219 10
                    break;
220
                }
221
                
222
                // Skipping whitespaces and comments.
223 17
                if (($token->type === Token::TYPE_WHITESPACE)
224 17
                    || ($token->type === Token::TYPE_COMMENT)
225
                ) {
226 17
                    continue;
227
                }
228
229
                // Handle optional AS keyword before alias
230 17
                if ($token->type === Token::TYPE_KEYWORD
231 16
                    && $token->keyword === 'AS') {
232 9
                    if ($asFound || !empty($ret->alias)) {
233 1
                        $parser->error('Potential duplicate alias of CASE expression.', $token);
234 1
                        break;
235
                    }
236 9
                    $asFound = true;
237 9
                    continue;
238
                }
239
240 15
                if ($asFound
241 7
                    && $token->type === Token::TYPE_KEYWORD
242 1
                    && ($token->flags & Token::FLAG_KEYWORD_RESERVED || $token->flags & Token::FLAG_KEYWORD_FUNCTION)) {
243 1
                    $parser->error('An alias expected after AS but got ' . $token->value, $token);
244 1
                    $asFound = false;
245 1
                    break;
246
                }
247
248 14
                if ($asFound
249 13
                    || $token->type === Token::TYPE_STRING
250 13
                    || ($token->type === Token::TYPE_SYMBOL && !$token->flags & Token::FLAG_SYMBOL_VARIABLE)
251 13
                    || $token->type === Token::TYPE_NONE
252
                ) {
253
                    // An alias is expected (the keyword `AS` was previously found).
254 9
                    if (!empty($ret->alias)) {
255 1
                        $parser->error('An alias was previously found.', $token);
256 1
                        break;
257
                    }
258 9
                    $ret->alias = $token->value;
259 9
                    $asFound = false;
260
261 9
                    continue;
262
                }
263
264 11
                break;
265
            }
266 24
            if ($asFound) {
267 2
                $parser->error('An alias was expected after AS.', $list->tokens[$list->idx - 1]);
268
            }
269
270 24
            $ret->expr = self::build($ret);
271
        }
272
273 29
        --$list->idx;
274
275 29
        return $ret;
276
    }
277
278
    /**
279
     * @param CaseExpression $component the component to be built
280
     * @param array          $options   parameters for building
281
     *
282
     * @return string
283
     */
284 24
    public static function build($component, array $options = array())
285
    {
286 24
        $ret = 'CASE ';
287 24
        if (isset($component->value)) {
288
            // Syntax type 0
289 13
            $ret .= $component->value . ' ';
290 13
            $val_cnt = count($component->compare_values);
291 13
            $res_cnt = count($component->results);
292 13 View Code Duplication
            for ($i = 0; $i < $val_cnt && $i < $res_cnt; ++$i) {
293 13
                $ret .= 'WHEN ' . $component->compare_values[$i] . ' ';
294 13
                $ret .= 'THEN ' . $component->results[$i] . ' ';
295
            }
296
        } else {
297
            // Syntax type 1
298 11
            $val_cnt = count($component->conditions);
299 11
            $res_cnt = count($component->results);
300 11 View Code Duplication
            for ($i = 0; $i < $val_cnt && $i < $res_cnt; ++$i) {
301 11
                $ret .= 'WHEN ' . Condition::build($component->conditions[$i]) . ' ';
302 11
                $ret .= 'THEN ' . $component->results[$i] . ' ';
303
            }
304
        }
305 24
        if (isset($component->else_result)) {
306 13
            $ret .= 'ELSE ' . $component->else_result . ' ';
307
        }
308 24
        $ret .= 'END';
309
310 24
        if ($component->alias) {
311 9
            $ret .= ' AS ' . Context::escape($component->alias);
312
        }
313
314 24
        return $ret;
315
    }
316
}
317