Completed
Pull Request — master (#112)
by Michal
374:43 queued 309:52
created

CreateStatement::build()   D

Complexity

Conditions 20
Paths 160

Size

Total Lines 83
Code Lines 67

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 64
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 20
eloc 67
nc 160
nop 0
dl 0
loc 83
ccs 64
cts 64
cp 1
crap 20
rs 4.5704
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
/**
4
 * `CREATE` statement.
5
 */
6
7
namespace SqlParser\Statements;
8
9
use SqlParser\Parser;
10
use SqlParser\Statement;
11
use SqlParser\Token;
12
use SqlParser\TokensList;
13
use SqlParser\Components\ArrayObj;
14
use SqlParser\Components\DataType;
15
use SqlParser\Components\CreateDefinition;
16
use SqlParser\Components\PartitionDefinition;
17
use SqlParser\Components\Expression;
18
use SqlParser\Components\OptionsArray;
19
use SqlParser\Components\ParameterDefinition;
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 10
     * @return string
266
     */
267 10
    public function build()
268 10
    {
269 5
        $fields = '';
270 4
        if (!empty($this->fields)) {
271 5
            if (is_array($this->fields)) {
272 1
                $fields = CreateDefinition::build($this->fields).' ';
273 1
            } elseif ($this->fields instanceof ArrayObj) {
274 5
                $fields = ArrayObj::build($this->fields);
275 10
            }
276
        }
277 1
        if ($this->options->has('DATABASE')) {
278 1
            return 'CREATE '
279 1
                .OptionsArray::build($this->options).' '
280 9
                .Expression::build($this->name).' '
281
                .OptionsArray::build($this->entityOptions);
282 1
        } elseif ($this->options->has('TABLE') && !is_null($this->select)) {
283 1
            return 'CREATE '
284 1
                .OptionsArray::build($this->options).' '
285 8
                .Expression::build($this->name).' '
286 4
                .$this->select->build();
287
        } elseif ($this->options->has('TABLE') && !is_null($this->like)) {
288 4
            return 'CREATE '
289 1
                .OptionsArray::build($this->options).' '
290 1
                .Expression::build($this->name).' LIKE '
291 4
                .Expression::build($this->like);
292 1
        } elseif ($this->options->has('TABLE')) {
293 1
            $partition = '';
294 4
295 1
            if (!empty($this->partitionBy)) {
296 1
                $partition .= "\nPARTITION BY ".$this->partitionBy;
297 4
            }
298 1
            if (!empty($this->partitionsNum)) {
299 1
                $partition .= "\nPARTITIONS ".$this->partitionsNum;
300 4
            }
301 1
            if (!empty($this->subpartitionBy)) {
302 1
                $partition .= "\nSUBPARTITION BY ".$this->subpartitionBy;
303
            }
304
            if (!empty($this->subpartitionsNum)) {
305 4
                $partition .= "\nSUBPARTITIONS ".$this->subpartitionsNum;
306 4
            }
307 4
            if (!empty($this->partitions)) {
308 4
                $partition .= "\n".PartitionDefinition::build($this->partitions);
309 4
            }
310 4
311
            return 'CREATE '
312 1
                .OptionsArray::build($this->options).' '
313 1
                .Expression::build($this->name).' '
314 1
                .$fields
315 1
                .OptionsArray::build($this->entityOptions)
316 3
                .$partition;
317
        } elseif ($this->options->has('VIEW')) {
318 1
            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 2
        } elseif ($this->options->has('TRIGGER')) {
324 2
            return 'CREATE '
325 2
                .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
        } elseif (($this->options->has('PROCEDURE'))
331 1
            || ($this->options->has('FUNCTION'))
332 1
        ) {
333 1
            $tmp = '';
334 1
            if ($this->options->has('FUNCTION')) {
335
                $tmp = 'RETURNS '.DataType::build($this->return);
336
            }
337 1
338 1
            return 'CREATE '
339 1
                .OptionsArray::build($this->options).' '
340
                .Expression::build($this->name).' '
341
                .ParameterDefinition::build($this->parameters).' '
342
                .$tmp.' '.TokensList::build($this->body);
343
        }
344
345
        return 'CREATE '
346
            .OptionsArray::build($this->options).' '
347
            .Expression::build($this->name).' '
348 42
            .TokensList::build($this->body);
349
    }
350 42
351
    /**
352
     * @param Parser     $parser the instance that requests parsing
353 42
     * @param TokensList $list   the list of tokens to be parsed
354 42
     */
355
    public function parse(Parser $parser, TokensList $list)
356
    {
357 42
        ++$list->idx; // Skipping `CREATE`.
358 42
359 42
        // Parsing options.
360
        $this->options = OptionsArray::parse($parser, $list, static::$OPTIONS);
361 42
        ++$list->idx; // Skipping last option.
362 42
363
        // Parsing the field name.
364 42
        $this->name = Expression::parse(
365
            $parser,
366 42
            $list,
367 1
            array(
368 1
                'parseField' => 'table',
369 1
                'breakOnAlias' => true,
370 1
            )
371 1
        );
372 41
373
        if ((!isset($this->name)) || ($this->name === '')) {
374
            $parser->error(
375
                __('The name of the entity was expected.'),
376
                $list->tokens[$list->idx]
377
            );
378
        } else {
379
            ++$list->idx; // Skipping field.
380 42
        }
381 42
382 42
        /**
383 22
         * Token parsed at this moment.
384 22
         *
385
         * @var Token
386 42
         */
387 1
        $token = $list->tokens[$list->idx];
388 1
        $nextidx = $list->idx + 1;
389 1
        while ($nextidx < $list->count && $list->tokens[$nextidx]->type == Token::TYPE_WHITESPACE) {
390
            ++$nextidx;
391 1
        }
392 42
393
        if ($this->options->has('DATABASE')) {
394 2
            $this->entityOptions = OptionsArray::parse(
395 2
                $parser,
396 39
                $list,
397 39
                static::$DB_OPTIONS
398 39
            );
399 39
        } elseif ($this->options->has('TABLE')
400
            && ($token->type == Token::TYPE_KEYWORD)
401 1
            && ($token->value == 'SELECT')
402 1
        ) {
403 39
            /* CREATE TABLE ... SELECT */
404 20
            $this->select = new SelectStatement($parser, $list);
405 20
        } elseif ($this->options->has('TABLE')
406 1
            && ($token->type == Token::TYPE_KEYWORD) && ($token->value == 'AS')
407 1
            && ($list->tokens[$nextidx]->type == Token::TYPE_KEYWORD)
408 1
            && ($list->tokens[$nextidx]->value == 'SELECT')
409 1
        ) {
410 1
            /* CREATE TABLE ... AS SELECT */
411 20
            $list->idx = $nextidx;
412
            $this->select = new SelectStatement($parser, $list);
413 20
        } elseif ($this->options->has('TABLE')
414 20
            && $token->type == Token::TYPE_KEYWORD
415 20
            && $token->value == 'LIKE'
416
        ) {
417 20
            /* CREATE TABLE `new_tbl` LIKE 'orig_tbl' */
418
            $list->idx = $nextidx;
419
            $this->like = Expression::parse(
420
                $parser,
421
                $list,
422
                array(
423
                    'parseField' => 'table',
424
                    'breakOnAlias' => true,
425 20
                )
426
            );
427
            // The 'LIKE' keyword was found, but no table_name was found next to it
428
            if ($this->like == null) {
429
                $parser->error(
430
                    __('A table name was expected.'),
431
                    $list->tokens[$list->idx]
432
                );
433
            }
434 20
        } elseif ($this->options->has('TABLE')) {
435
            $this->fields = CreateDefinition::parse($parser, $list);
436
            if (empty($this->fields)) {
437
                $parser->error(
438
                    __('At least one column definition was expected.'),
439 20
                    $list->tokens[$list->idx]
440
                );
441
            }
442
            ++$list->idx;
443
444
            $this->entityOptions = OptionsArray::parse(
445 20
                $parser,
446
                $list,
447
                static::$TABLE_OPTIONS
448 20
            );
449 17
450
            /**
451
             * The field that is being filled (`partitionBy` or
452
             * `subpartitionBy`).
453 20
             *
454 1
             * @var string
455
             */
456
            $field = null;
457 20
458 2
            /**
459 2
             * The number of brackets. `false` means no bracket was found
460 20
             * previously. At least one bracket is required to validate the
461 2
             * expression.
462 2
             *
463 20
             * @var int|bool
464 2
             */
465 2
            $brackets = false;
466 2
467 20
            /*
468 2
             * Handles partitions.
469 2
             */
470 2
            for (; $list->idx < $list->count; ++$list->idx) {
471 20
                /**
472
                 * Token parsed at this moment.
473
                 *
474
                 * @var Token
475
                 */
476
                $token = $list->tokens[$list->idx];
477 2
478
                // End of statement.
479
                if ($token->type === Token::TYPE_DELIMITER) {
480
                    break;
481 2
                }
482 2
483 2
                // Skipping comments.
484 2
                if ($token->type === Token::TYPE_COMMENT) {
485
                    continue;
486
                }
487 2
488
                if (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'PARTITION BY')) {
489
                    $field = 'partitionBy';
490
                    $brackets = false;
491
                } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'SUBPARTITION BY')) {
492
                    $field = 'subpartitionBy';
493 2
                    $brackets = false;
494 2
                } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->value === 'PARTITIONS')) {
495 2
                    $token = $list->getNextOfType(Token::TYPE_NUMBER);
496 2
                    --$list->idx; // `getNextOfType` also advances one position.
497 20
                    $this->partitionsNum = $token->value;
498 3
                } elseif (($token->type === Token::TYPE_KEYWORD) && ($token->value === '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 2
                     * Handling the content of `PARTITION BY` and `SUBPARTITION BY`.
505 2
                     */
506 2
507 3
                    // Counting brackets.
508
                    if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
509 20
                        // This is used instead of `++$brackets` because,
510 38
                        // initially, `$brackets` is `false` cannot be
511 13
                        // incremented.
512 18
                        $brackets = $brackets + 1;
513 10
                    } elseif (($token->type === Token::TYPE_OPERATOR) && ($token->value === ')')) {
514 10
                        --$brackets;
515 5
                    }
516 5
517 2
                    // Building the expression used for partitioning.
518 2
                    $this->$field .= ($token->type === Token::TYPE_WHITESPACE) ? ' ' : $token->token;
519
520 2
                    // Last bracket was read, the expression ended.
521 2
                    // Comparing with `0` and not `false`, because `false` means
522 3
                    // that no bracket was found and at least one must is
523 3
                    // required.
524 3
                    if ($brackets === 0) {
525
                        $this->$field = trim($this->$field);
526 3
                        $field = null;
527
                    }
528 5
                } elseif (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
529 10
                    if (!empty($this->partitionBy)) {
530
                        $this->partitions = ArrayObj::parse(
0 ignored issues
show
Documentation Bug introduced by
It seems like \SqlParser\Components\Ar...\PartitionDefinition')) can also be of type object<SqlParser\Components\ArrayObj>. However, the property $partitions is declared as type array<integer,object<Sql...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 10
                            $parser,
532 10
                            $list,
533 10
                            array(
534
                                'type' => 'SqlParser\\Components\\PartitionDefinition',
535 10
                            )
536 10
                        );
537
                    }
538 10
                    break;
539 9
                }
