Unverified Commit d4fec7cd authored by Guilherme Blanco's avatar Guilherme Blanco Committed by Sergei Morozov

Decouple unique index from unique constraint

parent 9dbec4b5
...@@ -23,6 +23,7 @@ use Doctrine\DBAL\Schema\Index; ...@@ -23,6 +23,7 @@ use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Sequence; use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff; use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Schema\UniqueConstraint;
use Doctrine\DBAL\TransactionIsolationLevel; use Doctrine\DBAL\TransactionIsolationLevel;
use Doctrine\DBAL\Types; use Doctrine\DBAL\Types;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Type;
...@@ -1534,15 +1535,30 @@ abstract class AbstractPlatform ...@@ -1534,15 +1535,30 @@ abstract class AbstractPlatform
$options['indexes'] = []; $options['indexes'] = [];
$options['primary'] = []; $options['primary'] = [];
if (($createFlags&self::CREATE_INDEXES) > 0) { if (($createFlags & self::CREATE_INDEXES) > 0) {
foreach ($table->getIndexes() as $index) { foreach ($table->getIndexes() as $index) {
/** @var $index Index */ /** @var $index Index */
if ($index->isPrimary()) { if (! $index->isPrimary()) {
$options['primary'] = $index->getQuotedColumns($this);
$options['primary_index'] = $index;
} else {
$options['indexes'][$index->getQuotedName($this)] = $index; $options['indexes'][$index->getQuotedName($this)] = $index;
continue;
} }
$options['primary'] = $index->getQuotedColumns($this);
$options['primary_index'] = $index;
}
foreach ($table->getUniqueConstraints() as $uniqueConstraint) {
/** @var UniqueConstraint $uniqueConstraint */
$options['uniqueConstraints'][$uniqueConstraint->getQuotedName($this)] = $uniqueConstraint;
}
}
if (($createFlags & self::CREATE_FOREIGNKEYS) > 0) {
$options['foreignKeys'] = [];
foreach ($table->getForeignKeys() as $fkConstraint) {
$options['foreignKeys'][] = $fkConstraint;
} }
} }
...@@ -1551,7 +1567,6 @@ abstract class AbstractPlatform ...@@ -1551,7 +1567,6 @@ abstract class AbstractPlatform
foreach ($table->getColumns() as $column) { foreach ($table->getColumns() as $column) {
/** @var Column $column */ /** @var Column $column */
if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn)) { if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn)) {
$eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this); $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this);
$this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs); $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs);
...@@ -1579,13 +1594,6 @@ abstract class AbstractPlatform ...@@ -1579,13 +1594,6 @@ abstract class AbstractPlatform
$columns[$columnData['name']] = $columnData; $columns[$columnData['name']] = $columnData;
} }
if (($createFlags&self::CREATE_FOREIGNKEYS) > 0) {
$options['foreignKeys'] = [];
foreach ($table->getForeignKeys() as $fkConstraint) {
$options['foreignKeys'][] = $fkConstraint;
}
}
if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) { if ($this->_eventManager !== null && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) {
$eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this); $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this);
$this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs); $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs);
......
...@@ -893,10 +893,11 @@ class SqlitePlatform extends AbstractPlatform ...@@ -893,10 +893,11 @@ class SqlitePlatform extends AbstractPlatform
$sql = []; $sql = [];
$tableSql = []; $tableSql = [];
if (! $this->onSchemaAlterTable($diff, $tableSql)) { if (! $this->onSchemaAlterTable($diff, $tableSql)) {
$dataTable = new Table('__temp__' . $table->getName()); $dataTable = new Table('__temp__' . $table->getName());
$newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions()); $newTable = new Table($table->getQuotedName($this), $columns, $this->getPrimaryIndexInAlteredTable($diff), [], $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions());
$newTable->addOption('alter', true); $newTable->addOption('alter', true);
$sql = $this->getPreAlterTableIndexForeignKeySQL($diff); $sql = $this->getPreAlterTableIndexForeignKeySQL($diff);
......
...@@ -263,12 +263,14 @@ abstract class AbstractSchemaManager ...@@ -263,12 +263,14 @@ abstract class AbstractSchemaManager
{ {
$columns = $this->listTableColumns($tableName); $columns = $this->listTableColumns($tableName);
$foreignKeys = []; $foreignKeys = [];
if ($this->_platform->supportsForeignKeyConstraints()) { if ($this->_platform->supportsForeignKeyConstraints()) {
$foreignKeys = $this->listTableForeignKeys($tableName); $foreignKeys = $this->listTableForeignKeys($tableName);
} }
$indexes = $this->listTableIndexes($tableName); $indexes = $this->listTableIndexes($tableName);
return new Table($tableName, $columns, $indexes, $foreignKeys, false, []); return new Table($tableName, $columns, $indexes, [], $foreignKeys, false, []);
} }
/** /**
...@@ -587,6 +589,7 @@ abstract class AbstractSchemaManager ...@@ -587,6 +589,7 @@ abstract class AbstractSchemaManager
public function alterTable(TableDiff $tableDiff) public function alterTable(TableDiff $tableDiff)
{ {
$queries = $this->_platform->getAlterTableSQL($tableDiff); $queries = $this->_platform->getAlterTableSQL($tableDiff);
if (! is_array($queries) || ! count($queries)) { if (! is_array($queries) || ! count($queries)) {
return; return;
} }
......
...@@ -57,6 +57,7 @@ class Index extends AbstractAsset implements Constraint ...@@ -57,6 +57,7 @@ class Index extends AbstractAsset implements Constraint
$isUnique = $isUnique || $isPrimary; $isUnique = $isUnique || $isPrimary;
$this->_setName($indexName); $this->_setName($indexName);
$this->_isUnique = $isUnique; $this->_isUnique = $isUnique;
$this->_isPrimary = $isPrimary; $this->_isPrimary = $isPrimary;
$this->options = $options; $this->options = $options;
...@@ -64,6 +65,7 @@ class Index extends AbstractAsset implements Constraint ...@@ -64,6 +65,7 @@ class Index extends AbstractAsset implements Constraint
foreach ($columns as $column) { foreach ($columns as $column) {
$this->_addColumn($column); $this->_addColumn($column);
} }
foreach ($flags as $flag) { foreach ($flags as $flag) {
$this->addFlag($flag); $this->addFlag($flag);
} }
......
...@@ -18,7 +18,8 @@ class SchemaException extends DBALException ...@@ -18,7 +18,8 @@ class SchemaException extends DBALException
public const SEQUENCE_ALREADY_EXISTS = 80; public const SEQUENCE_ALREADY_EXISTS = 80;
public const INDEX_INVALID_NAME = 90; public const INDEX_INVALID_NAME = 90;
public const FOREIGNKEY_DOESNT_EXIST = 100; public const FOREIGNKEY_DOESNT_EXIST = 100;
public const NAMESPACE_ALREADY_EXISTS = 110; public const CONSTRAINT_DOESNT_EXIST = 110;
public const NAMESPACE_ALREADY_EXISTS = 120;
/** /**
* @param string $tableName * @param string $tableName
...@@ -142,6 +143,21 @@ class SchemaException extends DBALException ...@@ -142,6 +143,21 @@ class SchemaException extends DBALException
return new self("There exists no sequence with the name '" . $sequenceName . "'.", self::SEQUENCE_DOENST_EXIST); return new self("There exists no sequence with the name '" . $sequenceName . "'.", self::SEQUENCE_DOENST_EXIST);
} }
/**
* @param string $constraintName
* @param string $table
*
* @return self
*/
public static function uniqueConstraintDoesNotExist($constraintName, $table)
{
return new self(sprintf(
'There exists no unique constraint with the name "%s" on table "%s".',
$constraintName,
$table
), self::CONSTRAINT_DOESNT_EXIST);
}
/** /**
* @param string $fkName * @param string $fkName
* @param string $table * @param string $table
......
...@@ -7,7 +7,9 @@ use Doctrine\DBAL\Schema\Visitor\Visitor; ...@@ -7,7 +7,9 @@ use Doctrine\DBAL\Schema\Visitor\Visitor;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Type;
use const ARRAY_FILTER_USE_KEY; use const ARRAY_FILTER_USE_KEY;
use function array_filter; use function array_filter;
use function array_keys;
use function array_merge; use function array_merge;
use function array_unique;
use function in_array; use function in_array;
use function is_numeric; use function is_numeric;
use function is_string; use function is_string;
...@@ -35,6 +37,9 @@ class Table extends AbstractAsset ...@@ -35,6 +37,9 @@ class Table extends AbstractAsset
/** @var string */ /** @var string */
protected $_primaryKeyName = false; protected $_primaryKeyName = false;
/** @var UniqueConstraint[] */
protected $_uniqueConstraints = [];
/** @var ForeignKeyConstraint[] */ /** @var ForeignKeyConstraint[] */
protected $_fkConstraints = []; protected $_fkConstraints = [];
...@@ -48,14 +53,22 @@ class Table extends AbstractAsset ...@@ -48,14 +53,22 @@ class Table extends AbstractAsset
* @param string $tableName * @param string $tableName
* @param Column[] $columns * @param Column[] $columns
* @param Index[] $indexes * @param Index[] $indexes
* @param UniqueConstraint[] $uniqueConstraints
* @param ForeignKeyConstraint[] $fkConstraints * @param ForeignKeyConstraint[] $fkConstraints
* @param int $idGeneratorType * @param int $idGeneratorType
* @param mixed[] $options * @param mixed[] $options
* *
* @throws DBALException * @throws DBALException
*/ */
public function __construct($tableName, array $columns = [], array $indexes = [], array $fkConstraints = [], $idGeneratorType = 0, array $options = []) public function __construct(
{ $tableName,
array $columns = [],
array $indexes = [],
array $uniqueConstraints = [],
array $fkConstraints = [],
$idGeneratorType = 0,
array $options = []
) {
if (strlen($tableName) === 0) { if (strlen($tableName) === 0) {
throw DBALException::invalidTableName($tableName); throw DBALException::invalidTableName($tableName);
} }
...@@ -70,8 +83,12 @@ class Table extends AbstractAsset ...@@ -70,8 +83,12 @@ class Table extends AbstractAsset
$this->_addIndex($idx); $this->_addIndex($idx);
} }
foreach ($fkConstraints as $constraint) { foreach ($uniqueConstraints as $uniqueConstraint) {
$this->_addForeignKeyConstraint($constraint); $this->_addUniqueConstraint($uniqueConstraint);
}
foreach ($fkConstraints as $fkConstraint) {
$this->_addForeignKeyConstraint($fkConstraint);
} }
$this->_options = $options; $this->_options = $options;
...@@ -85,18 +102,6 @@ class Table extends AbstractAsset ...@@ -85,18 +102,6 @@ class Table extends AbstractAsset
$this->_schemaConfig = $schemaConfig; $this->_schemaConfig = $schemaConfig;
} }
/**
* @return int
*/
protected function _getMaxIdentifierLength()
{
if ($this->_schemaConfig instanceof SchemaConfig) {
return $this->_schemaConfig->getMaxIdentifierLength();
}
return 63;
}
/** /**
* Sets the Primary Key. * Sets the Primary Key.
* *
...@@ -117,6 +122,26 @@ class Table extends AbstractAsset ...@@ -117,6 +122,26 @@ class Table extends AbstractAsset
return $this; return $this;
} }
/**
* @param mixed[] $columnNames
* @param string|null $indexName
* @param mixed[] $options
*
* @return self
*/
public function addUniqueConstraint(array $columnNames, $indexName = null, array $options = [])
{
if ($indexName === null) {
$indexName = $this->_generateIdentifierName(
array_merge([$this->getName()], $columnNames),
'uniq',
$this->_getMaxIdentifierLength()
);
}
return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $options));
}
/** /**
* @param mixed[][] $columnNames * @param mixed[][] $columnNames
* @param string|null $indexName * @param string|null $indexName
...@@ -161,9 +186,11 @@ class Table extends AbstractAsset ...@@ -161,9 +186,11 @@ class Table extends AbstractAsset
public function dropIndex($indexName) public function dropIndex($indexName)
{ {
$indexName = $this->normalizeIdentifier($indexName); $indexName = $this->normalizeIdentifier($indexName);
if (! $this->hasIndex($indexName)) { if (! $this->hasIndex($indexName)) {
throw SchemaException::indexDoesNotExist($indexName, $this->_name); throw SchemaException::indexDoesNotExist($indexName, $this->_name);
} }
unset($this->_indexes[$indexName]); unset($this->_indexes[$indexName]);
} }
...@@ -252,37 +279,6 @@ class Table extends AbstractAsset ...@@ -252,37 +279,6 @@ class Table extends AbstractAsset
return false; return false;
} }
/**
* @param mixed[][] $columnNames
* @param string $indexName
* @param bool $isUnique
* @param bool $isPrimary
* @param string[] $flags
* @param mixed[] $options
*
* @return Index
*
* @throws SchemaException
*/
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
{
if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
throw SchemaException::indexNameInvalid($indexName);
}
foreach ($columnNames as $columnName => $indexColOptions) {
if (is_numeric($columnName) && is_string($indexColOptions)) {
$columnName = $indexColOptions;
}
if (! $this->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
}
}
return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
}
/** /**
* @param string $columnName * @param string $columnName
* @param string $typeName * @param string $typeName
...@@ -311,9 +307,11 @@ class Table extends AbstractAsset ...@@ -311,9 +307,11 @@ class Table extends AbstractAsset
*/ */
public function renameColumn($oldColumnName, $newColumnName) public function renameColumn($oldColumnName, $newColumnName)
{ {
throw new DBALException('Table#renameColumn() was removed, because it drops and recreates ' . throw new DBALException(
'the column instead. There is no fix available, because a schema diff cannot reliably detect if a ' . 'Table#renameColumn() was removed, because it drops and recreates the column instead. ' .
'column was renamed or one column was created and another one dropped.'); '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.'
);
} }
/** /**
...@@ -327,6 +325,7 @@ class Table extends AbstractAsset ...@@ -327,6 +325,7 @@ class Table extends AbstractAsset
public function changeColumn($columnName, array $options) public function changeColumn($columnName, array $options)
{ {
$column = $this->getColumn($columnName); $column = $this->getColumn($columnName);
$column->setOptions($options); $column->setOptions($options);
return $this; return $this;
...@@ -342,6 +341,7 @@ class Table extends AbstractAsset ...@@ -342,6 +341,7 @@ class Table extends AbstractAsset
public function dropColumn($columnName) public function dropColumn($columnName)
{ {
$columnName = $this->normalizeIdentifier($columnName); $columnName = $this->normalizeIdentifier($columnName);
unset($this->_columns[$columnName]); unset($this->_columns[$columnName]);
return $this; return $this;
...@@ -362,7 +362,13 @@ class Table extends AbstractAsset ...@@ -362,7 +362,13 @@ class Table extends AbstractAsset
*/ */
public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], $constraintName = null) public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options = [], $constraintName = null)
{ {
$constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array) $this->getName(), $localColumnNames), 'fk', $this->_getMaxIdentifierLength()); if (! $constraintName) {
$constraintName = $this->_generateIdentifierName(
array_merge((array) $this->getName(), $localColumnNames),
'fk',
$this->_getMaxIdentifierLength()
);
}
return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options); return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
} }
...@@ -424,9 +430,8 @@ class Table extends AbstractAsset ...@@ -424,9 +430,8 @@ class Table extends AbstractAsset
$name, $name,
$options $options
); );
$this->_addForeignKeyConstraint($constraint);
return $this; return $this->_addForeignKeyConstraint($constraint);
} }
/** /**
...@@ -443,137 +448,95 @@ class Table extends AbstractAsset ...@@ -443,137 +448,95 @@ class Table extends AbstractAsset
} }
/** /**
* @return void * Returns whether this table has a foreign key constraint with the given name.
* *
* @throws SchemaException * @param string $constraintName
*
* @return bool
*/ */
protected function _addColumn(Column $column) public function hasForeignKey($constraintName)
{ {
$columnName = $column->getName(); $constraintName = $this->normalizeIdentifier($constraintName);
$columnName = $this->normalizeIdentifier($columnName);
if (isset($this->_columns[$columnName])) {
throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
}
$this->_columns[$columnName] = $column; return isset($this->_fkConstraints[$constraintName]);
} }
/** /**
* Adds an index to the table. * Returns the foreign key constraint with the given name.
* *
* @return self * @param string $constraintName The constraint name.
* *
* @throws SchemaException * @return ForeignKeyConstraint
*
* @throws SchemaException If the foreign key does not exist.
*/ */
protected function _addIndex(Index $indexCandidate) public function getForeignKey($constraintName)
{ {
$indexName = $indexCandidate->getName(); $constraintName = $this->normalizeIdentifier($constraintName);
$indexName = $this->normalizeIdentifier($indexName);
$replacedImplicitIndexes = [];
foreach ($this->implicitIndexes as $name => $implicitIndex) {
if (! $implicitIndex->isFullfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
continue;
}
$replacedImplicitIndexes[] = $name;
}
if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
($this->_primaryKeyName !== false && $indexCandidate->isPrimary())
) {
throw SchemaException::indexAlreadyExists($indexName, $this->_name);
}
foreach ($replacedImplicitIndexes as $name) {
unset($this->_indexes[$name], $this->implicitIndexes[$name]);
}
if ($indexCandidate->isPrimary()) { if (! $this->hasForeignKey($constraintName)) {
$this->_primaryKeyName = $indexName; throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
} }
$this->_indexes[$indexName] = $indexCandidate; return $this->_fkConstraints[$constraintName];
return $this;
} }
/** /**
* Removes the foreign key constraint with the given name.
*
* @param string $constraintName The constraint name.
*
* @return void * @return void
*
* @throws SchemaException
*/ */
protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) public function removeForeignKey($constraintName)
{ {
$constraint->setLocalTable($this); $constraintName = $this->normalizeIdentifier($constraintName);
if (strlen($constraint->getName())) {
$name = $constraint->getName();
} else {
$name = $this->_generateIdentifierName(
array_merge((array) $this->getName(), $constraint->getLocalColumns()),
'fk',
$this->_getMaxIdentifierLength()
);
}
$name = $this->normalizeIdentifier($name);
$this->_fkConstraints[$name] = $constraint;
// add an explicit index on the foreign key columns. If there is already an index that fulfils this requirements drop the request.
// In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes
// lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns).
$indexName = $this->_generateIdentifierName(
array_merge([$this->getName()], $constraint->getColumns()),
'idx',
$this->_getMaxIdentifierLength()
);
$indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
foreach ($this->_indexes as $existingIndex) { if (! $this->hasForeignKey($constraintName)) {
if ($indexCandidate->isFullfilledBy($existingIndex)) { throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
return;
}
} }
$this->_addIndex($indexCandidate); unset($this->_fkConstraints[$constraintName]);
$this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
} }
/** /**
* Returns whether this table has a foreign key constraint with the given name. * Returns whether this table has a unique constraint with the given name.
* *
* @param string $constraintName * @param string $constraintName
* *
* @return bool * @return bool
*/ */
public function hasForeignKey($constraintName) public function hasUniqueConstraint($constraintName)
{ {
$constraintName = $this->normalizeIdentifier($constraintName); $constraintName = $this->normalizeIdentifier($constraintName);
return isset($this->_fkConstraints[$constraintName]); return isset($this->_uniqueConstraints[$constraintName]);
} }
/** /**
* Returns the foreign key constraint with the given name. * Returns the unique constraint with the given name.
* *
* @param string $constraintName The constraint name. * @param string $constraintName The constraint name.
* *
* @return ForeignKeyConstraint * @return UniqueConstraint
* *
* @throws SchemaException If the foreign key does not exist. * @throws SchemaException If the foreign key does not exist.
*/ */
public function getForeignKey($constraintName) public function getUniqueConstraint($constraintName)
{ {
$constraintName = $this->normalizeIdentifier($constraintName); $constraintName = $this->normalizeIdentifier($constraintName);
if (! $this->hasForeignKey($constraintName)) {
throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name); if (! $this->hasUniqueConstraint($constraintName)) {
throw SchemaException::uniqueConstraintDoesNotExist($constraintName, $this->_name);
} }
return $this->_fkConstraints[$constraintName]; return $this->_uniqueConstraints[$constraintName];
} }
/** /**
* Removes the foreign key constraint with the given name. * Removes the unique constraint with the given name.
* *
* @param string $constraintName The constraint name. * @param string $constraintName The constraint name.
* *
...@@ -581,14 +544,15 @@ class Table extends AbstractAsset ...@@ -581,14 +544,15 @@ class Table extends AbstractAsset
* *
* @throws SchemaException * @throws SchemaException
*/ */
public function removeForeignKey($constraintName) public function removeUniqueConstraint($constraintName)
{ {
$constraintName = $this->normalizeIdentifier($constraintName); $constraintName = $this->normalizeIdentifier($constraintName);
if (! $this->hasForeignKey($constraintName)) {
throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name); if (! $this->hasUniqueConstraint($constraintName)) {
throw SchemaException::uniqueConstraintDoesNotExist($constraintName, $this->_name);
} }
unset($this->_fkConstraints[$constraintName]); unset($this->_uniqueConstraints[$constraintName]);
} }
/** /**
...@@ -598,27 +562,19 @@ class Table extends AbstractAsset ...@@ -598,27 +562,19 @@ class Table extends AbstractAsset
*/ */
public function getColumns() public function getColumns()
{ {
$primaryKeyColumns = []; $pkCols = [];
$fkCols = [];
if ($this->hasPrimaryKey()) { if ($this->hasPrimaryKey()) {
$primaryKeyColumns = $this->filterColumns($this->getPrimaryKey()->getColumns()); $pkCols = $this->filterColumns($this->getPrimaryKey()->getColumns());
} }
return array_merge($primaryKeyColumns, $this->getForeignKeyColumns(), $this->_columns); foreach ($this->getForeignKeys() as $fk) {
} /** @var ForeignKeyConstraint $fk */
$fkCols = array_merge($fkCols, $fk->getColumns());
/**
* Returns foreign key columns
*
* @return Column[]
*/
private function getForeignKeyColumns()
{
$foreignKeyColumns = [];
foreach ($this->getForeignKeys() as $foreignKey) {
/** @var ForeignKeyConstraint $foreignKey */
$foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getColumns());
} }
return $this->filterColumns($foreignKeyColumns);
return array_unique(array_merge($pkCols, $fkCols, array_keys($this->_columns)));
} }
/** /**
...@@ -661,6 +617,7 @@ class Table extends AbstractAsset ...@@ -661,6 +617,7 @@ class Table extends AbstractAsset
public function getColumn($columnName) public function getColumn($columnName)
{ {
$columnName = $this->normalizeIdentifier($columnName); $columnName = $this->normalizeIdentifier($columnName);
if (! $this->hasColumn($columnName)) { if (! $this->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $this->_name); throw SchemaException::columnDoesNotExist($columnName, $this->_name);
} }
...@@ -675,11 +632,9 @@ class Table extends AbstractAsset ...@@ -675,11 +632,9 @@ class Table extends AbstractAsset
*/ */
public function getPrimaryKey() public function getPrimaryKey()
{ {
if (! $this->hasPrimaryKey()) { return $this->hasPrimaryKey()
return null; ? $this->getIndex($this->_primaryKeyName)
} : null;
return $this->getIndex($this->_primaryKeyName);
} }
/** /**
...@@ -733,6 +688,7 @@ class Table extends AbstractAsset ...@@ -733,6 +688,7 @@ class Table extends AbstractAsset
public function getIndex($indexName) public function getIndex($indexName)
{ {
$indexName = $this->normalizeIdentifier($indexName); $indexName = $this->normalizeIdentifier($indexName);
if (! $this->hasIndex($indexName)) { if (! $this->hasIndex($indexName)) {
throw SchemaException::indexDoesNotExist($indexName, $this->_name); throw SchemaException::indexDoesNotExist($indexName, $this->_name);
} }
...@@ -748,6 +704,16 @@ class Table extends AbstractAsset ...@@ -748,6 +704,16 @@ class Table extends AbstractAsset
return $this->_indexes; return $this->_indexes;
} }
/**
* Returns the unique constraints.
*
* @return UniqueConstraint[]
*/
public function getUniqueConstraints()
{
return $this->_uniqueConstraints;
}
/** /**
* Returns the foreign key constraints. * Returns the foreign key constraints.
* *
...@@ -816,15 +782,166 @@ class Table extends AbstractAsset ...@@ -816,15 +782,166 @@ class Table extends AbstractAsset
foreach ($this->_columns as $k => $column) { foreach ($this->_columns as $k => $column) {
$this->_columns[$k] = clone $column; $this->_columns[$k] = clone $column;
} }
foreach ($this->_indexes as $k => $index) { foreach ($this->_indexes as $k => $index) {
$this->_indexes[$k] = clone $index; $this->_indexes[$k] = clone $index;
} }
foreach ($this->_fkConstraints as $k => $fk) { foreach ($this->_fkConstraints as $k => $fk) {
$this->_fkConstraints[$k] = clone $fk; $this->_fkConstraints[$k] = clone $fk;
$this->_fkConstraints[$k]->setLocalTable($this); $this->_fkConstraints[$k]->setLocalTable($this);
} }
} }
/**
* @return int
*/
protected function _getMaxIdentifierLength()
{
return $this->_schemaConfig instanceof SchemaConfig
? $this->_schemaConfig->getMaxIdentifierLength()
: 63;
}
/**
* @return void
*
* @throws SchemaException
*/
protected function _addColumn(Column $column)
{
$columnName = $column->getName();
$columnName = $this->normalizeIdentifier($columnName);
if (isset($this->_columns[$columnName])) {
throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
}
$this->_columns[$columnName] = $column;
}
/**
* Adds an index to the table.
*
* @return self
*
* @throws SchemaException
*/
protected function _addIndex(Index $indexCandidate)
{
$indexName = $indexCandidate->getName();
$indexName = $this->normalizeIdentifier($indexName);
$replacedImplicitIndexes = [];
foreach ($this->implicitIndexes as $name => $implicitIndex) {
if (! $implicitIndex->isFullfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) {
continue;
}
$replacedImplicitIndexes[] = $name;
}
if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
($this->_primaryKeyName !== false && $indexCandidate->isPrimary())
) {
throw SchemaException::indexAlreadyExists($indexName, $this->_name);
}
foreach ($replacedImplicitIndexes as $name) {
unset($this->_indexes[$name], $this->implicitIndexes[$name]);
}
if ($indexCandidate->isPrimary()) {
$this->_primaryKeyName = $indexName;
}
$this->_indexes[$indexName] = $indexCandidate;
return $this;
}
/**
* @return self
*/
protected function _addUniqueConstraint(UniqueConstraint $uniqueConstraint)
{
$name = strlen($uniqueConstraint->getName())
? $uniqueConstraint->getName()
: $this->_generateIdentifierName(
array_merge((array) $this->getName(), $uniqueConstraint->getLocalColumns()),
'fk',
$this->_getMaxIdentifierLength()
);
$name = $this->normalizeIdentifier($name);
$this->_uniqueConstraints[$name] = $uniqueConstraint;
// If there is already an index that fulfills this requirements drop the request. In the case of __construct
// calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
// This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
$indexName = $this->_generateIdentifierName(
array_merge([$this->getName()], $uniqueConstraint->getColumns()),
'idx',
$this->_getMaxIdentifierLength()
);
$indexCandidate = $this->_createIndex($uniqueConstraint->getColumns(), $indexName, true, false);
foreach ($this->_indexes as $existingIndex) {
if ($indexCandidate->isFullfilledBy($existingIndex)) {
return $this;
}
}
$this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
return $this;
}
/**
* @return self
*/
protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
{
$constraint->setLocalTable($this);
$name = strlen($constraint->getName())
? $constraint->getName()
: $this->_generateIdentifierName(
array_merge((array) $this->getName(), $constraint->getLocalColumns()),
'fk',
$this->_getMaxIdentifierLength()
);
$name = $this->normalizeIdentifier($name);
$this->_fkConstraints[$name] = $constraint;
// add an explicit index on the foreign key columns.
// If there is already an index that fulfills this requirements drop the request. In the case of __construct
// calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates.
// This creates computation overhead in this case, however no duplicate indexes are ever added (column based).
$indexName = $this->_generateIdentifierName(
array_merge([$this->getName()], $constraint->getColumns()),
'idx',
$this->_getMaxIdentifierLength()
);
$indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
foreach ($this->_indexes as $existingIndex) {
if ($indexCandidate->isFullfilledBy($existingIndex)) {
return $this;
}
}
$this->_addIndex($indexCandidate);
$this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
return $this;
}
/** /**
* Normalizes a given identifier. * Normalizes a given identifier.
* *
...@@ -838,4 +955,63 @@ class Table extends AbstractAsset ...@@ -838,4 +955,63 @@ class Table extends AbstractAsset
{ {
return $this->trimQuotes(strtolower($identifier)); return $this->trimQuotes(strtolower($identifier));
} }
/**
* @param mixed[] $columnNames
* @param string $indexName
* @param mixed[] $options
*
* @return UniqueConstraint
*
* @throws SchemaException
*/
private function _createUniqueConstraint(array $columnNames, $indexName, array $options = [])
{
if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
throw SchemaException::indexNameInvalid($indexName);
}
foreach ($columnNames as $columnName => $indexColOptions) {
if (is_numeric($columnName) && is_string($indexColOptions)) {
$columnName = $indexColOptions;
}
if (! $this->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
}
}
return new UniqueConstraint($indexName, $columnNames, $options);
}
/**
* @param mixed[] $columnNames
* @param string $indexName
* @param bool $isUnique
* @param bool $isPrimary
* @param string[] $flags
* @param mixed[] $options
*
* @return Index
*
* @throws SchemaException
*/
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
{
if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
throw SchemaException::indexNameInvalid($indexName);
}
foreach ($columnNames as $columnName => $indexColOptions) {
if (is_numeric($columnName) && is_string($indexColOptions)) {
$columnName = $indexColOptions;
}
if (! $this->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
}
}
return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
}
} }
<?php
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use InvalidArgumentException;
use function array_keys;
use function array_map;
use function is_string;
use function strtolower;
/**
* Class for a unique constraint.
*/
class UniqueConstraint extends AbstractAsset implements Constraint
{
/**
* Asset identifier instances of the column names the unique constraint is associated with.
* array($columnName => Identifier)
*
* @var Identifier[]
*/
protected $columns = [];
/**
* Platform specific options
*
* @var mixed[]
*/
private $options = [];
/**
* @param string $indexName
* @param string[] $columns
* @param mixed[] $options
*/
public function __construct($indexName, array $columns, array $options = [])
{
$this->_setName($indexName);
$this->options = $options;
foreach ($columns as $column) {
$this->_addColumn($column);
}
}
/**
* @param string $column
*
* @return void
*
* @throws InvalidArgumentException
*/
protected function _addColumn($column)
{
if (! is_string($column)) {
throw new InvalidArgumentException('Expecting a string as Index Column');
}
$this->_columns[$column] = new Identifier($column);
}
/**
* {@inheritdoc}
*/
public function getColumns()
{
return array_keys($this->_columns);
}
/**
* {@inheritdoc}
*/
public function getQuotedColumns(AbstractPlatform $platform)
{
$columns = [];
foreach ($this->_columns as $column) {
$columns[] = $column->getQuotedName($platform);
}
return $columns;
}
/**
* @return string[]
*/
public function getUnquotedColumns()
{
return array_map([$this, 'trimQuotes'], $this->getColumns());
}
/**
* @param string $name
*
* @return bool
*/
public function hasOption($name)
{
return isset($this->options[strtolower($name)]);
}
/**
* @param string $name
*
* @return mixed
*/
public function getOption($name)
{
return $this->options[strtolower($name)];
}
/**
* @return mixed[]
*/
public function getOptions()
{
return $this->options;
}
}
...@@ -314,6 +314,7 @@ class ExceptionTest extends DbalFunctionalTestCase ...@@ -314,6 +314,7 @@ class ExceptionTest extends DbalFunctionalTestCase
$table->addColumn('id', 'integer'); $table->addColumn('id', 'integer');
$this->expectException($exceptionClass); $this->expectException($exceptionClass);
foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) { foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
$conn->exec($sql); $conn->exec($sql);
} }
......
...@@ -976,7 +976,7 @@ class SchemaManagerFunctionalTestCase extends DbalFunctionalTestCase ...@@ -976,7 +976,7 @@ class SchemaManagerFunctionalTestCase extends DbalFunctionalTestCase
protected function getTestTable($name, $options = []) protected function getTestTable($name, $options = [])
{ {
$table = new Table($name, [], [], [], false, $options); $table = new Table($name, [], [], [], [], false, $options);
$table->setSchemaConfig($this->schemaManager->createSchemaConfig()); $table->setSchemaConfig($this->schemaManager->createSchemaConfig());
$table->addColumn('id', 'integer', ['notnull' => true]); $table->addColumn('id', 'integer', ['notnull' => true]);
$table->setPrimaryKey(['id']); $table->setPrimaryKey(['id']);
...@@ -987,7 +987,7 @@ class SchemaManagerFunctionalTestCase extends DbalFunctionalTestCase ...@@ -987,7 +987,7 @@ class SchemaManagerFunctionalTestCase extends DbalFunctionalTestCase
protected function getTestCompositeTable($name) protected function getTestCompositeTable($name)
{ {
$table = new Table($name, [], [], [], false, []); $table = new Table($name, [], [], [], [], false, []);
$table->setSchemaConfig($this->schemaManager->createSchemaConfig()); $table->setSchemaConfig($this->schemaManager->createSchemaConfig());
$table->addColumn('id', 'integer', ['notnull' => true]); $table->addColumn('id', 'integer', ['notnull' => true]);
$table->addColumn('other_id', 'integer', ['notnull' => true]); $table->addColumn('other_id', 'integer', ['notnull' => true]);
...@@ -999,14 +999,17 @@ class SchemaManagerFunctionalTestCase extends DbalFunctionalTestCase ...@@ -999,14 +999,17 @@ class SchemaManagerFunctionalTestCase extends DbalFunctionalTestCase
protected function assertHasTable($tables, $tableName) protected function assertHasTable($tables, $tableName)
{ {
$foundTable = false; $foundTable = false;
foreach ($tables as $table) { foreach ($tables as $table) {
self::assertInstanceOf(Table::class, $table, 'No Table instance was found in tables array.'); self::assertInstanceOf(Table::class, $table, 'No Table instance was found in tables array.');
if (strtolower($table->getName()) !== 'list_tables_test_new_name') { if (strtolower($table->getName()) !== 'list_tables_test_new_name') {
continue; continue;
} }
$foundTable = true; $foundTable = true;
} }
self::assertTrue($foundTable, 'Could not find new table'); self::assertTrue($foundTable, 'Could not find new table');
} }
......
...@@ -1272,6 +1272,7 @@ class ComparatorTest extends TestCase ...@@ -1272,6 +1272,7 @@ class ComparatorTest extends TestCase
'id_table1' => new Column('id_table1', Type::getType('integer')), 'id_table1' => new Column('id_table1', Type::getType('integer')),
], ],
[], [],
[],
[ [
new ForeignKeyConstraint(['id_table1'], 'table1', ['id'], 'fk_table2_table1'), new ForeignKeyConstraint(['id_table1'], 'table1', ['id'], 'fk_table2_table1'),
] ]
...@@ -1285,6 +1286,7 @@ class ComparatorTest extends TestCase ...@@ -1285,6 +1286,7 @@ class ComparatorTest extends TestCase
'id_table3' => new Column('id_table3', Type::getType('integer')), 'id_table3' => new Column('id_table3', Type::getType('integer')),
], ],
[], [],
[],
[ [
new ForeignKeyConstraint(['id_table3'], 'table3', ['id'], 'fk_table2_table3'), new ForeignKeyConstraint(['id_table3'], 'table3', ['id'], 'fk_table2_table3'),
] ]
......
...@@ -200,7 +200,7 @@ class TableTest extends DbalTestCase ...@@ -200,7 +200,7 @@ class TableTest extends DbalTestCase
{ {
$constraint = new ForeignKeyConstraint([], 'foo', []); $constraint = new ForeignKeyConstraint([], 'foo', []);
$tableA = new Table('foo', [], [], [$constraint]); $tableA = new Table('foo', [], [], [], [$constraint]);
$constraints = $tableA->getForeignKeys(); $constraints = $tableA->getForeignKeys();
self::assertCount(1, $constraints); self::assertCount(1, $constraints);
...@@ -209,7 +209,7 @@ class TableTest extends DbalTestCase ...@@ -209,7 +209,7 @@ class TableTest extends DbalTestCase
public function testOptions() public function testOptions()
{ {
$table = new Table('foo', [], [], [], false, ['foo' => 'bar']); $table = new Table('foo', [], [], [], [], false, ['foo' => 'bar']);
self::assertTrue($table->hasOption('foo')); self::assertTrue($table->hasOption('foo'));
self::assertEquals('bar', $table->getOption('foo')); self::assertEquals('bar', $table->getOption('foo'));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment