From c8cff960cab7797f478d31d274982b9ff9a2e116 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 5 Oct 2026 16:35:47 +0400 Subject: [PATCH 1/2] test(MySQL): cover adding an auto-increment column to an existing table Assisted-By: Claude Opus 5.5 --- .../MySQL/Schema/AutoIncrementColumnTest.php | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/Database/Functional/Driver/MySQL/Schema/AutoIncrementColumnTest.php b/tests/Database/Functional/Driver/MySQL/Schema/AutoIncrementColumnTest.php index e3bc8a5f..646d40bd 100644 --- a/tests/Database/Functional/Driver/MySQL/Schema/AutoIncrementColumnTest.php +++ b/tests/Database/Functional/Driver/MySQL/Schema/AutoIncrementColumnTest.php @@ -23,6 +23,12 @@ public static function primaryTypesProvider(): iterable yield 'bigPrimary' => ['bigPrimary', 'bigPrimary']; } + public static function addedIndexProvider(): iterable + { + yield 'unique index' => [true]; + yield 'index' => [false]; + } + public function testAutoIncrementColumnIsNotAddedToPrimaryKey(): void { $schema = $this->schema('auto_increment'); @@ -101,6 +107,63 @@ public function testAutoIncrementColumnWithoutKeyThrowsException(): void $schema->save(); } + /** + * @dataProvider addedIndexProvider + */ + public function testAddAutoIncrementColumnToExistingTable(bool $unique): void + { + $schema = $this->schema('auto_increment'); + $schema->string('id', 36)->nullable(false); + $schema->string('title')->nullable(true); + $schema->setPrimaryKeys(['id']); + $schema->index(['title']); + $schema->save(); + + $table = $this->database->table('auto_increment'); + $table->insertOne(['id' => 'a', 'title' => 'first']); + $table->insertOne(['id' => 'b', 'title' => 'second']); + + $schema = $this->schema('auto_increment'); + $schema->integer('number', autoIncrement: true)->nullable(false); + $schema->index(['number'])->unique($unique); + $schema->string('note')->nullable(true); + $schema->index(['note']); + $schema->save(); + + $this->assertSameAsInDB($schema); + + $saved = $this->schema('auto_increment'); + $this->assertSame(['id'], $saved->getPrimaryKeys()); + $this->assertTrue($saved->hasIndex(['number'])); + $this->assertTrue($saved->hasIndex(['note'])); + + $table->insertOne(['id' => 'c', 'title' => 'third']); + $this->assertSame( + [['id' => 'a', 'number' => 1], ['id' => 'b', 'number' => 2], ['id' => 'c', 'number' => 3]], + $table->select('id', 'number')->orderBy('id')->fetchAll(), + ); + } + + public function testAddAutoIncrementColumnWithoutKeyThrowsException(): void + { + $schema = $this->schema('auto_increment'); + $schema->string('id', 36)->nullable(false); + $schema->setPrimaryKeys(['id']); + $schema->save(); + + $schema = $this->schema('auto_increment'); + $schema->integer('number', autoIncrement: true)->nullable(false); + + try { + $schema->save(); + $this->fail('SchemaException expected'); + } catch (SchemaException $e) { + $this->assertStringContainsString('`number`', $e->getMessage()); + } + + $this->assertFalse($this->schema('auto_increment')->hasColumn('number')); + } + public function testExistingTableIsReflectedWithItsPrimaryKey(): void { $this->database->execute( From 1c9e9d23153edc20d03cee6192b49625f7ee9f0f Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 5 Oct 2026 16:35:48 +0400 Subject: [PATCH 2/2] fix(MySQL): add an auto-increment column together with its index MySQL rejects an AUTO_INCREMENT column without a key (error 1075), so adding one to an existing table and creating its index afterwards always failed. The column and the indexes covering it now go into a single ALTER TABLE; a column with no index fails with the same SchemaException as on table creation, before the column is added. Assisted-By: Claude Opus 5.5 --- src/Driver/MySQL/MySQLHandler.php | 43 ++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/Driver/MySQL/MySQLHandler.php b/src/Driver/MySQL/MySQLHandler.php index 38af4f80..cfd92346 100644 --- a/src/Driver/MySQL/MySQLHandler.php +++ b/src/Driver/MySQL/MySQLHandler.php @@ -19,6 +19,7 @@ use Cycle\Database\Schema\AbstractForeignKey; use Cycle\Database\Schema\AbstractIndex; use Cycle\Database\Schema\AbstractTable; +use Cycle\Database\Schema\ComparatorInterface; class MySQLHandler extends Handler { @@ -158,19 +159,53 @@ protected function createStatement(AbstractTable $table): string return $statement . " ENGINE {$table->getEngine()}"; } + #[\Override] + protected function createColumns(AbstractTable $table, ComparatorInterface $comparator): void + { + foreach ($comparator->addedColumns() as $column) { + $this->assertValid($column); + + // MySQL rejects an AUTO_INCREMENT column without a key, so its index is added in the same statement. + $statement = "ALTER TABLE {$this->identify($table)} ADD COLUMN {$column->sqlStatement($this->getDriver())}"; + foreach ($this->autoIncrementIndexes($table, [$column], $comparator->addedIndexes()) as $index) { + $statement .= ", ADD {$index->sqlStatement($this->getDriver(), false)}"; + } + + $this->run($statement); + } + } + + #[\Override] + protected function createIndexes(AbstractTable $table, ComparatorInterface $comparator): void + { + $created = $this->autoIncrementIndexes($table, $comparator->addedColumns(), $comparator->addedIndexes()); + foreach ($comparator->addedIndexes() as $index) { + if (!\in_array($index, $created, true)) { + $this->createIndex($table, $index); + } + } + } + /** * Indexes covering AUTO_INCREMENT columns that do not lead the primary key. * + * @param AbstractColumn[]|null $columns Columns to check, all table columns by default. + * @param AbstractIndex[]|null $indexes Indexes to search, all table indexes by default. + * * @return list * * @throws SchemaException When such a column is neither in the primary key nor in an index. */ - protected function autoIncrementIndexes(AbstractTable $table): array - { + protected function autoIncrementIndexes( + AbstractTable $table, + ?array $columns = null, + ?array $indexes = null, + ): array { $primaryKeys = $table->getPrimaryKeys(); + $indexes ??= $table->getIndexes(); $result = []; - foreach ($table->getColumns() as $column) { + foreach ($columns ?? $table->getColumns() as $column) { if ( ($column->getAttributes()['autoIncrement'] ?? false) !== true || ($primaryKeys[0] ?? null) === $column->getName() @@ -179,7 +214,7 @@ protected function autoIncrementIndexes(AbstractTable $table): array } $found = \in_array($column->getName(), $primaryKeys, true); - foreach ($table->getIndexes() as $index) { + foreach ($indexes as $index) { if (\in_array($column->getName(), $index->getColumns(), true)) { $found = true; \in_array($index, $result, true) or $result[] = $index;