Passed
Push — master ( b18edd...c70841 )
by William
03:11 queued 12s
created

CreateStatement::parse()   F

Complexity

Conditions 58
Paths 1280

Size

Total Lines 258
Code Lines 140

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 132
CRAP Score 58

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 58
eloc 140
nc 1280
nop 2
dl 0
loc 258
rs 0
c 2
b 1
f 0
ccs 132
cts 132
cp 1
crap 58

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

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
634 20
                                $parser,
635 20
                                $list,
636 20
                                ['type' => 'PhpMyAdmin\\SqlParser\\Components\\PartitionDefinition']
637
                            );
638
                        }
639
640 32
                        break;
641
                    }
642
                }
643
            }
644 108
        } elseif ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) {
645 44
            $this->parameters = ParameterDefinition::parse($parser, $list);
646 44
            if ($this->options->has('FUNCTION')) {
647 24
                $prevToken = $token;
648 24
                $token = $list->getNextOfType(Token::TYPE_KEYWORD);
649 24
                if ($token === null || $token->keyword !== 'RETURNS') {
650 12
                    $parser->error('A "RETURNS" keyword was expected.', $token ?? $prevToken);
651
                } else {
652 12
                    ++$list->idx;
653 12
                    $this->return = DataType::parse($parser, $list);
654
                }
655
            }
656
657 44
            ++$list->idx;
658
659 44
            $this->entityOptions = OptionsArray::parse($parser, $list, static::$FUNC_OPTIONS);
660 44
            ++$list->idx;
661
662 44
            for (; $list->idx < $list->count; ++$list->idx) {
663 36
                $token = $list->tokens[$list->idx];
664 36
                $this->body[] = $token;
665
            }
666 64
        } elseif ($this->options->has('VIEW')) {
667
            /** @var Token $token */
668 44
            $token = $list->getNext(); // Skipping whitespaces and comments.
669
670
            // Parsing columns list.
671 44
            if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
672 12
                --$list->idx; // getNext() also goes forward one field.
673 12
                $this->fields = ArrayObj::parse($parser, $list);
674 12
                ++$list->idx; // Skipping last token from the array.
675 12
                $list->getNext();
676
            }
677
678
            // Parsing the SELECT expression if the view started with it.
679
            if (
680 44
                $token->type === Token::TYPE_KEYWORD
681 44
                && $token->keyword === 'AS'
682 44
                && $list->tokens[$nextidx]->type === Token::TYPE_KEYWORD
683 44
                && $list->tokens[$nextidx]->value === 'SELECT'
684
            ) {
685 24
                $list->idx = $nextidx;
686 24
                $this->select = new SelectStatement($parser, $list);
687
            }
688
689
            // Parsing all other tokens
690 44
            for (; $list->idx < $list->count; ++$list->idx) {
691 44
                $token = $list->tokens[$list->idx];
692 44
                if ($token->type === Token::TYPE_DELIMITER) {
693 44
                    break;
694
                }
695
696 44
                $this->body[] = $token;
697
            }
698 20
        } elseif ($this->options->has('TRIGGER')) {
699
            // Parsing the time and the event.
700 4
            $this->entityOptions = OptionsArray::parse($parser, $list, static::$TRIGGER_OPTIONS);
701 4
            ++$list->idx;
702
703 4
            $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ON');
704 4
            ++$list->idx; // Skipping `ON`.
705
706
            // Parsing the name of the table.
707 4
            $this->table = Expression::parse(
708 4
                $parser,
709 4
                $list,
710
                [
711 4
                    'parseField' => 'table',
712
                    'breakOnAlias' => true,
713
                ]
714
            );
715 4
            ++$list->idx;
716
717 4
            $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'FOR EACH ROW');
718 4
            ++$list->idx; // Skipping `FOR EACH ROW`.
719
720 4
            for (; $list->idx < $list->count; ++$list->idx) {
721 4
                $token = $list->tokens[$list->idx];
722 4
                $this->body[] = $token;
723
            }
724
        } else {
725 16
            for (; $list->idx < $list->count; ++$list->idx) {
726 16
                $token = $list->tokens[$list->idx];
727 16
                if ($token->type === Token::TYPE_DELIMITER) {
728 16
                    break;
729
                }
730
731 4
                $this->body[] = $token;
732
            }
733
        }
734 284
    }
735
}
736