540 9
            }
541 9
        } elseif (($this->options->has('PROCEDURE'))
542 18
            || ($this->options->has('FUNCTION'))
543 3
        ) {
544
            $this->parameters = ParameterDefinition::parse($parser, $list);
545
            if ($this->options->has('FUNCTION')) {
546 3
                $token = $list->getNextOfType(Token::TYPE_KEYWORD);
547 2
                if ($token->value !== 'RETURNS') {
548 2
                    $parser->error(
549 2
                        __('A "RETURNS" keyword was expected.'),
550 2
                        $token
551 2
                    );
552
                } else {
553
                    ++$list->idx;
554 3
                    $this->return = DataType::parse(
555 3
                        $parser,
556 3
                        $list
557 3
                    );
558
                }
559 3
            }
560 3
            ++$list->idx;
561 8
562
            $this->entityOptions = OptionsArray::parse(
563 1
                $parser,
564 1
                $list,
565 1
                static::$FUNC_OPTIONS
566
            );
567 1
            ++$list->idx;
568 1
569
            for (; $list->idx < $list->count; ++$list->idx) {
570 1
                $token = $list->tokens[$list->idx];
571 1
                $this->body[] = $token;
572
            }
573
        } elseif ($this->options->has('VIEW')) {
574 1
            $token = $list->getNext(); // Skipping whitespaces and comments.
575 1
576 1
            // Parsing columns list.
577
            if (($token->type === Token::TYPE_OPERATOR) && ($token->value === '(')) {
578 1
                --$list->idx; // getNext() also goes forward one field.
579 1
                $this->fields = ArrayObj::parse($parser, $list);
580
                ++$list->idx; // Skipping last token from the array.
581 1
                $list->getNext();
582 1
            }
583
584 1
            // Parsing the `AS` keyword.
585 1 View Code Duplication
            for (; $list->idx < $list->count; ++$list->idx) {
586
                $token = $list->tokens[$list->idx];
587 1
                if ($token->type === Token::TYPE_DELIMITER) {
588 1
                    break;
589 1
                }
590 1
                $this->body[] = $token;
591 1
            }
592 4
        } elseif ($this->options->has('TRIGGER')) {
593 4
            // Parsing the time and the event.
594 4
            $this->entityOptions = OptionsArray::parse(
595 4
                $parser,
596
                $list,
597 1
                static::$TRIGGER_OPTIONS
598 1
            );
599
            ++$list->idx;
600 42
601
            $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'ON');
602
            ++$list->idx; // Skipping `ON`.
603
604
            // Parsing the name of the table.
605
            $this->table = Expression::parse(
606
                $parser,
607
                $list,
608
                array(
609
                    'parseField' => 'table',
610
                    'breakOnAlias' => true,
611
                )
612
            );
613
            ++$list->idx;
614
615
            $list->getNextOfTypeAndValue(Token::TYPE_KEYWORD, 'FOR EACH ROW');
616
            ++$list->idx; // Skipping `FOR EACH ROW`.
617
618
            for (; $list->idx < $list->count; ++$list->idx) {
619
                $token = $list->tokens[$list->idx];
620
                $this->body[] = $token;
621
            }
622
        } else {
623 View Code Duplication
            for (; $list->idx < $list->count; ++$list->idx) {
624
                $token = $list->tokens[$list->idx];
625
                if ($token->type === Token::TYPE_DELIMITER) {
626
                    break;
627
                }
628
                $this->body[] = $token;
629
            }
630
        }
631
    }
632
}
633