From 125961727fa58bc41cb2a1273fb77ec269cc1dac Mon Sep 17 00:00:00 2001 From: Andrii Lutskevych Date: Thu, 4 Dec 2025 00:49:42 +0100 Subject: [PATCH 1/8] [2323] Add Native Support for ALGORITHM=INSTANT in Migrations for MYSQL --- CONTRIBUTING.md | 7 +- Dockerfile | 2 +- src/Phinx/Db/Adapter/MysqlAdapter.php | 179 +++++++++++++++++- src/Phinx/Db/Table/Column.php | 58 ++++++ src/Phinx/Db/Util/AlterInstructions.php | 78 ++++++++ tests/Phinx/Db/Adapter/MysqlAdapterTest.php | 190 ++++++++++++++++++++ 6 files changed, 509 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e97854bab..7dbf055e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,13 +77,16 @@ install [docker-compose](https://docs.docker.com/compose/) for your platform. docker-compose run --rm phinx ``` -1. Install dependencies: + If you use Mac with Apple Silicon add `platform: linux/amd64` for `mysql` and `postgres` services first. Otherwise, + you might have an error `no matching manifest for linux/arm64/v8 in the manifest list entries` + +2. Install dependencies: ``` composer update ``` -1. Run unittest: +3. Run unittest: ``` vendor/bin/phpunit diff --git a/Dockerfile b/Dockerfile index c01e191cc..7f82a4832 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:7.3 +FROM php:8.1 # system dependecies RUN apt-get update && apt-get install -y \ diff --git a/src/Phinx/Db/Adapter/MysqlAdapter.php b/src/Phinx/Db/Adapter/MysqlAdapter.php index 8a767a74b..53a7981a9 100644 --- a/src/Phinx/Db/Adapter/MysqlAdapter.php +++ b/src/Phinx/Db/Adapter/MysqlAdapter.php @@ -93,6 +93,77 @@ class MysqlAdapter extends PdoAdapter public const FIRST = 'FIRST'; + /** + * MySQL ALTER TABLE ALGORITHM options + * + * These constants control how MySQL performs ALTER TABLE operations: + * - ALGORITHM_DEFAULT: Let MySQL choose the best algorithm + * - ALGORITHM_INSTANT: Instant operation (no table copy, MySQL 8.0+ / MariaDB 10.3+) + * - ALGORITHM_INPLACE: In-place operation (no full table copy) + * - ALGORITHM_COPY: Traditional table copy algorithm + * + * Usage: + * ```php + * use Migrations\Db\Adapter\MysqlAdapter; + * + * // ALGORITHM=INSTANT alone (recommended) + * $table->addColumn('status', 'string', [ + * 'null' => true, + * 'algorithm' => MysqlAdapter::ALGORITHM_INSTANT, + * ]); + * + * // Or with ALGORITHM=INPLACE and explicit LOCK + * $table->addColumn('status', 'string', [ + * 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, + * 'lock' => MysqlAdapter::LOCK_NONE, + * ]); + * ``` + * + * Important: ALGORITHM=INSTANT cannot be combined with LOCK=NONE, LOCK=SHARED, + * or LOCK=EXCLUSIVE (MySQL restriction). Use ALGORITHM=INSTANT alone or with + * LOCK=DEFAULT only. + * + * Note: ALGORITHM_INSTANT requires MySQL 8.0+ or MariaDB 10.3+ and only works for + * compatible operations (adding nullable columns, dropping columns, etc.). + * If the operation cannot be performed instantly, MySQL will return an error. + * + * @see https://dev.mysql.com/doc/refman/8.0/en/alter-table.html + * @see https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html + * @see https://mariadb.com/kb/en/alter-table/#algorithm + */ + public const ALGORITHM_DEFAULT = 'DEFAULT'; + public const ALGORITHM_INSTANT = 'INSTANT'; + public const ALGORITHM_INPLACE = 'INPLACE'; + public const ALGORITHM_COPY = 'COPY'; + + /** + * MySQL ALTER TABLE LOCK options + * + * These constants control the locking behavior during ALTER TABLE operations: + * - LOCK_DEFAULT: Let MySQL choose the appropriate lock level + * - LOCK_NONE: Allow concurrent reads and writes (least restrictive) + * - LOCK_SHARED: Allow concurrent reads, block writes + * - LOCK_EXCLUSIVE: Block all concurrent access (most restrictive) + * + * Usage: + * ```php + * use Migrations\Db\Adapter\MysqlAdapter; + * + * $table->changeColumn('name', 'string', [ + * 'limit' => 500, + * 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, + * 'lock' => MysqlAdapter::LOCK_NONE, + * ]); + * ``` + * + * @see https://dev.mysql.com/doc/refman/8.0/en/alter-table.html + * @see https://mariadb.com/kb/en/alter-table/#lock + */ + public const LOCK_DEFAULT = 'DEFAULT'; + public const LOCK_NONE = 'NONE'; + public const LOCK_SHARED = 'SHARED'; + public const LOCK_EXCLUSIVE = 'EXCLUSIVE'; + /** * {@inheritDoc} * @@ -533,7 +604,16 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter $alter .= $this->afterClause($column); - return new AlterInstructions([$alter]); + $instructions = new AlterInstructions([$alter]); + + if ($column->getAlgorithm() !== null) { + $instructions->setAlgorithm($column->getAlgorithm()); + } + if ($column->getLock() !== null) { + $instructions->setLock($column->getLock()); + } + + return $instructions; } /** @@ -616,7 +696,16 @@ protected function getChangeColumnInstructions(string $tableName, string $column $this->afterClause($newColumn), ); - return new AlterInstructions([$alter]); + $instructions = new AlterInstructions([$alter]); + + if ($newColumn->getAlgorithm() !== null) { + $instructions->setAlgorithm($newColumn->getAlgorithm()); + } + if ($newColumn->getLock() !== null) { + $instructions->setLock($newColumn->getLock()); + } + + return $instructions; } /** @@ -1510,6 +1599,92 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string return $def; } + /** + * {@inheritDoc} + * + * Overridden to support ALGORITHM and LOCK clauses from AlterInstructions. + * + * @param string $tableName The table name + * @param \Phinx\Db\Util\AlterInstructions $instructions The alter instructions + * @throws \InvalidArgumentException + * @return void + */ + protected function executeAlterSteps(string $tableName, AlterInstructions $instructions): void + { + $algorithm = $instructions->getAlgorithm(); + $lock = $instructions->getLock(); + + if ($algorithm === null && $lock === null) { + parent::executeAlterSteps($tableName, $instructions); + + return; + } + + $algorithmLockClause = ''; + $upperAlgorithm = null; + $upperLock = null; + + if ($algorithm !== null) { + $upperAlgorithm = strtoupper($algorithm); + $validAlgorithms = [ + self::ALGORITHM_DEFAULT, + self::ALGORITHM_INSTANT, + self::ALGORITHM_INPLACE, + self::ALGORITHM_COPY, + ]; + if (!in_array($upperAlgorithm, $validAlgorithms, true)) { + throw new InvalidArgumentException(sprintf( + 'Invalid algorithm "%s". Valid options: %s', + $algorithm, + implode(', ', $validAlgorithms), + )); + } + $algorithmLockClause .= ', ALGORITHM=' . $upperAlgorithm; + } + + if ($lock !== null) { + $upperLock = strtoupper($lock); + $validLocks = [ + self::LOCK_DEFAULT, + self::LOCK_NONE, + self::LOCK_SHARED, + self::LOCK_EXCLUSIVE, + ]; + if (!in_array($upperLock, $validLocks, true)) { + throw new InvalidArgumentException(sprintf( + 'Invalid lock "%s". Valid options: %s', + $lock, + implode(', ', $validLocks), + )); + } + $algorithmLockClause .= ', LOCK=' . $upperLock; + } + + if ($upperAlgorithm === self::ALGORITHM_INSTANT && $upperLock !== null && $upperLock !== self::LOCK_DEFAULT) { + throw new InvalidArgumentException( + 'ALGORITHM=INSTANT cannot be combined with LOCK=NONE, LOCK=SHARED, or LOCK=EXCLUSIVE. ' . + 'Either use ALGORITHM=INSTANT alone, or use ALGORITHM=INSTANT with LOCK=DEFAULT.', + ); + } + + $alterTemplate = sprintf('ALTER TABLE %s %%s', $this->quoteTableName($tableName)); + + if ($instructions->getAlterParts()) { + $alter = sprintf($alterTemplate, implode(', ', $instructions->getAlterParts()) . $algorithmLockClause); + $this->execute($alter); + } + + $state = []; + foreach ($instructions->getPostSteps() as $instruction) { + if (is_callable($instruction)) { + $state = $instruction($state); + continue; + } + + $this->execute($instruction); + } + } + /** * Describes a database table. This is a MySQL adapter specific method. * diff --git a/src/Phinx/Db/Table/Column.php b/src/Phinx/Db/Table/Column.php index 3941f037e..9ce50de3c 100644 --- a/src/Phinx/Db/Table/Column.php +++ b/src/Phinx/Db/Table/Column.php @@ -162,6 +162,16 @@ class Column */ protected ?array $values = null; + /** + * @var string|null + */ + protected ?string $algorithm = null; + + /** + * @var string|null + */ + protected ?string $lock = null; + /** * Column constructor */ @@ -708,6 +718,52 @@ public function getEncoding(): ?string return $this->encoding; } + /** + * Sets the ALTER TABLE algorithm (MySQL-specific). + * + * @param string $algorithm Algorithm + * @return $this + */ + public function setAlgorithm(string $algorithm) + { + $this->algorithm = $algorithm; + + return $this; + } + + /** + * Gets the ALTER TABLE algorithm. + * + * @return string|null + */ + public function getAlgorithm(): ?string + { + return $this->algorithm; + } + + /** + * Sets the ALTER TABLE lock mode (MySQL-specific). + * + * @param string $lock Lock mode + * @return $this + */ + public function setLock(string $lock) + { + $this->lock = $lock; + + return $this; + } + + /** + * Gets the ALTER TABLE lock mode. + * + * @return string|null + */ + public function getLock(): ?string + { + return $this->lock; + } + /** * Sets the column SRID. * @@ -757,6 +813,8 @@ protected function getValidOptions(): array 'seed', 'increment', 'generated', + 'algorithm', + 'lock', ]; } diff --git a/src/Phinx/Db/Util/AlterInstructions.php b/src/Phinx/Db/Util/AlterInstructions.php index bbde1b947..19f4a1962 100644 --- a/src/Phinx/Db/Util/AlterInstructions.php +++ b/src/Phinx/Db/Util/AlterInstructions.php @@ -8,6 +8,8 @@ namespace Phinx\Db\Util; +use InvalidArgumentException; + /** * Contains all the information for running an ALTER command for a table, * and any post-steps required after the fact. @@ -24,6 +26,16 @@ class AlterInstructions */ protected array $postSteps = []; + /** + * @var string|null MySQL-specific: ALGORITHM clause + */ + protected ?string $algorithm = null; + + /** + * @var string|null MySQL-specific: LOCK clause + */ + protected ?string $lock = null; + /** * Constructor * @@ -83,16 +95,82 @@ public function getPostSteps(): array return $this->postSteps; } + /** + * Sets the ALGORITHM clause (MySQL-specific) + * + * @param string|null $algorithm The algorithm to use + * @return void + */ + public function setAlgorithm(?string $algorithm): void + { + $this->algorithm = $algorithm; + } + + /** + * Gets the ALGORITHM clause (MySQL-specific) + * + * @return string|null + */ + public function getAlgorithm(): ?string + { + return $this->algorithm; + } + + /** + * Sets the LOCK clause (MySQL-specific) + * + * @param string|null $lock The lock mode to use + * @return void + */ + public function setLock(?string $lock): void + { + $this->lock = $lock; + } + + /** + * Gets the LOCK clause (MySQL-specific) + * + * @return string|null + */ + public function getLock(): ?string + { + return $this->lock; + } + /** * Merges another AlterInstructions object to this one * * @param \Phinx\Db\Util\AlterInstructions $other The other collection of instructions to merge in + * @throws \InvalidArgumentException When algorithm or lock specifications conflict * @return void */ public function merge(AlterInstructions $other): void { $this->alterParts = array_merge($this->alterParts, $other->getAlterParts()); $this->postSteps = array_merge($this->postSteps, $other->getPostSteps()); + + if ($other->getAlgorithm() !== null) { + if ($this->algorithm !== null && $this->algorithm !== $other->getAlgorithm()) { + throw new InvalidArgumentException(sprintf( + 'Conflicting algorithm specifications in batched operations: "%s" and "%s". ' . + 'All operations in a batch must use the same algorithm, or specify it on only one operation.', + $this->algorithm, + $other->getAlgorithm(), + )); + } + $this->algorithm = $other->getAlgorithm(); + } + if ($other->getLock() !== null) { + if ($this->lock !== null && $this->lock !== $other->getLock()) { + throw new InvalidArgumentException(sprintf( + 'Conflicting lock specifications in batched operations: "%s" and "%s". ' . + 'All operations in a batch must use the same lock mode, or specify it on only one operation.', + $this->lock, + $other->getLock(), + )); + } + $this->lock = $other->getLock(); + } } /** diff --git a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php index 1b6ec3d47..36633a74c 100644 --- a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php +++ b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -2797,4 +2797,194 @@ public function testPdoNotPersistentConnection() $adapter = new MysqlAdapter(MYSQL_DB_CONFIG); $this->assertFalse($adapter->getConnection()->getAttribute(PDO::ATTR_PERSISTENT)); } + + public function testAddColumnWithAlgorithmInstant() + { + $table = new Table('users', [], $this->adapter); + $table->addColumn('email', 'string') + ->create(); + + $table->addColumn('status', 'string', [ + 'null' => true, + 'algorithm' => MysqlAdapter::ALGORITHM_INSTANT, + ])->update(); + + $this->assertTrue($this->adapter->hasColumn('users', 'status')); + } + + public function testAddColumnWithAlgorithmAndLock() + { + $table = new Table('products', [], $this->adapter); + $table->addColumn('name', 'string') + ->create(); + + // Use ALGORITHM=INPLACE with LOCK=NONE (INSTANT can't have explicit locks) + $table->addColumn('price', 'decimal', [ + 'precision' => 10, + 'scale' => 2, + 'null' => true, + 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, + 'lock' => MysqlAdapter::LOCK_NONE, + ])->update(); + + $this->assertTrue($this->adapter->hasColumn('products', 'price')); + } + + public function testChangeColumnWithAlgorithm() + { + $table = new Table('items', [], $this->adapter); + $table->addColumn('description', 'string', ['limit' => 100]) + ->create(); + + $table->changeColumn('description', 'string', [ + 'limit' => 255, + 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, + 'lock' => MysqlAdapter::LOCK_SHARED, + ])->update(); + + $columns = $this->adapter->getColumns('items'); + foreach ($columns as $column) { + if ($column->getName() === 'description') { + $this->assertEquals(255, $column->getLimit()); + } + } + } + + public function testBatchedOperationsWithSameAlgorithm() + { + $table = new Table('batch_test', [], $this->adapter); + $table->addColumn('col1', 'string') + ->create(); + + $table->addColumn('col2', 'string', [ + 'null' => true, + 'algorithm' => MysqlAdapter::ALGORITHM_INSTANT, + ]) + ->addColumn('col3', 'string', [ + 'null' => true, + 'algorithm' => MysqlAdapter::ALGORITHM_INSTANT, + ]) + ->update(); + + $this->assertTrue($this->adapter->hasColumn('batch_test', 'col2')); + $this->assertTrue($this->adapter->hasColumn('batch_test', 'col3')); + } + + public function testBatchedOperationsWithConflictingAlgorithmsThrowsException() + { + $table = new Table('conflict_test', [], $this->adapter); + $table->addColumn('col1', 'string') + ->create(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Conflicting algorithm specifications'); + + $table->addColumn('col2', 'string', [ + 'null' => true, + 'algorithm' => MysqlAdapter::ALGORITHM_INSTANT, + ]) + ->addColumn('col3', 'string', [ + 'null' => true, + 'algorithm' => MysqlAdapter::ALGORITHM_COPY, + ]) + ->update(); + } + + public function testBatchedOperationsWithConflictingLocksThrowsException() + { + $table = new Table('lock_conflict_test', [], $this->adapter); + $table->addColumn('col1', 'string') + ->create(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Conflicting lock specifications'); + + $table->addColumn('col2', 'string', [ + 'null' => true, + 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, + 'lock' => MysqlAdapter::LOCK_NONE, + ]) + ->addColumn('col3', 'string', [ + 'null' => true, + 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, + 'lock' => MysqlAdapter::LOCK_SHARED, + ]) + ->update(); + } + + public function testInvalidAlgorithmThrowsException() + { + $table = new Table('invalid_algo', [], $this->adapter); + $table->addColumn('col1', 'string') + ->create(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid algorithm'); + + $table->addColumn('col2', 'string', [ + 'algorithm' => 'INVALID', + ])->update(); + } + + public function testInvalidLockThrowsException() + { + $table = new Table('invalid_lock', [], $this->adapter); + $table->addColumn('col1', 'string') + ->create(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid lock'); + + $table->addColumn('col2', 'string', [ + 'lock' => 'INVALID', + ])->update(); + } + + public function testAlgorithmInstantWithExplicitLockThrowsException() + { + $table = new Table('instant_lock_test', [], $this->adapter); + $table->addColumn('col1', 'string') + ->create(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('ALGORITHM=INSTANT cannot be combined with LOCK=NONE'); + + $table->addColumn('col2', 'string', [ + 'null' => true, + 'algorithm' => MysqlAdapter::ALGORITHM_INSTANT, + 'lock' => MysqlAdapter::LOCK_NONE, + ])->update(); + } + + public function testAlgorithmConstantsAreDefined() + { + $this->assertEquals('DEFAULT', MysqlAdapter::ALGORITHM_DEFAULT); + $this->assertEquals('INSTANT', MysqlAdapter::ALGORITHM_INSTANT); + $this->assertEquals('INPLACE', MysqlAdapter::ALGORITHM_INPLACE); + $this->assertEquals('COPY', MysqlAdapter::ALGORITHM_COPY); + } + + public function testLockConstantsAreDefined() + { + $this->assertEquals('DEFAULT', MysqlAdapter::LOCK_DEFAULT); + $this->assertEquals('NONE', MysqlAdapter::LOCK_NONE); + $this->assertEquals('SHARED', MysqlAdapter::LOCK_SHARED); + $this->assertEquals('EXCLUSIVE', MysqlAdapter::LOCK_EXCLUSIVE); + } + + public function testAlgorithmWithMixedCase() + { + $table = new Table('mixed_case', [], $this->adapter); + $table->addColumn('col1', 'string') + ->create(); + + // Should work with lowercase (use INPLACE with LOCK, not INSTANT) + $table->addColumn('col2', 'string', [ + 'null' => true, + 'algorithm' => 'inplace', + 'lock' => 'none', + ])->update(); + + $this->assertTrue($this->adapter->hasColumn('mixed_case', 'col2')); + } } From 3ea9ee6e496c2cdcee76428d3c08bc0b73351279 Mon Sep 17 00:00:00 2001 From: Andrii Lutskevych Date: Thu, 4 Dec 2025 01:03:38 +0100 Subject: [PATCH 2/8] [2323] add adapter connect --- tests/Phinx/Db/Adapter/MysqlAdapterTest.php | 48 +++++++++++++++------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php index 36633a74c..be403fe89 100644 --- a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php +++ b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -2798,8 +2798,10 @@ public function testPdoNotPersistentConnection() $this->assertFalse($adapter->getConnection()->getAttribute(PDO::ATTR_PERSISTENT)); } - public function testAddColumnWithAlgorithmInstant() + public function testAddColumnWithAlgorithmInstant(): void { + $this->adapter->connect(); + $table = new Table('users', [], $this->adapter); $table->addColumn('email', 'string') ->create(); @@ -2812,8 +2814,10 @@ public function testAddColumnWithAlgorithmInstant() $this->assertTrue($this->adapter->hasColumn('users', 'status')); } - public function testAddColumnWithAlgorithmAndLock() + public function testAddColumnWithAlgorithmAndLock(): void { + $this->adapter->connect(); + $table = new Table('products', [], $this->adapter); $table->addColumn('name', 'string') ->create(); @@ -2830,8 +2834,10 @@ public function testAddColumnWithAlgorithmAndLock() $this->assertTrue($this->adapter->hasColumn('products', 'price')); } - public function testChangeColumnWithAlgorithm() + public function testChangeColumnWithAlgorithm(): void { + $this->adapter->connect(); + $table = new Table('items', [], $this->adapter); $table->addColumn('description', 'string', ['limit' => 100]) ->create(); @@ -2850,8 +2856,10 @@ public function testChangeColumnWithAlgorithm() } } - public function testBatchedOperationsWithSameAlgorithm() + public function testBatchedOperationsWithSameAlgorithm(): void { + $this->adapter->connect(); + $table = new Table('batch_test', [], $this->adapter); $table->addColumn('col1', 'string') ->create(); @@ -2870,8 +2878,10 @@ public function testBatchedOperationsWithSameAlgorithm() $this->assertTrue($this->adapter->hasColumn('batch_test', 'col3')); } - public function testBatchedOperationsWithConflictingAlgorithmsThrowsException() + public function testBatchedOperationsWithConflictingAlgorithmsThrowsException(): void { + $this->adapter->connect(); + $table = new Table('conflict_test', [], $this->adapter); $table->addColumn('col1', 'string') ->create(); @@ -2890,8 +2900,10 @@ public function testBatchedOperationsWithConflictingAlgorithmsThrowsException() ->update(); } - public function testBatchedOperationsWithConflictingLocksThrowsException() + public function testBatchedOperationsWithConflictingLocksThrowsException(): void { + $this->adapter->connect(); + $table = new Table('lock_conflict_test', [], $this->adapter); $table->addColumn('col1', 'string') ->create(); @@ -2912,8 +2924,10 @@ public function testBatchedOperationsWithConflictingLocksThrowsException() ->update(); } - public function testInvalidAlgorithmThrowsException() + public function testInvalidAlgorithmThrowsException(): void { + $this->adapter->connect(); + $table = new Table('invalid_algo', [], $this->adapter); $table->addColumn('col1', 'string') ->create(); @@ -2926,8 +2940,10 @@ public function testInvalidAlgorithmThrowsException() ])->update(); } - public function testInvalidLockThrowsException() + public function testInvalidLockThrowsException(): void { + $this->adapter->connect(); + $table = new Table('invalid_lock', [], $this->adapter); $table->addColumn('col1', 'string') ->create(); @@ -2940,8 +2956,10 @@ public function testInvalidLockThrowsException() ])->update(); } - public function testAlgorithmInstantWithExplicitLockThrowsException() + public function testAlgorithmInstantWithExplicitLockThrowsException(): void { + $this->adapter->connect(); + $table = new Table('instant_lock_test', [], $this->adapter); $table->addColumn('col1', 'string') ->create(); @@ -2956,24 +2974,30 @@ public function testAlgorithmInstantWithExplicitLockThrowsException() ])->update(); } - public function testAlgorithmConstantsAreDefined() + public function testAlgorithmConstantsAreDefined(): void { + $this->adapter->connect(); + $this->assertEquals('DEFAULT', MysqlAdapter::ALGORITHM_DEFAULT); $this->assertEquals('INSTANT', MysqlAdapter::ALGORITHM_INSTANT); $this->assertEquals('INPLACE', MysqlAdapter::ALGORITHM_INPLACE); $this->assertEquals('COPY', MysqlAdapter::ALGORITHM_COPY); } - public function testLockConstantsAreDefined() + public function testLockConstantsAreDefined(): void { + $this->adapter->connect(); + $this->assertEquals('DEFAULT', MysqlAdapter::LOCK_DEFAULT); $this->assertEquals('NONE', MysqlAdapter::LOCK_NONE); $this->assertEquals('SHARED', MysqlAdapter::LOCK_SHARED); $this->assertEquals('EXCLUSIVE', MysqlAdapter::LOCK_EXCLUSIVE); } - public function testAlgorithmWithMixedCase() + public function testAlgorithmWithMixedCase(): void { + $this->adapter->connect(); + $table = new Table('mixed_case', [], $this->adapter); $table->addColumn('col1', 'string') ->create(); From 57ed5d4dfc189d09895255e01815239a1a4594bb Mon Sep 17 00:00:00 2001 From: Andrii Lutskevych Date: Thu, 4 Dec 2025 01:19:21 +0100 Subject: [PATCH 3/8] [2323] add mysql8 check --- tests/Phinx/Db/Adapter/MysqlAdapterTest.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php index be403fe89..e7c38f69d 100644 --- a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php +++ b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -2802,6 +2802,10 @@ public function testAddColumnWithAlgorithmInstant(): void { $this->adapter->connect(); + if (!$this->usingMysql8()) { + $this->markTestSkipped('Cannot test Instant algorithm on mysql versions less than 8'); + } + $table = new Table('users', [], $this->adapter); $table->addColumn('email', 'string') ->create(); @@ -2843,7 +2847,7 @@ public function testChangeColumnWithAlgorithm(): void ->create(); $table->changeColumn('description', 'string', [ - 'limit' => 255, + 'limit' => 200, 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, 'lock' => MysqlAdapter::LOCK_SHARED, ])->update(); @@ -2851,7 +2855,7 @@ public function testChangeColumnWithAlgorithm(): void $columns = $this->adapter->getColumns('items'); foreach ($columns as $column) { if ($column->getName() === 'description') { - $this->assertEquals(255, $column->getLimit()); + $this->assertEquals(200, $column->getLimit()); } } } @@ -2860,6 +2864,10 @@ public function testBatchedOperationsWithSameAlgorithm(): void { $this->adapter->connect(); + if (!$this->usingMysql8()) { + $this->markTestSkipped('Cannot test Instant algorithm on mysql versions less than 8'); + } + $table = new Table('batch_test', [], $this->adapter); $table->addColumn('col1', 'string') ->create(); From 2d3914c10fe0edd04c7fa1a81f393e67a4931333 Mon Sep 17 00:00:00 2001 From: Andrii Lutskevych Date: Thu, 4 Dec 2025 01:32:30 +0100 Subject: [PATCH 4/8] [2323] try another limit --- tests/Phinx/Db/Adapter/MysqlAdapterTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php index e7c38f69d..1de6c3b1e 100644 --- a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php +++ b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -2843,11 +2843,11 @@ public function testChangeColumnWithAlgorithm(): void $this->adapter->connect(); $table = new Table('items', [], $this->adapter); - $table->addColumn('description', 'string', ['limit' => 100]) + $table->addColumn('description', 'string', ['limit' => 10]) ->create(); $table->changeColumn('description', 'string', [ - 'limit' => 200, + 'limit' => 20, 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, 'lock' => MysqlAdapter::LOCK_SHARED, ])->update(); From 4df4807e1975c48a5a2a896cf8af03cd5da0af46 Mon Sep 17 00:00:00 2001 From: Andrii Lutskevych Date: Thu, 4 Dec 2025 01:38:37 +0100 Subject: [PATCH 5/8] [2323] add check for mysql 8 --- tests/Phinx/Db/Adapter/MysqlAdapterTest.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php index 1de6c3b1e..147f99d34 100644 --- a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php +++ b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -2842,12 +2842,16 @@ public function testChangeColumnWithAlgorithm(): void { $this->adapter->connect(); + if (version_compare($this->adapter->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.0') === -1) { + $this->markTestSkipped('Cannot test inplace algorithm on mysql versions less than 8'); + } + $table = new Table('items', [], $this->adapter); - $table->addColumn('description', 'string', ['limit' => 10]) + $table->addColumn('description', 'string', ['limit' => 100]) ->create(); $table->changeColumn('description', 'string', [ - 'limit' => 20, + 'limit' => 255, 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, 'lock' => MysqlAdapter::LOCK_SHARED, ])->update(); @@ -2855,7 +2859,7 @@ public function testChangeColumnWithAlgorithm(): void $columns = $this->adapter->getColumns('items'); foreach ($columns as $column) { if ($column->getName() === 'description') { - $this->assertEquals(200, $column->getLimit()); + $this->assertEquals(255, $column->getLimit()); } } } From 54a02ce3f32b13b58dbff4b7d15c4bef82fba9cf Mon Sep 17 00:00:00 2001 From: Andrii Lutskevych Date: Thu, 4 Dec 2025 01:40:56 +0100 Subject: [PATCH 6/8] [2323] fix check for mysql 8 --- tests/Phinx/Db/Adapter/MysqlAdapterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php index 147f99d34..a58addcba 100644 --- a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php +++ b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -2842,7 +2842,7 @@ public function testChangeColumnWithAlgorithm(): void { $this->adapter->connect(); - if (version_compare($this->adapter->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.0') === -1) { + if (!$this->usingMysql8()) { $this->markTestSkipped('Cannot test inplace algorithm on mysql versions less than 8'); } From 0ef7884d8fb491fffe1f9177a2efd577ef6cf1e2 Mon Sep 17 00:00:00 2001 From: Andrii Lutskevych Date: Thu, 4 Dec 2025 13:57:16 +0100 Subject: [PATCH 7/8] [2323] change limit --- tests/Phinx/Db/Adapter/MysqlAdapterTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php index a58addcba..25b3e5e9c 100644 --- a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php +++ b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -2851,7 +2851,7 @@ public function testChangeColumnWithAlgorithm(): void ->create(); $table->changeColumn('description', 'string', [ - 'limit' => 255, + 'limit' => 250, 'algorithm' => MysqlAdapter::ALGORITHM_INPLACE, 'lock' => MysqlAdapter::LOCK_SHARED, ])->update(); @@ -2859,7 +2859,7 @@ public function testChangeColumnWithAlgorithm(): void $columns = $this->adapter->getColumns('items'); foreach ($columns as $column) { if ($column->getName() === 'description') { - $this->assertEquals(255, $column->getLimit()); + $this->assertEquals(250, $column->getLimit()); } } } From 36160ca31a7db15c129d94ce6499925aa360e49c Mon Sep 17 00:00:00 2001 From: Andrii Lutskevych Date: Thu, 4 Dec 2025 14:18:41 +0100 Subject: [PATCH 8/8] [2323] add tests for column --- tests/Phinx/Db/Table/ColumnTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Phinx/Db/Table/ColumnTest.php b/tests/Phinx/Db/Table/ColumnTest.php index 6489e4e59..ce932cb87 100644 --- a/tests/Phinx/Db/Table/ColumnTest.php +++ b/tests/Phinx/Db/Table/ColumnTest.php @@ -4,6 +4,7 @@ namespace Test\Phinx\Db\Table; use Phinx\Config\FeatureFlags; +use Phinx\Db\Adapter\MysqlAdapter; use Phinx\Db\Table\Column; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -50,4 +51,22 @@ public function testColumnNullFeatureFlag() $column = new Column(); $this->assertFalse($column->isNull()); } + + public function testSetAlgorithm(): void + { + $column = new Column(); + $this->assertNull($column->getAlgorithm()); + + $column->setOptions(['algorithm' => MysqlAdapter::ALGORITHM_INPLACE]); + $this->assertSame(MysqlAdapter::ALGORITHM_INPLACE, $column->getAlgorithm()); + } + + public function testSetLock(): void + { + $column = new Column(); + $this->assertNull($column->getLock()); + + $column->setOptions(['lock' => MysqlAdapter::LOCK_NONE]); + $this->assertSame(MysqlAdapter::LOCK_NONE, $column->getLock()); + } }