Skip to content

Commit 8a454d8

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 832ef28 commit 8a454d8

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
@@ -126,9 +126,10 @@ public function down(Schema $schema): void
126126
protected function addSql(
127127
string $sql,
128128
array $params = [],
129-
array $types = []
129+
array $types = [],
130+
bool $executeAsStatement = false
130131
): void {
131-
$this->plannedSql[] = new Query($sql, $params, $types);
132+
$this->plannedSql[] = new Query($sql, $params, $types, $executeAsStatement);
132133
}
133134

134135
/** @return Query[] */

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
@@ -290,8 +290,13 @@ private function executeResult(MigratorConfiguration $configuration): void
290290
$this->outputSqlQuery($query, $configuration);
291291

292292
$stopwatchEvent = $this->stopwatch->start('query');
293-
// executeQuery() must be used here because $query might return a result set, for instance REPAIR does
294-
$this->connection->executeQuery($query->getStatement(), $query->getParameters(), $query->getTypes());
293+
294+
if ($query->getExecuteAsStatement()) {
295+
$this->connection->executeStatement($query->getStatement(), $query->getParameters(), $query->getTypes());
296+
} else {
297+
$this->connection->executeQuery($query->getStatement(), $query->getParameters(), $query->getTypes());
298+
}
299+
295300
$stopwatchEvent->stop();
296301

297302
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
@@ -214,6 +234,7 @@ public function testExecuteDryRun(): void
214234
self::assertSame(Direction::UP, $result->getDirection());
215235

216236
yield new Query('INSERT INTO doctrine_migration_versions (version, executed_at, execution_time) VALUE (' . $result->getVersion() . ', NOW(), 0)');
237+
yield new Query('CREATE TRIGGER something...', executeAsStatement: true);
217238
});
218239

219240
$this->connection
@@ -224,6 +245,10 @@ public function testExecuteDryRun(): void
224245
->expects(self::never())
225246
->method('executeUpdate');
226247

248+
$this->connection
249+
->expects(self::never())
250+
->method('executeStatement');
251+
227252
$migratorConfiguration = (new MigratorConfiguration())
228253
->setDryRun(true)
229254
->setTimeAllQueries(true);
@@ -237,7 +262,7 @@ public function testExecuteDryRun(): void
237262

238263
$queries = $result->getSql();
239264

240-
self::assertCount(3, $queries);
265+
self::assertCount(4, $queries);
241266
self::assertSame('SELECT 1', $queries[0]->getStatement());
242267
self::assertSame([1], $queries[0]->getParameters());
243268
self::assertSame([3], $queries[0]->getTypes());
@@ -250,6 +275,11 @@ public function testExecuteDryRun(): void
250275
self::assertSame([], $queries[2]->getParameters());
251276
self::assertSame([], $queries[2]->getTypes());
252277

278+
self::assertSame('CREATE TRIGGER something...', $queries[3]->getStatement());
279+
self::assertSame([], $queries[3]->getParameters());
280+
self::assertSame([], $queries[3]->getTypes());
281+
self::assertTrue($queries[3]->getExecuteAsStatement());
282+
253283
self::assertNotNull($result->getTime());
254284
self::assertSame(State::NONE, $result->getState());
255285
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)