Failed Conditions
Pull Request — 3.0.x (#3980)
by Guilherme
30:17 queued 13s
created

Table::removeUniqueConstraint()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 9
ccs 0
cts 5
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
namespace Doctrine\DBAL\Schema;
4
5
use Doctrine\DBAL\DBALException;
6
use Doctrine\DBAL\Schema\Visitor\Visitor;
7
use Doctrine\DBAL\Types\Type;
8
use const ARRAY_FILTER_USE_KEY;
9
use function array_filter;
10
use function array_keys;
11
use function array_merge;
12
use function in_array;
13
use function is_numeric;
14
use function is_string;
15
use function preg_match;
16
use function strlen;
17
use function strtolower;
18
19
/**
20
 * Object Representation of a table.
21
 */
22
class Table extends AbstractAsset
23
{
24
    /** @var Column[] */
25
    protected $_columns = [];
26
27
    /** @var Index[] */
28
    protected $_indexes = [];
29
30
    /** @var string|null */
31
    protected $_primaryKeyName = null;
32
33
    /** @var UniqueConstraint[] */
34
    protected $uniqueConstraints = [];
35
36
    /** @var ForeignKeyConstraint[] */
37
    protected $_fkConstraints = [];
38
39
    /** @var mixed[] */
40
    protected $_options = [
41
        'create_options' => [],
42
    ];
43
44
    /** @var SchemaConfig|null */
45
    protected $_schemaConfig = null;
46
47
    /** @var Index[] */
48
    private $implicitIndexes = [];
49
50
    /**
51
     * @param Column[]               $columns
52
     * @param Index[]                $indexes
53
     * @param UniqueConstraint[]     $uniqueConstraints
54
     * @param ForeignKeyConstraint[] $fkConstraints
55
     * @param mixed[]                $options
56
     *
57
     * @throws DBALException
58
     */
59 14477
    public function __construct(
60
        string $name,
61
        array $columns = [],
62
        array $indexes = [],
63
        array $uniqueConstraints = [],
64
        array $fkConstraints = [],
65
        array $options = []
66
    ) {
67 14477
        if ($name === '') {
68 22
            throw DBALException::invalidTableName($name);
69
        }
70
71 14455
        $this->_setName($name);
72
73 14455
        foreach ($columns as $column) {
74 2062
            $this->_addColumn($column);
75
        }
76
77 14433
        foreach ($indexes as $idx) {
78 654
            $this->_addIndex($idx);
79
        }
80
81 14389
        foreach ($uniqueConstraints as $uniqueConstraint) {
82
            $this->_addUniqueConstraint($uniqueConstraint);
83
        }
84
85 14389
        foreach ($fkConstraints as $constraint) {
86 253
            $this->_addForeignKeyConstraint($constraint);
87
        }
88
89 14389
        $this->_options = array_merge($this->_options, $options);
90 14389
    }
91
92
    /**
93
     * @return void
94
     */
95 1443
    public function setSchemaConfig(SchemaConfig $schemaConfig)
96
    {
97 1443
        $this->_schemaConfig = $schemaConfig;
98 1443
    }
99
100
    /**
101
     * Sets the Primary Key.
102
     *
103
     * @param string[]     $columnNames
104
     * @param string|false $indexName
105
     *
106
     * @return self
107
     */
108 5827
    public function setPrimaryKey(array $columnNames, $indexName = false)
109
    {
110 5827
        if ($indexName === false) {
111 5805
            $indexName = 'primary';
112
        }
113
114 5827
        $this->_addIndex($this->_createIndex($columnNames, $indexName, true, true));
115
116 5827
        foreach ($columnNames as $columnName) {
117 5827
            $column = $this->getColumn($columnName);
118 5827
            $column->setNotnull(true);
119
        }
120
121 5827
        return $this;
122
    }
123
124
    /**
125
     * @param string[] $columnNames
126
     * @param string[] $flags
127
     * @param mixed[]  $options
128
     *
129
     * @return self
130
     */
131 1815
    public function addIndex(array $columnNames, ?string $indexName = null, array $flags = [], array $options = [])
132
    {
133 1815
        if ($indexName === null) {
134 393
            $indexName = $this->_generateIdentifierName(
135 393
                array_merge([$this->getName()], $columnNames),
136 393
                'idx',
137 393
                $this->_getMaxIdentifierLength()
138
            );
139
        }
140
141 1815
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
142
    }
143
144
    /**
145
     * @param string[] $columnNames
146
     * @param string[] $flags
147
     * @param mixed[]  $options
148
     *
149
     * @return self
150
     */
151
    public function addUniqueConstraint(array $columnNames, ?string $indexName = null, array $flags = [], array $options = []) : Table
152
    {
153
        if ($indexName === null) {
154
            $indexName = $this->_generateIdentifierName(
155
                array_merge([$this->getName()], $columnNames),
156
                'uniq',
157
                $this->_getMaxIdentifierLength()
158
            );
159
        }
160
161
        return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options));
162
    }
