Table.php 22.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
Benjamin Eberlei's avatar
Benjamin Eberlei committed
16
 * and is licensed under the MIT license. For more information, see
17 18 19 20 21 22
 * <http://www.doctrine-project.org>.
 */

namespace Doctrine\DBAL\Schema;

use Doctrine\DBAL\Types\Type;
23
use Doctrine\DBAL\Schema\Visitor\Visitor;
24
use Doctrine\DBAL\DBALException;
25 26

/**
Benjamin Morel's avatar
Benjamin Morel committed
27
 * Object Representation of a table.
28
 *
Benjamin Morel's avatar
Benjamin Morel committed
29 30 31
 * @link   www.doctrine-project.org
 * @since  2.0
 * @author Benjamin Eberlei <kontakt@beberlei.de>
32 33 34 35 36 37 38 39 40
 */
class Table extends AbstractAsset
{
    /**
     * @var string
     */
    protected $_name = null;

    /**
41
     * @var Column[]
42 43 44 45
     */
    protected $_columns = array();

    /**
46
     * @var Index[]
47 48 49 50 51 52 53 54 55
     */
    protected $_indexes = array();

    /**
     * @var string
     */
    protected $_primaryKeyName = false;

    /**
56
     * @var ForeignKeyConstraint[]
57
     */
58
    protected $_fkConstraints = array();
59 60 61 62 63 64

    /**
     * @var array
     */
    protected $_options = array();

65
    /**
66
     * @var SchemaConfig
67 68 69
     */
    protected $_schemaConfig = null;

70
    /**
71 72 73 74 75 76
     * @param string                 $tableName
     * @param Column[]               $columns
     * @param Index[]                $indexes
     * @param ForeignKeyConstraint[] $fkConstraints
     * @param integer                $idGeneratorType
     * @param array                  $options
77
     *
78
     * @throws DBALException
79
     */
80
    public function __construct($tableName, array $columns=array(), array $indexes=array(), array $fkConstraints=array(), $idGeneratorType = 0, array $options=array())
81
    {
82 83 84 85
        if (strlen($tableName) == 0) {
            throw DBALException::invalidTableName($tableName);
        }

86
        $this->_setName($tableName);
87

88
        foreach ($columns as $column) {
89 90
            $this->_addColumn($column);
        }
91

92
        foreach ($indexes as $idx) {
93 94 95
            $this->_addIndex($idx);
        }

96
        foreach ($fkConstraints as $constraint) {
97
            $this->_addForeignKeyConstraint($constraint);
98 99 100 101 102
        }

        $this->_options = $options;
    }

103
    /**
104
     * @param SchemaConfig $schemaConfig
Benjamin Morel's avatar
Benjamin Morel committed
105 106
     *
     * @return void
107 108 109 110 111 112 113
     */
    public function setSchemaConfig(SchemaConfig $schemaConfig)
    {
        $this->_schemaConfig = $schemaConfig;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
114
     * @return integer
115 116 117 118 119 120 121 122 123 124
     */
    protected function _getMaxIdentifierLength()
    {
        if ($this->_schemaConfig instanceof SchemaConfig) {
            return $this->_schemaConfig->getMaxIdentifierLength();
        } else {
            return 63;
        }
    }

125
    /**
Benjamin Morel's avatar
Benjamin Morel committed
126
     * Sets the Primary Key.
127
     *
Benjamin Morel's avatar
Benjamin Morel committed
128 129 130
     * @param array          $columns
     * @param string|boolean $indexName
     *
131
     * @return self
132
     */
133
    public function setPrimaryKey(array $columns, $indexName = false)
134
    {
135
        $this->_addIndex($this->_createIndex($columns, $indexName ?: "primary", true, true));
136

137
        foreach ($columns as $columnName) {
138 139 140 141
            $column = $this->getColumn($columnName);
            $column->setNotnull(true);
        }

142
        return $this;
143 144 145
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
146 147 148
     * @param array       $columnNames
     * @param string|null $indexName
     * @param array       $flags
149
     * @param array       $options
Benjamin Morel's avatar
Benjamin Morel committed
150
     *
151
     * @return self
152
     */
153
    public function addIndex(array $columnNames, $indexName = null, array $flags = array(), array $options = array())
154
    {
Steve Müller's avatar
Steve Müller committed
155
        if ($indexName == null) {
156
            $indexName = $this->_generateIdentifierName(
157
                array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
158
            );
159 160
        }

161
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
162 163
    }

164
    /**
Benjamin Morel's avatar
Benjamin Morel committed
165
     * Drops the primary key from this table.
166 167 168 169 170 171
     *
     * @return void
     */
    public function dropPrimaryKey()
    {
        $this->dropIndex($this->_primaryKeyName);
172
        $this->_primaryKeyName = false;
173 174 175
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
176 177 178
     * Drops an index from this table.
     *
     * @param string $indexName The index name.
179 180
     *
     * @return void
Benjamin Morel's avatar
Benjamin Morel committed
181
     *
182
     * @throws SchemaException If the index does not exist.
183 184 185 186
     */
    public function dropIndex($indexName)
    {
        $indexName = strtolower($indexName);
187
        if ( ! $this->hasIndex($indexName)) {
188 189 190 191 192
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
        }
        unset($this->_indexes[$indexName]);
    }

193
    /**
Benjamin Morel's avatar
Benjamin Morel committed
194 195
     * @param array       $columnNames
     * @param string|null $indexName
196
     * @param array       $options
197
     *
198
     * @return self
199
     */
200
    public function addUniqueIndex(array $columnNames, $indexName = null, array $options = array())
201
    {
202
        if ($indexName === null) {
203
            $indexName = $this->_generateIdentifierName(
204
                array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength()
205
            );
206 207
        }

208
        return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, array(), $options));
209 210
    }

211 212 213 214 215 216 217
    /**
     * Renames an index.
     *
     * @param string      $oldIndexName The name of the index to rename from.
     * @param string|null $newIndexName The name of the index to rename to.
     *                                  If null is given, the index name will be auto-generated.
     *
218
     * @return self This table instance.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
     *
     * @throws SchemaException if no index exists for the given current name
     *                         or if an index with the given new name already exists on this table.
     */
    public function renameIndex($oldIndexName, $newIndexName = null)
    {
        $oldIndexName           = strtolower($oldIndexName);
        $normalizedNewIndexName = strtolower($newIndexName);

        if ($oldIndexName === $normalizedNewIndexName) {
            return $this;
        }

        if ( ! $this->hasIndex($oldIndexName)) {
            throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
        }

        if ($this->hasIndex($normalizedNewIndexName)) {
            throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
        }

        $oldIndex = $this->_indexes[$oldIndexName];

        if ($oldIndex->isPrimary()) {
            $this->dropPrimaryKey();

            return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName);
        }

        unset($this->_indexes[$oldIndexName]);

        if ($oldIndex->isUnique()) {
            return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName);
        }

