Completed
Push — master ( 14c8dc...87c59f )
by Michal
23:27
created

CreateStatement   D

Complexity

Total Complexity 77

Size/Duplication

Total Lines 606
Duplicated Lines 2.31 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 99.53%

Importance

Changes 0
Metric Value
wmc 77
lcom 1
cbo 12
dl 14
loc 606
ccs 210
cts 211
cp 0.9953
rs 4.8477
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
D build() 0 83 20
F parse() 14 278 57

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CreateStatement often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CreateStatement, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * `CREATE` statement.
5
 */
6
7
namespace PhpMyAdmin\SqlParser\Statements;
8
9
use PhpMyAdmin\SqlParser\Components\ArrayObj;
10
use PhpMyAdmin\SqlParser\Components\CreateDefinition;
11
use PhpMyAdmin\SqlParser\Components\DataType;
12
use PhpMyAdmin\SqlParser\Components\Expression;
13
use PhpMyAdmin\SqlParser\Components\OptionsArray;
14
use PhpMyAdmin\SqlParser\Components\ParameterDefinition;
15
use PhpMyAdmin\SqlParser\Components\PartitionDefinition;
16
use PhpMyAdmin\SqlParser\Parser;
17
use PhpMyAdmin\SqlParser\Statement;
18
use PhpMyAdmin\SqlParser\Token;
19
use PhpMyAdmin\SqlParser\TokensList;
20
21
/**
22
 * `CREATE` statement.
23
 *
24
 * @category   Statements
25
 *
26
 * @license    https://www.gnu.org/licenses/gpl-2.0.txt GPL-2.0+
27
 */
