Skip to content

Commit

Permalink
Introduce addDefferedSql method in migration class
Browse files Browse the repository at this point in the history
This will allow to add queries that needs to be executed after changes
made to schema object.
  • Loading branch information
srsbiz committed Dec 14, 2024
1 parent a3fc5b3 commit 4d308f8
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 3 deletions.
3 changes: 3 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
to enable wrapping all migrations in a single transaction. To disable it, you can use the `--no-all-or-nothing`
option instead. Both options override the configuration value.

## Migration classes
- It is now possible to add SQL statements, that needs to be executed after changes made to schema object. Use
`addDeferredSql` method in your migrations for this purpose.


# Upgrade to 3.6
Expand Down
26 changes: 25 additions & 1 deletion docs/en/reference/migration-classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ addSql
You can use the ``addSql`` method within the ``up`` and ``down`` methods. Internally the ``addSql`` calls are passed
to the executeQuery method in the DBAL. This means that you can use the power of prepared statements easily and that
you don't need to copy paste the same query with different parameters. You can just pass those different parameters
to the addSql method as parameters.
to the addSql method as parameters. These queries are executed before changes applied to ``$schema``.

.. code-block:: php
Expand All @@ -205,6 +205,30 @@ to the addSql method as parameters.
}
}
addDeferredSql
~~~~~~~~~~~~~~

Works just like the ``addSql`` method, but queries are deferred to be executed after changes to ``$schema`` were
planned.

.. code-block:: php
public function up(Schema $schema): void
{
$schema->getTable('user')->addColumn('happy', 'boolean')->setDefault(false);
$users = [
['name' => 'mike', 'id' => 1],
['name' => 'jwage', 'id' => 2],
['name' => 'ocramius', 'id' => 3],
];
foreach ($users as $user) {
// Use addDeferredSql since "happy" is new column, that will not yet be present in schema if called by using addSql
$this->addDeferredSql('UPDATE user SET happy = true WHERE name = :name AND id = :id', $user);
}
}
write
~~~~~

Expand Down
26 changes: 26 additions & 0 deletions src/AbstractMigration.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ abstract class AbstractMigration
/** @var Query[] */
private array $plannedSql = [];

/** @var Query[] */
private array $deferredSql = [];

private bool $frozen = false;

public function __construct(Connection $connection, private readonly LoggerInterface $logger)
Expand Down Expand Up @@ -135,12 +138,35 @@ protected function addSql(
$this->plannedSql[] = new Query($sql, $params, $types);
}

/**
* Adds SQL queries which should be executed after schema changes
* @param mixed[] $params
* @param mixed[] $types
*/
protected function addDeferredSql(
string $sql,
array $params = [],
array $types = [],
): void {
if ($this->frozen) {
throw FrozenMigration::new();
}

$this->deferredSql[] = new Query($sql, $params, $types);
}

/** @return Query[] */
public function getSql(): array
{
return $this->plannedSql;
}

/** @return Query[] */
public function getDeferredSql(): array
{
return $this->deferredSql;
}

public function freeze(): void
{
$this->frozen = true;
Expand Down
4 changes: 4 additions & 0 deletions src/Version/DbalExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ private function executeMigration(
$this->addSql(new Query($sql));
}

foreach ($migration->getDeferredSql() as $deferredSqlQuery) {
$this->addSql($deferredSqlQuery);
}

$migration->freeze();

if (count($this->sql) !== 0) {
Expand Down
16 changes: 16 additions & 0 deletions tests/AbstractMigrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ public function testAddSql(): void
self::assertEquals([new Query('SELECT 1', [1], [2])], $this->migration->getSql());
}

public function testAddDeferredSql(): void
{
$this->migration->exposedAddDeferredSql('SELECT 2', [1], [2]);

self::assertEquals([new Query('SELECT 2', [1], [2])], $this->migration->getDeferredSql());
}

public function testThrowFrozenMigrationException(): void
{
$this->expectException(FrozenMigration::class);
Expand All @@ -60,6 +67,15 @@ public function testThrowFrozenMigrationException(): void
$this->migration->exposedAddSql('SELECT 1', [1], [2]);
}

public function testThrowFrozenMigrationExceptionOnDeferredAdd(): void
{
$this->expectException(FrozenMigration::class);
$this->expectExceptionMessage('The migration is frozen and cannot be edited anymore.');

$this->migration->freeze();
$this->migration->exposedAddDeferredSql('SELECT 2', [1], [2]);
}

public function testWarnIfOutputMessage(): void
{
$this->migration->warnIf(true, 'Warning was thrown');
Expand Down
32 changes: 30 additions & 2 deletions tests/MigratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
use Doctrine\Migrations\MigratorConfiguration;
use Doctrine\Migrations\ParameterFormatter;
use Doctrine\Migrations\Provider\DBALSchemaDiffProvider;
use Doctrine\Migrations\Provider\SchemaDiffProvider;
use Doctrine\Migrations\Tests\Stub\Functional\MigrateNotTouchingTheSchema;
use Doctrine\Migrations\Tests\Stub\Functional\MigrateWithDeferredSql;
use Doctrine\Migrations\Tests\Stub\Functional\MigrationThrowsError;
use Doctrine\Migrations\Tests\Stub\NonTransactional\MigrationNonTransactional;
use Doctrine\Migrations\Version\DbalExecutor;
Expand Down Expand Up @@ -73,6 +75,32 @@ public function testGetSql(): void
);
}