        return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags());
    }

257
    /**
Benjamin Morel's avatar
Benjamin Morel committed
258 259 260
     * Checks if an index begins in the order of the given columns.
     *
     * @param array $columnsNames
261
     *
Benjamin Morel's avatar
Benjamin Morel committed
262
     * @return boolean
263 264 265
     */
    public function columnsAreIndexed(array $columnsNames)
    {
266
        foreach ($this->getIndexes() as $index) {
267 268
            /* @var $index Index */
            if ($index->spansColumns($columnsNames)) {
269 270 271
                return true;
            }
        }
Benjamin Morel's avatar
Benjamin Morel committed
272

273 274 275
        return false;
    }

276
    /**
Benjamin Morel's avatar
Benjamin Morel committed
277 278 279 280 281
     * @param array   $columnNames
     * @param string  $indexName
     * @param boolean $isUnique
     * @param boolean $isPrimary
     * @param array   $flags
282
     * @param array   $options
283
     *
284
     * @return Index
Benjamin Morel's avatar
Benjamin Morel committed
285
     *
286
     * @throws SchemaException
287
     */
288
    private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array(), array $options = array())
289 290 291 292 293
    {
        if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
            throw SchemaException::indexNameInvalid($indexName);
        }

294
        foreach ($columnNames as $columnName => $indexColOptions) {
beberlei's avatar
beberlei committed
295 296 297 298
            if (is_numeric($columnName) && is_string($indexColOptions)) {
                $columnName = $indexColOptions;
            }

299
            if ( ! $this->hasColumn($columnName)) {
300
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
301 302
            }
        }