28
class CreateStatement extends Statement
29
{
30
    /**
31
     * Options for `CREATE` statements.
32
     *
33
     * @var array
34
     */
35
    public static $OPTIONS = array(
36
        // CREATE TABLE
37
        'TEMPORARY' => 1,
38
39
        // CREATE VIEW
40
        'OR REPLACE' => array(2, 'var='),
41
        'ALGORITHM' => array(3, 'var='),
42
        // `DEFINER` is also used for `CREATE FUNCTION / PROCEDURE`
43
        'DEFINER' => array(4, 'expr='),
44
        'SQL SECURITY' => array(5, 'var'),
45
46
        'DATABASE' => 6,
47
        'EVENT' => 6,
48
        'FUNCTION' => 6,
49
        'INDEX' => 6,
50
        'UNIQUE INDEX' => 6,
51
        'FULLTEXT INDEX' => 6,
52
        'SPATIAL INDEX' => 6,
53
        'PROCEDURE' => 6,
54
        'SERVER' => 6,
55
        'TABLE' => 6,
56
        'TABLESPACE' => 6,
57
        'TRIGGER' => 6,
58
        'USER' => 6,
59
        'VIEW' => 6,
60
61
        // CREATE TABLE
62
        'IF NOT EXISTS' => 7,
63
    );
64
65
    /**
66
     * All database options.
67
     *
68
     * @var array
69
     */
70
    public static $DB_OPTIONS = array(
71
        'CHARACTER SET' => array(1, 'var='),
72
        'CHARSET' => array(1, 'var='),
73
        'DEFAULT CHARACTER SET' => array(1, 'var='),
74
        'DEFAULT CHARSET' => array(1, 'var='),
75
        'DEFAULT COLLATE' => array(2, 'var='),
76
        'COLLATE' => array(2, 'var='),
77
    );
78
79
    /**
80
     * All table options.
81
     *
82
     * @var array
83
     */
84
    public static $TABLE_OPTIONS = array(
85
        'ENGINE' => array(1, 'var='),
86
        'AUTO_INCREMENT' => array(2, 'var='),
87
        'AVG_ROW_LENGTH' => array(3, 'var'),
88
        'CHARACTER SET' => array(4, 'var='),
89
        'CHARSET' => array(4, 'var='),
90
        'DEFAULT CHARACTER SET' => array(4, 'var='),
91
        'DEFAULT CHARSET' => array(4, 'var='),
92
        'CHECKSUM' => array(5, 'var'),
93
        'DEFAULT COLLATE' => array(6, 'var='),
94
        'COLLATE' => array(6, 'var='),
95
        'COMMENT' => array(7, 'var='),
96
        'CONNECTION' => array(8, 'var'),
97
        'DATA DIRECTORY' => array(9, 'var'),
98
        'DELAY_KEY_WRITE' => array(10, 'var'),
99
        'INDEX DIRECTORY' => array(11, 'var'),
100
        'INSERT_METHOD' => array(12, 'var'),
101
        'KEY_BLOCK_SIZE' => array(13, 'var'),
102
        'MAX_ROWS' => array(14, 'var'),
103
        'MIN_ROWS' => array(15, 'var'),
104
        'PACK_KEYS' => array(16, 'var'),
105
        'PASSWORD' => array(17, 'var'),
106
        'ROW_FORMAT' => array(18, 'var'),
107
        'TABLESPACE' => array(19, 'var'),
108
        'STORAGE' => array(20, 'var'),
109
        'UNION' => array(21, 'var'),
110
    );
111
112
    /**
113
     * All function options.
114
     *
115
     * @var array
116
     */
117
    public static $FUNC_OPTIONS = array(
118
        'COMMENT' => array(1, 'var='),
119
        'LANGUAGE SQL' => 2,
120
        'DETERMINISTIC' => 3,
121
        'NOT DETERMINISTIC' => 3,
122
        'CONTAINS SQL' => 4,
123
        'NO SQL' => 4,
124
        'READS SQL DATA' => 4,
125
        'MODIFIES SQL DATA' => 4,
126
        'SQL SECURITY DEFINER' => array(5, 'var'),
127
    );
128
129
    /**
130
     * All trigger options.
131
     *
132
     * @var array
133
     */
134
    public static $TRIGGER_OPTIONS = array(
135
        'BEFORE' => 1,
136
        'AFTER' => 1,
137
        'INSERT' => 2,
138
        'UPDATE' => 2,
139
        'DELETE' => 2,
140
    );
141
142
    /**
143
     * The name of the entity that is created.
144
     *
145
     * Used by all `CREATE` statements.
146
     *
147
     * @var Expression
148
     */
149
    public $name;
150
151
    /**
152
     * The options of the entity (table, procedure, function, etc.).
153
     *
154
     * Used by `CREATE TABLE`, `CREATE FUNCTION` and `CREATE PROCEDURE`.
155
     *
156
     * @var OptionsArray
157
     *
158
     * @see static::$TABLE_OPTIONS
159
     * @see static::$FUNC_OPTIONS
160
     * @see static::$TRIGGER_OPTIONS
161
     */
162
    public $entityOptions;
163
164
    /**
165
     * If `CREATE TABLE`, a list of columns and keys.
166
     * If `CREATE VIEW`, a list of columns.
167
     *
168
     * Used by `CREATE TABLE` and `CREATE VIEW`.
169
     *
170
     * @var CreateDefinition[]|ArrayObj
171
     */
172
    public $fields;
173
174
    /**
175
     * If `CREATE TABLE ... SELECT`.
176
     *
177
     * Used by `CREATE TABLE`
178
     *
179
     * @var SelectStatement
180
     */
181
    public $select;
182
183
    /**
184
     * If `CREATE TABLE ... LIKE`.
185
     *
186
     * Used by `CREATE TABLE`
187
     *
188
     * @var Expression
189
     */
190
    public $like;
191
192
    /**
193
     * Expression used for partitioning.
194
     *
195
     * @var string
196
     */
197
    public $partitionBy;
198
199
    /**
200
     * The number of partitions.
201
     *
202
     * @var int
203
     */
204
    public $partitionsNum;
205
206
    /**
207
     * Expression used for subpartitioning.
208
     *
209
     * @var string
210
     */
211
    public $subpartitionBy;
212
213
    /**
214
     * The number of subpartitions.
215
     *
216
     * @var int
217
     */
218
    public $subpartitionsNum;
219
220
    /**
221
     * The partition of the new table.
222
     *
223
     * @var PartitionDefinition[]
224
     */
225
    public $partitions;
226
227
    /**
228
     * If `CREATE TRIGGER` the name of the table.
229
     *
230
     * Used by `CREATE TRIGGER`.
231
     *
232
     * @var Expression
233
     */
234
    public $table;
235
236
    /**
237
     * The return data type of this routine.
238
     *
239
     * Used by `CREATE FUNCTION`.
240
     *
241
     * @var DataType
242
     */
243
    public $return;
244
245
    /**
246
     * The parameters of this routine.
247
     *
248
     * Used by `CREATE FUNCTION` and `CREATE PROCEDURE`.
249
     *
250
     * @var ParameterDefinition[]
251
     */
252
    public $parameters;
253
254
    /**
255
     * The body of this function or procedure. For views, it is the select
256
     * statement that gets the.
257
     *
258
     * Used by `CREATE FUNCTION`, `CREATE PROCEDURE` and `CREATE VIEW`.
259
     *
260
     * @var Token[]|string
261
     */
262
    public $body = array();
263
264
    /**
265
     * @return string
266
     */
267 10
    public function build()
268
    {
269 10
        $fields = '';
270 10
        if (!empty($this->fields)) {
271 5
            if (is_array($this->fields)) {
272 4
                $fields = CreateDefinition::build($this->fields) . ' ';
273 1
            } elseif ($this->fields instanceof ArrayObj) {
274 1
                $fields = ArrayObj::build($this->fields);
275
            }
276
        }
277 10
        if ($this->options->has('DATABASE')) {
278
            return 'CREATE '
279 1
                . OptionsArray::build($this->options) . ' '
280 1
                . Expression::build($this->name) . ' '
281 1
                . OptionsArray::build($this->entityOptions);
282 9
        } elseif ($this->options->has('TABLE') && !is_null($this->select)) {
283
            return 'CREATE '
284 1
                . OptionsArray::build($this->options) . ' '
285 1
                . Expression::build($this->name) . ' '
286 1
                . $this->select->build();
287 8
        } elseif ($this->options->has('TABLE') && !is_null($this->like)) {
288
            return 'CREATE '
289 1
                . OptionsArray::build($this->options) . ' '
290 1
                . Expression::build($this->name) . ' LIKE '
291 1
                . Expression::build($this->like);
292 8
        } elseif ($this->options->has('TABLE')) {
293 4
            $partition = '';
294
295 4
            if (!empty($this->partitionBy)) {
296 1
                $partition .= "\nPARTITION BY " . $this->partitionBy;
297
            }
298 4
            if (!empty($this->partitionsNum)) {
299 1
                $partition .= "\nPARTITIONS " . $this->partitionsNum;
300
            }
301 4
            if (!empty($this->subpartitionBy)) {
302 1
                $partition .= "\nSUBPARTITION BY " . $this->subpartitionBy;
303
            }
304 4
            if (!empty($this->subpartitionsNum)) {
305 1
                $partition .= "\nSUBPARTITIONS " . $this->subpartitionsNum;
306
            }
307 4
            if (!empty($this->partitions)) {
308 1
                $partition .= "\n" . PartitionDefinition::build($this->partitions);
309
            }
310
311
            return 'CREATE '
312 4
                . OptionsArray::build($this->options) . ' '
313 4
                . Expression::build($this->name) . ' '
314 4
                . $fields
315 4
                . OptionsArray::build($this->entityOptions)
316 4
                . $partition;
317 4
        } elseif ($this->options->has('VIEW')) {
318
            return 'CREATE '
319 1
                . OptionsArray::build($this->options) . ' '
320 1
                . Expression::build($this->name) . ' '
321 1
                . $fields . ' AS ' . TokensList::build($this->body) . ' '
322 1
                . OptionsArray::build($this->entityOptions);
323 3
        } elseif ($this->options->has('TRIGGER')) {
324
            return 'CREATE '
325 1
                . OptionsArray::build($this->options) . ' '
326 1
                . Expression::build($this->name) . ' '
327 1
                . OptionsArray::build($this->entityOptions) . ' '
328 1
                . 'ON ' . Expression::build($this->table) . ' '
329 1
                . 'FOR EACH ROW ' . TokensList::build($this->body);
330 2
        } elseif (($this->options->has('PROCEDURE'))
331 2
            || ($this->options->has('FUNCTION'))
332
        ) {
333 1
            $tmp = '';
334 1
            if ($this->options->has('FUNCTION')) {
335 1
                $tmp = 'RETURNS ' . DataType::build($this->return);
336
            }
337
338
            return 'CREATE '
339 1
                . OptionsArray::build($this->options) . ' '
340 1
                . Expression::build($this->name) . ' '
341 1
                . ParameterDefinition::build($this->parameters) . ' '
342 1
                . $tmp . ' ' . TokensList::build($this->body);
343
        }
344
345
        return 'CREATE '
346 1
            . OptionsArray::build($this->options) . ' '
347 1
            . Expression::build($this->name) . ' '
348 1
            . TokensList::build($this->body);
349
    }
350
351
    /**
352
     * @param Parser     $parser the instance that requests parsing
353
     * @param TokensList $list   the list of tokens to be parsed
354
     */
355 53
    public function parse(Parser $parser, TokensList $list)
356
    {
357 53
        ++$list->idx; // Skipping `CREATE`.
358
359
        // Parsing options.
360 53
        $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
361 53
        ++$list->idx; // Skipping last option.
362
363
        // Parsing the field name.
364 53
        $this->name = Expression::parse(
365 53
            $parser,
366 53
            $list,
367
            array(
368 53
                'parseField' => 'table',
369
                'breakOnAlias' => true,
370
            )
371
        );
372
373 53
        if ((!isset($this->name)) || ($this->name === '')) {
374 1
            $parser->error(
375 1
                'The name of the entity was expected.',
376 1
                $list->tokens[$list->idx]
377
            );
378
        } else {
379 52
            ++$list->idx; // Skipping field.
380
        }
381
382
        /**
383
         * Token parsed at this moment.
384
         *
385
         * @var Token
386
         */
387 53
        $token = $list->tokens[$list->idx];
388 53
        $nextidx = $list->idx + 1;
389 53
        while ($nextidx < $list->count && $list->tokens[$nextidx]->type == Token::TYPE_WHITESPACE) {
390 28
            ++$nextidx;
391
        }
392
393 53
        if ($this->options->has('DATABASE')) {
394 1
            $this->entityOptions = OptionsArray::parse(
395 1
                $parser,
396 1
                $list,
397 1
                static::$DB_OPTIONS
398
            );
399 52
        } elseif ($this->options->has('TABLE')
400 31
            && ($token->type == Token::TYPE_KEYWORD)
401 6
            && ($token->keyword == 'SELECT')
402
        ) {
403
            /* CREATE TABLE ... SELECT */
404 2
            $this->select = new SelectStatement($parser, $list);
405 50
        } elseif ($this->options->has('TABLE')
406 29
            && ($token->type == Token::TYPE_KEYWORD) && ($token->keyword == 'AS')
407 1
            && ($list->tokens[$nextidx]->type == Token::TYPE_KEYWORD)
408 1
            && ($list->tokens[$nextidx]->value == 'SELECT')
409
        ) {
410
            /* CREATE TABLE ... AS SELECT */
411 1
            $list->idx = $nextidx;
412 1
            $this->select = new SelectStatement($parser, $list);
413 49
        } elseif ($this->options->has('TABLE')
414 28
            && $token->type == Token::TYPE_KEYWORD
415 3
            && $token->keyword == 'LIKE'
416
        ) {
417
            /* CREATE TABLE `new_tbl` LIKE 'orig_tbl' */
418 3
            $list->idx = $nextidx;
419 3
            $this->like = Expression::parse(
420 3
                $parser,
421 3
                $list,
422
                array(
423 3
                    'parseField' => 'table',
424
                    'breakOnAlias' => true,
425
                )
426
            );
427
            // The 'LIKE' keyword was found, but no table_name was found next to it
428 3
            if ($this->like == null) {
429 1
                $parser->error(
430 1
                    'A table name was expected.',
431 1
                    $list->tokens[$list->idx]
432
                );
433
            }
434 47
        } elseif ($this->options->has('TABLE')) {
435 26
            $this->fields = CreateDefinition::parse($parser, $list);
436 26
            if (empty($this->fields)) {
437 3
                $parser->error(
438 3
                    'At least one column definition was expected.',
439 3
                    $list->tokens[$list->idx]
440
                );
441
            }
442 26
            ++$list->idx;
443
444 26
            $this->entityOptions = OptionsArray::parse(
445 26
                $parser,
446 26
                $list,
447 26
                static::$TABLE_OPTIONS
448
            );
449
450
            /**
451
             * The field that is being filled (`partitionBy` or
452
             * `subpartitionBy`).
453
             *
454
             * @var string
455
             */
456 26
            $field = null;
457
458
            /**
459
             * The number of brackets. `false` means no bracket was found
460
             * previously. At least one bracket is required to validate the
461
             * expression.
462
             *
463
             * @var int|bool
464
             */
465 26
            $brackets = false;
466
467
            /*
468
             * Handles partitions.
469
             */
470 26
            for (; $list->idx < $list->count; ++$list->idx) {
471
                /**
472
                 * Token parsed at this moment.
473
                 *
474
                 * @var Token
475
                 */
476 26
                $token = $list->tokens[$list->idx];
477
478
                // End of statement.
479 26
                if ($token->type === Token::TYPE_DELIMITER) {
480 21
                    break;
481
                }
482
483
                // Skipping comments.
484 26
                if ($token->type === Token::TYPE_COMMENT) {
485 1
                    continue;
486
                }
487
488 26
                if (($token->type === Token::TYPE_KEYWORD) && ($token->keyword === 'PARTITION BY')) {
489 2
                    $field = 'partitionBy';
490 2
                    $brackets = false;
491 26
                } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->keyword === 'SUBPARTITION BY')) {
492 2
                    $field = 'subpartitionBy';
493 2
                    $brackets = false;
494 26
                } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->keyword === 'PARTITIONS')) {
495 2
                    $token = $list->getNextOfType(Token::TYPE_NUMBER);
496 2
                    --$list->idx; // `getNextOfType` also advances one position.
497 2
                    $this->partitionsNum = $token->value;
498 26
                } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->keyword === 'SUBPARTITIONS')) {
499 2
                    $token = $list->getNextOfType(Token::TYPE_NUMBER);
500 2
                    --$list->idx; // `getNextOfType` also advances one position.
501 2
                    $this->subpartitionsNum = $token->value;
502
                } elseif (!empty($field)) {
503
                    /*
504
                     * Handling the content of `PARTITION BY` and `SUBPARTITION BY`.
505
                     */
506
507
                    // Counting brackets.
508 2
                    if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
509
                        // This is used instead of `++$brackets` because,
510
                        // initially, `$brackets` is `false` cannot be
511
                        // incremented.
512 2
                        $brackets = $brackets + 1;
513 2
                    } elseif (($token->type === Token::TYPE_OPERATOR) && ($token->value === ')')) {
514 2
                        --$brackets;
515
                    }
