Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e36c213
Add support for renaming columns via the change command.
Oct 7, 2021
564929c
Add tests.
Oct 7, 2021
4cc5d0f
Update TableOperation.php
Oct 12, 2021
6f896e1
Make recommended adjustments to building query string.
Oct 12, 2021
15a82fe
Update table operations for Change to operate on the new column.
Oct 12, 2021
e952704
make the changeColumn method more dev friendly by accepting a new col…
Oct 12, 2021
8d96cd8
Get tests to working state.
Oct 12, 2021
7881e02
Add proper reverse method for change.
Oct 12, 2021
c4d4c26
Update TableOperationTest.php
Oct 12, 2021
938d4ad
Add tests to test applying and rewinding changes.
Oct 15, 2021
5f8f565
Added proper integration test parts where we run the migrations on th…
Oct 15, 2021
a991b4e
Add PHP 8.1-8.4 support
devin-ai-integration[bot] Jul 28, 2025
eaa65bc
Fix deprecated GitHub Actions versions
devin-ai-integration[bot] Jul 28, 2025
46ee98f
Drop PHP 7.4 support, require PHP >=8.0 for dependency compatibility
devin-ai-integration[bot] Jul 28, 2025
68e3ecb
Implement before/after name concepts for ColumnOperation to fix reduc…
devin-ai-integration[bot] Jul 28, 2025
0e2019c
Update phpspec/prophecy to 1.22.0 for PHP 8.2-8.4 compatibility
devin-ai-integration[bot] Jul 28, 2025
67bbe03
Fix deprecated GitHub Actions versions
devin-ai-integration[bot] Jul 28, 2025
3ebbec9
Remove explicit upper PHP version
bensinclair Jul 28, 2025
10013a4
Update composer.lock after removing upper PHP version constraint
devin-ai-integration[bot] Jul 28, 2025
79f7f05
Fix PHP 8.0 compatibility by constraining doctrine/instantiator and s…
devin-ai-integration[bot] Jul 28, 2025
2d1a86c
Resolve merge conflicts: keep updated GitHub Actions workflow and int…
devin-ai-integration[bot] Jul 28, 2025
4200f2f
Update dependencies for PHP 8.3/8.4 compatibility
devin-ai-integration[bot] Jul 28, 2025
0abc14f
Fix PHPUnit compatibility issues for PHP 8.2
devin-ai-integration[bot] Jul 28, 2025
d0ce3c2
Fix column matching logic in TableOperation.apply() for proper reduction
devin-ai-integration[bot] Jul 29, 2025
370634a
Combine MODIFY and CHANGE conditions
bensinclair Jul 29, 2025
973264a
Update README.md
bensinclair Jul 29, 2025
0b56665
Fix compatibility issues with newer PHP versions
bensinclair Jul 29, 2025
1d3b305
Merge branch 'devin/1753744668-php-8-4-support' into enhance-change-c…
bensinclair Jul 29, 2025
5a7d959
Merge branch 'master' into enhance-change-columns
joshmcrae Oct 23, 2025
f275b01
Support test matrix
joshmcrae Oct 23, 2025
cd77076
Mark skipped during setup
joshmcrae Oct 23, 2025
889c688
Simplified logic around column names
joshmcrae Oct 23, 2025
e198949
Fixed application of drop to renamed column
joshmcrae Oct 23, 2025
262a480
Update logic for apply alter table to alter table
joshmcrae Oct 23, 2025
208fd24
Account for CHANGE COLUM applied to ADD COLUMN
joshmcrae Oct 23, 2025
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ $ composer require tithely/exo
## Documentation
- [Introduction](doc/01-introduction.md)
- [Usage](doc/02-usage.md)
- [Testing](doc/03-testing.md)
2 changes: 1 addition & 1 deletion src/ExecMigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static function create(string $name): ExecMigration
* @param string $name
* @param string|null $body
*/
private function __construct(string $name, string $body = null)
private function __construct(string $name, ?string $body = null)
{
$this->name = $name;
$this->body = $body;
Expand Down
4 changes: 2 additions & 2 deletions src/FunctionMigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,13 @@ public static function drop(string $name): FunctionMigration
private function __construct(
string $name,
string $operation,
ReturnTypeOperation $returnType = null,
?ReturnTypeOperation $returnType = null,
bool $deterministic = false,
string $dataUse = 'READS SQL DATA',
string $language = 'plpgsql',
array $parameterOperations = [],
array $variableOperations = [],
string $body = null
?string $body = null
) {
$this->name = $name;
$this->operation = $operation;
Expand Down
2 changes: 1 addition & 1 deletion src/History.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function add(string $version, MigrationInterface $migrationOrView)
* @param array|null $versions
* @return History
*/
public function clone(array $versions = null): History
public function clone(?array $versions = null): History
{
$history = new History();

Expand Down
39 changes: 39 additions & 0 deletions src/Operation/ColumnOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@ final class ColumnOperation extends AbstractOperation
{
const ADD = 'add';
const MODIFY = 'modify';
const CHANGE = 'change';
const DROP = 'drop';

/**
* @var string
*/
private string $operation;

/**
* @var string
*/
private string $beforeName;

/**
* @var string
*/
private string $afterName;

/**
* @var array
*/
Expand All @@ -30,6 +41,14 @@ public function __construct(string $name, string $operation, array $options)
$this->name = $name;
$this->operation = $operation;
$this->options = $options;

if ($operation === self::CHANGE && isset($options['new_name'])) {
$this->beforeName = $name;
$this->afterName = $options['new_name'];
} else {
$this->beforeName = $name;
$this->afterName = $name;
}
}

/**
Expand All @@ -51,4 +70,24 @@ public function getOptions(): array
{
return $this->options;
}

/**
* Returns the before name (original name for CHANGE operations).
*
* @return string
*/
public function getBeforeName(): string
{
return $this->beforeName;
}

/**
* Returns the after name (new name for CHANGE operations).
*
* @return string
*/
public function getAfterName(): string
{
return $this->afterName;
}
}
2 changes: 1 addition & 1 deletion src/Operation/ExecOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final class ExecOperation extends AbstractOperation
* @param string $name
* @param ?string $body
*/
public function __construct(string $name, string $body = null) {
public function __construct(string $name, ?string $body = null) {
$this->name = $name;
$this->body = $body;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Operation/FunctionOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ final class FunctionOperation extends AbstractOperation implements ReversibleOpe
public function __construct(
string $name,
string $operation,
ReturnTypeOperation $returnType = null,
?ReturnTypeOperation $returnType = null,
bool $deterministic = false,
string $dataUse = 'READS SQL DATA',
string $language = 'plpgsql',
array $parameterOperations = [],
array $variableOperations = [],
string $body = null
?string $body = null
) {
$this->name = $name;
$this->operation = $operation;
Expand Down
2 changes: 1 addition & 1 deletion src/Operation/ProcedureOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function __construct(
string $language = 'plpgsql',
array $inParameterOperations = [],
array $outParameterOperations = [],
string $body = null
?string $body = null
) {
$this->name = $name;
$this->operation = $operation;
Expand Down
70 changes: 61 additions & 9 deletions src/Operation/TableOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,26 @@ public function reverse(?ReversibleOperationInterface $originalOperation = null)
$columnOperations[] = $originalColumn;
}

if ($columnOperation->getOperation() === ColumnOperation::MODIFY) {
if ($columnOperation->getOperation() === ColumnOperation::MODIFY || $columnOperation->getOperation() === ColumnOperation::CHANGE) {
if (!$originalColumn) {
throw new LogicException('Cannot revert a column that does not exist.');
}

$columnOperations[] = new ColumnOperation(
$columnOperation->getName(),
ColumnOperation::MODIFY,
$originalColumn->getOptions()
);
if ($columnOperation->getOperation() === ColumnOperation::MODIFY) {
$columnOperations[] = new ColumnOperation(
$columnOperation->getName(),
$columnOperation->getOperation(),
$originalColumn->getOptions()
);
} else {
$options = $originalColumn->getOptions();
$options['new_name'] = $columnOperation->getBeforeName();
$columnOperations[] = new ColumnOperation(
$columnOperation->getAfterName(),
$columnOperation->getOperation(),
$options
);
}
}
}

Expand Down Expand Up @@ -192,7 +202,7 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati
$offset = array_search($options['after'], array_keys($columns)) + 1;
}

// Remove existing operation for the column
// Remove existing operation for the column using proper name matching
foreach ($columns as $existing => $column) {
if ($column->getName() === $columnOperation->getName()) {
unset($columns[$existing]);
Expand All @@ -213,6 +223,15 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati
$options
);

array_splice($columns, $offset, 0, [$addOperation]);
break;
case ColumnOperation::CHANGE:
$addOperation = new ColumnOperation(
$columnOperation->getAfterName(),
ColumnOperation::ADD,
$options
);

array_splice($columns, $offset, 0, [$addOperation]);
break;
}
Expand Down Expand Up @@ -253,12 +272,14 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati

foreach ($operation->getColumnOperations() as $columnOperation) {
$originalOperation = $columnOperation->getOperation();
$originalName = $columnOperation->getName();

// Remove existing operation for the column
// Remove existing operation for the column using proper name matching
foreach ($columns as $existing => $column) {
if ($column->getName() === $columnOperation->getName()) {
if ($column->getAfterName() === $columnOperation->getName()) {
unset($columns[$existing]);
$originalOperation = $column->getOperation();
$originalName = $column->getBeforeName();
break;
}
}
Expand All @@ -269,14 +290,45 @@ public function apply(ReducibleOperationInterface $operation): ?ReducibleOperati
$columns[] = $columnOperation;
break;
case ColumnOperation::DROP:
if ($originalOperation == ColumnOperation::CHANGE) {
$columnOperation = new ColumnOperation(
$originalName,
$columnOperation->getOperation(),
$columnOperation->getOptions()
);
}

if ($originalOperation !== ColumnOperation::ADD) {
$columns[] = $columnOperation;
}
break;
case ColumnOperation::MODIFY:
$options = $columnOperation->getOptions();

if ($originalOperation == ColumnOperation::CHANGE) {
$options['new_name'] = $columnOperation->getName();
}

$columns[] = new ColumnOperation(
$columnOperation->getName(),
$originalOperation,
$options
);
break;
case ColumnOperation::CHANGE:
$columnName = $columnOperation->getName();

if ($originalOperation == ColumnOperation::ADD) {
$columnName = $columnOperation->getAfterName();
}

if ($originalOperation == ColumnOperation::MODIFY) {
$originalOperation = ColumnOperation::CHANGE;
}

$columns[] = new ColumnOperation(
$columnName,
$originalOperation,
$columnOperation->getOptions()
);
break;
Expand Down
2 changes: 1 addition & 1 deletion src/Operation/ViewOperation.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class ViewOperation extends AbstractOperation implements ReversibleOperati
* @param string $operation
* @param string|null $body
*/
public function __construct(string $name, string $operation, string $body = null)
public function __construct(string $name, string $operation, ?string $body = null)
{
$this->name = $name;
$this->operation = $operation;
Expand Down
2 changes: 1 addition & 1 deletion src/ProcedureMigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private function __construct(
string $language = 'plpgsql',
array $inParameterOperations = [],
array $outParameterOperations = [],
string $body = null
?string $body = null
) {
$this->name = $name;
$this->operation = $operation;
Expand Down
10 changes: 10 additions & 0 deletions src/Statement/MysqlStatementBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,16 @@ protected function buildTable(TableOperation $operation): string
$this->buildColumn($columnOperation->getName(), $columnOperation->getOptions())
);
break;
case ColumnOperation::CHANGE:
$specifications[] = sprintf(
'CHANGE COLUMN %s %s',
$this->buildIdentifier($columnOperation->getName()),
$this->buildColumn(
$columnOperation->getOptions()['new_name'],
$columnOperation->getOptions()
)
);
break;
case ColumnOperation::DROP:
$specifications[] = sprintf(
'DROP COLUMN %s',
Expand Down
28 changes: 28 additions & 0 deletions src/TableMigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,34 @@ public function modifyColumn(string $column, array $options): TableMigration
return $this;
}

/**
* Pushes a new change column operation.
*
* @param string $oldColumn
* @param string $newColumn
* @param array $options
* @return TableMigration
*/
public function changeColumn(string $oldColumn, string $newColumn, array $options): TableMigration
{
if ($this->operation === TableOperation::CREATE) {
throw new LogicException('Cannot change columns in a create migration.');
}

if ($this->operation === TableOperation::DROP) {
throw new LogicException('Cannot change columns in a drop migration.');
}

if ($oldColumn === $newColumn) {
$this->columnOperations[] = new ColumnOperation($oldColumn, ColumnOperation::MODIFY, $options);
} else {
$options['new_name'] = $newColumn;
$this->columnOperations[] = new ColumnOperation($oldColumn, ColumnOperation::CHANGE, $options);
}

return $this;
}

/**
* Pushes a new drop column operation.
*
Expand Down
2 changes: 1 addition & 1 deletion src/ViewMigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public static function drop(string $name)
* @param string $operation
* @param string|null $body
*/
private function __construct(string $name, string $operation, string $body = null)
private function __construct(string $name, string $operation, ?string $body = null)
{
$this->name = $name;
$this->operation = $operation;
Expand Down
4 changes: 4 additions & 0 deletions tests/Fixtures/TestChange/20211015_create_users.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

return Exo\TableMigration::create('users')
->addColumn('username', ['type' => 'string']);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

return Exo\TableMigration::alter('users')
->changeColumn('username', 'email', ['type' => 'string']);
4 changes: 4 additions & 0 deletions tests/Fixtures/TestChange/20211017_add_column_username.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

return Exo\TableMigration::alter('users')
->addColumn('username', ['type' => 'string']);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

return Exo\TableMigration::alter('users')
->changeColumn('username', 'user_id', ['type' => 'integer']);
Loading