Benjamin Morel's avatar
Benjamin Morel committed
303

304
        return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
305 306 307 308
    }

    /**
     * @param string $columnName
Benjamin Morel's avatar
Benjamin Morel committed
309 310 311
     * @param string $typeName
     * @param array  $options
     *
312
     * @return Column
313
     */
314
    public function addColumn($columnName, $typeName, array $options=array())
315 316 317 318
    {
        $column = new Column($columnName, Type::getType($typeName), $options);

        $this->_addColumn($column);
Benjamin Morel's avatar
Benjamin Morel committed
319

320
        return $column;
321 322 323
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
324
     * Renames a Column.
325 326 327
     *
     * @param string $oldColumnName
     * @param string $newColumnName
Benjamin Morel's avatar
Benjamin Morel committed
328
     *
329
     * @return self
Benjamin Morel's avatar
Benjamin Morel committed
330
     *
331
     * @throws DBALException
332 333 334
     */
    public function renameColumn($oldColumnName, $newColumnName)
    {
335 336 337
        throw new DBALException("Table#renameColumn() was removed, because it drops and recreates " .
            "the column instead. There is no fix available, because a schema diff cannot reliably detect if a " .
            "column was renamed or one column was created and another one dropped.");
338 339 340
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
341
     * Change Column Details.
342
     *
343
     * @param string $columnName
Benjamin Morel's avatar
Benjamin Morel committed
344 345
     * @param array  $options
     *
346
     * @return self
347 348 349 350 351
     */
    public function changeColumn($columnName, array $options)
    {
        $column = $this->getColumn($columnName);
        $column->setOptions($options);
Benjamin Morel's avatar
Benjamin Morel committed
352

353 354 355 356
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
357
     * Drops a Column from the Table.
358
     *
359
     * @param string $columnName
Benjamin Morel's avatar
Benjamin Morel committed
360
     *
361
     * @return self
362 363 364
     */
    public function dropColumn($columnName)
    {
365
        $columnName = strtolower($columnName);
366
        unset($this->_columns[$columnName]);
Benjamin Morel's avatar
Benjamin Morel committed
367

368 369 370 371
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
372
     * Adds a foreign key constraint.
373
     *
Benjamin Morel's avatar
Benjamin Morel committed
374
     * Name is inferred from the local columns.
375
     *
376 377 378 379 380
     * @param Table|string $foreignTable Table schema instance or table name
     * @param array        $localColumnNames
     * @param array        $foreignColumnNames
     * @param array        $options
     * @param string|null  $constraintName
Benjamin Morel's avatar
Benjamin Morel committed
381
     *
382
     * @return self
383
     */
384
    public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array(), $constraintName = null)
385
    {
386
        $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array)$this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength());
Benjamin Morel's avatar
Benjamin Morel committed
387

388
        return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
389 390
    }

391
    /**
Benjamin Morel's avatar
Benjamin Morel committed
392
     * Adds a foreign key constraint.
393
     *
Pascal Borreli's avatar
Pascal Borreli committed
394
     * Name is to be generated by the database itself.
395
     *
396
     * @deprecated Use {@link addForeignKeyConstraint}
Benjamin Morel's avatar
Benjamin Morel committed
397
     *
398 399 400 401
     * @param Table|string $foreignTable Table schema instance or table name
     * @param array        $localColumnNames
     * @param array        $foreignColumnNames
     * @param array        $options
Benjamin Morel's avatar
Benjamin Morel committed
402
     *
403
     * @return self
404 405 406
     */
    public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
    {
407
        return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
408 409
    }

410
    /**
Benjamin Morel's avatar
Benjamin Morel committed
411
     * Adds a foreign key constraint with a given name.
412
     *
413
     * @deprecated Use {@link addForeignKeyConstraint}
Benjamin Morel's avatar
Benjamin Morel committed
414
     *
415 416 417 418 419
     * @param string       $name
     * @param Table|string $foreignTable Table schema instance or table name
     * @param array        $localColumnNames
     * @param array        $foreignColumnNames
     * @param array        $options
Benjamin Morel's avatar
Benjamin Morel committed
420
     *
421
     * @return self
Benjamin Morel's avatar
Benjamin Morel committed
422
     *
423
     * @throws SchemaException
424 425
     */
    public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array())
