Schema.php 11.8 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 24
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
use Doctrine\DBAL\Schema\Visitor\Visitor;
Benjamin Morel's avatar
Benjamin Morel committed
25
use Doctrine\DBAL\Platforms\AbstractPlatform;
26 27

/**
Benjamin Morel's avatar
Benjamin Morel committed
28
 * Object representation of a database schema.
29
 *
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
 * 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
51 52 53
 * @link   www.doctrine-project.org
 * @since  2.0
 * @author Benjamin Eberlei <kontakt@beberlei.de>
54 55 56 57
 */
class Schema extends AbstractAsset
{
    /**
Benjamin Morel's avatar
Benjamin Morel committed
58
     * @var \Doctrine\DBAL\Schema\Table[]
59 60
     */
    protected $_tables = array();
61

62
    /**
Benjamin Morel's avatar
Benjamin Morel committed
63
     * @var \Doctrine\DBAL\Schema\Sequence[]
64 65 66
     */
    protected $_sequences = array();

67
    /**
Benjamin Morel's avatar
Benjamin Morel committed
68
     * @var \Doctrine\DBAL\Schema\SchemaConfig
69
     */
70
    protected $_schemaConfig = false;
71

72
    /**
Benjamin Morel's avatar
Benjamin Morel committed
73 74 75
     * @param \Doctrine\DBAL\Schema\Table[]      $tables
     * @param \Doctrine\DBAL\Schema\Sequence[]   $sequences
     * @param \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig
76
     */
77
    public function __construct(array $tables=array(), array $sequences=array(), SchemaConfig $schemaConfig=null)
78
    {
79 80 81 82
        if ($schemaConfig == null) {
            $schemaConfig = new SchemaConfig();
        }
        $this->_schemaConfig = $schemaConfig;
83
        $this->_setName($schemaConfig->getName() ?: 'public');
84

85
        foreach ($tables as $table) {
86 87
            $this->_addTable($table);
        }
88

89
        foreach ($sequences as $sequence) {
90 91
            $this->_addSequence($sequence);
        }
92 93 94
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
95
     * @return boolean
96 97 98
     */
    public function hasExplicitForeignKeyIndexes()
    {
99
        return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
100 101 102
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
103 104 105 106 107
     * @param \Doctrine\DBAL\Schema\Table $table
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
108 109 110
     */
    protected function _addTable(Table $table)
    {
111
        $tableName = $table->getFullQualifiedName($this->getName());
Steve Müller's avatar
Steve Müller committed
112
        if (isset($this->_tables[$tableName])) {
113 114 115 116
            throw SchemaException::tableAlreadyExists($tableName);
        }

        $this->_tables[$tableName] = $table;
117
        $table->setSchemaConfig($this->_schemaConfig);
118 119 120
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
121 122 123 124 125
     * @param \Doctrine\DBAL\Schema\Sequence $sequence
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
126 127 128
     */
    protected function _addSequence(Sequence $sequence)
    {
129
        $seqName = $sequence->getFullQualifiedName($this->getName());
130 131 132 133 134 135 136
        if (isset($this->_sequences[$seqName])) {
            throw SchemaException::sequenceAlreadyExists($seqName);
        }
        $this->_sequences[$seqName] = $sequence;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
137
     * Gets all tables of this schema.
138
     *
Benjamin Morel's avatar
Benjamin Morel committed
139
     * @return \Doctrine\DBAL\Schema\Table[]
140 141 142 143 144 145 146 147
     */
    public function getTables()
    {
        return $this->_tables;
    }

    /**
     * @param string $tableName
Benjamin Morel's avatar
Benjamin Morel committed
148 149 150 151
     *
     * @return \Doctrine\DBAL\Schema\Table
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
152 153 154
     */
    public function getTable($tableName)
    {
155
        $tableName = $this->getFullQualifiedAssetName($tableName);
156 157 158 159 160 161 162
        if (!isset($this->_tables[$tableName])) {
            throw SchemaException::tableDoesNotExist($tableName);
        }

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

163
    /**
Benjamin Morel's avatar
Benjamin Morel committed
164 165
     * @param string $name
     *
166 167 168 169
     * @return string
     */
    private function getFullQualifiedAssetName($name)
    {
170
        if ($this->isIdentifierQuoted($name)) {
171 172
            $name = $this->trimQuotes($name);
        }
173 174 175
        if (strpos($name, ".") === false) {
            $name = $this->getName() . "." . $name;
        }
Benjamin Morel's avatar
Benjamin Morel committed
176

177 178 179
        return strtolower($name);
    }

Marco Pivetta's avatar
Marco Pivetta committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    /**
     * Does this schema have a namespace with the given name?
     *
     * @param string $namespaceName
     *
     * @return boolean
     */
    public function hasNamespace($namespaceName)
    {
        foreach ($this->_tables as $table) {
            if ($table->getNamespaceName() === $namespaceName) {
                return true;
            }
        }

        return false;
    }

198 199
    /**
     * Does this schema have a table with the given name?
200
     *
Benjamin Morel's avatar
Benjamin Morel committed
201 202 203
     * @param string $tableName
     *
     * @return boolean
204 205 206
     */
    public function hasTable($tableName)
    {
207
        $tableName = $this->getFullQualifiedAssetName($tableName);
Benjamin Morel's avatar
Benjamin Morel committed
208

209 210 211
        return isset($this->_tables[$tableName]);
    }

212
    /**
Benjamin Morel's avatar
Benjamin Morel committed
213
     * Gets all table names, prefixed with a schema name, even the default one if present.
214 215 216
     *
     * @return array
     */
217
    public function getTableNames()
218
    {
219
        return array_keys($this->_tables);
220 221
    }

Benjamin Morel's avatar
Benjamin Morel committed
222 223 224 225 226
    /**
     * @param string $sequenceName
     *
     * @return boolean
     */
227 228
    public function hasSequence($sequenceName)
    {
229
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
Benjamin Morel's avatar
Benjamin Morel committed
230

231 232 233 234
        return isset($this->_sequences[$sequenceName]);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
235 236
     * @param string $sequenceName
     *
237
     * @return \Doctrine\DBAL\Schema\Sequence
Benjamin Morel's avatar
Benjamin Morel committed
238 239
     *
     * @throws \Doctrine\DBAL\Schema\SchemaException
240 241 242
     */
    public function getSequence($sequenceName)
    {
243
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
Steve Müller's avatar
Steve Müller committed
244
        if (!$this->hasSequence($sequenceName)) {
245 246
            throw SchemaException::sequenceDoesNotExist($sequenceName);
        }
Benjamin Morel's avatar
Benjamin Morel committed
247

248 249 250 251
        return $this->_sequences[$sequenceName];
    }

    /**
252
     * @return \Doctrine\DBAL\Schema\Sequence[]
253 254 255 256 257 258 259
     */
    public function getSequences()
    {
        return $this->_sequences;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
260 261 262
     * Creates a new table.
     *
     * @param string $tableName
263
     *
Benjamin Morel's avatar
Benjamin Morel committed
264
     * @return \Doctrine\DBAL\Schema\Table
265 266 267 268 269
     */
    public function createTable($tableName)
    {
        $table = new Table($tableName);
        $this->_addTable($table);
270 271 272 273 274

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

275 276 277 278
        return $table;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
279
     * Renames a table.
280 281 282
     *
     * @param string $oldTableName
     * @param string $newTableName
Benjamin Morel's avatar
Benjamin Morel committed
283 284
     *
     * @return \Doctrine\DBAL\Schema\Schema
285 286 287 288 289 290 291 292
     */
    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
293

294 295 296 297
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
298
     * Drops a table from the schema.
299 300
     *
     * @param string $tableName
Benjamin Morel's avatar
Benjamin Morel committed
301 302
     *
     * @return \Doctrine\DBAL\Schema\Schema
303 304 305
     */
    public function dropTable($tableName)
    {
306
        $tableName = $this->getFullQualifiedAssetName($tableName);
Benjamin Morel's avatar
Benjamin Morel committed
307
        $this->getTable($tableName);
308
        unset($this->_tables[$tableName]);
Benjamin Morel's avatar
Benjamin Morel committed
309

310 311 312 313
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
314 315 316 317 318
     * Creates a new sequence.
     *
     * @param string  $sequenceName
     * @param integer $allocationSize
     * @param integer $initialValue
319
     *
Benjamin Morel's avatar
Benjamin Morel committed
320
     * @return \Doctrine\DBAL\Schema\Sequence
321 322 323 324 325
     */
    public function createSequence($sequenceName, $allocationSize=1, $initialValue=1)
    {
        $seq = new Sequence($sequenceName, $allocationSize, $initialValue);
        $this->_addSequence($seq);
Benjamin Morel's avatar
Benjamin Morel committed
326

327
        return $seq;
328 329 330 331
    }

    /**
     * @param string $sequenceName
Benjamin Morel's avatar
Benjamin Morel committed
332 333
     *
     * @return \Doctrine\DBAL\Schema\Schema
334 335 336
     */
    public function dropSequence($sequenceName)
    {
337
        $sequenceName = $this->getFullQualifiedAssetName($sequenceName);
338
        unset($this->_sequences[$sequenceName]);
Benjamin Morel's avatar
Benjamin Morel committed
339

340 341 342 343
        return $this;
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
344
     * Returns an array of necessary SQL queries to create the schema on the given platform.
345
     *
346
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
347
     *
348
     * @return array
349
     */
Benjamin Morel's avatar
Benjamin Morel committed
350
    public function toSql(AbstractPlatform $platform)
351 352 353 354 355 356 357
    {
        $sqlCollector = new CreateSchemaSqlCollector($platform);
        $this->visit($sqlCollector);

        return $sqlCollector->getQueries();
    }

358
    /**
Benjamin Morel's avatar
Benjamin Morel committed
359
     * Return an array of necessary SQL queries to drop the schema on the given platform.
360
     *
361
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
362
     *
363 364
     * @return array
     */
Benjamin Morel's avatar
Benjamin Morel committed
365
    public function toDropSql(AbstractPlatform $platform)
366 367 368 369 370 371 372
    {
        $dropSqlCollector = new DropSchemaSqlCollector($platform);
        $this->visit($dropSqlCollector);

        return $dropSqlCollector->getQueries();
    }

373
    /**
Benjamin Morel's avatar
Benjamin Morel committed
374
     * @param \Doctrine\DBAL\Schema\Schema              $toSchema
375
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
376 377
     *
     * @return array
378
     */
Benjamin Morel's avatar
Benjamin Morel committed
379
    public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform)
380
    {
381
        $comparator = new Comparator();
382
        $schemaDiff = $comparator->compare($this, $toSchema);
Benjamin Morel's avatar
Benjamin Morel committed
383

384
        return $schemaDiff->toSql($platform);
385 386
    }

387
    /**
Benjamin Morel's avatar
Benjamin Morel committed
388
     * @param \Doctrine\DBAL\Schema\Schema              $fromSchema
389
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
Benjamin Morel's avatar
Benjamin Morel committed
390 391
     *
     * @return array
392
     */
Benjamin Morel's avatar
Benjamin Morel committed
393
    public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform)
394
    {
395
        $comparator = new Comparator();
396
        $schemaDiff = $comparator->compare($fromSchema, $this);
Benjamin Morel's avatar
Benjamin Morel committed
397

398
        return $schemaDiff->toSql($platform);
399 400
    }

401
    /**
Benjamin Morel's avatar
Benjamin Morel committed
402 403 404
     * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor
     *
     * @return void
405 406 407 408
     */
    public function visit(Visitor $visitor)
    {
        $visitor->acceptSchema($this);
409

410
        foreach ($this->_tables as $table) {
411 412
            $table->visit($visitor);
        }
413
        foreach ($this->_sequences as $sequence) {
414 415 416
            $sequence->visit($visitor);
        }
    }
417 418 419 420 421 422 423 424

    /**
     * Cloning a Schema triggers a deep clone of all related assets.
     *
     * @return void
     */
    public function __clone()
    {
425
        foreach ($this->_tables as $k => $table) {
426 427
            $this->_tables[$k] = clone $table;
        }
428
        foreach ($this->_sequences as $k => $sequence) {
429 430 431
            $this->_sequences[$k] = clone $sequence;
        }
    }
432
}