516
517
                    // Building the expression used for partitioning.
518 2
                    $this->$field .= ($token->type === Token::TYPE_WHITESPACE) ? ' ' : $token->token;
519
520
                    // Last bracket was read, the expression ended.
521
                    // Comparing with `0` and not `false`, because `false` means
522
                    // that no bracket was found and at least one must is
523
                    // required.
524 2
                    if ($brackets === 0) {
525 2
                        $this->$field = trim($this->$field);
526 2
                        $field = null;
527
                    }
528 26
                } elseif (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
529 5
                    if (!empty($this->partitionBy)) {
530 2
                        $this->partitions = ArrayObj::parse(
0 ignored issues
show
Documentation Bug introduced by
It seems like \PhpMyAdmin\SqlParser\Co...\PartitionDefinition')) can also be of type object<PhpMyAdmin\SqlParser\Components\ArrayObj>. However, the property $partitions is declared as type array<integer,object<Php...s\PartitionDefinition>>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
531 2
                            $parser,
532 2
                            $list,
533
                            array(
534 2
                                'type' => 'PhpMyAdmin\\SqlParser\\Components\\PartitionDefinition',
535
                            )
536
                        );
537
                    }
538 5
                    break;
539
                }
540
            }
541 21
        } elseif (($this->options->has('PROCEDURE'))
542 16
            || ($this->options->has('FUNCTION'))
543
        ) {
544 11
            $this->parameters = ParameterDefinition::parse($parser, $list);
545 11
            if ($this->options->has('FUNCTION')) {
546 6
                $prev_token = $token;
547 6
                $token = $list->getNextOfType(Token::TYPE_KEYWORD);
548 6
                if (is_null($token) || $token->keyword !== 'RETURNS') {
549 3
                    $parser->error(
550 3
                        'A "RETURNS" keyword was expected.',
551 3
                        is_null($token) ? $prev_token : $token
552
                    );
553
                } else {
554 3
                    ++$list->idx;
555 3
                    $this->return = DataType::parse(
556 3
                        $parser,
557 3
                        $list
558
                    );
559
                }
560
            }
561 11
            ++$list->idx;
562
563 11
            $this->entityOptions = OptionsArray::parse(
564 11
                $parser,
565 11
                $list,
566 11
                static::$FUNC_OPTIONS
567
            );
568 11
            ++$list->idx;
569
570 11
            for (; $list->idx < $list->count; ++$list->idx) {
571 9
                $token = $list->tokens[$list->idx];
572 9
                $this->body[] = $token;
573
            }
574 10
        } elseif ($this->options->has('VIEW')) {
575 5
            $token = $list->getNext(); // Skipping whitespaces and comments.
576
577
            // Parsing columns list.
578 5
            if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
579 2
                --$list->idx; // getNext() also goes forward one field.
580 2
                $this->fields = ArrayObj::parse($parser, $list);
581 2
                ++$list->idx; // Skipping last token from the array.
582 2
                $list->getNext();
583
            }