426
    {
427
        if ($foreignTable instanceof Table) {
428
            foreach ($foreignColumnNames as $columnName) {
429
                if ( ! $foreignTable->hasColumn($columnName)) {
430
                    throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
431
                }
432 433
            }
        }
434

435
        foreach ($localColumnNames as $columnName) {
436
            if ( ! $this->hasColumn($columnName)) {
437
                throw SchemaException::columnDoesNotExist($columnName, $this->_name);
438 439
            }
        }
440

441
        $constraint = new ForeignKeyConstraint(
442
            $localColumnNames, $foreignTable, $foreignColumnNames, $name, $options
443
        );
444
        $this->_addForeignKeyConstraint($constraint);
445

446 447 448 449 450 451
        return $this;
    }

    /**
     * @param string $name
     * @param string $value
Benjamin Morel's avatar
Benjamin Morel committed
452
     *
453
     * @return self
454 455 456 457
     */
    public function addOption($name, $value)
    {
        $this->_options[$name] = $value;
Benjamin Morel's avatar
Benjamin Morel committed
458

459 460 461 462
        return $this;
    }

    /**
463
     * @param Column $column
Benjamin Morel's avatar
Benjamin Morel committed
464 465 466
     *
     * @return void
     *
467
     * @throws SchemaException
468 469 470
     */
    protected function _addColumn(Column $column)
    {
471 472 473
        $columnName = $column->getName();
        $columnName = strtolower($columnName);

474
        if (isset($this->_columns[$columnName])) {
475
            throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
476 477 478 479 480 481
        }

        $this->_columns[$columnName] = $column;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
482
     * Adds an index to the table.
483
     *
484
     * @param Index $indexCandidate
Benjamin Morel's avatar
Benjamin Morel committed
485
     *
486
     * @return self
Benjamin Morel's avatar
Benjamin Morel committed
487
     *
488
     * @throws SchemaException
489
     */
490
    protected function _addIndex(Index $indexCandidate)
491
    {
492
        // check for duplicates
493
        foreach ($this->_indexes as $existingIndex) {
494
            if ($indexCandidate->isFullfilledBy($existingIndex)) {
495 496 497 498
                return $this;
            }
        }

499
        $indexName = $indexCandidate->getName();
500
        $indexName = strtolower($indexName);
501

502
        if (isset($this->_indexes[$indexName]) || ($this->_primaryKeyName != false && $indexCandidate->isPrimary())) {
503
            throw SchemaException::indexAlreadyExists($indexName, $this->_name);
504 505
        }

506
        // remove overruled indexes
507
        foreach ($this->_indexes as $idxKey => $existingIndex) {
508 509 510 511 512 513
            if ($indexCandidate->overrules($existingIndex)) {
                unset($this->_indexes[$idxKey]);
            }
        }

        if ($indexCandidate->isPrimary()) {
514 515 516
            $this->_primaryKeyName = $indexName;
        }

517
        $this->_indexes[$indexName] = $indexCandidate;
Benjamin Morel's avatar
Benjamin Morel committed
518

519 520 521 522
        return $this;
    }

    /**
523
     * @param ForeignKeyConstraint $constraint
Benjamin Morel's avatar
Benjamin Morel committed
524 525
     *
     * @return void
526
     */
527
    protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
528
    {
529
        $constraint->setLocalTable($this);
530

Steve Müller's avatar
Steve Müller committed
531
        if (strlen($constraint->getName())) {
532 533 534
            $name = $constraint->getName();
        } else {
            $name = $this->_generateIdentifierName(
535
                array_merge((array)$this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength()
536 537
            );
        }
538
        $name = strtolower($name);
539 540

        $this->_fkConstraints[$name] = $constraint;
Pascal Borreli's avatar
Pascal Borreli committed
541
        // add an explicit index on the foreign key columns. If there is already an index that fulfils this requirements drop the request.
542
        // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes
Pascal Borreli's avatar
Pascal Borreli committed
543
        // lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns).
544
        $this->addIndex($constraint->getColumns());
545 546 547
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
548 549 550 551 552
     * Returns whether this table has a foreign key constraint with the given name.
     *
     * @param string $constraintName
     *
     * @return boolean
553 554 555
     */
    public function hasForeignKey($constraintName)
    {
556
        $constraintName = strtolower($constraintName);
Benjamin Morel's avatar
Benjamin Morel committed
557

558 559 560 561
        return isset($this->_fkConstraints[$constraintName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
562 563 564 565
     * Returns the foreign key constraint with the given name.
     *
     * @param string $constraintName The constraint name.
     *
566
     * @return ForeignKeyConstraint
Benjamin Morel's avatar
Benjamin Morel committed
567
     *
568
     * @throws SchemaException If the foreign key does not exist.
569 570 571
     */
    public function getForeignKey($constraintName)
    {
572
        $constraintName = strtolower($constraintName);
Steve Müller's avatar
Steve Müller committed
573
        if (!$this->hasForeignKey($constraintName)) {
574
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
575 576 577
        }

        return $this->_fkConstraints[$constraintName];
578 579
    }

Benjamin Morel's avatar
Benjamin Morel committed
580 581 582 583 584 585 586
    /**
     * Removes the foreign key constraint with the given name.
     *
     * @param string $constraintName The constraint name.
     *
     * @return void
     *
587
     * @throws SchemaException
Benjamin Morel's avatar
Benjamin Morel committed
588
     */
589 590 591
    public function removeForeignKey($constraintName)
    {
        $constraintName = strtolower($constraintName);
Steve Müller's avatar
Steve Müller committed
592
        if (!$this->hasForeignKey($constraintName)) {
593 594 595 596 597 598
            throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
        }

        unset($this->_fkConstraints[$constraintName]);
    }

599
    /**
600
     * @return Column[]
601 602 603
     */
    public function getColumns()
    {
604 605 606 607 608
        $columns = $this->_columns;

        $pkCols = array();
        $fkCols = array();

609
        if ($this->hasPrimaryKey()) {
610 611
            $pkCols = $this->getPrimaryKey()->getColumns();
        }
612
        foreach ($this->getForeignKeys() as $fk) {
613 614 615 616 617
            /* @var $fk ForeignKeyConstraint */
            $fkCols = array_merge($fkCols, $fk->getColumns());
        }
        $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns)));

Steve Müller's avatar
Steve Müller committed
618
        uksort($columns, function ($a, $b) use ($colNames) {
619 620
            return (array_search($a, $colNames) >= array_search($b, $colNames));
        });
Benjamin Morel's avatar
Benjamin Morel committed
621

622
        return $columns;
623 624 625
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
626
     * Returns whether this table has a Column with the given name.
627
     *
Benjamin Morel's avatar
Benjamin Morel committed
628 629 630
     * @param string $columnName The column name.
     *
     * @return boolean
631 632 633
     */
    public function hasColumn($columnName)
    {
634
        $columnName = $this->trimQuotes(strtolower($columnName));
Benjamin Morel's avatar
Benjamin Morel committed
635

636 637 638 639
        return isset($this->_columns[$columnName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
640 641 642
     * Returns the Column with the given name.
     *
     * @param string $columnName The column name.
643
     *
644
     * @return Column
Benjamin Morel's avatar
Benjamin Morel committed
645
     *
646
     * @throws SchemaException If the column does not exist.
647 648 649
     */
    public function getColumn($columnName)
    {
650
        $columnName = strtolower($this->trimQuotes($columnName));
651
        if ( ! $this->hasColumn($columnName)) {
652
            throw SchemaException::columnDoesNotExist($columnName, $this->_name);
653 654 655 656 657 658
        }

        return $this->_columns[$columnName];
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
659 660
     * Returns the primary key.
     *
661
     * @return Index|null The primary key, or null if this Table has no primary key.
662 663 664
     */
    public function getPrimaryKey()
    {
665
        if ( ! $this->hasPrimaryKey()) {
666 667
            return null;
        }
Benjamin Morel's avatar
Benjamin Morel committed
668

669 670 671
        return $this->getIndex($this->_primaryKeyName);
    }

Benjamin Morel's avatar
Benjamin Morel committed
672 673 674 675 676
    /**
     * Returns the primary key columns.
     *
     * @return array
     *
677
     * @throws DBALException
Benjamin Morel's avatar
Benjamin Morel committed
678
     */
679 680 681 682 683
    public function getPrimaryKeyColumns()
    {
        if ( ! $this->hasPrimaryKey()) {
            throw new DBALException("Table " . $this->getName() . " has no primary key.");
        }
Benjamin Morel's avatar
Benjamin Morel committed
684

685 686 687
        return $this->getPrimaryKey()->getColumns();
    }

688
    /**
Benjamin Morel's avatar
Benjamin Morel committed
689
     * Returns whether this table has a primary key.
690
     *
Benjamin Morel's avatar
Benjamin Morel committed
691
     * @return boolean
692 693 694 695 696 697
     */
    public function hasPrimaryKey()
    {
        return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName));
    }

698
    /**
Benjamin Morel's avatar
Benjamin Morel committed
699 700 701 702 703
     * Returns whether this table has an Index with the given name.
     *
     * @param string $indexName The index name.
     *
     * @return boolean
704 705 706
     */
    public function hasIndex($indexName)
    {
707
        $indexName = strtolower($indexName);
Benjamin Morel's avatar
Benjamin Morel committed
708

709 710 711 712
        return (isset($this->_indexes[$indexName]));
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
713 714 715 716
     * Returns the Index with the given name.
     *
     * @param string $indexName The index name.
     *
717
     * @return Index
Benjamin Morel's avatar
Benjamin Morel committed
718
     *
719
     * @throws SchemaException If the index does not exist.
720 721 722
     */
    public function getIndex($indexName)
    {
723
        $indexName = strtolower($indexName);
724
        if ( ! $this->hasIndex($indexName)) {
725
            throw SchemaException::indexDoesNotExist($indexName, $this->_name);
726
        }
Benjamin Morel's avatar
Benjamin Morel committed
727

728 729 730 731
        return $this->_indexes[$indexName];
    }

    /**
732
     * @return Index[]
733 734 735 736 737 738 739
     */
    public function getIndexes()
    {
        return $this->_indexes;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
740
     * Returns the foreign key constraints.
741
     *
742
     * @return ForeignKeyConstraint[]
743
     */
744
    public function getForeignKeys()
745
    {
746
        return $this->_fkConstraints;
747 748
    }

Benjamin Morel's avatar
Benjamin Morel committed
749 750 751 752 753
    /**
     * @param string $name
     *
     * @return boolean
     */
754 755 756 757 758
    public function hasOption($name)
    {
        return isset($this->_options[$name]);
    }

Benjamin Morel's avatar
Benjamin Morel committed
759 760 761 762 763
    /**
     * @param string $name
     *
     * @return mixed
     */
764 765 766 767 768
    public function getOption($name)
    {
        return $this->_options[$name];
    }

Benjamin Morel's avatar
Benjamin Morel committed
769 770 771
    /**
     * @return array
     */
772 773 774 775 776
    public function getOptions()
    {
        return $this->_options;
    }

777
    /**
778
     * @param Visitor $visitor
Benjamin Morel's avatar
Benjamin Morel committed
779 780
     *
     * @return void
781 782 783 784 785
     */
    public function visit(Visitor $visitor)
    {
        $visitor->acceptTable($this);

786
        foreach ($this->getColumns() as $column) {
787
            $visitor->acceptColumn($this, $column);
788 789
        }

790
        foreach ($this->getIndexes() as $index) {
791 792 793
            $visitor->acceptIndex($this, $index);
        }

794
        foreach ($this->getForeignKeys() as $constraint) {
795
            $visitor->acceptForeignKey($this, $constraint);
796 797
        }
    }
798 799

    /**
Benjamin Morel's avatar
Benjamin Morel committed
800 801 802
     * Clone of a Table triggers a deep clone of all affected assets.
     *
     * @return void
803 804 805
     */
    public function __clone()
    {
806
        foreach ($this->_columns as $k => $column) {
807 808
            $this->_columns[$k] = clone $column;
        }
809
        foreach ($this->_indexes as $k => $index) {
810 811
            $this->_indexes[$k] = clone $index;
        }
812
        foreach ($this->_fkConstraints as $k => $fk) {
813 814 815 816
            $this->_fkConstraints[$k] = clone $fk;
            $this->_fkConstraints[$k]->setLocalTable($this);
        }
    }
817
}