Skip to content

Commit 6bd2cb4

Browse files
committed
refactor(database): rename toSql to compile and extract raw sql compilation
1 parent 07e5d90 commit 6bd2cb4

18 files changed

+240
-202
lines changed

packages/database/src/Builder/QueryBuilders/CountQueryBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function bind(mixed ...$bindings): self
8484
*/
8585
public function toSql(): ImmutableString
8686
{
87-
return $this->build()->toSql();
87+
return $this->build()->compile();
8888
}
8989

9090
/**

packages/database/src/Builder/QueryBuilders/DeleteQueryBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public function bind(mixed ...$bindings): self
7373
*/
7474
public function toSql(): ImmutableString
7575
{
76-
return $this->build()->toSql();
76+
return $this->build()->compile();
7777
}
7878

7979
/**

packages/database/src/Builder/QueryBuilders/InsertQueryBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public function execute(mixed ...$bindings): ?PrimaryKey
7878
*/
7979
public function toSql(): ImmutableString
8080
{
81-
return $this->build()->toSql();
81+
return $this->build()->compile();
8282
}
8383

8484
/**

packages/database/src/Builder/QueryBuilders/SelectQueryBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ public function bind(mixed ...$bindings): self
288288
*/
289289
public function toSql(): ImmutableString
290290
{
291-
return $this->build()->toSql();
291+
return $this->build()->compile();
292292
}
293293

294294
/**

packages/database/src/Builder/QueryBuilders/UpdateQueryBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public function bind(mixed ...$bindings): self
108108
*/
109109
public function toSql(): ImmutableString
110110
{
111-
return $this->build()->toSql();
111+
return $this->build()->compile();
112112
}
113113

