Skip to content

Commit 7af9a12

Browse files
committed
Merge branch '4.3.x' into 5.0.x
2 parents 3363654 + 69d5e34 commit 7af9a12

File tree

7 files changed

+258
-60
lines changed

7 files changed

+258
-60
lines changed

UPGRADE.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ all drivers and middleware.
8484

8585
# Upgrade to 4.3
8686

87+
## Deprecated `AbstractAsset::isIdentifierQuoted()`
88+
89+
The `AbstractAsset::isIdentifierQuoted()` method has been deprecated. Parse the name and introspect its identifiers
90+
individually using `Identifier::isQuoted()` instead.
91+
92+
## Deprecated mixing unqualified and qualified names in a schema without a default namespace
93+
94+
If a schema lacks a default namespace configuration and has at least one object with an unqualified name, adding or
95+
referencing objects with qualified names is deprecated.
96+
97+
If a schema lacks a default namespace configuration and has at least one object with a qualified name, adding or
98+
referencing objects with unqualified names is deprecated.
99+
100+
Mixing unqualified and qualified names is permitted as long as the schema is configured to use a default namespace. In
101+
this case, the default namespace will be used to resolve unqualified names.
102+
87103
## Deprecated `AbstractAsset::getQuotedName()`
88104

89105
The `AbstractAsset::getQuotedName()` method has been deprecated. Use `NamedObject::getObjectName()` or

psalm.xml.dist

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@
6868
TODO: remove in 5.0.0
6969
-->
7070
<referencedMethod name="Doctrine\DBAL\Schema\AbstractAsset::getQuotedName" />
71+
72+
<!--
73+
https://github.com/doctrine/dbal/pull/6677
74+
TODO: remove in 5.0.0
75+
-->
76+
<referencedMethod name="Doctrine\DBAL\Schema\AbstractAsset::isIdentifierQuoted" />
7177
</errorLevel>
7278
</DeprecatedMethod>
7379
<DocblockTypeContradiction>

src/Schema/AbstractAsset.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName;
1212
use Doctrine\DBAL\Schema\Name\Parser;
1313
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
14+
use Doctrine\Deprecations\Deprecation;
1415

1516
use function array_map;
1617
use function assert;
@@ -119,9 +120,19 @@ public function isQuoted(): bool
119120

120121
/**
121122
* Checks if this identifier is quoted.
123+
*
124+
* @deprecated Parse the name and introspect its identifiers individually using {@see Identifier::isQuoted()}
125+
* instead.
122126
*/
123127
protected function isIdentifierQuoted(string $identifier): bool
124128
{
129+
Deprecation::triggerIfCalledFromOutside(
130+
'doctrine/dbal',
131+
'https://github.com/doctrine/dbal/pull/6677',
132+
'%s is deprecated and will be removed in 5.0.',
133+
__METHOD__,
134+
);
135+
125136
return isset($identifier[0]) && ($identifier[0] === '`' || $identifier[0] === '"' || $identifier[0] === '[');
126137
}
127138