163
164
    /**
165
     * Drops the primary key from this table.
166
     *
167
     * @return void
168
     */
169 398
    public function dropPrimaryKey()
170
    {
171 398
        if ($this->_primaryKeyName === null) {
172
            return;
173
        }
174
175 398
        $this->dropIndex($this->_primaryKeyName);
176 398
        $this->_primaryKeyName = null;
177 398
    }
178
179
    /**
180
     * Drops an index from this table.
181
     *
182
     * @param string $indexName The index name.
183
     *
184
     * @return void
185
     *
186
     * @throws SchemaException If the index does not exist.
187
     */
188 674
    public function dropIndex($indexName)
189
    {
190 674
        $indexName = $this->normalizeIdentifier($indexName);
191
192 674
        if (! $this->hasIndex($indexName)) {
193
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
194
        }
195
196 674
        unset($this->_indexes[$indexName]);
197 674
    }
198
199
    /**
200
     * @param string[]    $columnNames
201
     * @param string|null $indexName
202
     * @param mixed[]     $options
203
     *
204
     * @return self
205
     */
206 543
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
207
    {
208 543
        if ($indexName === null) {
209 361
            $indexName = $this->_generateIdentifierName(
210 361
                array_merge([$this->getName()], $columnNames),
211 361
                'uniq',
212 361
                $this->_getMaxIdentifierLength()
213
            );
214
        }
215
216 543
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
217
    }
218
219
    /**
220
     * Renames an index.
221
     *
222
     * @param string      $oldIndexName The name of the index to rename from.
223
     * @param string|null $newIndexName The name of the index to rename to.
224
     *                                  If null is given, the index name will be auto-generated.
225
     *
226
     * @return self This table instance.
227
     *
228
     * @throws SchemaException If no index exists for the given current name
229
     *                         or if an index with the given new name already exists on this table.
230
     */
231 307
    public function renameIndex($oldIndexName, $newIndexName = null)
232
    {
233 307
        $oldIndexName           = $this->normalizeIdentifier($oldIndexName);
234 307
        $normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
235
236 307
        if ($oldIndexName === $normalizedNewIndexName) {
237 198
            return $this;
238
        }
239
240 307
        if (! $this->hasIndex($oldIndexName)) {
241 22
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
242
        }
243
244 285
        if ($this->hasIndex($normalizedNewIndexName)) {
245 22
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
246
        }
247
248 263
        $oldIndex = $this->_indexes[$oldIndexName];
249
250 263
        if ($oldIndex->isPrimary()) {
251 22
            $this->dropPrimaryKey();
252
253 22
            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName ?? false);
254
        }
255
256 263
        unset($this->_indexes[$oldIndexName]);
257
258 263
        if ($oldIndex->isUnique()) {
259 44
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
260
        }
261
262 241
        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
263
    }
264
265
    /**
266
     * Checks if an index begins in the order of the given columns.
267
     *
268
     * @param string[] $columnNames
269
     *
270
     * @return bool
271
     */
272 43
    public function columnsAreIndexed(array $columnNames)
273
    {
274 43
        foreach ($this->getIndexes() as $index) {
275
            /** @var $index Index */
276 43
            if ($index->spansColumns($columnNames)) {
277 43
                return true;
278
            }
279
        }
280
281
        return false;
282
    }
283
284
    /**
285
     * @param string  $columnName
286
     * @param string  $typeName
287
     * @param mixed[] $options
288
     *
289
     * @return Column
290
     */
291 11636
    public function addColumn($columnName, $typeName, array $options = [])
292
    {
293 11636
        $column = new Column($columnName, Type::getType($typeName), $options);
294
295 11636
        $this->_addColumn($column);
296
297 11636
        return $column;
298
    }
