Failed Conditions
Pull Request — develop (#3348)
by Sergei
65:27
created

Schema::createSequence()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 1
1
<?php
2
3
namespace Doctrine\DBAL\Schema;
4
5
use Doctrine\DBAL\Platforms\AbstractPlatform;
6
use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector;
7
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
8
use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor;
9
use Doctrine\DBAL\Schema\Visitor\Visitor;
10
use function array_keys;
11
use function assert;
12
use function is_string;
13
use function strpos;
14
use function strtolower;
15
16
/**
17
 * Object representation of a database schema.
18
 *
19
 * Different vendors have very inconsistent naming with regard to the concept
20
 * of a "schema". Doctrine understands a schema as the entity that conceptually
21
 * wraps a set of database objects such as tables, sequences, indexes and
22
 * foreign keys that belong to each other into a namespace. A Doctrine Schema
23
 * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more
24
 * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL.
25
 *
26
 * Every asset in the doctrine schema has a name. A name consists of either a
27
 * namespace.local name pair or just a local unqualified name.
28
 *
29
 * The abstraction layer that covers a PostgreSQL schema is the namespace of an
30
 * database object (asset). A schema can have a name, which will be used as
31
 * default namespace for the unqualified database objects that are created in
32
 * the schema.
33
 *
34
 * In the case of MySQL where cross-database queries are allowed this leads to
35
 * databases being "misinterpreted" as namespaces. This is intentional, however
36
 * the CREATE/DROP SQL visitors will just filter this queries and do not
37
 * execute them. Only the queries for the currently connected database are
38
 * executed.
39
 */
40
class Schema extends AbstractAsset
41
{
42
    /**
43
     * The namespaces in this schema.
44
     *
45
     * @var string[]
46
     */
47
    private $namespaces = [];
48
49
    /** @var Table[] */
50
    protected $_tables = [];
51
52
    /** @var Sequence[] */
53
    protected $_sequences = [];
54
55
    /** @var SchemaConfig */
56
    protected $_schemaConfig = false;
57
58
    /**
59
     * @param Table[]    $tables
60
     * @param Sequence[] $sequences
61 1592
     * @param string[]   $namespaces
62
     */
63
    public function __construct(
64
        array $tables = [],
65
        array $sequences = [],
66
        ?SchemaConfig $schemaConfig = null,
67 1592
        array $namespaces = []
68 1385
    ) {
69
        if ($schemaConfig === null) {
70 1592
            $schemaConfig = new SchemaConfig();
71 1592
        }
72
        $this->_schemaConfig = $schemaConfig;
73 1592
        $this->_setName($schemaConfig->getName() ?: 'public');
74 8
75
        foreach ($namespaces as $namespace) {
76
            $this->createNamespace($namespace);
77 1592
        }
78 437
79
        foreach ($tables as $table) {
80
            $this->_addTable($table);
81 1569
        }
82 101
83
        foreach ($sequences as $sequence) {
84 1546
            $this->_addSequence($sequence);
85
        }
86
    }
87
88
    public function getName() : string
89
    {
90
        $name = parent::getName();
91
        assert(is_string($name));
92
93
        return $name;
94
    }
95
96
    public function hasExplicitForeignKeyIndexes() : bool
97
    {
98
        return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
99 1247
    }
100
101 1247
    /**
102 1247
     * @throws SchemaException
103
     */
104 1247
    protected function _addTable(Table $table) : void
105 23
    {
106
        $namespaceName = $table->getNamespaceName();
107
        $tableName     = $table->getFullQualifiedName($this->getName());
108 1247
109 184
        if (isset($this->_tables[$tableName])) {
110
            throw SchemaException::tableAlreadyExists($tableName);
111
        }
112 1247
113 1247
        if ($namespaceName !== null
114 1247
            && ! $table->isInDefaultNamespace($this->getName())
115
            && ! $this->hasNamespace($namespaceName)) {
116
            $this->createNamespace($namespaceName);
117
        }
118
119
        $this->_tables[$tableName] = $table;
120
        $table->setSchemaConfig($this->_schemaConfig);
121 400
    }
122
123 400
    /**
124 400
     * @throws SchemaException
125
     */
126 400
    protected function _addSequence(Sequence $sequence) : void
127 23
    {
128
        $namespaceName = $sequence->getNamespaceName();
129
        $seqName       = $sequence->getFullQualifiedName($this->getName());
130 400
131 23
        if (isset($this->_sequences[$seqName])) {
132
            throw SchemaException::sequenceAlreadyExists($seqName);
133
        }
134 400
135 400
        if ($namespaceName !== null
136
            && ! $sequence->isInDefaultNamespace($this->getName())
137
            && ! $this->hasNamespace($namespaceName)) {
138
            $this->createNamespace($namespaceName);
139
        }
140
141
        $this->_sequences[$seqName] = $sequence;
142 621
    }
143
144 621
    /**
145
     * Returns the namespaces of this schema.
146
     *
147
     * @return string[] A list of namespace names.
148
     */
149
    public function getNamespaces() : array
150
    {
151
        return $this->namespaces;
152 690
    }
153
154 690
    /**
155
     * Gets all tables of this schema.
156
     *
157
     * @return Table[]
158
     */
159
    public function getTables() : array
160
    {
161
        return $this->_tables;
162
    }
163
164 759
    /**
165
     * @throws SchemaException
166 759
     */
167 759
    public function getTable(string $tableName) : Table
168 23
    {
169
        $tableName = $this->getFullQualifiedAssetName($tableName);
170
        if (! isset($this->_tables[$tableName])) {
171 736
            throw SchemaException::tableDoesNotExist($tableName);
172
        }
173
174
        return $this->_tables[$tableName];
175
    }
176
177
    private function getFullQualifiedAssetName(string $name) : string
178
    {
179 1058
        $name = $this->getUnquotedAssetName($name);
180
181 1058
        if (strpos($name, '.') === false) {
182
            $name = $this->getName() . '.' . $name;
183 1058
        }
184 966
185
        return strtolower($name);
186
    }
187 1058
188
    /**
189
     * Returns the unquoted representation of a given asset name.
190
     *
191
     * @param string $assetName Quoted or unquoted representation of an asset name.
192
     */
193
    private function getUnquotedAssetName(string $assetName) : string
194
    {
195
        if ($this->isIdentifierQuoted($assetName)) {
196
            return $this->trimQuotes($assetName);
197 1173
        }
198
199 1173
        return $assetName;
200 46
    }
201
202
    /**
203 1150
     * Does this schema have a namespace with the given name?
204
     */
205
    public function hasNamespace(string $namespaceName) : bool
206
    {
207
        $namespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
208
209
        return isset($this->namespaces[$namespaceName]);
210
    }
211
212
    /**
213 236
     * Does this schema have a table with the given name?
214
     */
215 236
    public function hasTable(string $tableName) : bool
216
    {
217 236
        $tableName = $this->getFullQualifiedAssetName($tableName);
218
219
        return isset($this->_tables[$tableName]);
220
    }
221
222
    /**
223
     * Gets all table names, prefixed with a schema name, even the default one if present.
224
     *
225
     * @return string[]
226
     */
227 736
    public function getTableNames() : array
228
    {
229 736
        return array_keys($this->_tables);
230
    }
231 736
232
    public function hasSequence(string $sequenceName) : bool
233
    {
234
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
235
236
        return isset($this->_sequences[$sequenceName]);
237
    }
238
239
    /**
240
     * @throws SchemaException
241
     */
242
    public function getSequence(string $sequenceName) : Sequence
243
    {
244
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
245
        if (! $this->hasSequence($sequenceName)) {
246
            throw SchemaException::sequenceDoesNotExist($sequenceName);
247
        }
248
249 299
        return $this->_sequences[$sequenceName];
250
    }
251 299
252
    /**
253 299
     * @return Sequence[]
254
     */
255
    public function getSequences() : array
256
    {
257
        return $this->_sequences;
258
    }
259
260
    /**
261
     * Creates a new namespace.
262
     *
263 207
     * @param string $namespaceName The name of the namespace to create.
264
     *
265 207
     * @return $this
266 207
     *
267 23
     * @throws SchemaException
268
     */
269
    public function createNamespace(string $namespaceName) : self
270 184
    {
271
        $unquotedNamespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
272
273
        if (isset($this->namespaces[$unquotedNamespaceName])) {
274
            throw SchemaException::namespaceAlreadyExists($unquotedNamespaceName);
275
        }
276 667
277
        $this->namespaces[$unquotedNamespaceName] = $namespaceName;
278 667
279
        return $this;
280
    }
281
282
    /**
283
     * Creates a new table.
284
     */
285
    public function createTable(string $tableName) : Table
286
    {
287
        $table = new Table($tableName);
288
        $this->_addTable($table);
289
290 261
        foreach ($this->_schemaConfig->getDefaultTableOptions() as $name => $value) {
291
            $table->addOption($name, $value);
292 261
        }
293
294 261
        return $table;
295 23
    }
296
297
    /**
298 261
     * Renames a table.
299
     *
300 261
     * @return $this
301
     */
302
    public function renameTable(string $oldTableName, string $newTableName) : self
303
    {
304
        $table = $this->getTable($oldTableName);
305
        $table->_setName($newTableName);
306
307
        $this->dropTable($oldTableName);
308
        $this->_addTable($table);
309
310 856
        return $this;
311
    }
312 856
313 856
    /**
314
     * Drops a table from the schema.
315 856
     *
316
     * @return $this
317
     */
318
    public function dropTable(string $tableName) : self
319 856
    {
320
        $tableName = $this->getFullQualifiedAssetName($tableName);
321
        $this->getTable($tableName);
322
        unset($this->_tables[$tableName]);
323
324
        return $this;
325
    }
326
327
    /**
328
     * Creates a new sequence.
329
     */
330 23
    public function createSequence(string $sequenceName, int $allocationSize = 1, int $initialValue = 1) : Sequence
331
    {
332 23
        $seq = new Sequence($sequenceName, $allocationSize, $initialValue);
333 23
        $this->_addSequence($seq);
334
335 23
        return $seq;
336 23
    }
337
338 23
    /**
339
     * @return $this
340
     */
341
    public function dropSequence(string $sequenceName) : self
342
    {
343
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
344
        unset($this->_sequences[$sequenceName]);
345
346
        return $this;
347
    }
348 115
349
    /**
350 115
     * Returns an array of necessary SQL queries to create the schema on the given platform.
351 115
     *
352 115
     * @return string[]
353
     */
354 115
    public function toSql(AbstractPlatform $platform) : array
355
    {
356
        $sqlCollector = new CreateSchemaSqlCollector($platform);
357
        $this->visit($sqlCollector);
358
359
        return $sqlCollector->getQueries();
360
    }
361
362
    /**
363
     * Return an array of necessary SQL queries to drop the schema on the given platform.
364
     *
365
     * @return string[]
366 299
     */
367
    public function toDropSql(AbstractPlatform $platform) : array
368 299
    {
369 299
        $dropSqlCollector = new DropSchemaSqlCollector($platform);
370
        $this->visit($dropSqlCollector);
371 299
372
        return $dropSqlCollector->getQueries();
373
    }
374
375
    /**
376
     * @return string[]
377
     */
378
    public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform) : array
379 23
    {
380
        $comparator = new Comparator();
381 23
        $schemaDiff = $comparator->compare($this, $toSchema);
382 23
383
        return $schemaDiff->toSql($platform);
384 23
    }
385
386
    /**
387
     * @return string[]
388
     */
389
    public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform) : array
390
    {
391
        $comparator = new Comparator();
392 267
        $schemaDiff = $comparator->compare($fromSchema, $this);
393
394 267
        return $schemaDiff->toSql($platform);
395 267
    }
396
397 267
    public function visit(Visitor $visitor) : void
398
    {
399
        $visitor->acceptSchema($this);
400
401
        if ($visitor instanceof NamespaceVisitor) {
402
            foreach ($this->namespaces as $namespace) {
403
                $visitor->acceptNamespace($namespace);
404
            }
405 23
        }
406
407 23
        foreach ($this->_tables as $table) {
408 23
            $table->visit($visitor);
409
        }
410 23
411
        foreach ($this->_sequences as $sequence) {
412
            $sequence->visit($visitor);
413
        }
414
    }
415
416
    /**
417
     * Cloning a Schema triggers a deep clone of all related assets.
418
     *
419
     * @return void
420
     */
421
    public function __clone()
422
    {
423
        foreach ($this->_tables as $k => $table) {
424
            $this->_tables[$k] = clone $table;
425
        }
426
        foreach ($this->_sequences as $k => $sequence) {
427
            $this->_sequences[$k] = clone $sequence;
428
        }
429
    }
430
}
431