src/Schema/SQLServerSchemaManager.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,15 @@ public function createComparator(/* ComparatorConfig $config = new ComparatorCon
273273
);
274274
}
275275

276+
public function createSchemaConfig(): SchemaConfig
277+
{
278+
$config = parent::createSchemaConfig();
279+
280+
$config->setName($this->getCurrentSchemaName());
281+
282+
return $config;
283+
}
284+
276285
/** @throws Exception */
277286
private function getDatabaseCollation(): string
278287
{
@@ -291,6 +300,15 @@ private function getDatabaseCollation(): string
291300
return $this->databaseCollation;
292301
}
293302

303+
/** @throws Exception */
304+
private function getCurrentSchemaName(): ?string
305+
{
306+
$schemaName = $this->connection->fetchOne('SELECT SCHEMA_NAME()');
307+
assert($schemaName !== false);
308+
309+
return $schemaName;
310+
}
311+
294312
protected function selectTableNames(string $databaseName): Result
295313
{
296314
// The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams

src/Schema/Schema.php

Lines changed: 110 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,24 @@
66

77
use Doctrine\DBAL\Exception;
88
use Doctrine\DBAL\Platforms\AbstractPlatform;
9+
use Doctrine\DBAL\Schema\Exception\InvalidName;
910
use Doctrine\DBAL\Schema\Exception\NamespaceAlreadyExists;
1011
use Doctrine\DBAL\Schema\Exception\SequenceAlreadyExists;
1112
use Doctrine\DBAL\Schema\Exception\SequenceDoesNotExist;
1213
use Doctrine\DBAL\Schema\Exception\TableAlreadyExists;
1314
use Doctrine\DBAL\Schema\Exception\TableDoesNotExist;
15+
use Doctrine\DBAL\Schema\Name\Identifier;
1416
use Doctrine\DBAL\Schema\Name\OptionallyQualifiedName;
17+
use Doctrine\DBAL\Schema\Name\Parser;
1518
use Doctrine\DBAL\Schema\Name\Parser\UnqualifiedNameParser;
1619
use Doctrine\DBAL\Schema\Name\Parsers;
1720
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
1821
use Doctrine\DBAL\SQL\Builder\CreateSchemaObjectsSQLBuilder;
1922
use Doctrine\DBAL\SQL\Builder\DropSchemaObjectsSQLBuilder;
23+
use Doctrine\Deprecations\Deprecation;
2024

2125
use function array_values;
22-
use function str_contains;
26+
use function count;
2327
use function strtolower;
2428

2529
/**
@@ -35,10 +39,16 @@
3539
* Every asset in the doctrine schema has a name. A name consists of either a
3640
* namespace.local name pair or just a local unqualified name.
3741
*
38-
* The abstraction layer that covers a PostgreSQL schema is the namespace of an
42+
* Objects in a schema can be referenced by unqualified names or qualified
43+
* names but not both. Whether a given schema uses qualified or unqualified
44+
* names is determined at runtime by the presence of objects with unqualified
45+
* names and namespaces.
46+
*
47+
* The abstraction layer that covers a PostgreSQL schema is the namespace of a
3948
* database object (asset). A schema can have a name, which will be used as
4049
* default namespace for the unqualified database objects that are created in
41-
* the schema.
50+
* the schema. If a schema uses qualified names and has a name, unqualified
51+
* names will be resolved against the corresponding namespace.
4252
*
4353
* In the case of MySQL where cross-database queries are allowed this leads to
4454
* databases being "misinterpreted" as namespaces. This is intentional, however
@@ -65,6 +75,12 @@ class Schema extends AbstractOptionallyNamedObject
6575

6676
protected SchemaConfig $_schemaConfig;
6777

78+
/**
79+
* Indicates whether the schema uses unqualified names for its objects. Once this flag is set to true, it won't be
80+
* unset even after the objects with unqualified names have been dropped from the schema.
81+
*/
82+
private bool $usesUnqualifiedNames = false;
83+
6884
/**
6985
* @param array<Table> $tables
7086
* @param array<Sequence> $sequences
@@ -104,39 +120,39 @@ protected function getNameParser(): UnqualifiedNameParser
104120

105121
protected function _addTable(Table $table): void
106122
{
107-
$name = $table->getObjectName();
123+
$resolvedName = $this->resolveName($table->getObjectName());
108124

109-
$normalizedName = $this->normalizeName($name);
125+
$key = $this->getKeyFromResolvedName($resolvedName);
110126

111-
if (isset($this->_tables[$normalizedName])) {
112-
throw TableAlreadyExists::new($normalizedName);
127+
if (isset($this->_tables[$key])) {
128+
throw TableAlreadyExists::new($resolvedName->toString());
113129
}
114130

115-
$this->ensureNamespaceExists($name);
131+
$this->registerQualifier($resolvedName->getQualifier());
116132

117-
$this->_tables[$normalizedName] = $table;
133+
$this->_tables[$key] = $table;
118134
}
119135

120136
protected function _addSequence(Sequence $sequence): void
121137
{
122-
$name = $sequence->getObjectName();
138+
$resolvedName = $this->resolveName($sequence->getObjectName());
123139

124-
$normalizedName = $this->normalizeName($name);
140+
$key = $this->getKeyFromResolvedName($resolvedName);
125141

126-
if (isset($this->_sequences[$normalizedName])) {
127-
throw SequenceAlreadyExists::new($normalizedName);
142+
if (isset($this->_sequences[$key])) {
143+
throw SequenceAlreadyExists::new($resolvedName->toString());
128144
}
129145

130-
$this->ensureNamespaceExists($name);
146+
$this->registerQualifier($resolvedName->getQualifier());
131147

132-
$this->_sequences[$normalizedName] = $sequence;
148+
$this->_sequences[$key] = $sequence;
133149
}
134150

135-
private function ensureNamespaceExists(OptionallyQualifiedName $name): void
151+
private function registerQualifier(?Identifier $qualifier): void
136152
{
137-
$qualifier = $name->getQualifier();
138-
139153
if ($qualifier === null) {
154+
$this->usesUnqualifiedNames = true;
155+
140156
return;
141157
}
142158

@@ -175,41 +191,84 @@ public function getTables(): array
175191

176192
public function getTable(string $name): Table
177193
{
178-
$name = $this->getFullQualifiedAssetName($name);
179-
if (! isset($this->_tables[$name])) {
194+
$key = $this->getKeyFromName($name);
195+
if (! isset($this->_tables[$key])) {
180196
throw TableDoesNotExist::new($name);
181197
}
182198

183-
return $this->_tables[$name];
199+
return $this->_tables[$key];
184200
}
185201

186-
private function getFullQualifiedAssetName(string $name): string
202+
/**
203+
* Returns the key that will be used to store the given object in a collection of such objects based on its name.
204+
*
205+
* If the schema uses unqualified names, the object name must be unqualified. If the schema uses qualified names,
206+
* the object name must be qualified.
207+
*
208+
* The resulting key is the lower-cased full object name. Lower-casing is
209+
* actually wrong, but we have to do it to keep our sanity. If you are
210+
* using database objects that only differentiate in the casing (FOO vs
211+
* Foo) then you will NOT be able to use Doctrine Schema abstraction.
212+
*/
213+
private function getKeyFromResolvedName(OptionallyQualifiedName $name): string
187214
{
188-
$name = $this->getUnquotedAssetName($name);
215+
$key = $name->getUnqualifiedName()->getValue();
216+
$qualifier = $name->getQualifier();
189217

190-
if (! str_contains($name, '.')) {
191-
$name = $this->getName() . '.' . $name;
218+
if ($qualifier !== null) {
219+
if ($this->usesUnqualifiedNames) {
220+
Deprecation::trigger(
221+
'doctrine/dbal',
222+
'https://github.com/doctrine/dbal/pull/6677#user-content-qualified-names',
223+
'Using qualified names to create or reference objects in a schema that uses unqualified '
224+
. 'names is deprecated.',
225+
);
226+
}
227+
228+
$key = $qualifier->getValue() . '.' . $key;
229+
} elseif (count($this->namespaces) > 0) {
230+
Deprecation::trigger(
231+
'doctrine/dbal',
232+
'https://github.com/doctrine/dbal/pull/6677#user-content-unqualified-names',
233+
'Using unqualified names to create or reference objects in a schema that uses qualified '
234+
. 'names and lacks a default namespace configuration is deprecated.',
235+
);
192236
}
193237

194-
return strtolower($name);
238+
return strtolower($key);
195239
}
196240

197241
/**
198-
* The normalized name is qualified and lower-cased. Lower-casing is
199-
* actually wrong, but we have to do it to keep our sanity. If you are
200-
* using database objects that only differentiate in the casing (FOO vs
201-
* Foo) then you will NOT be able to use Doctrine Schema abstraction.
242+
* Returns the key that will be used to store the given object with the given name in a collection of such objects.
202243
*
203-
* Every non-namespaced element is prefixed with this schema name.
244+
* If the schema configuration has the default namespace, an unqualified name will be resolved to qualified against
245+
* that namespace.
204246
*/
205-
private function normalizeName(OptionallyQualifiedName $name): string
247+
private function getKeyFromName(string $input): string
206248
{
207-
$namespaceName = $name->getQualifier()?->getValue()
208-
?? $this->getName();
249+
$parser = Parsers::getOptionallyQualifiedNameParser();
250+
251+
try {
252+
$name = $parser->parse($input);
253+
} catch (Parser\Exception $e) {
254+
throw InvalidName::fromParserException($input, $e);
255+
}
256+
257+
return $this->getKeyFromResolvedName(
258+
$this->resolveName($name),
259+
);
260+
}
209261

210-
$name = $namespaceName . '.' . $name->getUnqualifiedName()->getValue();
262+
/**
263+
* Resolves the qualified or unqualified name against the current schema name and returns a qualified name.
264+
*/
265+
private function resolveName(OptionallyQualifiedName $name): OptionallyQualifiedName
266+
{
267+
if ($name->getQualifier() === null && $this->name !== null) {
268+
return new OptionallyQualifiedName($name->getUnqualifiedName(), $this->name->getIdentifier());
269+
}
211270

212-
return strtolower($name);
271+
return $name;
213272
}
214273

215274
/**
@@ -239,26 +298,26 @@ public function hasNamespace(string $name): bool
239298
*/
240299
public function hasTable(string $name): bool
241300
{
242-
$name = $this->getFullQualifiedAssetName($name);
301+
$key = $this->getKeyFromName($name);
243302

244-
return isset($this->_tables[$name]);
303+
return isset($this->_tables[$key]);
245304
}
246305

247306
public function hasSequence(string $name): bool
248307
{
249-
$name = $this->getFullQualifiedAssetName($name);
308+
$key = $this->getKeyFromName($name);
250309

251-
return isset($this->_sequences[$name]);
310+
return isset($this->_sequences[$key]);
252311
}
253312

254313
public function getSequence(string $name): Sequence
255314
{
256-
$name = $this->getFullQualifiedAssetName($name);
257-
if (! $this->hasSequence($name)) {
315+
$key = $this->getKeyFromName($name);
316+
if (! isset($this->_sequences[$key])) {
258317
throw SequenceDoesNotExist::new($name);
259318
}
260319

261-
return $this->_sequences[$name];
320+
return $this->_sequences[$key];
262321
}
263322

264323
/** @return list<Sequence> */
@@ -326,9 +385,12 @@ public function renameTable(string $oldName, string $newName): self
326385
*/
327386
public function dropTable(string $name): self
328387
{
329-
$name = $this->getFullQualifiedAssetName($name);
330-
$this->getTable($name);
331-
unset($this->_tables[$name]);
388+
$key = $this->getKeyFromName($name);
389+
if (! isset($this->_tables[$key])) {
390+
throw TableDoesNotExist::new($name);
391+
}
392+
393+
unset($this->_tables[$key]);
332394

333395
return $this;
334396
}
@@ -347,8 +409,8 @@ public function createSequence(string $name, int $allocationSize = 1, int $initi
347409
/** @return $this */
348410
public function dropSequence(string $name): self
349411
{
350-
$name = $this->getFullQualifiedAssetName($name);
351-
unset($this->_sequences[$name]);
412+
$key = $this->getKeyFromName($name);
413+
unset($this->_sequences[$key]);
352414

353415
return $this;
354416
}

0 commit comments

Comments
 (0)