public function testQueriesOrder(): void
{
$this->config->addMigrationsDirectory('DoctrineMigrations\\', __DIR__ . '/Stub/migrations-empty-folder');

$conn = $this->getSqliteConnection();
$migrator = $this->createTestMigrator(
schemaDiff: new DBALSchemaDiffProvider($conn->createSchemaManager(), $conn->getDatabasePlatform())
);

$migration = new MigrateWithDeferredSql($conn, $this->logger);
$plan = new MigrationPlan(new Version(MigrateWithDeferredSql::class), $migration, Direction::UP);
$planList = new MigrationPlanList([$plan], Direction::UP);

$sql = $migrator->migrate($planList, $this->migratorConfiguration);

self::assertArrayHasKey(MigrateWithDeferredSql::class, $sql);
self::assertSame(
[
'SELECT 1',
'CREATE TABLE test (id INTEGER NOT NULL)',
'INSERT INTO test(id) VALUES(123)',
],
array_map(strval(...), $sql[MigrateWithDeferredSql::class]),
);
}

public function testEmptyPlanShowsMessage(): void
{
$migrator = $this->createTestMigrator();
Expand All @@ -84,7 +112,7 @@ public function testEmptyPlanShowsMessage(): void
self::assertStringContainsString('No migrations', $this->logger->records[0]['message']);
}

protected function createTestMigrator(): DbalMigrator
protected function createTestMigrator(?SchemaDiffProvider $schemaDiff = null): DbalMigrator
{
$eventManager = new EventManager();
$eventDispatcher = new EventDispatcher($this->conn, $eventManager);
Expand All @@ -94,7 +122,7 @@ protected function createTestMigrator(): DbalMigrator
$stopwatch = new Stopwatch();
$paramFormatter = $this->createMock(ParameterFormatter::class);
$storage = $this->createMock(MetadataStorage::class);
$schemaDiff = $this->createMock(SchemaDiffProvider::class);
$schemaDiff = $schemaDiff ?: $this->createMock(SchemaDiffProvider::class);

return new DbalMigrator(
$this->conn,
Expand Down
9 changes: 9 additions & 0 deletions tests/Stub/AbstractMigrationStub.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,13 @@ public function exposedAddSql(string $sql, array $params = [], array $types = []
{
$this->addSql($sql, $params, $types);
}

/**
* @param int[] $params
* @param int[] $types
*/
public function exposedAddDeferredSql(string $sql, array $params = [], array $types = []): void
{
$this->addDeferredSql($sql, $params, $types);
}
}
23 changes: 23 additions & 0 deletions tests/Stub/Functional/MigrateWithDeferredSql.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Doctrine\Migrations\Tests\Stub\Functional;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

class MigrateWithDeferredSql extends AbstractMigration
{
public function up(Schema $schema): void
{
// Last to be executed
$this->addDeferredSql('INSERT INTO test(id) VALUES(123)');

// Executed after queries from addSql()
$schema->createTable('test')->addColumn('id', 'integer');

// First to be executed
$this->addSql('SELECT 1');
}
}

0 comments on commit 4d308f8

Please sign in to comment.