Skip to content

Commit b110123

Browse files
innocenzibrendt
andauthored
fix(database): support semicolons in queries (#1262)
Co-authored-by: brendt <[email protected]>
1 parent 86a28c5 commit b110123

File tree

11 files changed

+104
-60
lines changed

11 files changed

+104
-60
lines changed

packages/container/src/GenericContainer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ public function config(object $config): self
151151
return $this;
152152
}
153153

154+
/**
155+
* @template TClassName of object
156+
* @param class-string<TClassName> $className
157+
* @return null|TClassName
158+
*/
154159
public function get(string $className, ?string $tag = null, mixed ...$params): ?object
155160
{
156161
$this->resolveChain();

packages/database/src/GenericDatabase.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,19 @@ public function execute(Query $query): void
3131
$bindings = $this->resolveBindings($query);
3232

3333
try {
34-
foreach (explode(';', $query->toSql()) as $sql) {
35-
if (! trim($sql)) {
36-
continue;
37-
}
34+
// foreach (explode(';', $query->toSql()) as $sql) {
35+
// if (! trim($sql)) {
36+
// continue;
37+
// }
3838

39-
$statement = $this->connection->prepare($sql . ';');
39+
$statement = $this->connection->prepare($query->toSql());
4040

41-
$statement->execute($bindings);
41+
$statement->execute($bindings);
4242

43-
$this->lastStatement = $statement;
44-
$this->lastQuery = $query;
45-
}
43+
$this->lastStatement = $statement;
44+
$this->lastQuery = $query;
45+
46+
// }
4647
} catch (PDOException $pdoException) {
4748
throw new QueryException($query, $bindings, $pdoException);
4849
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Tempest\Database;
4+
5+
interface HasLeadingStatements
6+
{
7+
public array $leadingStatements {
8+
get;
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Tempest\Database;
4+
5+
interface HasTrailingStatements
6+
{
7+
public array $trailingStatements {
8+
get;
9+
}
10+
}

packages/database/src/Migrations/MigrationManager.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
use Tempest\Database\DatabaseMigration as MigrationInterface;
1212
use Tempest\Database\DatabaseMigration;
1313
use Tempest\Database\Exceptions\QueryException;
14+
use Tempest\Database\HasLeadingStatements;
15+
use Tempest\Database\HasTrailingStatements;
1416
use Tempest\Database\Query;
1517
use Tempest\Database\QueryStatement;
18+
use Tempest\Database\QueryStatements\CompoundStatement;
1619
use Tempest\Database\QueryStatements\DropTableStatement;
1720
use Tempest\Database\QueryStatements\SetForeignKeyChecksStatement;
1821
use Tempest\Database\QueryStatements\ShowTablesStatement;
@@ -177,10 +180,31 @@ public function executeUp(MigrationInterface $migration): void
177180
return;
178181
}
179182

180-
$query = new Query($statement->compile($this->dialect));
183+
if ($statement instanceof CompoundStatement) {
184+
$statements = $statement->statements;
185+
} else {
186+
$statements = [$statement];
187+
}
188+
189+
if ($statement instanceof HasLeadingStatements) {
190+
$statements = [...$statement->leadingStatements, ...$statements];
191+
}
192+
193+
if ($statement instanceof HasTrailingStatements) {
194+
$statements = [...$statements, ...$statement->trailingStatements];
195+
}
181196

182197
try {
183-
$this->database->execute($query);
198+
foreach ($statements as $statement) {
199+
$sql = $statement->compile($this->dialect);
200+
201+
if (! trim($sql)) {
202+
continue;
203+
}
204+
205+
$query = new Query($sql);
206+
$this->database->execute($query);
207+
}
184208

185209
Migration::create(
186210
name: $migration->name,

packages/database/src/QueryStatements/AlterTableStatement.php

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,20 @@
77
use Tempest\Database\Builder\ModelDefinition;
88
use Tempest\Database\Builder\TableDefinition;
99
use Tempest\Database\Config\DatabaseDialect;
10+
use Tempest\Database\HasTrailingStatements;
1011
use Tempest\Database\QueryStatement;
1112
use Tempest\Support\Str\ImmutableString;
1213

1314
use function Tempest\Support\arr;
1415
use function Tempest\Support\str;
1516

16-
final class AlterTableStatement implements QueryStatement
17+
final class AlterTableStatement implements QueryStatement, HasTrailingStatements
1718
{
19+
private(set) array $trailingStatements = [];
20+
1821
public function __construct(
1922
private readonly string $tableName,
2023
private array $statements = [],
21-
private array $createIndexStatements = [],
2224
) {}
2325

2426
/** @param class-string $modelClass */
@@ -36,7 +38,7 @@ public function add(QueryStatement $statement): self
3638

3739
public function unique(string ...$columns): self
3840
{
39-
$this->createIndexStatements[] = new UniqueStatement(
41+
$this->trailingStatements[] = new UniqueStatement(
4042
tableName: $this->tableName,
4143
columns: $columns,
4244
);
@@ -46,7 +48,7 @@ public function unique(string ...$columns): self
4648

4749
public function index(string ...$columns): self
4850
{
49-
$this->createIndexStatements[] = new IndexStatement(
51+
$this->trailingStatements[] = new IndexStatement(
5052
tableName: $this->tableName,
5153
columns: $columns,
5254
);
@@ -102,15 +104,6 @@ public function compile(DatabaseDialect $dialect): string
102104
$alterTable = '';
103105
}
104106

105-
if ($this->createIndexStatements !== []) {
106-
$createIndices = PHP_EOL . arr($this->createIndexStatements)
107-
->map(fn (QueryStatement $queryStatement) => str($queryStatement->compile($dialect))->trim()->replace(' ', ' '))
108-
->implode(';' . PHP_EOL)
109-
->append(';');
110-
} else {
111-
$createIndices = '';
112-
}
113-
114-
return $alterTable . $createIndices;
107+
return $alterTable;
115108
}
116109
}

packages/database/src/QueryStatements/CompoundStatement.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
final readonly class CompoundStatement implements QueryStatement
1313
{
14-
private array $statements;
14+
/** @var array|\Tempest\Database\QueryStatement[] */
15+
private(set) array $statements;
1516

1617
public function __construct(QueryStatement ...$statements)
1718
{

packages/database/src/QueryStatements/CreateTableStatement.php

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,21 @@
88
use Tempest\Database\Builder\ModelDefinition;
99
use Tempest\Database\Builder\TableDefinition;
1010
use Tempest\Database\Config\DatabaseDialect;
11+
use Tempest\Database\HasTrailingStatements;
1112
use Tempest\Database\QueryStatement;
1213
use Tempest\Support\Str\ImmutableString;
1314
use UnitEnum;
1415

1516
use function Tempest\Support\arr;
1617
use function Tempest\Support\str;
1718

18-
final class CreateTableStatement implements QueryStatement
19+
final class CreateTableStatement implements QueryStatement, HasTrailingStatements
1920
{
21+
private(set) array $trailingStatements = [];
22+
2023
public function __construct(
2124
private readonly string $tableName,
2225
private array $statements = [],
23-
private array $indexStatements = [],
2426
) {}
2527

2628
/** @param class-string $modelClass */
@@ -235,7 +237,7 @@ public function set(
235237

236238
public function unique(string ...$columns): self
237239
{
238-
$this->indexStatements[] = new UniqueStatement(
240+
$this->trailingStatements[] = new UniqueStatement(
239241
tableName: $this->tableName,
240242
columns: $columns,
241243
);
@@ -245,7 +247,7 @@ public function unique(string ...$columns): self
245247

246248
public function index(string ...$columns): self
247249
{
248-
$this->indexStatements[] = new IndexStatement(
250+
$this->trailingStatements[] = new IndexStatement(
249251
tableName: $this->tableName,
250252
columns: $columns,
251253
);
@@ -275,15 +277,6 @@ public function compile(DatabaseDialect $dialect): string
275277
->toString(),
276278
);
277279

278-
if ($this->indexStatements !== []) {
279-
$createIndices = PHP_EOL . arr($this->indexStatements)
280-
->map(fn (QueryStatement $queryStatement) => str($queryStatement->compile($dialect))->trim()->replace(' ', ' '))
281-
->implode(';' . PHP_EOL)
282-
->append(';');
283-
} else {
284-
$createIndices = '';
285-
}
286-
287-
return $createTable . $createIndices;
280+
return $createTable;
288281
}
289282
}

packages/database/src/QueryStatements/DropTableStatement.php

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@
66

77
use Tempest\Database\Builder\ModelDefinition;
88
use Tempest\Database\Config\DatabaseDialect;
9+
use Tempest\Database\HasLeadingStatements;
910
use Tempest\Database\QueryStatement;
1011

11-
final class DropTableStatement implements QueryStatement
12+
final class DropTableStatement implements QueryStatement, HasLeadingStatements
1213
{
1314
use CanExecuteStatement;
1415

16+
private(set) array $leadingStatements;
17+
1518
public function __construct(
1619
private readonly string $tableName,
17-
/** @var \Tempest\Database\QueryStatements\DropConstraintStatement[] $dropReferences */
18-
private array $dropReferences = [],
1920
) {}
2021

2122
public function dropReference(string $foreign): self
2223
{
23-
$this->dropReferences[] = new DropConstraintStatement($this->tableName, $foreign);
24+
$this->leadingStatements[] = new DropConstraintStatement($this->tableName, $foreign);
2425

2526
return $this;
2627
}
@@ -33,17 +34,9 @@ public static function forModel(string $modelClass): self
3334

3435
public function compile(DatabaseDialect $dialect): string
3536
{
36-
$statements = [];
37-
38-
foreach ($this->dropReferences as $dropReference) {
39-
$statements[] = $dropReference->compile($dialect);
40-
}
41-
42-
$statements[] = match ($dialect) {
37+
return match ($dialect) {
4338
DatabaseDialect::POSTGRESQL => sprintf('DROP TABLE IF EXISTS `%s` CASCADE', $this->tableName),
4439
default => sprintf('DROP TABLE IF EXISTS `%s`', $this->tableName),
4540
};
46-
47-
return implode('; ', $statements) . ';';
4841
}
4942
}

packages/database/tests/QueryStatements/AlterTableStatementTest.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,12 @@ final class AlterTableStatementTest extends TestCase
2222
#[TestWith([DatabaseDialect::SQLITE])]
2323
public function test_alter_for_only_indexes(DatabaseDialect $dialect): void
2424
{
25-
$expected = 'CREATE INDEX `table_foo` ON `table` (`foo`); CREATE UNIQUE INDEX `table_bar` ON `table` (`bar`);';
26-
$statement = new AlterTableStatement('table')
25+
$alterStatement = new AlterTableStatement('table')
2726
->index('foo')
28-
->unique('bar')
29-
->compile($dialect);
27+
->unique('bar');
3028

31-
$normalized = self::removeDuplicateWhitespace($statement);
32-
33-
$this->assertEqualsIgnoringCase($expected, $normalized);
29+
$this->assertEqualsIgnoringCase('CREATE INDEX `table_foo` ON `table` (`foo`)', $alterStatement->trailingStatements[0]->compile($dialect));
30+
$this->assertEqualsIgnoringCase('CREATE UNIQUE INDEX `table_bar` ON `table` (`bar`)', $alterStatement->trailingStatements[1]->compile($dialect));
3431
}
3532

3633
#[TestWith([DatabaseDialect::MYSQL])]

0 commit comments

Comments
 (0)