Schema.php 14 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
Benjamin Eberlei's avatar
Benjamin Eberlei committed
16
 * and is licensed under the MIT license. For more information, see
17 18 19 20 21 22
 * <http://www.doctrine-project.org>.
 */

namespace Doctrine\DBAL\Schema;

use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector;
23
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
24
use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor;
25
use Doctrine\DBAL\Schema\Visitor\Visitor;
Benjamin Morel's avatar
Benjamin Morel committed
26
use Doctrine\DBAL\Platforms\AbstractPlatform;
27 28

/**
Benjamin Morel's avatar
Benjamin Morel committed
29
 * Object representation of a database schema.
30
 *
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
 * Different vendors have very inconsistent naming with regard to the concept
 * of a "schema". Doctrine understands a schema as the entity that conceptually
 * wraps a set of database objects such as tables, sequences, indexes and
 * foreign keys that belong to each other into a namespace. A Doctrine Schema
 * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more
 * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL.
 *
 * Every asset in the doctrine schema has a name. A name consists of either a
 * namespace.local name pair or just a local unqualified name.
 *
 * The abstraction layer that covers a PostgreSQL schema is the namespace of an
 * database object (asset). A schema can have a name, which will be used as
 * default namespace for the unqualified database objects that are created in
 * the schema.
 *
 * In the case of MySQL where cross-database queries are allowed this leads to
 * databases being "misinterpreted" as namespaces. This is intentional, however
 * the CREATE/DROP SQL visitors will just filter this queries and do not
 * execute them. Only the queries for the currently connected database are
 * executed.
 *
Benjamin Morel's avatar
Benjamin Morel committed
52 53 54
 * @link   www.doctrine-project.org
 * @since  2.0
 * @author Benjamin Eberlei <kontakt@beberlei.de>
55 56 57
 */
