diff --git a/src/Db/Adapter/MysqlAdapter.php b/src/Db/Adapter/MysqlAdapter.php index 01dfe8c1..694f821b 100644 --- a/src/Db/Adapter/MysqlAdapter.php +++ b/src/Db/Adapter/MysqlAdapter.php @@ -29,6 +29,11 @@ */ class MysqlAdapter extends AbstractAdapter { + /** + * Maximum length for identifiers (table names, column names, constraint names, etc.) + */ + protected const IDENTIFIER_MAX_LENGTH = 64; + /** * @var string[] */ @@ -1197,7 +1202,7 @@ protected function getIndexSqlDefinition(Index $index): string */ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $tableName): string { - $constraintName = $foreignKey->getName() ?: ($tableName . '_' . implode('_', $foreignKey->getColumns())); + $constraintName = $foreignKey->getName() ?: $this->getUniqueForeignKeyName($tableName, $foreignKey->getColumns()); $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName); $columnNames = []; foreach ($foreignKey->getColumns() as $column) { @@ -1221,6 +1226,35 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta return $def; } + /** + * Generate a unique foreign key constraint name. + * + * @param string $tableName Table name + * @param array $columns Column names + * @return string + */ + protected function getUniqueForeignKeyName(string $tableName, array $columns): string + { + $baseName = $tableName . '_' . implode('_', $columns); + $maxLength = static::IDENTIFIER_MAX_LENGTH - 3; + if (strlen($baseName) > $maxLength) { + $baseName = substr($baseName, 0, $maxLength); + } + $existingKeys = $this->getForeignKeys($tableName); + $existingNames = array_column($existingKeys, 'name'); + + if (!in_array($baseName, $existingNames, true)) { + return $baseName; + } + + $counter = 2; + while (in_array($baseName . '_' . $counter, $existingNames, true)) { + $counter++; + } + + return $baseName . '_' . $counter; + } + /** * Returns MySQL column types (inherited and MySQL specified). * diff --git a/src/Db/Adapter/PostgresAdapter.php b/src/Db/Adapter/PostgresAdapter.php index 878f81ee..b0e61d22 100644 --- a/src/Db/Adapter/PostgresAdapter.php +++ b/src/Db/Adapter/PostgresAdapter.php @@ -27,6 +27,11 @@ class PostgresAdapter extends AbstractAdapter { + /** + * Maximum length for identifiers (table names, column names, constraint names, etc.) + */ + protected const IDENTIFIER_MAX_LENGTH = 63; + public const GENERATED_ALWAYS = 'ALWAYS'; public const GENERATED_BY_DEFAULT = 'BY DEFAULT'; /** @@ -949,11 +954,7 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin */ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $tableName): string { - $parts = $this->getSchemaName($tableName); - - $constraintName = $foreignKey->getName() ?: ( - $parts['table'] . '_' . implode('_', $foreignKey->getColumns()) . '_fkey' - ); + $constraintName = $foreignKey->getName() ?: $this->getUniqueForeignKeyName($tableName, $foreignKey->getColumns()); $columnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getColumns())); $refColumnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getReferencedColumns())); $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName) . @@ -972,6 +973,36 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta return $def; } + /** + * Generate a unique foreign key constraint name. + * + * @param string $tableName Table name + * @param array $columns Column names + * @return string + */ + protected function getUniqueForeignKeyName(string $tableName, array $columns): string + { + $parts = $this->getSchemaName($tableName); + $baseName = $parts['table'] . '_' . implode('_', $columns) . '_fkey'; + $maxLength = static::IDENTIFIER_MAX_LENGTH - 3; + if (strlen($baseName) > $maxLength) { + $baseName = substr($baseName, 0, $maxLength); + } + $existingKeys = $this->getForeignKeys($tableName); + $existingNames = array_column($existingKeys, 'name'); + + if (!in_array($baseName, $existingNames, true)) { + return $baseName; + } + + $counter = 2; + while (in_array($baseName . '_' . $counter, $existingNames, true)) { + $counter++; + } + + return $baseName . '_' . $counter; + } + /** * @inheritDoc */ diff --git a/src/Db/Adapter/SqliteAdapter.php b/src/Db/Adapter/SqliteAdapter.php index f78ff76b..82a3812d 100644 --- a/src/Db/Adapter/SqliteAdapter.php +++ b/src/Db/Adapter/SqliteAdapter.php @@ -1675,7 +1675,7 @@ public function getColumnTypes(): array */ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $tableName): string { - $constraintName = $foreignKey->getName() ?: ($tableName . '_' . implode('_', $foreignKey->getColumns())); + $constraintName = $foreignKey->getName() ?: $this->getUniqueForeignKeyName($tableName, $foreignKey->getColumns()); $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName); $columnNames = []; foreach ($foreignKey->getColumns() as $column) { @@ -1697,6 +1697,31 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta return $def; } + /** + * Generate a unique foreign key constraint name. + * + * @param string $tableName Table name + * @param array $columns Column names + * @return string + */ + protected function getUniqueForeignKeyName(string $tableName, array $columns): string + { + $baseName = $tableName . '_' . implode('_', $columns); + $existingKeys = $this->getForeignKeys($tableName); + $existingNames = array_column($existingKeys, 'name'); + + if (!in_array($baseName, $existingNames, true)) { + return $baseName; + } + + $counter = 2; + while (in_array($baseName . '_' . $counter, $existingNames, true)) { + $counter++; + } + + return $baseName . '_' . $counter; + } + /** * @inheritDoc */ diff --git a/src/Db/Adapter/SqlserverAdapter.php b/src/Db/Adapter/SqlserverAdapter.php index e804d590..67f08c00 100644 --- a/src/Db/Adapter/SqlserverAdapter.php +++ b/src/Db/Adapter/SqlserverAdapter.php @@ -28,6 +28,11 @@ */ class SqlserverAdapter extends AbstractAdapter { + /** + * Maximum length for identifiers (table names, column names, constraint names, etc.) + */ + protected const IDENTIFIER_MAX_LENGTH = 128; + /** * @var string[] */ @@ -864,7 +869,7 @@ protected function getIndexSqlDefinition(Index $index, string $tableName): strin */ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $tableName): string { - $constraintName = $foreignKey->getName() ?: $tableName . '_' . implode('_', $foreignKey->getColumns()); + $constraintName = $foreignKey->getName() ?: $this->getUniqueForeignKeyName($tableName, $foreignKey->getColumns()); $columnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getColumns())); $refColumnList = implode(', ', array_map($this->quoteColumnName(...), $foreignKey->getReferencedColumns())); @@ -881,6 +886,35 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta return $def; } + /** + * Generate a unique foreign key constraint name. + * + * @param string $tableName Table name + * @param array $columns Column names + * @return string + */ + protected function getUniqueForeignKeyName(string $tableName, array $columns): string + { + $baseName = $tableName . '_' . implode('_', $columns); + $maxLength = static::IDENTIFIER_MAX_LENGTH - 3; + if (strlen($baseName) > $maxLength) { + $baseName = substr($baseName, 0, $maxLength); + } + $existingKeys = $this->getForeignKeys($tableName); + $existingNames = array_column($existingKeys, 'name'); + + if (!in_array($baseName, $existingNames, true)) { + return $baseName; + } + + $counter = 2; + while (in_array($baseName . '_' . $counter, $existingNames, true)) { + $counter++; + } + + return $baseName . '_' . $counter; + } + /** * Creates the specified schema. *