CreateStatement::build()   F
last analyzed

Complexity

Conditions 22
Paths 129

Size

Total Lines 113
Code Lines 77

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 77
CRAP Score 22

Importance

Changes 0
Metric Value
cc 22
eloc 77
nc 129
nop 0
dl 0
loc 113
ccs 77
cts 77
cp 1
crap 22
rs 3.925
c 0
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\CreateDefinition;
9
use PhpMyAdmin\SqlParser\Components\DataType;
10
use PhpMyAdmin\SqlParser\Components\Expression;
11
use PhpMyAdmin\SqlParser\Components\OptionsArray;
12
use PhpMyAdmin\SqlParser\Components\ParameterDefinition;
13
use PhpMyAdmin\SqlParser\Components\PartitionDefinition;
14
use PhpMyAdmin\SqlParser\Parser;
15
use PhpMyAdmin\SqlParser\Parsers\ArrayObjs;
16
use PhpMyAdmin\SqlParser\Parsers\CreateDefinitions;
17
use PhpMyAdmin\SqlParser\Parsers\DataTypes;
18
use PhpMyAdmin\SqlParser\Parsers\Expressions;
19
use PhpMyAdmin\SqlParser\Parsers\OptionsArrays;
20
use PhpMyAdmin\SqlParser\Parsers\ParameterDefinitions;
21
use PhpMyAdmin\SqlParser\Parsers\PartitionDefinitions;
22
use PhpMyAdmin\SqlParser\Statement;
23
use PhpMyAdmin\SqlParser\Token;
24
use PhpMyAdmin\SqlParser\TokensList;
25
use PhpMyAdmin\SqlParser\TokenType;
26
27
use function is_array;
28
use function trim;
29
30
/**
31
 * `CREATE` statement.
32
 */
33
class CreateStatement extends Statement
34
{
35
    /**
36
     * Options for `CREATE` statements.
37
     *
38
     * @var array<string, int|array<int, int|string>>
39
     * @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})>
40
     */
41
    public static array $statementOptions = [
42
        // CREATE TABLE
43
        'TEMPORARY' => 1,
44
45
        // CREATE VIEW
46
        'OR REPLACE' => 2,
47
        'ALGORITHM' => [
48
            3,
49
            'var=',
50
        ],
51
        // `DEFINER` is also used for `CREATE FUNCTION / PROCEDURE`
52
        'DEFINER' => [
53
            4,
54
            'expr=',
55
        ],
56
        // Used in `CREATE VIEW`
57
        'SQL SECURITY' => [
58
            5,
59
            'var',
60
        ],
61
62
        'DATABASE' => 6,
63
        'EVENT' => 6,
64
        'FUNCTION' => 6,
65
        'INDEX' => 6,
66
        'UNIQUE INDEX' => 6,
67
        'FULLTEXT INDEX' => 6,
68
        'SPATIAL INDEX' => 6,
69
        'PROCEDURE' => 6,
70
        'SERVER' => 6,
71
        'TABLE' => 6,
72
        'TABLESPACE' => 6,
73
        'TRIGGER' => 6,
74
        'USER' => 6,
75
        'VIEW' => 6,
76
        'SCHEMA' => 6,
77
78
        // CREATE TABLE
79
        'IF NOT EXISTS' => 7,
80
    ];
81
82
    /**
83
     * All database options.
84
     */
85
    private const DATABASE_OPTIONS = [
86
        'CHARACTER SET' => [
87
            1,
88
            'var=',
89
        ],
90
        'CHARSET' => [
91
            1,
92
            'var=',
93
        ],
94
        'DEFAULT CHARACTER SET' => [
95
            1,
96
            'var=',
97
        ],
98
        'DEFAULT CHARSET' => [
99
            1,
100
            'var=',
101
        ],
102
        'DEFAULT COLLATE' => [
103
            2,
104
            'var=',
105
        ],
106
        'COLLATE' => [
107
            2,
108
            'var=',
109
        ],
110
    ];
111
112
    /**
113
     * All table options.
114
     */
115
    private const TABLE_OPTIONS = [
116
        'ENGINE' => [
117
            1,
118
            'var=',
119
        ],
120
        'AUTO_INCREMENT' => [
121
            2,
122
            'var=',
123
        ],
124
        'AVG_ROW_LENGTH' => [
125
            3,
126
            'var',
127
        ],
128
        'CHARACTER SET' => [
129
            4,
130
            'var=',
131
        ],
132
        'CHARSET' => [
133
            4,
134
            'var=',
135
        ],
136
        'DEFAULT CHARACTER SET' => [
137
            4,
138
            'var=',
139
        ],
140
        'DEFAULT CHARSET' => [
141
            4,
142
            'var=',
143
        ],
144
        'CHECKSUM' => [
145
            5,
146
            'var',
147
        ],
148
        'DEFAULT COLLATE' => [
149
            6,
150
            'var=',
151
        ],
152
        'COLLATE' => [
153
            6,
154
            'var=',
155
        ],
156
        'COMMENT' => [
157
            7,
158
            'var=',
159
        ],
160
        'CONNECTION' => [
161
            8,
162
            'var',
163
        ],
164
        'DATA DIRECTORY' => [
165
            9,
166
            'var',
167
        ],
168
        'DELAY_KEY_WRITE' => [
169
            10,
170
            'var',
171
        ],
172
        'INDEX DIRECTORY' => [
173
            11,
174
            'var',
175
        ],
176
        'INSERT_METHOD' => [
177
            12,
178
            'var',
179
        ],
180
        'KEY_BLOCK_SIZE' => [
181
            13,
182
            'var',
183
        ],
184
        'MAX_ROWS' => [
185
            14,
186
            'var',
187
        ],
188
        'MIN_ROWS' => [
189
            15,
190
            'var',
191
        ],
192
        'PACK_KEYS' => [
193
            16,
194
            'var',
195
        ],
196
        'PASSWORD' => [
197
            17,
198
            'var',
199
        ],
200
        'ROW_FORMAT' => [
201
            18,
202
            'var',
203
        ],
204
        'TABLESPACE' => [
205
            19,
206
            'var',
207
        ],
208
        'STORAGE' => [
209
            20,
210
            'var',
211
        ],
212
        'UNION' => [
213
            21,
214
            'var',
215
        ],
216
        'PAGE_COMPRESSED' => [
217
            22,
218
            'var',
219
        ],
220
        'PAGE_COMPRESSION_LEVEL' => [
221
            23,
222
            'var',
223
        ],
224
    ];
225
226
    /**
227
     * All function options.
228
     */
229
    private const FUNCTION_OPTIONS = [
230
        'NOT' => [
231
            2,
232
            'var',
233
        ],
234
        'FUNCTION' => [
235
            3,
236
            'var=',
237
        ],
238
        'PROCEDURE' => [
239
            3,
240
            'var=',
241
        ],
242
        'CONTAINS SQL' => 4,
243
        'NO SQL' => 4,
244
        'READS SQL DATA' => 4,
245
        'MODIFIES SQL DATA' => 4,
246
        'SQL SECURITY' => [
247
            6,
248
            'var',
249
        ],
250
        'LANGUAGE' => [
251
            7,
252
            'var',
253
        ],
254
        'COMMENT' => [
255
            8,
256
            'var',
257
        ],
258
259
        'CREATE' => 1,
260
        'DETERMINISTIC' => 2,
261
    ];
262
263
    /**
264
     * All trigger options.
265
     */
266
    private const TRIGGER_OPTIONS = [
267
        'BEFORE' => 1,
268
        'AFTER' => 1,
269
        'INSERT' => 2,
270
        'UPDATE' => 2,
271
        'DELETE' => 2,
272
    ];
273
274
    /**
275
     * The name of the entity that is created.
276
     *
277
     * Used by all `CREATE` statements.
278
     */
279
    public Expression|null $name = null;
280
281
    /**
282
     * The options of the entity (table, procedure, function, etc.).
283
     *
284
     * Used by `CREATE TABLE`, `CREATE FUNCTION` and `CREATE PROCEDURE`.
285
     *
286
     * @see CreateStatement::TABLE_OPTIONS
287
     * @see CreateStatement::FUNCTION_OPTIONS
288
     * @see CreateStatement::TRIGGER_OPTIONS
289
     */
290
    public OptionsArray|null $entityOptions = null;
291
292
    /**
293
     * If `CREATE TABLE`, a list of columns and keys.
294
     * If `CREATE VIEW`, a list of columns.
295
     *
296
     * Used by `CREATE TABLE` and `CREATE VIEW`.
297
     *
298
     * @var CreateDefinition[]|ArrayObj|null
299
     */
300
    public array|ArrayObj|null $fields = null;
301
302
    /**
303
     * If `CREATE TABLE WITH`.
304
     * If `CREATE TABLE AS WITH`.
305
     * If `CREATE VIEW AS WITH`.
306
     *
307
     * Used by `CREATE TABLE`, `CREATE VIEW`
308
     */
309
    public WithStatement|null $with = null;
310
311
    /**
312
     * If `CREATE TABLE ... SELECT`.
313
     * If `CREATE VIEW AS ` ... SELECT`.
314
     *
315
     * Used by `CREATE TABLE`, `CREATE VIEW`
316
     */
317
    public SelectStatement|null $select = null;
318
319
    /**
320
     * If `CREATE TABLE ... LIKE`.
321
     *
322
     * Used by `CREATE TABLE`
323
     */
324
    public Expression|null $like = null;
325
326
    /**
327
     * Expression used for partitioning.
328
     */
329
    public string|null $partitionBy = null;
330
331
    /**
332
     * The number of partitions.
333
     */
334
    public int|null $partitionsNum = null;
335
336
    /**
337
     * Expression used for subpartitioning.
338
     */
339
    public string|null $subpartitionBy = null;
340
341
    /**
342
     * The number of subpartitions.
343
     */
344
    public int|null $subpartitionsNum = null;
345
346
    /**
347
     * The partition of the new table.
348
     *
349
     * @var PartitionDefinition[]|null
350
     */
351
    public array|null $partitions = null;
352
353
    /**
354
     * If `CREATE TRIGGER` the name of the table.
355
     *
356
     * Used by `CREATE TRIGGER`.
357
     */
358
    public Expression|null $table = null;
359
360
    /**
361
     * The return data type of this routine.
362
     *
363
     * Used by `CREATE FUNCTION`.
364
     */
365
    public DataType|null $return = null;
366
367
    /**
368
     * The parameters of this routine.
369
     *
370
     * Used by `CREATE FUNCTION` and `CREATE PROCEDURE`.
371
     *
372
     * @var ParameterDefinition[]|null
373
     */
374
    public array|null $parameters = null;
375
376
    /**
377
     * The body of this function or procedure.
378
     * For views, it is the select statement that creates the view.
379
     * Used by `CREATE FUNCTION`, `CREATE PROCEDURE` and `CREATE VIEW`.
380
     *
381
     * @var Token[]
382
     */
383
    public array $body = [];
384
385 44
    public function build(): string
386
    {
387 44
        $fields = '';
388 44
        if ($this->fields !== null && $this->fields !== []) {
389 28
            if (is_array($this->fields)) {
390 26
                $fields = CreateDefinitions::buildAll($this->fields) . ' ';
391
            } else {
392 2
                $fields = $this->fields->build();
393
            }
394
        }
395
396 44
        if ($this->options->has('DATABASE') || $this->options->has('SCHEMA')) {
0 ignored issues
show
Bug introduced by
The method has() 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

396
        if ($this->options->/** @scrutinizer ignore-call */ has('DATABASE') || $this->options->has('SCHEMA')) {

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...
397 2
            return 'CREATE '
398 2
                . $this->options->build() . ' '
399 2
                . $this->name->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

399
                . $this->name->/** @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...
400 2
                . $this->entityOptions->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

400
                . $this->entityOptions->/** @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...
401
        }
402
403 42
        if ($this->options->has('TABLE')) {
404 28
            if ($this->select !== null) {
405 2
                return 'CREATE '
406 2
                    . $this->options->build() . ' '
407 2
                    . $this->name->build() . ' '
408 2
                    . $this->select->build();
409
            }
410
411 26
            if ($this->like !== null) {
412 2
                return 'CREATE '
413 2
                    . $this->options->build() . ' '
414 2
                    . $this->name->build() . ' LIKE '
415 2
                    . $this->like->build();
416
            }
417
418 26
            if ($this->with !== null) {
419 2
                return 'CREATE '
420 2
                . $this->options->build() . ' '
421 2
                . $this->name->build() . ' '
422 2
                . $this->with->build();
423
            }
424
425 26
            $partition = '';
426
427 26
            if (! empty($this->partitionBy)) {
428 6
                $partition .= "\nPARTITION BY " . $this->partitionBy;
429
            }
430
431 26
            if (! empty($this->partitionsNum)) {
432 2
                $partition .= "\nPARTITIONS " . $this->partitionsNum;
433
            }
434
435 26
            if (! empty($this->subpartitionBy)) {
436 4
                $partition .= "\nSUBPARTITION BY " . $this->subpartitionBy;
437
            }
438
439 26
            if (! empty($this->subpartitionsNum)) {
440 2
                $partition .= "\nSUBPARTITIONS " . $this->subpartitionsNum;
441
            }
442
443 26
            if (! empty($this->partitions)) {
444 6
                $partition .= "\n" . PartitionDefinitions::buildAll($this->partitions);
445
            }
446
447 26
            return 'CREATE '
448 26
                . $this->options->build() . ' '
449 26
                . $this->name->build() . ' '
450 26
                . $fields
451 26
                . ($this->entityOptions?->build() ?? '')
452 26
                . $partition;
453
        }
454
455 16
        if ($this->options->has('VIEW')) {
456 4
            $builtStatement = '';
457 4
            if ($this->select !== null) {
458 2
                $builtStatement = $this->select->build();
459 4
            } elseif ($this->with !== null) {
460 4
                $builtStatement = $this->with->build();
461
            }
462
463 4
            return 'CREATE '
464 4
                . $this->options->build() . ' '
465 4
                . $this->name->build() . ' '
466 4
                . $fields . ' AS ' . $builtStatement
467 4
                . TokensList::buildFromArray($this->body) . ' '
468 4
                . ($this->entityOptions?->build() ?? '');
469
        }
470
471 12
        if ($this->options->has('TRIGGER')) {
472 4
            return 'CREATE '
473 4
                . $this->options->build() . ' '
474 4
                . $this->name->build() . ' '
475 4
                . $this->entityOptions->build() . ' '
476 4
                . 'ON ' . $this->table->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

476
                . 'ON ' . $this->table->/** @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...
477 4
                . 'FOR EACH ROW ' . TokensList::buildFromArray($this->body);
478
        }
479
480 8
        if ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) {
481 6
            $tmp = '';
482 6
            if ($this->options->has('FUNCTION')) {
483 4
                $tmp = 'RETURNS ' . $this->return->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

483
                $tmp = 'RETURNS ' . $this->return->/** @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...
484
            }
485
486 6
            return 'CREATE '
487 6
                . $this->options->build() . ' '
488 6
                . $this->name->build() . ' '
489 6
                . ParameterDefinitions::buildAll($this->parameters) . ' '
0 ignored issues
show
Bug introduced by
It seems like $this->parameters can also be of type null; however, parameter $component of PhpMyAdmin\SqlParser\Par...Definitions::buildAll() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

489
                . ParameterDefinitions::buildAll(/** @scrutinizer ignore-type */ $this->parameters) . ' '
Loading history...
490 6
                . $tmp . ' ' . $this->entityOptions->build() . ' '
491 6
                . TokensList::buildFromArray($this->body);
492
        }
493
494 2
        return 'CREATE '
495 2
            . $this->options->build() . ' '
496 2
            . $this->name->build() . ' '
497 2
            . TokensList::buildFromArray($this->body);
498
    }
499
500
    /**
501
     * @param Parser     $parser the instance that requests parsing
502
     * @param TokensList $list   the list of tokens to be parsed
503
     */
504 216
    public function parse(Parser $parser, TokensList $list): void
505
    {
506 216
        ++$list->idx; // Skipping `CREATE`.
507
508
        // Parsing options.
509 216
        $this->options = OptionsArrays::parse($parser, $list, static::$statementOptions);
510 216
        ++$list->idx; // Skipping last option.
511
512 216
        $isDatabase = $this->options->has('DATABASE') || $this->options->has('SCHEMA');
513 216
        $fieldName = $isDatabase ? 'database' : 'table';
514
515
        // Parsing the field name.
516 216
        $this->name = Expressions::parse(
517 216
            $parser,
518 216
            $list,
519 216
            [
520 216
                'parseField' => $fieldName,
521 216
                'breakOnAlias' => true,
522 216
            ],
523 216
        );
524
525 216
        if ($this->name === null) {
526 2
            $parser->error('The name of the entity was expected.', $list->tokens[$list->idx]);
527
        } else {
528 214
            ++$list->idx; // Skipping field.
529
        }
530
531
        /**
532
         * Token parsed at this moment.
533
         */
534 216
        $token = $list->tokens[$list->idx];
535 216
        $nextidx = $list->idx + 1;
536 216
        while ($nextidx < $list->count && $list->tokens[$nextidx]->type === TokenType::Whitespace) {
537 138
            ++$nextidx;
538
        }
539
540 216
        if ($isDatabase) {
541 10
            $this->entityOptions = OptionsArrays::parse($parser, $list, self::DATABASE_OPTIONS);
542 206
        } elseif ($this->options->has('TABLE')) {
543 132
            if (($token->type === TokenType::Keyword) && ($token->keyword === 'SELECT')) {
544
                /* CREATE TABLE ... SELECT */
545 4
                $this->select = new SelectStatement($parser, $list);
546 128
            } elseif ($token->type === TokenType::Keyword && ($token->keyword === 'WITH')) {
547
                /* CREATE TABLE WITH */
548 12
                $this->with = new WithStatement($parser, $list);
549
            } elseif (
550 118
                ($token->type === TokenType::Keyword) && ($token->keyword === 'AS')
551 118
                && ($list->tokens[$nextidx]->type === TokenType::Keyword)
552
            ) {
553 4
                if ($list->tokens[$nextidx]->value === 'SELECT') {
554
                    /* CREATE TABLE ... AS SELECT */
555 2
                    $list->idx = $nextidx;
556 2
                    $this->select = new SelectStatement($parser, $list);
557 2
                } elseif ($list->tokens[$nextidx]->value === 'WITH') {
558
                    /* CREATE TABLE WITH */
559 2
                    $list->idx = $nextidx;
560 2
                    $this->with = new WithStatement($parser, $list);
561
                }
562 114
            } elseif ($token->type === TokenType::Keyword && $token->keyword === 'LIKE') {
563
                /* CREATE TABLE `new_tbl` LIKE 'orig_tbl' */
564 6
                $list->idx = $nextidx;
565 6
                $this->like = Expressions::parse(
566 6
                    $parser,
567 6
                    $list,
568 6
                    [
569 6
                        'parseField' => 'table',
570 6
                        'breakOnAlias' => true,
571 6
                    ],
572 6
                );
573
                // The 'LIKE' keyword was found, but no table_name was found next to it
574 6
                if ($this->like === null) {
575 2
                    $parser->error('A table name was expected.', $list->tokens[$list->idx]);
576
                }
577
            } else {
578 110
                $this->fields = CreateDefinitions::parse($parser, $list);
579 110
                if ($this->fields === []) {
580 6
                    $parser->error('At least one column definition was expected.', $list->tokens[$list->idx]);
581
                }
582
583 110
                ++$list->idx;
584
585 110
                $this->entityOptions = OptionsArrays::parse($parser, $list, self::TABLE_OPTIONS);
586
587
                /**
588
                 * The field that is being filled (`partitionBy` or
589
                 * `subpartitionBy`).
590
                 */
591 110
                $field = null;
592
593
                /**
594
                 * The number of brackets. `false` means no bracket was found
595
                 * previously. At least one bracket is required to validate the
596
                 * expression.
597
                 */
598 110
                $brackets = false;
599
600
                /*
601
                 * Handles partitions.
602
                 */
603 110
                for (; $list->idx < $list->count; ++$list->idx) {
604
                    /**
605
                     * Token parsed at this moment.
606
                     */
607 110
                    $token = $list->tokens[$list->idx];
608
609
                    // End of statement.
610 110
                    if ($token->type === TokenType::Delimiter) {
611 90
                        break;
612
                    }
613
614
                    // Skipping comments.
615 110
                    if ($token->type === TokenType::Comment) {
616 2
                        continue;
617
                    }
618
619 110
                    if (($token->type === TokenType::Keyword) && ($token->keyword === 'PARTITION BY')) {
620 16
                        $field = 'partitionBy';
621 16
                        $brackets = false;
622 110
                    } elseif (($token->type === TokenType::Keyword) && ($token->keyword === 'SUBPARTITION BY')) {
623 12
                        $field = 'subpartitionBy';
624 12
                        $brackets = false;
625 110
                    } elseif (($token->type === TokenType::Keyword) && ($token->keyword === 'PARTITIONS')) {
626 6
                        $token = $list->getNextOfType(TokenType::Number);
627 6
                        --$list->idx; // `getNextOfType` also advances one position.
628 6
                        $this->partitionsNum = $token->value;
629 110
                    } elseif (($token->type === TokenType::Keyword) && ($token->keyword === 'SUBPARTITIONS')) {
630 6
                        $token = $list->getNextOfType(TokenType::Number);
631 6
                        --$list->idx; // `getNextOfType` also advances one position.
632 6
                        $this->subpartitionsNum = $token->value;
633 110
                    } elseif (! empty($field)) {
634
                        /*
635
                         * Handling the content of `PARTITION BY` and `SUBPARTITION BY`.
636
                         */
637
638
                        // Counting brackets.
639 16
                        if ($token->type === TokenType::Operator) {
640 16
                            if ($token->value === '(') {
641
                                // This is used instead of `++$brackets` because,
642
                                // initially, `$brackets` is `false` cannot be
643
                                // incremented.
644 16
                                $brackets += 1;
645 16
                            } elseif ($token->value === ')') {
646 16
                                --$brackets;
647
                            }
648
                        }
649
650
                        // Building the expression used for partitioning.
651 16
                        $this->$field .= $token->type === TokenType::Whitespace ? ' ' : $token->token;
652
653
                        // Last bracket was read, the expression ended.
654
                        // Comparing with `0` and not `false`, because `false` means
655
                        // that no bracket was found and at least one must is
656
                        // required.
657 16
                        if ($brackets === 0) {
658 16
                            $this->$field = trim($this->$field);
659 16
                            $field = null;
660
                        }
661 110
                    } elseif (($token->type === TokenType::Operator) && ($token->value === '(')) {
662 20
                        if (! empty($this->partitionBy)) {
663 14
                            $this->partitions = ArrayObjs::parse(
0 ignored issues
show
Documentation Bug introduced by
It seems like PhpMyAdmin\SqlParser\Par...ionDefinitions::class)) of type PhpMyAdmin\SqlParser\Components\ArrayObj is incompatible with the declared type PhpMyAdmin\SqlParser\Com...titionDefinition[]|null 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...
664 14
                                $parser,
665 14
                                $list,
666 14
                                ['type' => PartitionDefinitions::class],
667 14
                            );
668
                        }
669
670 20
                        break;
671
                    }
672
                }
673
            }
674 78
        } elseif ($this->options->has('PROCEDURE') || $this->options->has('FUNCTION')) {
675 32
            $this->parameters = ParameterDefinitions::parse($parser, $list);
676 32
            if ($this->options->has('FUNCTION')) {
677 16
                $prevToken = $token;
678 16
                $token = $list->getNextOfType(TokenType::Keyword);
679 16
                if ($token === null || $token->keyword !== 'RETURNS') {
680 6
                    $parser->error('A "RETURNS" keyword was expected.', $token ?? $prevToken);
681
                } else {
682 10
                    ++$list->idx;
683 10
                    $this->return = DataTypes::parse($parser, $list);
684
                }
685
            }
686
687 32
            ++$list->idx;
688
689 32
            $this->entityOptions = OptionsArrays::parse($parser, $list, self::FUNCTION_OPTIONS);
690 32
            ++$list->idx;
691
692 32
            for (; $list->idx < $list->count; ++$list->idx) {
693 28
                $token = $list->tokens[$list->idx];
694 28
                if ($token->type === TokenType::Delimiter) {
695 28
                    break;
696
                }
697
698 28
                $this->body[] = $token;
699
            }
700 46
        } elseif ($this->options->has('VIEW')) {
701
            /** @var Token $token */
702 30
            $token = $list->getNext(); // Skipping whitespaces and comments.
703
704
            // Parsing columns list.
705 30
            if (($token->type === TokenType::Operator) && ($token->value === '(')) {
706 6
                --$list->idx; // getNext() also goes forward one field.
707 6
                $this->fields = ArrayObjs::parse($parser, $list);
708 6
                ++$list->idx; // Skipping last token from the array.
709 6
                $list->getNext();
710
            }
711
712
            // Parsing the SELECT expression if the view started with it.
713
            if (
714 30
                $token->type === TokenType::Keyword
715 30
                && $token->keyword === 'AS'
716 30
                && $list->tokens[$nextidx]->type === TokenType::Keyword
717
            ) {
718 24
                if ($list->tokens[$nextidx]->value === 'SELECT') {
719 18
                    $list->idx = $nextidx;
720 18
                    $this->select = new SelectStatement($parser, $list);
721 18
                    ++$list->idx; // Skipping last token from the select.
722 8
                } elseif ($list->tokens[$nextidx]->value === 'WITH') {
723 8
                    ++$list->idx;
724 8
                    $this->with = new WithStatement($parser, $list);
725
                }
726
            }
727
728
            // Parsing all other tokens
729 30
            for (; $list->idx < $list->count; ++$list->idx) {
730 30
                $token = $list->tokens[$list->idx];
731 30
                if ($token->type === TokenType::Delimiter) {
732 30
                    break;
733
                }
734
735 14
                $this->body[] = $token;
736
            }
737 16
        } elseif ($this->options->has('TRIGGER')) {
738
            // Parsing the time and the event.
739 6
            $this->entityOptions = OptionsArrays::parse($parser, $list, self::TRIGGER_OPTIONS);
740 6
            ++$list->idx;
741
742 6
            $list->getNextOfTypeAndValue(TokenType::Keyword, 'ON');
743 6
            ++$list->idx; // Skipping `ON`.
744
745
            // Parsing the name of the table.
746 6
            $this->table = Expressions::parse(
747 6
                $parser,
748 6
                $list,
749 6
                [
750 6
                    'parseField' => 'table',
751 6
                    'breakOnAlias' => true,
752 6
                ],
753 6
            );
754 6
            ++$list->idx;
755
756 6
            $list->getNextOfTypeAndValue(TokenType::Keyword, 'FOR EACH ROW');
757 6
            ++$list->idx; // Skipping `FOR EACH ROW`.
758
759 6
            for (; $list->idx < $list->count; ++$list->idx) {
760 6
                $token = $list->tokens[$list->idx];
761 6
                if ($token->type === TokenType::Delimiter) {
762 6
                    break;
763
                }
764
765 6
                $this->body[] = $token;
766
            }
767
        } else {
768 10
            for (; $list->idx < $list->count; ++$list->idx) {
769 10
                $token = $list->tokens[$list->idx];
770 10
                if ($token->type === TokenType::Delimiter) {
771 10
                    break;
772
                }
773
774 4
                $this->body[] = $token;
775
            }
776
        }
777
    }
778
}
779