299
300
    /**
301
     * Renames a Column.
302
     *
303
     * @deprecated
304
     *
305
     * @param string $oldColumnName
306
     * @param string $newColumnName
307
     *
308
     * @return void
309
     *
310
     * @throws DBALException
311
     */
312
    public function renameColumn($oldColumnName, $newColumnName)
0 ignored issues
show
Unused Code introduced by
The parameter $oldColumnName is not used and could be removed. ( Ignorable by Annotation )

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

312
    public function renameColumn(/** @scrutinizer ignore-unused */ $oldColumnName, $newColumnName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $newColumnName is not used and could be removed. ( Ignorable by Annotation )

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

312
    public function renameColumn($oldColumnName, /** @scrutinizer ignore-unused */ $newColumnName)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
313
    {
314
        throw new DBALException(
315
            'Table#renameColumn() was removed, because it drops and recreates ' .
316
            'the column instead. There is no fix available, because a schema diff cannot reliably detect if a ' .
317
            'column was renamed or one column was created and another one dropped.'
318
        );
319
    }
320
321
    /**
322
     * Change Column Details.
323
     *
324
     * @param string  $columnName
325
     * @param mixed[] $options
326
     *
327
     * @return self
328
     */
329 265
    public function changeColumn($columnName, array $options)
330
    {
331 265
        $column = $this->getColumn($columnName);
332
333 265
        $column->setOptions($options);
334
335 265
        return $this;
336
    }
337
338
    /**
339
     * Drops a Column from the Table.
340
     *
341
     * @param string $columnName
342
     *
343
     * @return self
344
     */
345 198
    public function dropColumn($columnName)
346
    {
347 198
        $columnName = $this->normalizeIdentifier($columnName);
348
349 198
        unset($this->_columns[$columnName]);
350
351 198
        return $this;
352
    }
353
354
    /**
355
     * Adds a foreign key constraint.
356
     *
357
     * Name is inferred from the local columns.
358
     *
359
     * @param Table|string $foreignTable       Table schema instance or table name
360
     * @param string[]     $localColumnNames
361
     * @param string[]     $foreignColumnNames
362
     * @param mixed[]      $options
363
     * @param string|null  $constraintName
364
     *
365
     * @return self
366
     */
367 1812
    public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], $constraintName = null)
368
    {
369 1812
        if ($constraintName === null) {
370 954
            $constraintName = $this->_generateIdentifierName(array_merge((array) $this->getName(), $localColumnNames), 'fk', $this->_getMaxIdentifierLength());
371
        }
372
373 1812
        return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
374
    }
375
376
    /**
377
     * Adds a foreign key constraint.
378
     *
379
     * Name is to be generated by the database itself.
380
     *
381
     * @deprecated Use {@link addForeignKeyConstraint}
382
     *
383
     * @param Table|string $foreignTable       Table schema instance or table name
384
     * @param string[]     $localColumnNames
385
     * @param string[]     $foreignColumnNames
386
     * @param mixed[]      $options
387
     *
388
     * @return self
389
     */
390 71
    public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [])
391
    {
392 71
        return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
393
    }
394
395
    /**
396
     * Adds a foreign key constraint with a given name.
397
     *
398
     * @deprecated Use {@link addForeignKeyConstraint}
399
     *
400
     * @param string       $name
401
     * @param Table|string $foreignTable       Table schema instance or table name
402
     * @param string[]     $localColumnNames
403
     * @param string[]     $foreignColumnNames
404
     * @param mixed[]      $options
405
     *
406
     * @return self
407
     *
408
     * @throws SchemaException
409
     */
410 1834
    public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [])
411
    {
412 1834
        if ($foreignTable instanceof Table) {
413 923
            foreach ($foreignColumnNames as $columnName) {
414 923
                if (! $foreignTable->hasColumn($columnName)) {
415 22
                    throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
416
                }
417
            }
418
        }
419
420 1812
        foreach ($localColumnNames as $columnName) {
421 1812
            if (! $this->hasColumn($columnName)) {
422 22
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
423
            }
424
        }
425
426 1790
        $constraint = new ForeignKeyConstraint(
427 1790
            $localColumnNames,
428
            $foreignTable,
429
            $foreignColumnNames,
430
            $name,
431
            $options
432
        );
433
434 1790
        return $this->_addForeignKeyConstraint($constraint);
435
    }
