DeleteStatement::parse()   F
last analyzed

Complexity

Conditions 30
Paths 22

Size

Total Lines 166
Code Lines 104

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 101
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 30
eloc 104
nc 22
nop 2
dl 0
loc 166
ccs 101
cts 101
cp 1
crap 30
rs 3.3333
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpMyAdmin\SqlParser\Statements;
6
7
use PhpMyAdmin\SqlParser\Components\ArrayObj;
8
use PhpMyAdmin\SqlParser\Components\Condition;
9
use PhpMyAdmin\SqlParser\Components\Expression;
10
use PhpMyAdmin\SqlParser\Components\JoinKeyword;
11
use PhpMyAdmin\SqlParser\Components\Limit;
12
use PhpMyAdmin\SqlParser\Components\OrderKeyword;
13
use PhpMyAdmin\SqlParser\Parser;
14
use PhpMyAdmin\SqlParser\Parsers\Conditions;
15
use PhpMyAdmin\SqlParser\Parsers\ExpressionArray;
16
use PhpMyAdmin\SqlParser\Parsers\Expressions;
17
use PhpMyAdmin\SqlParser\Parsers\JoinKeywords;
18
use PhpMyAdmin\SqlParser\Parsers\Limits;
19
use PhpMyAdmin\SqlParser\Parsers\OptionsArrays;
20
use PhpMyAdmin\SqlParser\Parsers\OrderKeywords;
21
use PhpMyAdmin\SqlParser\Statement;
22
use PhpMyAdmin\SqlParser\TokensList;
23
use PhpMyAdmin\SqlParser\TokenType;
24
25
use function stripos;
26
use function strlen;
27
28
/**
29
 * `DELETE` statement.
30
 *
31
 * DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name
32
 *     [PARTITION (partition_name,...)]
33
 *     [WHERE where_condition]
34
 *     [ORDER BY ...]
35
 *     [LIMIT row_count]
36
 *
37
 * Multi-table syntax
38
 *
39
 * DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
40
 *   tbl_name[.*] [, tbl_name[.*]] ...
41
 *   FROM table_references
42
 *   [WHERE where_condition]
43
 *
44
 * OR
45
 *
46
 * DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
47
 *   FROM tbl_name[.*] [, tbl_name[.*]] ...
48
 *   USING table_references
49
 *   [WHERE where_condition]
50
 */
