diff --git a/.github/workflows/website-schema.yml b/.github/workflows/website-schema.yml new file mode 100644 index 0000000000..e251588e5d --- /dev/null +++ b/.github/workflows/website-schema.yml @@ -0,0 +1,21 @@ + +name: "Website config validation" + +on: + pull_request: + branches: + - "*.x" + paths: + - ".doctrine-project.json" + - ".github/workflows/website-schema.yml" + push: + branches: + - "*.x" + paths: + - ".doctrine-project.json" + - ".github/workflows/website-schema.yml" + +jobs: + json-validate: + name: "Validate JSON schema" + uses: "doctrine/.github/.github/workflows/website-schema.yml@7.1.0" diff --git a/UPGRADE.md b/UPGRADE.md index 1ee3467175..1b1c83a7c3 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -112,7 +112,8 @@ The `Sequence::isAutoIncrementsFor()` method has been deprecated. ## Deprecated using invalid database object names -Using the following objects with an empty name is deprecated: `Column`, `View`, `Sequence`, `Identifier`. +Using the following objects with an empty name is deprecated: `Table`, `Column`, `Index`, `View`, `Sequence`, +`Identifier`. Using the following objects with a qualified name is deprecated: `Column`, `ForeignKeyConstraint`, `Index`, `Schema`, `UniqueConstraint`. If the object name contains a dot, the name should be quoted. diff --git a/src/Platforms/AbstractMySQLPlatform.php b/src/Platforms/AbstractMySQLPlatform.php index 5b70a14ae9..d44975e4bf 100644 --- a/src/Platforms/AbstractMySQLPlatform.php +++ b/src/Platforms/AbstractMySQLPlatform.php @@ -230,21 +230,21 @@ protected function _getCreateTableSQL(string $name, array $columns, array $optio { $queryFields = $this->getColumnDeclarationListSQL($columns); - if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + if (! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition); } } // add all indexes - if (isset($options['indexes']) && ! empty($options['indexes'])) { + if (! empty($options['indexes'])) { foreach ($options['indexes'] as $definition) { $queryFields .= ', ' . $this->getIndexDeclarationSQL($definition); } } // attach all primary keys - if (isset($options['primary']) && ! empty($options['primary'])) { + if (! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index ba55e6862b..be9cb5dffd 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -809,7 +809,7 @@ private function buildCreateTableSQL(Table $table, bool $createForeignKeys): arr foreach ($table->getIndexes() as $index) { if (! $index->isPrimary()) { - $options['indexes'][$index->getQuotedName($this)] = $index; + $options['indexes'][] = $index; continue; } @@ -819,7 +819,7 @@ private function buildCreateTableSQL(Table $table, bool $createForeignKeys): arr } foreach ($table->getUniqueConstraints() as $uniqueConstraint) { - $options['uniqueConstraints'][$uniqueConstraint->getQuotedName($this)] = $uniqueConstraint; + $options['uniqueConstraints'][] = $uniqueConstraint; } if ($createForeignKeys) { @@ -961,17 +961,17 @@ protected function _getCreateTableSQL(string $name, array $columns, array $optio { $columnListSql = $this->getColumnDeclarationListSQL($columns); - if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + if (! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition); } } - if (isset($options['primary']) && ! empty($options['primary'])) { + if (! empty($options['primary'])) { $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; } - if (isset($options['indexes']) && ! empty($options['indexes'])) { + if (! empty($options['indexes'])) { foreach ($options['indexes'] as $definition) { $columnListSql .= ', ' . $this->getIndexDeclarationSQL($definition); } @@ -1147,8 +1147,13 @@ public function getCreateSchemaSQL(string $schemaName): string */ public function getCreateUniqueConstraintSQL(UniqueConstraint $constraint, string $tableName): string { - return 'ALTER TABLE ' . $tableName . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this) . ' UNIQUE' - . ' (' . implode(', ', $constraint->getQuotedColumns($this)) . ')'; + $sql = 'ALTER TABLE ' . $tableName . ' ADD'; + + if ($constraint->getName() !== '') { + $sql .= ' CONSTRAINT ' . $constraint->getQuotedName($this); + } + + return $sql . ' UNIQUE (' . implode(', ', $constraint->getQuotedColumns($this)) . ')'; } /** @@ -1491,9 +1496,10 @@ public function getUniqueConstraintDeclarationSQL(UniqueConstraint $constraint): throw new InvalidArgumentException('Incomplete definition. "columns" required.'); } - $chunks = ['CONSTRAINT']; + $chunks = []; if ($constraint->getName() !== '') { + $chunks[] = 'CONSTRAINT'; $chunks[] = $constraint->getQuotedName($this); } diff --git a/src/Platforms/PostgreSQLPlatform.php b/src/Platforms/PostgreSQLPlatform.php index a860c6cafc..84fd5aa791 100644 --- a/src/Platforms/PostgreSQLPlatform.php +++ b/src/Platforms/PostgreSQLPlatform.php @@ -379,7 +379,7 @@ protected function _getCreateTableSQL(string $name, array $columns, array $optio { $queryFields = $this->getColumnDeclarationListSQL($columns); - if (isset($options['primary']) && ! empty($options['primary'])) { + if (! empty($options['primary'])) { $keyColumns = array_unique(array_values($options['primary'])); $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; } @@ -390,7 +390,7 @@ protected function _getCreateTableSQL(string $name, array $columns, array $optio $sql = [$query]; - if (isset($options['indexes']) && ! empty($options['indexes'])) { + if (! empty($options['indexes'])) { foreach ($options['indexes'] as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } diff --git a/src/Platforms/SQLServerPlatform.php b/src/Platforms/SQLServerPlatform.php index 80192ba748..fadbf70bd3 100644 --- a/src/Platforms/SQLServerPlatform.php +++ b/src/Platforms/SQLServerPlatform.php @@ -204,13 +204,13 @@ protected function _getCreateTableSQL(string $name, array $columns, array $optio $columnListSql = $this->getColumnDeclarationListSQL($columns); - if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + if (! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $definition) { $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition); } } - if (isset($options['primary']) && ! empty($options['primary'])) { + if (! empty($options['primary'])) { $flags = ''; if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) { $flags = ' NONCLUSTERED'; @@ -231,7 +231,7 @@ protected function _getCreateTableSQL(string $name, array $columns, array $optio $sql = [$query]; - if (isset($options['indexes']) && ! empty($options['indexes'])) { + if (! empty($options['indexes'])) { foreach ($options['indexes'] as $index) { $sql[] = $this->getCreateIndexSQL($index, $name); } diff --git a/src/Platforms/SQLitePlatform.php b/src/Platforms/SQLitePlatform.php index 6827804bef..1789b32bba 100644 --- a/src/Platforms/SQLitePlatform.php +++ b/src/Platforms/SQLitePlatform.php @@ -269,7 +269,7 @@ protected function _getCreateTableSQL(string $name, array $columns, array $optio { $queryFields = $this->getColumnDeclarationListSQL($columns); - if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + if (! empty($options['uniqueConstraints'])) { foreach ($options['uniqueConstraints'] as $definition) { $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition); } @@ -296,13 +296,13 @@ protected function _getCreateTableSQL(string $name, array $columns, array $optio return $query; } - if (isset($options['indexes']) && ! empty($options['indexes'])) { + if (! empty($options['indexes'])) { foreach ($options['indexes'] as $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } } - if (isset($options['unique']) && ! empty($options['unique'])) { + if (! empty($options['unique'])) { foreach ($options['unique'] as $indexDef) { $query[] = $this->getCreateIndexSQL($indexDef, $name); } diff --git a/src/Schema/Index.php b/src/Schema/Index.php index 28a7469bec..591b1ac0f8 100644 --- a/src/Schema/Index.php +++ b/src/Schema/Index.php @@ -17,8 +17,8 @@ use function count; use function strtolower; -/** @extends AbstractOptionallyNamedObject */ -class Index extends AbstractOptionallyNamedObject +/** @extends AbstractNamedObject */ +class Index extends AbstractNamedObject { /** * Asset identifier instances of the column names the index is associated with. @@ -51,7 +51,7 @@ public function __construct( array $flags = [], private readonly array $options = [], ) { - parent::__construct($name); + parent::__construct($name ?? ''); $this->_isUnique = $isUnique || $isPrimary; $this->_isPrimary = $isPrimary; diff --git a/tests/Functional/Schema/ForeignKeyConstraintTest.php b/tests/Functional/Schema/ForeignKeyConstraintTest.php new file mode 100644 index 0000000000..49e57c299f --- /dev/null +++ b/tests/Functional/Schema/ForeignKeyConstraintTest.php @@ -0,0 +1,49 @@ +dropTableIfExists('users'); + $this->dropTableIfExists('roles'); + $this->dropTableIfExists('teams'); + + $roles = new Table('roles'); + $roles->addColumn('id', Types::INTEGER); + $roles->setPrimaryKey(['id']); + + $teams = new Table('teams'); + $teams->addColumn('id', Types::INTEGER); + $teams->setPrimaryKey(['id']); + + $users = new Table('users', [ + new Column('id', Type::getType(Types::INTEGER)), + new Column('role_id', Type::getType(Types::INTEGER)), + new Column('team_id', Type::getType(Types::INTEGER)), + ], [], [], [ + new ForeignKeyConstraint(['role_id'], 'roles', ['id']), + new ForeignKeyConstraint(['team_id'], 'teams', ['id']), + ]); + $users->setPrimaryKey(['id']); + + $sm = $this->connection->createSchemaManager(); + $sm->createTable($roles); + $sm->createTable($teams); + $sm->createTable($users); + + $table = $sm->introspectTable('users'); + + self::assertCount(2, $table->getForeignKeys()); + } +} diff --git a/tests/Functional/Schema/UniqueConstraintTest.php b/tests/Functional/Schema/UniqueConstraintTest.php new file mode 100644 index 0000000000..3c06427eb6 --- /dev/null +++ b/tests/Functional/Schema/UniqueConstraintTest.php @@ -0,0 +1,36 @@ +dropTableIfExists('users'); + + $users = new Table('users', [ + new Column('id', Type::getType(Types::INTEGER)), + new Column('username', Type::getType(Types::STRING), ['length' => 32]), + new Column('email', Type::getType(Types::STRING), ['length' => 255]), + ], [], [ + new UniqueConstraint('', ['username']), + new UniqueConstraint('', ['email']), + ], []); + + $sm = $this->connection->createSchemaManager(); + $sm->createTable($users); + + // we want to assert that the two empty names don't clash, but introspection of unique constraints is currently + // not supported. for now, we just assert that the table can be created without exceptions. + $this->expectNotToPerformAssertions(); + } +} diff --git a/tests/Schema/IndexTest.php b/tests/Schema/IndexTest.php index 0eb13502be..4538d55fdb 100644 --- a/tests/Schema/IndexTest.php +++ b/tests/Schema/IndexTest.php @@ -4,14 +4,17 @@ namespace Doctrine\DBAL\Tests\Schema; -use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Schema\Exception\InvalidName; use Doctrine\DBAL\Schema\Index; use Doctrine\DBAL\Schema\Name\Identifier; +use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class IndexTest extends TestCase { + use VerifyDeprecations; + /** @param mixed[] $options */ private function createIndex(bool $unique = false, bool $primary = false, array $options = []): Index { @@ -174,21 +177,24 @@ public function testOptions(): void self::assertSame(['where' => 'name IS NULL'], $idx2->getOptions()); } - /** @throws Exception */ - public function testGetNonNullObjectName(): void + public function testEmptyName(): void { - $index = new Index('idx_user_id', ['user_id']); - $name = $index->getObjectName(); + $this->expectException(InvalidName::class); - self::assertNotNull($name); - self::assertEquals(Identifier::unquoted('idx_user_id'), $name->getIdentifier()); + new Index(null, ['user_id']); } - /** @throws Exception */ - public function testGetNullObjectName(): void + public function testQualifiedName(): void { - $index = new Index(null, ['user_id']); + $this->expectException(InvalidName::class); + + new Index('auth.idx_user_id', ['user_id']); + } + + public function testGetObjectName(): void + { + $index = new Index('idx_user_id', ['user_id']); - self::assertNull($index->getObjectName()); + self::assertEquals(Identifier::unquoted('idx_user_id'), $index->getObjectName()->getIdentifier()); } }