Skip to content

Commit 8b0ac22

Browse files
committed
Make it possible to addSql() that is executed as a statement
| Q | A |------------- | ----------- | Type | improvement | BC Break | no | Fixed issues | fixes #1325 #### Summary When the DBAL connection uses `mysqli` as the driver, prepared statements are sent to the MySQL server through a dedicated protocol mechanism. For example, `CREATE TRIGGER` statements are not possible in this case. To use SQL statements like `CREATE TRIGGER`, the `DbalExecutor` may not (at least in the case of the `mysqli` driver) use `Connection::executeQuery()`, but has to call `Connection::executeStatement()`. See #1325 for more details. This PR adds a new `executeAsStatement` parameter to `\Doctrine\Migrations\AbstractMigration::addSql()`, which defaults to `false` (current behaviour). By setting it to true, a migration can pass the information to the `DbalExecutor` that the statement must be executed with `executeStatement()`, not `executeQuery()`.
1 parent dc441bb commit 8b0ac22

File tree

5 files changed

+69
-9
lines changed

5 files changed

+69
-9
lines changed

lib/Doctrine/Migrations/AbstractMigration.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,10 @@ public function down(Schema $schema): void
142142
protected function addSql(
143143
string $sql,
144144
array $params = [],
145-
array $types = []
145+
array $types = [],
146+
bool $executeAsStatement = false
146147
): void {
147-
$this->plannedSql[] = new Query($sql, $params, $types);
148+
$this->plannedSql[] = new Query($sql, $params, $types, $executeAsStatement);
148149
}
149150

150151
/**

lib/Doctrine/Migrations/Query/Query.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,22 @@ final class Query
2121
/** @var mixed[] */
2222
private array $types;
2323

24+
private bool $executeAsStatement;
25+
2426
/**
2527
* @param mixed[] $parameters
2628
* @param mixed[] $types
2729
*/
28-
public function __construct(string $statement, array $parameters = [], array $types = [])
30+
public function __construct(string $statement, array $parameters = [], array $types = [], bool $executeAsStatement = false)
2931
{
3032
if (count($types) > count($parameters)) {
3133
throw InvalidArguments::wrongTypesArgumentCount($statement, count($parameters), count($types));
3234
}
3335

34-
$this->statement = $statement;
35-
$this->parameters = $parameters;
36-
$this->types = $types;
36+
$this->statement = $statement;
37+
$this->parameters = $parameters;
38+
$this->types = $types;
39+
$this->executeAsStatement = $executeAsStatement;
3740
}
3841

3942
public function __toString(): string
@@ -57,4 +60,9 @@ public function getTypes(): array
5760
{
5861
return $this->types;
5962
}
63+
64+
public function getExecuteAsStatement(): bool
65+
{
66+
return $this->executeAsStatement;
67+
}
6068
}

lib/Doctrine/Migrations/Version/DbalExecutor.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,13 @@ private function executeResult(MigratorConfiguration $configuration): void
294294
$this->outputSqlQuery($query, $configuration);
295295

296296
$stopwatchEvent = $this->stopwatch->start('query');
297-
// executeQuery() must be used here because $query might return a result set, for instance REPAIR does
298-
$this->connection->executeQuery($query->getStatement(), $query->getParameters(), $query->getTypes());
297+
298+
if ($query->getExecuteAsStatement()) {
299+
$this->connection->executeStatement($query->getStatement(), $query->getParameters(), $query->getTypes());
300+
} else {
301+
$this->connection->executeQuery($query->getStatement(), $query->getParameters(), $query->getTypes());
302+
}
303+
299304
$stopwatchEvent->stop();
300305

301306
if (! $configuration->getTimeAllQueries()) {

tests/Doctrine/Migrations/Tests/Version/ExecutorTest.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Doctrine\Migrations\Query\Query;
1818
use Doctrine\Migrations\Tests\TestLogger;
1919
use Doctrine\Migrations\Tests\Version\Fixture\EmptyTestMigration;
20+
use Doctrine\Migrations\Tests\Version\Fixture\TestMigrationWithStatement;
2021
use Doctrine\Migrations\Tests\Version\Fixture\VersionExecutorTestMigration;
2122
use Doctrine\Migrations\Version\DbalExecutor;
2223
use Doctrine\Migrations\Version\Direction;
@@ -94,6 +95,25 @@ public function testExecuteWithNoQueries(): void
9495
], $this->logger->logs);
9596
}
9697

98+
public function testExecuteWithStatement(): void
99+
{
100+
$migratorConfiguration = new MigratorConfiguration();
101+
102+
$migration = new TestMigrationWithStatement($this->connection, $this->logger);
103+
$version = new Version('xx');
104+
$plan = new MigrationPlan($version, $migration, Direction::UP);
105+
106+
$this->connection
107+
->expects(self::once())
108+
->method('executeStatement')
109+
->with('CREATE TRIGGER', [], []);
110+
111+
$this->versionExecutor->execute(
112+
$plan,
113+
$migratorConfiguration
114+
);
115+
}
116+
97117
public function testExecuteUp(): void
98118
{
99119
$this->metadataStorage
@@ -216,6 +236,7 @@ public function testExecuteDryRun(): void
216236
self::assertSame(Direction::UP, $result->getDirection());
217237

218238
yield new Query('INSERT INTO doctrine_migration_versions (version, executed_at, execution_time) VALUE (' . $result->getVersion() . ', NOW(), 0)');
239+
yield new Query('CREATE TRIGGER something...', executeAsStatement: true);
219240
});
220241

221242
$this->connection
@@ -226,6 +247,10 @@ public function testExecuteDryRun(): void
226247
->expects(self::never())
227248
->method('executeUpdate');
228249

250+
$this->connection
251+
->expects(self::never())
252+
->method('executeStatement');
253+
229254
$migratorConfiguration = (new MigratorConfiguration())
230255
->setDryRun(true)
231256
->setTimeAllQueries(true);
@@ -239,7 +264,7 @@ public function testExecuteDryRun(): void
239264

240265
$queries = $result->getSql();
241266

242-
self::assertCount(3, $queries);
267+
self::assertCount(4, $queries);
243268
self::assertSame('SELECT 1', $queries[0]->getStatement());
244269
self::assertSame([1], $queries[0]->getParameters());
245270
self::assertSame([3], $queries[0]->getTypes());
@@ -252,6 +277,11 @@ public function testExecuteDryRun(): void
252277
self::assertSame([], $queries[2]->getParameters());
253278
self::assertSame([], $queries[2]->getTypes());
254279

280+
self::assertSame('CREATE TRIGGER something...', $queries[3]->getStatement());
281+
self::assertSame([], $queries[3]->getParameters());
282+
self::assertSame([], $queries[3]->getTypes());
283+
self::assertTrue($queries[3]->getExecuteAsStatement());
284+
255285
self::assertNotNull($result->getTime());
256286
self::assertSame(State::NONE, $result->getState());
257287
self::assertTrue($this->migration->preUpExecuted);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Migrations\Tests\Version\Fixture;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
class TestMigrationWithStatement extends AbstractMigration
11+
{
12+
public function up(Schema $schema): void
13+
{
14+
$this->addSql('CREATE TRIGGER', executeAsStatement: true);
15+
}
16+
}

0 commit comments

Comments
 (0)