436
437
    /**
438
     * @param string $name
439
     * @param mixed  $value
440
     *
441
     * @return self
442
     */
443 1737
    public function addOption($name, $value)
444
    {
445 1737
        $this->_options[$name] = $value;
446
447 1737
        return $this;
448
    }
449
450
    /**
451
     * Returns whether this table has a foreign key constraint with the given name.
452
     *
453
     * @param string $constraintName
454
     *
455
     * @return bool
456
     */
457 242
    public function hasForeignKey($constraintName)
458
    {
459 242
        $constraintName = $this->normalizeIdentifier($constraintName);
460
461 242
        return isset($this->_fkConstraints[$constraintName]);
462
    }
463
464
    /**
465
     * Returns the foreign key constraint with the given name.
466
     *
467
     * @param string $constraintName The constraint name.
468
     *
469
     * @return ForeignKeyConstraint
470
     *
471
     * @throws SchemaException If the foreign key does not exist.
472
     */
473 177
    public function getForeignKey($constraintName)
474
    {
475 177
        $constraintName = $this->normalizeIdentifier($constraintName);
476
477 177
        if (! $this->hasForeignKey($constraintName)) {
478
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
479
        }
480
481 177
        return $this->_fkConstraints[$constraintName];
482
    }
483
484
    /**
485
     * Removes the foreign key constraint with the given name.
486
     *
487
     * @param string $constraintName The constraint name.
488
     *
489
     * @return void
490
     *
491
     * @throws SchemaException
492
     */
493 220
    public function removeForeignKey($constraintName)
494
    {
495 220
        $constraintName = $this->normalizeIdentifier($constraintName);
496
497 220
        if (! $this->hasForeignKey($constraintName)) {
498
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
499
        }
500
501 220
        unset($this->_fkConstraints[$constraintName]);
502 220
    }
503
504
    /**
505
     * Returns whether this table has a unique constraint with the given name.
506
     */
507
    public function hasUniqueConstraint(string $name) : bool
508
    {
509
        $name = $this->normalizeIdentifier($name);
510
511
        return isset($this->uniqueConstraints[$name]);
512
    }
513
514
    /**
515
     * Returns the unique constraint with the given name.
516
     *
517
     * @throws SchemaException If the unique constraint does not exist.
518
     */
519
    public function getUniqueConstraint(string $name) : UniqueConstraint
520
    {
521
        $name = $this->normalizeIdentifier($name);
522
523
        if (! $this->hasUniqueConstraint($name)) {
524
            throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name);
525
        }
526
527
        return $this->uniqueConstraints[$name];
528
    }
529
530
    /**
531
     * Removes the unique constraint with the given name.
532
     *
533
     * @throws SchemaException If the unique constraint does not exist.
534
     */
535
    public function removeUniqueConstraint(string $name) : void
536
    {
537
        $name = $this->normalizeIdentifier($name);
538
539
        if (! $this->hasForeignKey($name)) {
540
            throw SchemaException::uniqueConstraintDoesNotExist($name, $this->_name);
541
        }
542
543
        unset($this->uniqueConstraints[$name]);
544
    }
545
546
    /**
547
     * Returns ordered list of columns (primary keys are first, then foreign keys, then the rest)
548
     *
549
     * @return Column[]
550
     */
551 9221
    public function getColumns()
552
    {
553 9221
        $primaryKeyColumns = $this->hasPrimaryKey() ? $this->getPrimaryKeyColumns() : [];
554 9221
        $foreignKeyColumns = $this->getForeignKeyColumns();
555 9221
        $remainderColumns  = $this->filterColumns(
556 9221
            array_merge(array_keys($primaryKeyColumns), array_keys($foreignKeyColumns)),
557 9221
            true
558
        );
559
560 9221
        return array_merge($primaryKeyColumns, $foreignKeyColumns, $remainderColumns);
561
    }
562
563
    /**
564
     * Returns whether this table has a Column with the given name.
565
     *
566
     * @param string $columnName The column name.
567
     *
568
     * @return bool
569
     */
570 10055
    public function hasColumn($columnName)
571
    {
572 10055
        $columnName = $this->normalizeIdentifier($columnName);
573
574 10055
        return isset($this->_columns[$columnName]);
575
    }
