Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM php:7.3
FROM php:8.1

# system dependecies
RUN apt-get update && apt-get install -y \
Expand Down
179 changes: 177 additions & 2 deletions src/Phinx/Db/Adapter/MysqlAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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}
*
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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.
*
Expand Down
58 changes: 58 additions & 0 deletions src/Phinx/Db/Table/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -757,6 +813,8 @@ protected function getValidOptions(): array
'seed',
'increment',
'generated',
'algorithm',
'lock',
];
}

Expand Down
Loading