114114
/**

packages/database/src/Exceptions/QueryWasInvalid.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,38 @@
66

77
use Exception;
88
use PDOException;
9+
use Tempest\Core\HasContext;
910
use Tempest\Database\Query;
1011
use Tempest\Support\Json;
1112

12-
final class QueryWasInvalid extends Exception
13+
final class QueryWasInvalid extends Exception implements HasContext
1314
{
1415
public readonly PDOException $pdoException;
1516

16-
public function __construct(Query $query, array $bindings, PDOException $previous)
17-
{
17+
public function __construct(
18+
private(set) Query $query,
19+
private(set) array $bindings,
20+
PDOException $previous,
21+
) {
1822
$this->pdoException = $previous;
1923

2024
$message = $previous->getMessage();
2125

22-
$message .= PHP_EOL . PHP_EOL . $query->toSql() . PHP_EOL;
23-
26+
$message .= PHP_EOL . PHP_EOL . $query->toRawSql() . PHP_EOL;
2427
$message .= PHP_EOL . 'bindings: ' . Json\encode($bindings, pretty: true);
2528

2629
parent::__construct(
2730
message: $message,
2831
previous: $previous,
2932
);
3033
}
34+
35+
public function context(): iterable
36+
{
37+
return [
38+
'query' => $this->query,
39+
'bindings' => $this->bindings,
40+
'raw_query' => $this->query->toRawSql(),
41+
];
42+
}
3143
}

packages/database/src/GenericDatabase.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function execute(BuildsQuery|Query $query): void
4444
$bindings = $this->resolveBindings($query);
4545

4646
try {
47-
$statement = $this->connection->prepare($query->toSql()->toString());
47+
$statement = $this->connection->prepare($query->compile()->toString());
4848
$statement->execute($bindings);
4949

5050
$this->lastStatement = $statement;
@@ -56,7 +56,7 @@ public function execute(BuildsQuery|Query $query): void
5656

5757
public function getLastInsertId(): ?PrimaryKey
5858
{
59-
$sql = $this->lastQuery->toSql();
59+
$sql = $this->lastQuery->compile();
6060

6161
if (! $sql->trim()->startsWith('INSERT')) {
6262
return null;
@@ -88,7 +88,7 @@ public function fetch(BuildsQuery|Query $query): array
8888
$bindings = $this->resolveBindings($query);
8989

9090
try {
91-
$pdoQuery = $this->connection->prepare($query->toSql()->toString());
91+
$pdoQuery = $this->connection->prepare($query->compile()->toString());
9292
$pdoQuery->execute($bindings);
9393

9494
return $pdoQuery->fetchAll(PDO::FETCH_NAMED);

packages/database/src/Migrations/MigrationManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ private function getMinifiedSqlFromStatement(?QueryStatement $statement): string
323323
$query = new Query($statement->compile($this->dialect));
324324

325325
// Remove comments
326-
$sql = preg_replace('/--.*$/m', '', $query->toSql()->toString()); // Remove SQL single-line comments
326+
$sql = preg_replace('/--.*$/m', '', $query->compile()->toString()); // Remove SQL single-line comments
327327
$sql = preg_replace('/\/\*[\s\S]*?\*\//', '', $sql); // Remove block comments
328328

329329
// Remove blank lines and excessive spaces

packages/database/src/Query.php

Lines changed: 3 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ public function fetchFirst(mixed ...$bindings): ?array
6161
}
6262

6363
/**
64-
* Returns the SQL statement without the bindings.
64+
* Compile the query to a SQL statement without the bindings.
6565
*/
66-
public function toSql(): ImmutableString
66+
public function compile(): ImmutableString
6767
{
6868
$sql = $this->sql;
6969
$dialect = $this->dialect;
@@ -84,87 +84,7 @@ public function toSql(): ImmutableString
8484
*/
8585
public function toRawSql(): ImmutableString
8686
{
87-
$sql = $this->toSql();
88-
$resolvedBindings = $this->resolveBindingsForDisplay();
89-
90-
if (! array_is_list($resolvedBindings)) {
91-
return $this->replaceNamedBindings((string) $sql, $resolvedBindings);
92-
}
93-
94-
return $this->replacePositionalBindings((string) $sql, array_values($resolvedBindings));
95-
}
96-
97-
private function replaceNamedBindings(string $sql, array $bindings): ImmutableString
98-
{
99-
foreach ($bindings as $key => $value) {
100-
$placeholder = ':' . $key;
101-
$formattedValue = $this->formatValueForSql($value);
102-
$sql = str_replace($placeholder, $formattedValue, $sql);
103-
}
104-
105-
return new ImmutableString($sql);
106-
}
107-
108-
private function replacePositionalBindings(string $sql, array $bindings): ImmutableString
109-
{
110-
$bindingIndex = 0;
111-
$result = '';
112-
$length = strlen($sql);
113-
114-
for ($i = 0; $i < $length; $i++) {
115-
if ($sql[$i] === '?' && $bindingIndex < count($bindings)) {
116-
$value = $bindings[$bindingIndex];
117-
$result .= $this->formatValueForSql($value);
118-
$bindingIndex++;
119-
} else {
120-
$result .= $sql[$i];
121-
}
122-
}
123-
124-
return new ImmutableString($result);
125-
}
126-
127-
private function resolveBindingsForDisplay(): array
128-
{
129-
$bindings = [];
130-
131-
foreach ($this->bindings as $key => $value) {
132-
if (is_bool($value)) {
133-
$value = match ($this->dialect) {
134-
DatabaseDialect::POSTGRESQL => $value ? 'true' : 'false',
135-
default => $value ? '1' : '0',
136-
};
137-
}
138-
139-
if ($value instanceof Query) {
140-
$value = '(' . $value->toRawSql() . ')';
141-
}
142-
143-
$bindings[$key] = $value;
144-
}
145-
146-
return $bindings;
147-
}
148-
149-
private function formatValueForSql(mixed $value): string
150-
{
151-
if ($value === null) {
152-
return 'NULL';
153-
}
154-
155-
if (is_string($value)) {
156-
if (str_starts_with($value, '(') && str_ends_with($value, ')')) {
157-
return $value;
158-
}
159-
160-
return "'" . str_replace("'", "''", $value) . "'";
161-
}
162-
163-
if (is_numeric($value)) {
164-
return (string) $value;
165-
}
166-
167-
return "'" . str_replace("'", "''", (string) $value) . "'";
87+
return new RawSql($this->dialect, (string) $this->compile(), $this->bindings)->toImmutableString();
16888
}
16989

17090
public function append(string $append): self

packages/database/src/RawSql.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace Tempest\Database;
4+
5+
use Tempest\Database\Config\DatabaseDialect;
6+
use Tempest\Support\Str\ImmutableString;
7+
8+
final class RawSql
9+
{
10+
public function __construct(
11+
private(set) DatabaseDialect $dialect,
12+
private(set) string $sql,
13+
private(set) array $bindings,
14+
) {}
15+
16+
public function compile(): string
17+
{
18+
$resolvedBindings = $this->resolveBindingsForDisplay();
19+
20+
if (! array_is_list($resolvedBindings)) {
21+
return $this->replaceNamedBindings($this->sql, $resolvedBindings);
22+
}
23+
24+
return $this->replacePositionalBindings($this->sql, array_values($resolvedBindings));
25+
}
26+
27+
public function toImmutableString(): ImmutableString
28+
{
29+
return new ImmutableString($this->compile());
30+
}
31+
32+
public function __toString(): string
33+
{
34+
return $this->compile();
35+
}
36+
37+
private function replaceNamedBindings(string $sql, array $bindings): string
38+
{
39+
foreach ($bindings as $key => $value) {
40+
$placeholder = ':' . $key;
41+
$formattedValue = $this->formatValueForSql($value);
42+
$sql = str_replace($placeholder, $formattedValue, $sql);
43+
}
44+
45+
return $sql;
46+
}
47+
48+
private function replacePositionalBindings(string $sql, array $bindings): string
49+
{
50+
$bindingIndex = 0;
51+
$result = '';
52+
$length = strlen($sql);
53+
54+
for ($i = 0; $i < $length; $i++) {
55+
if ($sql[$i] === '?' && $bindingIndex < count($bindings)) {
56+
$value = $bindings[$bindingIndex];
57+
$result .= $this->formatValueForSql($value);
58+
$bindingIndex++;
59+
} else {
60+
$result .= $sql[$i];
61+
}
62+
}
63+
64+
return $result;
65+
}
66+
67+
private function resolveBindingsForDisplay(): array
68+
{
69+
$bindings = [];
70+
71+
foreach ($this->bindings as $key => $value) {
72+
if (is_bool($value)) {
73+
$value = match ($this->dialect) {
74+
DatabaseDialect::POSTGRESQL => $value ? 'true' : 'false',
75+
default => $value ? '1' : '0',
76+
};
77+
}
78+
79+
if ($value instanceof Query) {
80+
$value = '(' . $value->toRawSql() . ')';
81+
}
82+
83+
$bindings[$key] = $value;
84+
}
85+
86+
return $bindings;
87+
}
88+
89+
private function formatValueForSql(mixed $value): string
90+
{
91+
if ($value === null) {
92+
return 'NULL';
93+
}
94+
95+
if (is_string($value)) {
96+
if (str_starts_with($value, '(') && str_ends_with($value, ')')) {
97+
return $value;
98+
}
99+
100+
return "'" . str_replace("'", "''", $value) . "'";
101+
}
102+
103+
if (is_numeric($value)) {
104+
return (string) $value;
105+
}
106+
107+
return "'" . str_replace("'", "''", (string) $value) . "'";
108+
}
109+
}

0 commit comments

Comments
 (0)