576
577
    /**
578
     * Returns the Column with the given name.
579
     *
580
     * @param string $columnName The column name.
581
     *
582
     * @return Column
583
     *
584
     * @throws SchemaException If the column does not exist.
585
     */
586 7958
    public function getColumn($columnName)
587
    {
588 7958
        $columnName = $this->normalizeIdentifier($columnName);
589
590 7958
        if (! $this->hasColumn($columnName)) {
591 22
            throw SchemaException::columnDoesNotExist($columnName, $this->_name);
592
        }
593
594 7936
        return $this->_columns[$columnName];
595
    }
596
597
    /**
598
     * Returns the primary key.
599
     *
600
     * @return Index|null The primary key, or null if this Table has no primary key.
601
     */
602 4446
    public function getPrimaryKey()
603
    {
604 4446
        return $this->_primaryKeyName !== null
605 4446
            ? $this->getIndex($this->_primaryKeyName)
606 4446
            : null;
607
    }
608
609
    /**
610
     * Returns the primary key columns.
611
     *
612
     * @return Column[]
613
     *
614
     * @throws DBALException
615
     */
616 4336
    public function getPrimaryKeyColumns()
617
    {
618 4336
        $primaryKey = $this->getPrimaryKey();
619
620 4336
        if ($primaryKey === null) {
621
            throw new DBALException('Table ' . $this->getName() . ' has no primary key.');
622
        }
623
624 4336
        return $this->filterColumns($primaryKey->getColumns());
625
    }
626
627
    /**
628
     * Returns the foreign key columns
629
     *
630
     * @return Column[]
631
     */
632 9221
    public function getForeignKeyColumns()
633
    {
634 9221
        $foreignKeyColumns = [];
635
636 9221
        foreach ($this->getForeignKeys() as $foreignKey) {
637 894
            $foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getLocalColumns());
638
        }
639
640 9221
        return $this->filterColumns($foreignKeyColumns);
641
    }
642
643
    /**
644
     * Returns whether this table has a primary key.
645
     *
646
     * @return bool
647
     */
648 9309
    public function hasPrimaryKey()
649
    {
650 9309
        return $this->_primaryKeyName !== null && $this->hasIndex($this->_primaryKeyName);
651
    }
652
653
    /**
654
     * Returns whether this table has an Index with the given name.
655
     *
656
     * @param string $indexName The index name.
657
     *
658
     * @return bool
659
     */
660 5619
    public function hasIndex($indexName)
661
    {
662 5619
        $indexName = $this->normalizeIdentifier($indexName);
663
664 5619
        return isset($this->_indexes[$indexName]);
665
    }
666
667
    /**
668
     * Returns the Index with the given name.
669
     *
670
     * @param string $indexName The index name.
671
     *
672
     * @return Index
673
     *
674
     * @throws SchemaException If the index does not exist.
675
     */
676 5135
    public function getIndex($indexName)
677
    {
678 5135
        $indexName = $this->normalizeIdentifier($indexName);
679 5135
        if (! $this->hasIndex($indexName)) {
680 22
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
681
        }
682
683 5113
        return $this->_indexes[$indexName];
684
    }
685
686
    /**
687
     * @return Index[]
688
     */
689 9023
    public function getIndexes()
690
    {
691 9023
        return $this->_indexes;
692
    }
693
694
    /**
695
     * Returns the unique constraints.
696
     *
697
     * @return UniqueConstraint[]
698
     */
699 6731
    public function getUniqueConstraints() : array
700
    {
701 6731
        return $this->uniqueConstraints;
702
    }
703
704
    /**
705
     * Returns the foreign key constraints.
706
     *
707
     * @return ForeignKeyConstraint[]
708
     */
709 9617
    public function getForeignKeys()
710
    {
711 9617
        return $this->_fkConstraints;
712
    }
713
714
    /**
715
     * @param string $name
716
     *
717
     * @return bool
718
     */
719 4399
    public function hasOption($name)
720
    {
721 4399
        return isset($this->_options[$name]);
722
    }
723
724
    /**
725
     * @param string $name
726
     *
727
     * @return mixed
728
     */
729 221
    public function getOption($name)
730
    {
731 221
        return $this->_options[$name];
732
    }
733
734
    /**
735
     * @return mixed[]
736
     */
737 6995
    public function getOptions()
738
    {
739 6995
        return $this->_options;
740
    }
