Skip to content

Commit c2d55b6

Browse files
committed
Auto-split dotted identifiers in quoteIdentifier, move table resolution to SqlProcessor
Rename quoteIdentifier params ($identifier/$identifier2 → $name/$prefix) and auto-split on '.' when no prefix is given, eliminating redundant explode calls at 6 call sites. Move resolveTable/resolveTableWithAlias/getQuotedPrefix from TableIdentifier into SqlProcessor so TableIdentifier is a pure data object.
1 parent 5fe8adf commit c2d55b6

File tree

10 files changed

+103
-96
lines changed

10 files changed

+103
-96
lines changed

src/Adapter/Platform/AbstractPlatform.php

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
use function addcslashes;
1313
use function array_map;
14+
use function explode;
1415
use function implode;
16+
use function str_contains;
1517
use function str_replace;
1618

1719
/**
@@ -35,29 +37,33 @@ abstract class AbstractPlatform implements PlatformInterface
3537
* {@inheritDoc}
3638
*/
3739
#[Override]
38-
public function quoteIdentifier(string $identifier, ?string $identifier2 = null): string
40+
public function quoteIdentifier(string $name, ?string $prefix = null): string
3941
{
42+
if ($prefix === null && str_contains($name, '.')) {
43+
[$prefix, $name] = explode('.', $name, 2);
44+
}
45+
4046
if (! $this->quoteIdentifiers) {
41-
return $identifier2 !== null
42-
? $identifier . $this->identifierSeparator . $identifier2
43-
: $identifier;
47+
return $prefix !== null
48+
? $prefix . $this->identifierSeparator . $name
49+
: $name;
4450
}
4551

46-
if ($identifier2 !== null) {
47-
$key = $identifier . '.' . $identifier2;
52+
if ($prefix !== null) {
53+
$key = $prefix . '.' . $name;
4854
return $this->identifierCache[$key]
4955
??= $this->quoteIdentifier[0]
50-
. str_replace($this->quoteIdentifier[0], $this->quoteIdentifierTo, $identifier)
56+
. str_replace($this->quoteIdentifier[0], $this->quoteIdentifierTo, $prefix)
5157
. $this->quoteIdentifier[1]
5258
. $this->identifierSeparator
5359
. $this->quoteIdentifier[0]
54-
. str_replace($this->quoteIdentifier[0], $this->quoteIdentifierTo, $identifier2)
60+
. str_replace($this->quoteIdentifier[0], $this->quoteIdentifierTo, $name)
5561
. $this->quoteIdentifier[1];
5662
}
5763

58-
return $this->identifierCache[$identifier]
64+
return $this->identifierCache[$name]
5965
??= $this->quoteIdentifier[0]
60-
. str_replace($this->quoteIdentifier[0], $this->quoteIdentifierTo, $identifier)
66+
. str_replace($this->quoteIdentifier[0], $this->quoteIdentifierTo, $name)
6167
. $this->quoteIdentifier[1];
6268
}
6369

src/Adapter/Platform/PlatformInterface.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ public function getSqlPlatformDecorator(): SqlPlatform;
2424
public function getQuoteIdentifierSymbol(): string;
2525

2626
/**
27-
* Quote identifier — pass two segments for dot-qualified identifiers (e.g. table, column).
27+
* Quote identifier — dotted names (e.g. "table.column") are auto-split.
28+
* Pass $prefix explicitly for schema-qualified tables.
2829
*/
29-
public function quoteIdentifier(string $identifier, ?string $identifier2 = null): string;
30+
public function quoteIdentifier(string $name, ?string $prefix = null): string;
3031

3132
/**
3233
* Quote identifier chain

src/Sql/Argument/Identifier.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,11 @@
88
use PhpDb\Sql\ArgumentType;
99
use PhpDb\Sql\Part\SqlProcessor;
1010

11-
use function explode;
12-
1311
final readonly class Identifier implements ArgumentInterface
1412
{
15-
/** @var string[] Pre-split segments (e.g. ['foo','bar'] for 'foo.bar') */
16-
public array $segments;
17-
1813
public function __construct(
1914
private string $identifier
2015
) {
21-
$this->segments = explode('.', $identifier);
2216
}
2317

2418
public function getType(): ArgumentType
@@ -33,6 +27,6 @@ public function getValue(): string
3327

3428
public function render(SqlProcessor $processor, string $paramPrefix, int &$paramIndex): string
3529
{
36-
return $processor->platform->quoteIdentifier($this->segments[0], $this->segments[1] ?? null);
30+
return $processor->platform->quoteIdentifier($this->identifier);
3731
}
3832
}

