Passed
Push — master ( 6c3f09...c54108 )
by William
03:43
created

CaseExpression::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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