741
742
    /**
743
     * @return void
744
     */
745 342
    public function visit(Visitor $visitor)
746
    {
747 342
        $visitor->acceptTable($this);
748
749 342
        foreach ($this->getColumns() as $column) {
750 276
            $visitor->acceptColumn($this, $column);
751
        }
752
753 342
        foreach ($this->getIndexes() as $index) {
754 192
            $visitor->acceptIndex($this, $index);
755
        }
756
757 342
        foreach ($this->getForeignKeys() as $constraint) {
758 88
            $visitor->acceptForeignKey($this, $constraint);
759
        }
760 342
    }
761
762
    /**
763
     * Clone of a Table triggers a deep clone of all affected assets.
764
     *
765
     * @return void
766
     */
767 1052
    public function __clone()
768
    {
769 1052
        foreach ($this->_columns as $k => $column) {
770 1030
            $this->_columns[$k] = clone $column;
771
        }
772 1052
        foreach ($this->_indexes as $k => $index) {
773 742
            $this->_indexes[$k] = clone $index;
774
        }
775 1052
        foreach ($this->_fkConstraints as $k => $fk) {
776 175
            $this->_fkConstraints[$k] = clone $fk;
777 175
            $this->_fkConstraints[$k]->setLocalTable($this);
778
        }
779 1052
    }
780
781 44
    public function setComment(?string $comment) : self
782
    {
783
        // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options.
784 44
        $this->addOption('comment', $comment);
785
786 44
        return $this;
787
    }
788
789 44
    public function getComment() : ?string
790
    {
791 44
        return $this->_options['comment'] ?? null;
792
    }
793
794
    /**
795
     * @return int
796
     */
797 2628
    protected function _getMaxIdentifierLength()
798
    {
799 2628
        if ($this->_schemaConfig instanceof SchemaConfig) {
800 227
            return $this->_schemaConfig->getMaxIdentifierLength();
801
        }
802
803 2443
        return 63;
804
    }
805
806
    /**
807
     * @return void
808
     *
809
     * @throws SchemaException
810
     */
811 12617
    protected function _addColumn(Column $column)
812
    {
813 12617
        $columnName = $column->getName();
814 12617
        $columnName = $this->normalizeIdentifier($columnName);
815
816 12617
        if (isset($this->_columns[$columnName])) {
817 22
            throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
818
        }
819
820 12617
        $this->_columns[$columnName] = $column;
821 12617
    }
822
823
    /**
824
     * Adds an index to the table.
825
     *
826
     * @return self
827
     *
828
     * @throws SchemaException
829
     */
830 8386
    protected function _addIndex(Index $indexCandidate)
831
    {
832 8386
        $indexName               = $indexCandidate->getName();
833 8386
        $indexName               = $this->normalizeIdentifier($indexName);
834 8386
        $replacedImplicitIndexes = [];
835
836 8386
        foreach ($this->implicitIndexes as $name => $implicitIndex) {
837 550
            if (! $implicitIndex->isFullfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
838 462
                continue;
839
            }
840
841 88
            $replacedImplicitIndexes[] = $name;
842
        }
843
844 8386
        if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
845 8386
            ($this->_primaryKeyName !== null && $indexCandidate->isPrimary())
846
        ) {
847 44
            throw SchemaException::indexAlreadyExists($indexName, $this->_name);
848
        }
849
850 8386
        foreach ($replacedImplicitIndexes as $name) {
851 88
            unset($this->_indexes[$name], $this->implicitIndexes[$name]);
852
        }
853
854 8386
        if ($indexCandidate->isPrimary()) {
855 5893
            $this->_primaryKeyName = $indexName;
856
        }
857
858 8386
        $this->_indexes[$indexName] = $indexCandidate;
859
860 8386
        return $this;
861
    }
862
863
    /**
864
     * @return self
865
     */
866
    protected function _addUniqueConstraint(UniqueConstraint $constraint) : Table
867
    {
868
        $mergedNames = array_merge([$this->getName()], $constraint->getColumns());
869
        $name        = strlen($constraint->getName()) > 0
870
            ? $constraint->getName()
871
            : $this->_generateIdentifierName($mergedNames, 'fk', $this->_getMaxIdentifierLength());
872
873
        $name = $this->normalizeIdentifier($name);
874
875
        $this->uniqueConstraints[$name] = $constraint;
876
877
        // If there is already an index that fulfills this requirements drop the request. In the case of __construct
878
        // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
879
        // This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
880
        $indexName = $this->_generateIdentifierName($mergedNames, 'idx', $this->_getMaxIdentifierLength());
881
882
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false);
883
884
        foreach ($this->_indexes as $existingIndex) {
885
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
886
                return $this;
887
            }