class Schema extends AbstractAsset
{
58 59 60 61 62 63 64
    /**
     * The namespaces in this schema.
     *
     * @var array
     */
    private $namespaces = array();

65
    /**
Benjamin Morel's avatar
Benjamin Morel committed
66
     * @var \Doctrine\DBAL\Schema\Table[]
67 68
     */
    protected $_tables = array();
69

70
    /**
Benjamin Morel's avatar
Benjamin Morel committed
71
     * @var \Doctrine\DBAL\Schema\Sequence[]
72 73 74
     */
    protected $_sequences = array();

75
    /**
Benjamin Morel's avatar
Benjamin Morel committed
76
     * @var \Doctrine\DBAL\Schema\SchemaConfig
77
     */
78
    protected $_schemaConfig = false;
79

80
    /**
Benjamin Morel's avatar
Benjamin Morel committed
81 82 83
     * @param \Doctrine\DBAL\Schema\Table[]      $tables
     * @param \Doctrine\DBAL\Schema\Sequence[]   $sequences
     * @param \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig
84
     * @param array                              $namespaces
85
     */
86 87 88 89 90 91
    public function __construct(
        array $tables = array(),
        array $sequences = array(),
        SchemaConfig $schemaConfig = null,
        array $namespaces = array()
    ) {
92 93 94 95
        if ($schemaConfig == null) {
            $schemaConfig = new SchemaConfig();
        }
        $this->_schemaConfig = $schemaConfig;
96
        $this->_setName($schemaConfig->getName() ?: 'public');
97

98 99 100 101
        foreach ($namespaces as $namespace) {
            $this->createNamespace($namespace);
        }

102
        foreach ($tables as $table) {
103 104
            $this->_addTable($table);
        }
105

106
        foreach ($sequences as $sequence) {
107 108
            $this->_addSequence($sequence);
        }
109 110 111
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
112
     * @return boolean
113 114 115
     */
    public function hasExplicitForeignKeyIndexes()
    {
116
        return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
117 118 119
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
120 121 122 123 124
     * @param \Doctrine\DBAL\Schema\Table $table
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
125 126 127
     */
    protected function _addTable(Table $table)
    {
128
        $namespaceName = $table->getNamespaceName();
129
        $tableName = $table->getFullQualifiedName($this->getName());
130

Steve Müller's avatar
Steve Müller committed
131
        if (isset($this->_tables[$tableName])) {
132 133 134
            throw SchemaException::tableAlreadyExists($tableName);
        }

135 136 137 138
        if ( ! $table->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
            $this->createNamespace($namespaceName);
        }

139
        $this->_tables[$tableName] = $table;
140
        $table->setSchemaConfig($this->_schemaConfig);
141 142 143
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
144 145 146 147 148
     * @param \Doctrine\DBAL\Schema\Sequence $sequence
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
149 150 151
     */
    protected function _addSequence(Sequence $sequence)
    {
152
        $namespaceName = $sequence->getNamespaceName();
153
        $seqName = $sequence->getFullQualifiedName($this->getName());
154

155 156 157
        if (isset($this->_sequences[$seqName])) {
            throw SchemaException::sequenceAlreadyExists($seqName);
        }
158 159 160 161 162

        if ( ! $sequence->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
            $this->createNamespace($namespaceName);
        }

163 164 165
        $this->_sequences[$seqName] = $sequence;
    }

166 167 168 169 170 171 172 173 174 175
    /**
     * Returns the namespaces of this schema.
     *
     * @return array A list of namespace names.
     */
    public function getNamespaces()
    {
        return $this->namespaces;
    }

176
    /**
Benjamin Morel's avatar
Benjamin Morel committed
177
     * Gets all tables of this schema.
178
     *
Benjamin Morel's avatar
Benjamin Morel committed
179
     * @return \Doctrine\DBAL\Schema\Table[]
180 181 182 183 184 185 186 187
     */
    public function getTables()
    {
        return $this->_tables;
    }

    /**
     * @param string $tableName
Benjamin Morel's avatar
Benjamin Morel committed
188 189 190 191
     *
     * @return \Doctrine\DBAL\Schema\Table
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
192 193 194
     */
    public function getTable($tableName)
    {
195
        $tableName = $this->getFullQualifiedAssetName($tableName);
196 197 198 199 200 201 202
        if (!isset($this->_tables[$tableName])) {
            throw SchemaException::tableDoesNotExist($tableName);
        }

        return $this->_tables[$tableName];
    }

203
    /**
Benjamin Morel's avatar
Benjamin Morel committed
204 205
     * @param string $name
     *
206 207 208 209
     * @return string
     */
    private function getFullQualifiedAssetName($name)
    {
210 211
        $name = $this->getUnquotedAssetName($name);

212 213 214
        if (strpos($name, ".") === false) {
            $name = $this->getName() . "." . $name;
        }
Benjamin Morel's avatar
Benjamin Morel committed
215

216 217 218
        return strtolower($name);
    }

219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
    /**
     * Returns the unquoted representation of a given asset name.
     *
     * @param string $assetName Quoted or unquoted representation of an asset name.
     *
     * @return string
     */
    private function getUnquotedAssetName($assetName)
    {
        if ($this->isIdentifierQuoted($assetName)) {
            return $this->trimQuotes($assetName);
        }

        return $assetName;
    }

Marco Pivetta's avatar
Marco Pivetta committed
235 236 237 238 239 240 241 242 243
    /**
     * Does this schema have a namespace with the given name?
     *
     * @param string $namespaceName
     *
     * @return boolean
     */
    public function hasNamespace($namespaceName)
    {
244
        $namespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
Marco Pivetta's avatar
Marco Pivetta committed
245

246
        return isset($this->namespaces[$namespaceName]);
Marco Pivetta's avatar
Marco Pivetta committed
247 248
    }

249 250
    /**
     * Does this schema have a table with the given name?
251
     *
Benjamin Morel's avatar
Benjamin Morel committed
252 253 254
     * @param string $tableName
     *
     * @return boolean
255 256 257
     */
    public function hasTable($tableName)
    {
258
        $tableName = $this->getFullQualifiedAssetName($tableName);
Benjamin Morel's avatar
Benjamin Morel committed
259

260 261 262
        return isset($this->_tables[$tableName]);
    }

263
    /**
Benjamin Morel's avatar
Benjamin Morel committed
264
     * Gets all table names, prefixed with a schema name, even the default one if present.
265 266 267
     *
     * @return array
     */
268
    public function getTableNames()
269
    {
270
        return array_keys($this->_tables);
271 272
    }

Benjamin Morel's avatar
Benjamin Morel committed
273 274 275 276 277
    /**
     * @param string $sequenceName
     *
     * @return boolean
     */
278 279
    public function hasSequence($sequenceName)
    {
280
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
Benjamin Morel's avatar
Benjamin Morel committed
281

282 283 284 285
        return isset($this->_sequences[$sequenceName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
286 287
     * @param string $sequenceName
     *
288
     * @return \Doctrine\DBAL\Schema\Sequence
Benjamin Morel's avatar
Benjamin Morel committed
289 290
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
291 292 293
     */
    public function getSequence($sequenceName)
    {
294
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
Steve Müller's avatar
Steve Müller committed
295
        if (!$this->hasSequence($sequenceName)) {
296 297
            throw SchemaException::sequenceDoesNotExist($sequenceName);
        }
Benjamin Morel's avatar
Benjamin Morel committed
298

299 300 301 302
        return $this->_sequences[$sequenceName];
    }

    /**
303
     * @return \Doctrine\DBAL\Schema\Sequence[]
304 305 306 307 308 309
     */
    public function getSequences()
    {
        return $this->_sequences;
    }

310 311 312 313 314 315
    /**
     * Creates a new namespace.
     *
     * @param string $namespaceName The name of the namespace to create.
     *
     * @return \Doctrine\DBAL\Schema\Schema This schema instance.
316 317
     *
     * @throws SchemaException
318 319 320 321 322 323 324 325 326 327 328 329 330 331
     */
    public function createNamespace($namespaceName)
    {
        $unquotedNamespaceName = strtolower($this->getUnquotedAssetName($namespaceName));

        if (isset($this->namespaces[$unquotedNamespaceName])) {
            throw SchemaException::namespaceAlreadyExists($unquotedNamespaceName);
        }

        $this->namespaces[$unquotedNamespaceName] = $namespaceName;

        return $this;
    }

332
    /**
Benjamin Morel's avatar
Benjamin Morel committed
333 334 335
     * Creates a new table.
     *
     * @param string $tableName
336
     *
Benjamin Morel's avatar
Benjamin Morel committed
337
     * @return \Doctrine\DBAL\Schema\Table
338 339 340 341 342
     */
    public function createTable($tableName)
    {
        $table = new Table($tableName);
        $this->_addTable($table);
343 344 345 346 347

        foreach ($this->_schemaConfig->getDefaultTableOptions() as $name => $value) {
            $table->addOption($name, $value);
        }

348 349 350 351
        return $table;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
352
     * Renames a table.
353 354 355
     *
     * @param string $oldTableName
     * @param string $newTableName
Benjamin Morel's avatar
Benjamin Morel committed
356 357
     *
     * @return \Doctrine\DBAL\Schema\Schema
358 359 360 361 362 363 364 365
     */
    public function renameTable($oldTableName, $newTableName)
    {
        $table = $this->getTable($oldTableName);
        $table->_setName($newTableName);

        $this->dropTable($oldTableName);
        $this->_addTable($table);
Benjamin Morel's avatar
Benjamin Morel committed
366

367 368 369 370
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
371
     * Drops a table from the schema.
372 373
     *
     * @param string $tableName
Benjamin Morel's avatar
Benjamin Morel committed
374 375
     *
     * @return \Doctrine\DBAL\Schema\Schema
376 377 378
     */
    public function dropTable($tableName)
    {
379
        $tableName = $this->getFullQualifiedAssetName($tableName);
Benjamin Morel's avatar
Benjamin Morel committed
380
        $this->getTable($tableName);
381
        unset($this->_tables[$tableName]);
Benjamin Morel's avatar
Benjamin Morel committed
382

383 384 385 386
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
387 388 389 390 391
     * Creates a new sequence.
     *
     * @param string  $sequenceName
     * @param integer $allocationSize
     * @param integer $initialValue
392
     *
Benjamin Morel's avatar
Benjamin Morel committed
393
     * @return \Doctrine\DBAL\Schema\Sequence
394 395 396 397 398
     */
    public function createSequence($sequenceName, $allocationSize=1, $initialValue=1)
    {
        $seq = new Sequence($sequenceName, $allocationSize, $initialValue);
        $this->_addSequence($seq);
Benjamin Morel's avatar
Benjamin Morel committed
399

400
        return $seq;
401 402 403 404
    }

    /**
     * @param string $sequenceName
Benjamin Morel's avatar
Benjamin Morel committed
405 406
     *
     * @return \Doctrine\DBAL\Schema\Schema
407 408 409
     */
    public function dropSequence($sequenceName)
    {
410
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
411
        unset($this->_sequences[$sequenceName]);
Benjamin Morel's avatar
Benjamin Morel committed
412

413 414 415 416
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
417
     * Returns an array of necessary SQL queries to create the schema on the given platform.
418
     *
419
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
420
     *
421
     * @return array
422
     */
Benjamin Morel's avatar
Benjamin Morel committed
423
    public function toSql(AbstractPlatform $platform)
424 425 426 427 428 429 430
    {
        $sqlCollector = new CreateSchemaSqlCollector($platform);
        $this->visit($sqlCollector);

        return $sqlCollector->getQueries();
    }

431
    /**
Benjamin Morel's avatar
Benjamin Morel committed
432
     * Return an array of necessary SQL queries to drop the schema on the given platform.
433
     *
434
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
435
     *
436 437
     * @return array
     */
Benjamin Morel's avatar
Benjamin Morel committed
438
    public function toDropSql(AbstractPlatform $platform)
439 440 441 442 443 444 445
    {
        $dropSqlCollector = new DropSchemaSqlCollector($platform);
        $this->visit($dropSqlCollector);

        return $dropSqlCollector->getQueries();
    }

446
    /**
Benjamin Morel's avatar
Benjamin Morel committed
447
     * @param \Doctrine\DBAL\Schema\Schema              $toSchema
448
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
449 450
     *
     * @return array
451
     */
Benjamin Morel's avatar
Benjamin Morel committed
452
    public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform)
453
    {
454
        $comparator = new Comparator();
455
        $schemaDiff = $comparator->compare($this, $toSchema);
Benjamin Morel's avatar
Benjamin Morel committed
456

457
        return $schemaDiff->toSql($platform);
458 459
    }

460
    /**
Benjamin Morel's avatar
Benjamin Morel committed
461
     * @param \Doctrine\DBAL\Schema\Schema              $fromSchema
462
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
463 464
     *
     * @return array
465
     */
Benjamin Morel's avatar
Benjamin Morel committed
466
    public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform)
467
    {
468
        $comparator = new Comparator();
469
        $schemaDiff = $comparator->compare($fromSchema, $this);
Benjamin Morel's avatar
Benjamin Morel committed
470

471
        return $schemaDiff->toSql($platform);
472 473
    }

474
    /**
Benjamin Morel's avatar
Benjamin Morel committed
475 476 477
     * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor
     *
     * @return void
478 479 480 481
     */
    public function visit(Visitor $visitor)
    {
        $visitor->acceptSchema($this);
482

483 484 485 486 487 488
        if ($visitor instanceof NamespaceVisitor) {
            foreach ($this->namespaces as $namespace) {
                $visitor->acceptNamespace($namespace);
            }
        }

489
        foreach ($this->_tables as $table) {
490 491
            $table->visit($visitor);
        }
492

493
        foreach ($this->_sequences as $sequence) {
494 495 496
            $sequence->visit($visitor);
        }
    }
497 498 499 500 501 502 503 504

    /**
     * Cloning a Schema triggers a deep clone of all related assets.
     *
     * @return void
     */
    public function __clone()
    {
505
        foreach ($this->_tables as $k => $table) {
506 507
            $this->_tables[$k] = clone $table;
        }
508
        foreach ($this->_sequences as $k => $sequence) {
509 510 511
            $this->_sequences[$k] = clone $sequence;
        }
    }
512
}