src/Sql/Join.php

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
use ReturnTypeWillChange;
1414

1515
use function count;
16-
use function explode;
1716
use function implode;
18-
use function preg_replace_callback;
1917

2018
/**
2119
* Aggregate JOIN specifications.
@@ -29,9 +27,6 @@
2927
*/
3028
class Join extends AbstractPart implements Iterator, Countable
3129
{
32-
private const IDENTIFIER_PATTERN
33-
= '/\b(?!(?:AS|AND|OR|BETWEEN)\b)([a-zA-Z_]\w*+(?:\.[a-zA-Z_]\w*+)*)(?!\s*\()/i';
34-
3530
final public const JOIN_INNER = 'INNER';
3631

3732
final public const JOIN_OUTER = 'OUTER';
@@ -124,26 +119,18 @@ public function toSql(SqlProcessor $processor, string $paramPrefix = '', int &$p
124119
return null;
125120
}
126121

127-
$platform = $processor->platform;
128122
$joinSqlParts = [];
129123

130124
foreach ($this->specs as $j => $spec) {
131-
$renderedTable = $spec->table->resolveTableWithAlias($processor);
125+
$table = $processor->resolveTableWithAlias($spec->table);
132126

133127
if (! $spec->isExpressionOn) {
134-
$onClause = preg_replace_callback(
135-
self::IDENTIFIER_PATTERN,
136-
static fn($m) => $platform->quoteIdentifier(...explode('.', $m[1], 2)),
137-
$spec->on,
138-
);
128+
$onClause = $processor->renderQuotedIdentifiers($spec->on);
139129
} else {
140-
$onClause = $processor->renderExpression(
141-
$spec->on,
142-
'join' . ($j + 1) . 'part'
143-
);
130+
$onClause = $processor->renderExpression($spec->on, 'join' . ($j + 1) . 'part');
144131
}
145132

146-
$joinSqlParts[] = "{$spec->type} JOIN {$renderedTable} ON {$onClause}";
133+
$joinSqlParts[] = "{$spec->type} JOIN {$table} ON {$onClause}";
147134
}
148135

149136
return implode(' ', $joinSqlParts);

src/Sql/Part/Columns.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
use function count;
1111
use function current;
12-
use function explode;
1312
use function implode;
1413
use function is_array;
1514
use function is_numeric;
@@ -53,8 +52,7 @@ public function toSql(SqlProcessor $processor, string $paramPrefix = '', int &$p
5352
$column = $ref->column;
5453

5554
if (is_string($column)) {
56-
$parts = explode('.', $column, 2);
57-
$columnSql = $fromPrefix . $platform->quoteIdentifier($parts[0], $parts[1] ?? null);
55+
$columnSql = $fromPrefix . $platform->quoteIdentifier($column);
5856
} else {
5957
$columnSql = $processor->renderExpression($column, $ref->alias ?? 'column');
6058
}
@@ -73,7 +71,7 @@ public function toSql(SqlProcessor $processor, string $paramPrefix = '', int &$p
7371
if ($spec->columnRefs === []) {
7472
continue;
7573
}
76-
$joinPrefix = $spec->table->getQuotedPrefix($processor);
74+
$joinPrefix = $processor->getQuotedPrefix($spec->table);
7775
foreach ($spec->columnRefs as $ref) {
7876
if ($ref->isStar) {
7977
$fragments[] = $joinPrefix . '*';
@@ -83,8 +81,7 @@ public function toSql(SqlProcessor $processor, string $paramPrefix = '', int &$p
8381
$column = $ref->column;
8482

8583
if (is_string($column)) {
86-
$parts = explode('.', $column, 2);
87-
$columnSql = $joinPrefix . $platform->quoteIdentifier($parts[0], $parts[1] ?? null);
84+
$columnSql = $joinPrefix . $platform->quoteIdentifier($column);
8885
} else {
8986
$columnSql = $processor->renderExpression($column, $ref->alias ?? 'column');
9087
}

src/Sql/Part/From.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function toSql(SqlProcessor $processor, string $paramPrefix = '', int &$p
2222

2323
public function renderTable(SqlProcessor $processor): ?string
2424
{
25-
return $this->ref?->resolveTableWithAlias($processor);
25+
return $this->ref !== null ? $processor->resolveTableWithAlias($this->ref) : null;
2626
}
2727

2828
public function isEmpty(): bool
@@ -56,6 +56,6 @@ public function getQuotedPrefix(SqlProcessor $processor): string
5656
return '';
5757
}
5858

59-
return $this->ref->getQuotedPrefix($processor);
59+
return $processor->getQuotedPrefix($this->ref);
6060
}
6161
}

src/Sql/Part/GroupBy.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace PhpDb\Sql\Part;
66

7-
use function explode;
87
use function implode;
98
use function is_array;
109
use function is_string;
@@ -27,8 +26,7 @@ public function toSql(SqlProcessor $processor, string $paramPrefix = '', int &$p
2726
$column = $ref->column;
2827

2928
if (is_string($column)) {
30-
$parts = explode('.', $column, 2);
31-
$groups[] = $platform->quoteIdentifier($parts[0], $parts[1] ?? null);
29+
$groups[] = $platform->quoteIdentifier($column);
3230
} else {
3331
$groups[] = $processor->renderExpression($column);
3432
}

src/Sql/Part/OrderBy.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ public function toSql(SqlProcessor $processor, string $paramPrefix = '', int &$p
3434
$column = $spec->column;
3535

3636
if (is_string($column)) {
37-
$parts = explode('.', $column, 2);
38-
$orders[] = $platform->quoteIdentifier($parts[0], $parts[1] ?? null)
37+
$orders[] = $platform->quoteIdentifier($column)
3938
. ' ' . $spec->direction;
4039
} else {
4140
$orders[] = $processor->renderExpression($column);

src/Sql/Part/SqlProcessor.php

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,21 @@
1414
use PhpDb\Sql\Select;
1515
use PhpDb\Sql\TableIdentifier;
1616

17+
use function count;
18+
use function implode;
19+
use function is_string;
20+
use function preg_split;
1721
use function str_replace;
22+
use function strpos;
23+
use function substr;
24+
25+
use const PREG_SPLIT_DELIM_CAPTURE;
1826

1927
class SqlProcessor
2028
{
29+
private const IDENTIFIER_PATTERN
30+
= '/\b(?!(?:AS|AND|OR|BETWEEN)\b)([a-zA-Z_]\w*+(?:\.[a-zA-Z_]\w*+)*)(?!\s*\()/i';
31+
2132
private string $paramPrefix = '';
2233

2334
private int $subselectCount = 0;
@@ -92,11 +103,68 @@ public function resolveTable(Select|string|TableIdentifier|null $table): string|
92103
return $table;
93104
}
94105

95-
if ($table instanceof TableIdentifier) {
96-
return $table->resolveTable($this);
106+
if (! $table instanceof TableIdentifier) {
107+
$table = new TableIdentifier($table);
108+
}
109+
110+
return $this->resolveTableRef($table);
111+
}
112+
113+
public function resolveTableWithAlias(TableIdentifier $ref): string
114+
{
115+
$resolved = $this->resolveTableRef($ref);
116+
117+
if ($ref->getAlias() !== null) {
118+
$resolved .= ' AS ' . $this->platform->quoteIdentifier($ref->getAlias());
119+
}
120+
121+
return $resolved;
122+
}
123+
124+
public function getQuotedPrefix(TableIdentifier $ref): string
125+
{
126+
if ($ref->getAlias() !== null) {
127+
return $this->platform->quoteIdentifier($ref->getAlias())
128+
. $this->identifierSeparator;
97129
}
98130

99-
return (new TableIdentifier($table))->resolveTable($this);
131+
return $this->resolveTableRef($ref) . $this->identifierSeparator;
132+
}
133+
134+
private function resolveTableRef(TableIdentifier $ref): string
135+
{
136+
$table = $ref->getTable();
137+
138+
if (is_string($table)) {
139+
return $this->platform->quoteIdentifier(name: $table, prefix: $ref->getSchema());
140+
}
141+
142+
if ($table instanceof Select) {
143+
return '(' . $this->processSubSelect($table) . ')';
144+
}
145+
146+
$pi = 0;
147+
return $table->toSql($this, '', $pi);
148+
}
149+
150+
public function renderQuotedIdentifiers(string $part): string
151+
{
152+
$identifiers = preg_split(self::IDENTIFIER_PATTERN, $part, -1, PREG_SPLIT_DELIM_CAPTURE);
153+
$count = count($identifiers);
154+
155+
for ($idx = 1; $idx < $count; $idx += 2) {
156+
$dot = strpos($identifiers[$idx], '.');
157+
if ($dot !== false) {
158+
$identifiers[$idx] = '"' . substr($identifiers[$idx], 0, $dot) . '"."' . substr(
159+
$identifiers[$idx],
160+
$dot + 1
161+
) . '"';
162+
} else {
163+
$identifiers[$idx] = '"' . $identifiers[$idx] . '"';
164+
}
165+
}
166+
167+
return implode('', $identifiers);
100168
}
101169

102170
public function renderTable(string $table, ?string $alias = null): string
@@ -110,6 +178,7 @@ public function renderExpression(
110178
): string {
111179
if ($this->parameterContainer === null) {
112180
$paramIndex = 0;
181+
113182
return $expression->toSql($this, '', $paramIndex);
114183
}
115184

src/Sql/TableIdentifier.php

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
namespace PhpDb\Sql;
66

7-
use PhpDb\Sql\Part\SqlProcessor;
8-
97
use function current;
108
use function is_array;
119
use function is_string;
@@ -84,46 +82,4 @@ public function getTableAndSchema(): array
8482

8583
return [$this->table, $this->schema];
8684
}
87-
88-
public function resolveTable(SqlProcessor $processor): string
89-
{
90-
$table = $this->table;
91-
92-
if (is_string($table)) {
93-
$resolved = $processor->platform->quoteIdentifier($table);
94-
if ($this->schema !== null) {
95-
$resolved = $processor->platform->quoteIdentifier($this->schema)
96-
. $processor->identifierSeparator . $resolved;
97-
}
98-
return $resolved;
99-
}
100-
101-
if ($table instanceof Select) {
102-
return '(' . $processor->processSubSelect($table) . ')';
103-
}
104-
105-
$pi = 0;
106-
return $table->toSql($processor, '', $pi);
107-
}
108-
109-
public function resolveTableWithAlias(SqlProcessor $processor): string
110-
{
111-
$resolved = $this->resolveTable($processor);
112-
113-
if ($this->alias !== null) {
114-
$resolved .= ' AS ' . $processor->platform->quoteIdentifier($this->alias);
115-
}
116-
117-
return $resolved;
118-
}
119-
120-
public function getQuotedPrefix(SqlProcessor $processor): string
121-
{
122-
if ($this->alias !== null) {
123-
return $processor->platform->quoteIdentifier($this->alias)
124-
. $processor->identifierSeparator;
125-
}
126-
127-
return $this->resolveTable($processor) . $processor->identifierSeparator;
128-
}
12985
}

0 commit comments

Comments
 (0)