888
        }
889
890
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
891
892
        return $this;
893
    }
894
895
    /**
896
     * @return self
897
     */
898 1893
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
899
    {
900 1893
        $constraint->setLocalTable($this);
901
902 1893
        if (strlen($constraint->getName()) > 0) {
903 1870
            $name = $constraint->getName();
904
        } else {
905 23
            $name = $this->_generateIdentifierName(
906 23
                array_merge([$this->getName()], $constraint->getLocalColumns()),
907 23
                'fk',
908 23
                $this->_getMaxIdentifierLength()
909
            );
910
        }
911
912 1893
        $name = $this->normalizeIdentifier($name);
913
914 1893
        $this->_fkConstraints[$name] = $constraint;
915
916
        // add an explicit index on the foreign key columns. If there is already an index that fulfils this requirements drop the request.
917
        // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes
918
        // lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns).
919 1893
        $indexName      = $this->_generateIdentifierName(
920 1893
            array_merge([$this->getName()], $constraint->getColumns()),
921 1893
            'idx',
922 1893
            $this->_getMaxIdentifierLength()
923
        );
924 1893
        $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
925
926 1893
        foreach ($this->_indexes as $existingIndex) {
927 1314
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
928 831
                return $this;
929
            }
930
        }
931
932 1424
        $this->_addIndex($indexCandidate);
933 1424
        $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
934
935 1424
        return $this;
936
    }
937
938
    /**
939
     * @param string[] $columnNames
940
     * @param string   $indexName
941
     * @param bool     $isUnique
942
     * @param bool     $isPrimary
943
     * @param string[] $flags
944
     * @param mixed[]  $options
945
     *
946
     * @return Index
947
     *
948
     * @throws SchemaException
949
     */
950 8259
    private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
951
    {
952 8259
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) {
953 22
            throw SchemaException::indexNameInvalid($indexName);
954
        }
955
956 8237
        foreach ($columnNames as $columnName) {
957 8215
            if (! $this->hasColumn($columnName)) {
958 22
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
959
            }
960
        }
961
962 8215
        return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
963
    }
964
965
    /**
966
     * @param string[] $columnNames
967
     * @param string[] $flags
968
     * @param mixed[]  $options
969
     *
970
     * @throws SchemaException
971
     */
972
    private function _createUniqueConstraint(array $columnNames, string $indexName, array $flags = [], array $options = []) : UniqueConstraint
973
    {
974
        if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) {
975
            throw SchemaException::indexNameInvalid($indexName);
976
        }
977
978
        foreach ($columnNames as $columnName => $indexColOptions) {
979
            if (is_numeric($columnName) && is_string($indexColOptions)) {
980
                $columnName = $indexColOptions;
981
            }
982
983
            if (! $this->hasColumn($columnName)) {
984
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
985
            }
986
        }
987
988
        return new UniqueConstraint($indexName, $columnNames, $flags, $options);
989
    }
990
991
    /**
992
     * Normalizes a given identifier.
993
     *
994
     * Trims quotes and lowercases the given identifier.
995
     *
996
     * @return string The normalized identifier.
997
     */
998 12705
    private function normalizeIdentifier(?string $identifier) : string
999
    {
1000 12705
        if ($identifier === null) {
1001 22
            return '';
1002
        }
1003
1004 12705
        return $this->trimQuotes(strtolower($identifier));
1005
    }
1006
1007
    /**
1008
     * Returns only columns that have specified names
1009
     *
1010
     * @param string[] $columnNames
1011
     *
1012
     * @return Column[]
1013
     */
1014 9221
    private function filterColumns(array $columnNames, bool $reverse = false) : array
1015
    {
1016
        return array_filter($this->_columns, static function ($columnName) use ($columnNames, $reverse) : bool {
1017 8825
            return in_array($columnName, $columnNames, true) !== $reverse;
1018 9221
        }, ARRAY_FILTER_USE_KEY);
1019
    }
1020
}
1021