584
585
            // Parsing the `AS` keyword.
586 5 View Code Duplication
            for (; $list->idx < $list->count; ++$list->idx) {
587 5
                $token = $list->tokens[$list->idx];
588 5
                if ($token->type === Token::TYPE_DELIMITER) {
589 5
                    break;
590
                }
591 5
                $this->body[] = $token;
592
            }
593 5
        } elseif ($this->options->has('TRIGGER')) {
594
            // Parsing the time and the event.
595 1
            $this->entityOptions = OptionsArray::parse(
596 1
                $parser,
597 1
                $list,
598 1
                static::$TRIGGER_OPTIONS
599
            );
600 1
            ++$list->idx;
601
602 1
            $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ON');
603 1
            ++$list->idx; // Skipping `ON`.
604
605
            // Parsing the name of the table.
606 1
            $this->table = Expression::parse(
607 1
                $parser,
608 1
                $list,
609
                array(
610 1
                    'parseField' => 'table',
611
                    'breakOnAlias' => true,
612
                )
613
            );
614 1
            ++$list->idx;
615
616 1
            $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'FOR EACH ROW');
617 1
            ++$list->idx; // Skipping `FOR EACH ROW`.
618
619 1
            for (; $list->idx < $list->count; ++$list->idx) {
620 1
                $token = $list->tokens[$list->idx];
621 1
                $this->body[] = $token;
622
            }
623
        } else {
624 4 View Code Duplication
            for (; $list->idx < $list->count; ++$list->idx) {
625 4
                $token = $list->tokens[$list->idx];
626 4
                if ($token->type === Token::TYPE_DELIMITER) {
627 4
                    break;
628
                }
629 1
                $this->body[] = $token;
630
            }
631
        }
632 53
    }
633
}
634