51
class DeleteStatement extends Statement
52
{
53
    /**
54
     * Options for `DELETE` statements.
55
     *
56
     * @var array<string, int|array<int, int|string>>
57
     * @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})>
58
     */
59
    public static array $statementOptions = [
60
        'LOW_PRIORITY' => 1,
61
        'QUICK' => 2,
62
        'IGNORE' => 3,
63
    ];
64
65
    /**
66
     * The clauses of this statement, in order.
67
     *
68
     * @see Statement::$clauses
69
     *
70
     * @var array<string, array{non-empty-string, int-mask-of<self::ADD_*>}>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, array{non-...-mask-of<self::ADD_*>}> at position 6 could not be parsed: Expected ':' at position 6, but found 'non-empty-string'.
Loading history...
71
     */
72
    public static array $clauses = [
73
        'DELETE' => [
74
            'DELETE',
75
            Statement::ADD_KEYWORD,
76
        ],
77
        // Used for options.
78
        '_OPTIONS' => [
79
            '_OPTIONS',
80
            Statement::ADD_CLAUSE,
81
        ],
82
        'FROM' => [
83
            'FROM',
84
            Statement::ADD_CLAUSE | Statement::ADD_KEYWORD,
85
        ],
86
        'PARTITION' => [
87
            'PARTITION',
88
            Statement::ADD_CLAUSE | Statement::ADD_KEYWORD,
89
        ],
90
        'USING' => [
91
            'USING',
92
            Statement::ADD_CLAUSE | Statement::ADD_KEYWORD,
93
        ],
94
        'WHERE' => [
95
            'WHERE',
96
            Statement::ADD_CLAUSE | Statement::ADD_KEYWORD,
97
        ],
98
        'ORDER BY' => [
99
            'ORDER BY',
100
            Statement::ADD_CLAUSE | Statement::ADD_KEYWORD,
101
        ],
102
        'LIMIT' => [
103
            'LIMIT',
104
            Statement::ADD_CLAUSE | Statement::ADD_KEYWORD,
105
        ],
106
    ];
107
108
    /**
109
     * Table(s) used as sources for this statement.
110
     *
111
     * @var Expression[]|null
112
     */
113
    public array|null $from = null;
114
115
    /**
116
     * Joins.
117
     *
118
     * @var JoinKeyword[]|null
119
     */
120
    public array|null $join = null;
121
122
    /**
123
     * Tables used as sources for this statement.
124
     *
125
     * @var Expression[]|null
126
     */
127
    public array|null $using = null;
128
129
    /**
130
     * Columns used in this statement.
131
     *
132
     * @var Expression[]|null
133
     */
134
    public array|null $columns = null;
135
136
    /**
137
     * Partitions used as source for this statement.
138
     */
139
    public ArrayObj|null $partition = null;
140
141
    /**
142
     * Conditions used for filtering each row of the result set.
143
     *
144
     * @var Condition[]|null
145
     */
146
    public array|null $where = null;
147
148
    /**
149
     * Specifies the order of the rows in the result set.
150
     *
151
     * @var OrderKeyword[]|null
152
     */
153
    public array|null $order = null;
154
155
    /**
156
     * Conditions used for limiting the size of the result set.
157
     */
158
    public Limit|null $limit = null;
159
160 4
    public function build(): string
161
    {
162 4
        $ret = 'DELETE ' . $this->options->build();
0 ignored issues
show
Bug introduced by
The method build() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

162
        $ret = 'DELETE ' . $this->options->/** @scrutinizer ignore-call */ build();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
163
164 4
        if ($this->columns !== null && $this->columns !== []) {
165 2
            $ret .= ' ' . Expressions::buildAll($this->columns);
166
        }
167
168 4
        if ($this->from !== null && $this->from !== []) {
169 4
            $ret .= ' FROM ' . Expressions::buildAll($this->from);
170
        }
171
172 4
        if ($this->join !== null && $this->join !== []) {
173 2
            $ret .= ' ' . JoinKeywords::buildAll($this->join);
174
        }
175
176 4
        if ($this->using !== null && $this->using !== []) {
177 2
            $ret .= ' USING ' . Expressions::buildAll($this->using);
178
        }
179
180 4
        if ($this->where !== null && $this->where !== []) {
181 4
            $ret .= ' WHERE ' . Conditions::buildAll($this->where);
182
        }
183
184 4
        if ($this->order !== null && $this->order !== []) {
185 2
            $ret .= ' ORDER BY ' . OrderKeywords::buildAll($this->order);
186
        }
187
188 4
        if ($this->limit !== null && strlen((string) $this->limit) > 0) {
189 2
            $ret .= ' LIMIT ' . $this->limit->build();
190
        }
191
192 4
        return $ret;
193
    }
194
195
    /**
196
     * @param Parser     $parser the instance that requests parsing
197
     * @param TokensList $list   the list of tokens to be parsed
198
     */
199 62
    public function parse(Parser $parser, TokensList $list): void
200
    {
201 62
        ++$list->idx; // Skipping `DELETE`.
202
203
        // parse any options if provided
204 62
        $this->options = OptionsArrays::parse($parser, $list, static::$statementOptions);
205 62
        ++$list->idx;
206
207
        /**
208
         * The state of the parser.
209
         *
210
         * Below are the states of the parser.
211
         *
212
         *      0 ---------------------------------[ FROM ]----------------------------------> 2
213
         *      0 ------------------------------[ table[.*] ]--------------------------------> 1
214
         *      1 ---------------------------------[ FROM ]----------------------------------> 2
215
         *      2 --------------------------------[ USING ]----------------------------------> 3
216
         *      2 --------------------------------[ WHERE ]----------------------------------> 4
217
         *      2 --------------------------------[ ORDER ]----------------------------------> 5
218
         *      2 --------------------------------[ LIMIT ]----------------------------------> 6
219
         */
220 62
        $state = 0;
221
222
        /**
223
         * If the query is multi-table or not.
224
         */
225 62
        $multiTable = false;
226
227 62
        for (; $list->idx < $list->count; ++$list->idx) {
228
            /**
229
             * Token parsed at this moment.
230
             */
231 62
            $token = $list->tokens[$list->idx];
232
233
            // End of statement.
234 62
            if ($token->type === TokenType::Delimiter) {
235 38
                break;
236
            }
237
238 62
            if ($state === 0) {
239 62
                if ($token->type === TokenType::Keyword) {
240 52
                    if ($token->keyword !== 'FROM') {
241 2
                        $parser->error('Unexpected keyword.', $token);
242 2
                        break;
243
                    }
244
245 50
                    ++$list->idx; // Skip 'FROM'
246 50
                    $this->from = ExpressionArray::parse($parser, $list);
247
248 50
                    $state = 2;
249
                } else {
250 12
                    $this->columns = ExpressionArray::parse($parser, $list);
251 12
                    $state = 1;
252
                }
253 56
            } elseif ($state === 1) {
254 12
                if ($token->type !== TokenType::Keyword) {
255 2
                    $parser->error('Unexpected token.', $token);
256 2
                    break;
257
                }
258
259 10
                if ($token->keyword !== 'FROM') {
260 2
                    $parser->error('Unexpected keyword.', $token);
261 2
                    break;
262
                }
263
264 8
                ++$list->idx; // Skip 'FROM'
265 8
                $this->from = ExpressionArray::parse($parser, $list);
266
267 8
                $state = 2;
268 50
            } elseif ($state === 2) {
269 50
                if ($token->type === TokenType::Keyword) {
270 50
                    if (stripos($token->keyword, 'JOIN') !== false) {
271 4
                        ++$list->idx;
272 4
                        $this->join = JoinKeywords::parse($parser, $list);
273
274
                        // remain in state = 2
275
                    } else {
276 50
                        switch ($token->keyword) {
277 50
                            case 'USING':
278 14
                                ++$list->idx; // Skip 'USING'
279 14
                                $this->using = ExpressionArray::parse($parser, $list);
280 14
                                $state = 3;
281
282 14
                                $multiTable = true;
283 14
                                break;
284 38
                            case 'WHERE':
285 30
                                ++$list->idx; // Skip 'WHERE'
286 30
                                $this->where = Conditions::parse($parser, $list);
287 30
                                $state = 4;
288 30
                                break;
289 8
                            case 'ORDER BY':
290 4
                                ++$list->idx; // Skip 'ORDER BY'
291 4
                                $this->order = OrderKeywords::parse($parser, $list);
292 4
                                $state = 5;
293 4
                                break;
294 4
                            case 'LIMIT':
295 2
                                ++$list->idx; // Skip 'LIMIT'
296 2
                                $this->limit = Limits::parse($parser, $list);
297 2
                                $state = 6;
298 2
                                break;
299
                            default:
300 2
                                $parser->error('Unexpected keyword.', $token);
301 2
                                break 2;
302
                        }
303
                    }
304
                }
305 34
            } elseif ($state === 3) {
306 14
                if ($token->type !== TokenType::Keyword) {
307 4
                    $parser->error('Unexpected token.', $token);
308 4
                    break;
309
                }
310
311 10
                if ($token->keyword !== 'WHERE') {
312 2
                    $parser->error('Unexpected keyword.', $token);
313 2
                    break;
314
                }
315
316 8
                ++$list->idx; // Skip 'WHERE'
317 8
                $this->where = Conditions::parse($parser, $list);
318 8
                $state = 4;
319 24
            } elseif ($state === 4) {
320 22
                if ($multiTable === true && $token->type === TokenType::Keyword) {
321 4
                    $parser->error('This type of clause is not valid in Multi-table queries.', $token);
322 4
                    break;
323
                }
324
325 18
                if ($token->type === TokenType::Keyword) {
326 18
                    switch ($token->keyword) {
327 18
                        case 'ORDER BY':
328 14
                            ++$list->idx; // Skip 'ORDER  BY'
329 14
                            $this->order = OrderKeywords::parse($parser, $list);
330 14
                            $state = 5;
331 14
                            break;
332 4
                        case 'LIMIT':
333 2
                            ++$list->idx; // Skip 'LIMIT'
334 2
                            $this->limit = Limits::parse($parser, $list);
335 2
                            $state = 6;
336 2
                            break;
337
                        default:
338 2
                            $parser->error('Unexpected keyword.', $token);
339 2
                            break 2;
340
                    }
341
                }
342 12
            } elseif ($state === 5) {
343 12
                if ($token->type === TokenType::Keyword) {
344 12
                    if ($token->keyword !== 'LIMIT') {
345 4
                        $parser->error('Unexpected keyword.', $token);
346 4
                        break;
347
                    }
348
349 8
                    ++$list->idx; // Skip 'LIMIT'
350 8
                    $this->limit = Limits::parse($parser, $list);
351 8
                    $state = 6;
352
                }
353
            }
354
        }
355
356 62
        if ($state >= 2) {
357 56
            foreach ($this->from as $fromExpr) {
358 56
                $fromExpr->database = $fromExpr->table;
359 56
                $fromExpr->table = $fromExpr->column;
360 56
                $fromExpr->column = null;
361
            }
362
        }
363
364 62
        --$list->idx;
365
    }
366
}
367