AbstractSchemaManager.php 30.4 KB
Newer Older
romanb's avatar
romanb committed
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
 * <http://www.doctrine-project.org>.
romanb's avatar
romanb committed
18 19
 */

20
namespace Doctrine\DBAL\Schema;
romanb's avatar
romanb committed
21

22 23
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
24
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
25 26
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
27

romanb's avatar
romanb committed
28
/**
29
 * Base class for schema managers. Schema managers are used to inspect and/or
30
 * modify the database schema/structure.
romanb's avatar
romanb committed
31
 *
Benjamin Morel's avatar
Benjamin Morel committed
32 33 34 35 36 37
 * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
 * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
 * @author Roman Borschel <roman@code-factory.org>
 * @author Jonathan H. Wage <jonwage@gmail.com>
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 * @since  2.0
romanb's avatar
romanb committed
38
 */
39
abstract class AbstractSchemaManager
romanb's avatar
romanb committed
40
{
41
    /**
Benjamin Morel's avatar
Benjamin Morel committed
42
     * Holds instance of the Doctrine connection for this schema manager.
43
     *
44
     * @var \Doctrine\DBAL\Connection
45
     */
romanb's avatar
romanb committed
46 47
    protected $_conn;

48
    /**
Benjamin Morel's avatar
Benjamin Morel committed
49
     * Holds instance of the database platform used for this schema manager.
50
     *
51
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
52 53 54 55
     */
    protected $_platform;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
56
     * Constructor. Accepts the Connection instance to manage the schema for.
57
     *
Benjamin Morel's avatar
Benjamin Morel committed
58 59
     * @param \Doctrine\DBAL\Connection                      $conn
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform|null $platform
60
     */
61
    public function __construct(\Doctrine\DBAL\Connection $conn, AbstractPlatform $platform = null)
62
    {
63 64
        $this->_conn     = $conn;
        $this->_platform = $platform ?: $this->_conn->getDatabasePlatform();
65 66
    }

67
    /**
Benjamin Morel's avatar
Benjamin Morel committed
68
     * Returns the associated platform.
69
     *
70
     * @return \Doctrine\DBAL\Platforms\AbstractPlatform
71 72 73 74 75 76
     */
    public function getDatabasePlatform()
    {
        return $this->_platform;
    }

77
    /**
Benjamin Morel's avatar
Benjamin Morel committed
78
     * Tries any method on the schema manager. Normally a method throws an
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
     * exception when your DBMS doesn't support it or if an error occurs.
     * This method allows you to try and method on your SchemaManager
     * instance and will return false if it does not work or is not supported.
     *
     * <code>
     * $result = $sm->tryMethod('dropView', 'view_name');
     * </code>
     *
     * @return mixed
     */
    public function tryMethod()
    {
        $args = func_get_args();
        $method = $args[0];
        unset($args[0]);
        $args = array_values($args);

        try {
            return call_user_func_array(array($this, $method), $args);
        } catch (\Exception $e) {
            return false;
        }
    }

romanb's avatar
romanb committed
103
    /**
Benjamin Morel's avatar
Benjamin Morel committed
104
     * Lists the available databases for this connection.
romanb's avatar
romanb committed
105
     *
Benjamin Morel's avatar
Benjamin Morel committed
106
     * @return array
romanb's avatar
romanb committed
107 108 109
     */
    public function listDatabases()
    {
110
        $sql = $this->_platform->getListDatabasesSQL();
111 112 113 114

        $databases = $this->_conn->fetchAll($sql);

        return $this->_getPortableDatabasesList($databases);
romanb's avatar
romanb committed
115 116
    }

117 118 119 120 121 122 123 124 125 126 127 128 129 130
    /**
     * Returns a list of all namespaces in the current database.
     *
     * @return array
     */
    public function listNamespaceNames()
    {
        $sql = $this->_platform->getListNamespacesSQL();

        $namespaces = $this->_conn->fetchAll($sql);

        return $this->getPortableNamespacesList($namespaces);
    }

romanb's avatar
romanb committed
131
    /**
Benjamin Morel's avatar
Benjamin Morel committed
132 133 134
     * Lists the available sequences for this connection.
     *
     * @param string|null $database
romanb's avatar
romanb committed
135
     *
Benjamin Morel's avatar
Benjamin Morel committed
136
     * @return \Doctrine\DBAL\Schema\Sequence[]
romanb's avatar
romanb committed
137
     */
138
    public function listSequences($database = null)
romanb's avatar
romanb committed
139
    {
140 141 142
        if (is_null($database)) {
            $database = $this->_conn->getDatabase();
        }
143
        $sql = $this->_platform->getListSequencesSQL($database);
144 145 146

        $sequences = $this->_conn->fetchAll($sql);

147
        return $this->filterAssetNames($this->_getPortableSequencesList($sequences));
romanb's avatar
romanb committed
148 149 150
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
151
     * Lists the columns for a given table.
romanb's avatar
romanb committed
152
     *
153 154 155 156 157 158 159
     * In contrast to other libraries and to the old version of Doctrine,
     * this column definition does try to contain the 'primary' field for
     * the reason that it is not portable accross different RDBMS. Use
     * {@see listTableIndexes($tableName)} to retrieve the primary key
     * of a table. We're a RDBMS specifies more details these are held
     * in the platformDetails array.
     *
Benjamin Morel's avatar
Benjamin Morel committed
160 161 162 163
     * @param string      $table    The name of the table.
     * @param string|null $database
     *
     * @return \Doctrine\DBAL\Schema\Column[]
romanb's avatar
romanb committed
164
     */
165
    public function listTableColumns($table, $database = null)
romanb's avatar
romanb committed
166
    {
167
        if ( ! $database) {
168 169 170 171
            $database = $this->_conn->getDatabase();
        }

        $sql = $this->_platform->getListTableColumnsSQL($table, $database);
172 173 174

        $tableColumns = $this->_conn->fetchAll($sql);

175
        return $this->_getPortableTableColumnList($table, $database, $tableColumns);
romanb's avatar
romanb committed
176 177 178
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
179
     * Lists the indexes for a given table returning an array of Index instances.
romanb's avatar
romanb committed
180
     *
181 182
     * Keys of the portable indexes list are all lower-cased.
     *
Benjamin Morel's avatar
Benjamin Morel committed
183 184 185
     * @param string $table The name of the table.
     *
     * @return \Doctrine\DBAL\Schema\Index[]
romanb's avatar
romanb committed
186 187 188
     */
    public function listTableIndexes($table)
    {
189
        $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase());
190 191 192

        $tableIndexes = $this->_conn->fetchAll($sql);

193
        return $this->_getPortableTableIndexesList($tableIndexes, $table);
romanb's avatar
romanb committed
194 195
    }

196
    /**
Benjamin Morel's avatar
Benjamin Morel committed
197
     * Returns true if all the given tables exist.
198
     *
199
     * @param array $tableNames
Benjamin Morel's avatar
Benjamin Morel committed
200 201
     *
     * @return boolean
202 203 204 205
     */
    public function tablesExist($tableNames)
    {
        $tableNames = array_map('strtolower', (array)$tableNames);
Benjamin Morel's avatar
Benjamin Morel committed
206

207 208 209
        return count($tableNames) == count(\array_intersect($tableNames, array_map('strtolower', $this->listTableNames())));
    }

romanb's avatar
romanb committed
210
    /**
Benjamin Morel's avatar
Benjamin Morel committed
211
     * Returns a list of all tables in the current database.
romanb's avatar
romanb committed
212
     *
213
     * @return array
romanb's avatar
romanb committed
214
     */
215
    public function listTableNames()
romanb's avatar
romanb committed
216
    {
217
        $sql = $this->_platform->getListTablesSQL();
218 219

        $tables = $this->_conn->fetchAll($sql);
220
        $tableNames = $this->_getPortableTablesList($tables);
Benjamin Morel's avatar
Benjamin Morel committed
221

222 223
        return $this->filterAssetNames($tableNames);
    }
224

225
    /**
Benjamin Morel's avatar
Benjamin Morel committed
226
     * Filters asset names if they are configured to return only a subset of all
227 228 229
     * the found elements.
     *
     * @param array $assetNames
Benjamin Morel's avatar
Benjamin Morel committed
230
     *
231 232 233 234 235
     * @return array
     */
    protected function filterAssetNames($assetNames)
    {
        $filterExpr = $this->getFilterSchemaAssetsExpression();
236
        if ( ! $filterExpr) {
237 238
            return $assetNames;
        }
Benjamin Morel's avatar
Benjamin Morel committed
239

240 241 242
        return array_values (
            array_filter($assetNames, function ($assetName) use ($filterExpr) {
                $assetName = ($assetName instanceof AbstractAsset) ? $assetName->getName() : $assetName;
243
                return preg_match($filterExpr, $assetName);
244 245 246 247
            })
        );
    }

Benjamin Morel's avatar
Benjamin Morel committed
248 249 250
    /**
     * @return string|null
     */
251 252 253
    protected function getFilterSchemaAssetsExpression()
    {
        return $this->_conn->getConfiguration()->getFilterSchemaAssetsExpression();
254 255 256
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
257
     * Lists the tables for this connection.
258
     *
Benjamin Morel's avatar
Benjamin Morel committed
259
     * @return \Doctrine\DBAL\Schema\Table[]
260 261 262 263
     */
    public function listTables()
    {
        $tableNames = $this->listTableNames();
264

265
        $tables = array();
266
        foreach ($tableNames as $tableName) {
267
            $tables[] = $this->listTableDetails($tableName);
268 269 270
        }

        return $tables;
romanb's avatar
romanb committed
271 272
    }

273
    /**
Benjamin Morel's avatar
Benjamin Morel committed
274 275 276
     * @param string $tableName
     *
     * @return \Doctrine\DBAL\Schema\Table
277 278 279 280 281 282 283 284 285 286
     */
    public function listTableDetails($tableName)
    {
        $columns = $this->listTableColumns($tableName);
        $foreignKeys = array();
        if ($this->_platform->supportsForeignKeyConstraints()) {
            $foreignKeys = $this->listTableForeignKeys($tableName);
        }
        $indexes = $this->listTableIndexes($tableName);

287
        return new Table($tableName, $columns, $indexes, $foreignKeys, false, array());
288 289
    }

romanb's avatar
romanb committed
290
    /**
Benjamin Morel's avatar
Benjamin Morel committed
291
     * Lists the views this connection has.
romanb's avatar
romanb committed
292
     *
Benjamin Morel's avatar
Benjamin Morel committed
293
     * @return \Doctrine\DBAL\Schema\View[]
romanb's avatar
romanb committed
294
     */
295
    public function listViews()
romanb's avatar
romanb committed
296
    {
297
        $database = $this->_conn->getDatabase();
298
        $sql = $this->_platform->getListViewsSQL($database);
299 300 301
        $views = $this->_conn->fetchAll($sql);

        return $this->_getPortableViewsList($views);
romanb's avatar
romanb committed
302 303
    }

304
    /**
Benjamin Morel's avatar
Benjamin Morel committed
305 306 307 308
     * Lists the foreign keys for the given table.
     *
     * @param string      $table    The name of the table.
     * @param string|null $database
309
     *
Benjamin Morel's avatar
Benjamin Morel committed
310
     * @return \Doctrine\DBAL\Schema\ForeignKeyConstraint[]
311 312 313 314 315 316
     */
    public function listTableForeignKeys($table, $database = null)
    {
        if (is_null($database)) {
            $database = $this->_conn->getDatabase();
        }
317
        $sql = $this->_platform->getListTableForeignKeysSQL($table, $database);
318 319 320 321 322
        $tableForeignKeys = $this->_conn->fetchAll($sql);

        return $this->_getPortableTableForeignKeysList($tableForeignKeys);
    }

323 324
    /* drop*() Methods */

romanb's avatar
romanb committed
325
    /**
326
     * Drops a database.
327
     *
328
     * NOTE: You can not drop the database this SchemaManager is currently connected to.
romanb's avatar
romanb committed
329
     *
Benjamin Morel's avatar
Benjamin Morel committed
330 331 332
     * @param string $database The name of the database to drop.
     *
     * @return void
romanb's avatar
romanb committed
333
     */
334
    public function dropDatabase($database)
romanb's avatar
romanb committed
335
    {
336
        $this->_execSql($this->_platform->getDropDatabaseSQL($database));
romanb's avatar
romanb committed
337 338 339
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
340 341
     * Drops the given table.
     *
jeroendedauw's avatar
jeroendedauw committed
342
     * @param string $tableName The name of the table to drop.
romanb's avatar
romanb committed
343
     *
Benjamin Morel's avatar
Benjamin Morel committed
344
     * @return void
romanb's avatar
romanb committed
345
     */
jeroendedauw's avatar
jeroendedauw committed
346
    public function dropTable($tableName)
romanb's avatar
romanb committed
347
    {
jeroendedauw's avatar
jeroendedauw committed
348
        $this->_execSql($this->_platform->getDropTableSQL($tableName));
romanb's avatar
romanb committed
349 350 351
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
352
     * Drops the index from the given table.
romanb's avatar
romanb committed
353
     *
Benjamin Morel's avatar
Benjamin Morel committed
354 355 356 357
     * @param \Doctrine\DBAL\Schema\Index|string $index The name of the index.
     * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table.
     *
     * @return void
romanb's avatar
romanb committed
358
     */
359
    public function dropIndex($index, $table)
romanb's avatar
romanb committed
360
    {
Steve Müller's avatar
Steve Müller committed
361
        if ($index instanceof Index) {
362
            $index = $index->getQuotedName($this->_platform);
363 364
        }

365
        $this->_execSql($this->_platform->getDropIndexSQL($index, $table));
romanb's avatar
romanb committed
366 367 368
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
369 370 371 372
     * Drops the constraint from the given table.
     *
     * @param \Doctrine\DBAL\Schema\Constraint   $constraint
     * @param \Doctrine\DBAL\Schema\Table|string $table      The name of the table.
romanb's avatar
romanb committed
373
     *
Benjamin Morel's avatar
Benjamin Morel committed
374
     * @return void
romanb's avatar
romanb committed
375
     */
376
    public function dropConstraint(Constraint $constraint, $table)
romanb's avatar
romanb committed
377
    {
378
        $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table));
romanb's avatar
romanb committed
379 380 381
    }

    /**
romanb's avatar
romanb committed
382
     * Drops a foreign key from a table.
romanb's avatar
romanb committed
383
     *
Benjamin Morel's avatar
Benjamin Morel committed
384 385 386 387
     * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint|string $foreignKey The name of the foreign key.
     * @param \Doctrine\DBAL\Schema\Table|string                $table      The name of the table with the foreign key.
     *
     * @return void
romanb's avatar
romanb committed
388
     */
389
    public function dropForeignKey($foreignKey, $table)
romanb's avatar
romanb committed
390
    {
391
        $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table));
romanb's avatar
romanb committed
392 393 394
    }

    /**
romanb's avatar
romanb committed
395
     * Drops a sequence with a given name.
romanb's avatar
romanb committed
396
     *
romanb's avatar
romanb committed
397
     * @param string $name The name of the sequence to drop.
Benjamin Morel's avatar
Benjamin Morel committed
398 399
     *
     * @return void
romanb's avatar
romanb committed
400
     */
401
    public function dropSequence($name)
romanb's avatar
romanb committed
402
    {
403
        $this->_execSql($this->_platform->getDropSequenceSQL($name));
romanb's avatar
romanb committed
404 405
    }

406
    /**
Benjamin Morel's avatar
Benjamin Morel committed
407
     * Drops a view.
408
     *
Benjamin Morel's avatar
Benjamin Morel committed
409 410 411
     * @param string $name The name of the view.
     *
     * @return void
412 413 414
     */
    public function dropView($name)
    {
415
        $this->_execSql($this->_platform->getDropViewSQL($name));
416 417 418 419
    }

    /* create*() Methods */

romanb's avatar
romanb committed
420
    /**
romanb's avatar
romanb committed
421
     * Creates a new database.
romanb's avatar
romanb committed
422
     *
romanb's avatar
romanb committed
423
     * @param string $database The name of the database to create.
Benjamin Morel's avatar
Benjamin Morel committed
424 425
     *
     * @return void
romanb's avatar
romanb committed
426
     */
romanb's avatar
romanb committed
427
    public function createDatabase($database)
romanb's avatar
romanb committed
428
    {
429
        $this->_execSql($this->_platform->getCreateDatabaseSQL($database));
romanb's avatar
romanb committed
430 431 432
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
433
     * Creates a new table.
romanb's avatar
romanb committed
434
     *
Benjamin Morel's avatar
Benjamin Morel committed
435 436 437
     * @param \Doctrine\DBAL\Schema\Table $table
     *
     * @return void
romanb's avatar
romanb committed
438
     */
439
    public function createTable(Table $table)
romanb's avatar
romanb committed
440
    {
441
        $createFlags = AbstractPlatform::CREATE_INDEXES|AbstractPlatform::CREATE_FOREIGNKEYS;
442
        $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags));
romanb's avatar
romanb committed
443 444 445
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
446 447 448
     * Creates a new sequence.
     *
     * @param \Doctrine\DBAL\Schema\Sequence $sequence
romanb's avatar
romanb committed
449
     *
Benjamin Morel's avatar
Benjamin Morel committed
450 451 452
     * @return void
     *
     * @throws \Doctrine\DBAL\ConnectionException If something fails at database level.
romanb's avatar
romanb committed
453
     */
454
    public function createSequence($sequence)
romanb's avatar
romanb committed
455
    {
456
        $this->_execSql($this->_platform->getCreateSequenceSQL($sequence));
romanb's avatar
romanb committed
457 458 459
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
460 461 462 463
     * Creates a constraint on a table.
     *
     * @param \Doctrine\DBAL\Schema\Constraint   $constraint
     * @param \Doctrine\DBAL\Schema\Table|string $table
romanb's avatar
romanb committed
464
     *
Benjamin Morel's avatar
Benjamin Morel committed
465
     * @return void
466 467
     */
    public function createConstraint(Constraint $constraint, $table)
romanb's avatar
romanb committed
468
    {
469
        $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table));
romanb's avatar
romanb committed
470 471 472
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
473
     * Creates a new index on a table.
romanb's avatar
romanb committed
474
     *
Benjamin Morel's avatar
Benjamin Morel committed
475 476 477 478
     * @param \Doctrine\DBAL\Schema\Index        $index
     * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the index is to be created.
     *
     * @return void
romanb's avatar
romanb committed
479
     */
480
    public function createIndex(Index $index, $table)
romanb's avatar
romanb committed
481
    {
482
        $this->_execSql($this->_platform->getCreateIndexSQL($index, $table));
romanb's avatar
romanb committed
483 484 485
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
486 487 488 489
     * Creates a new foreign key.
     *
     * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey The ForeignKey instance.
     * @param \Doctrine\DBAL\Schema\Table|string         $table      The name of the table on which the foreign key is to be created.
romanb's avatar
romanb committed
490
     *
Benjamin Morel's avatar
Benjamin Morel committed
491
     * @return void
romanb's avatar
romanb committed
492
     */
493
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
romanb's avatar
romanb committed
494
    {
495
        $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table));
romanb's avatar
romanb committed
496 497
    }

498
    /**
Benjamin Morel's avatar
Benjamin Morel committed
499
     * Creates a new view.
500
     *
Benjamin Morel's avatar
Benjamin Morel committed
501 502 503
     * @param \Doctrine\DBAL\Schema\View $view
     *
     * @return void
504
     */
505
    public function createView(View $view)
506
    {
507
        $this->_execSql($this->_platform->getCreateViewSQL($view->getQuotedName($this->_platform), $view->getSql()));
508 509
    }

510 511
    /* dropAndCreate*() Methods */

512
    /**
Benjamin Morel's avatar
Benjamin Morel committed
513
     * Drops and creates a constraint.
514 515 516
     *
     * @see dropConstraint()
     * @see createConstraint()
Benjamin Morel's avatar
Benjamin Morel committed
517 518 519 520 521
     *
     * @param \Doctrine\DBAL\Schema\Constraint   $constraint
     * @param \Doctrine\DBAL\Schema\Table|string $table
     *
     * @return void
522
     */
523
    public function dropAndCreateConstraint(Constraint $constraint, $table)
524
    {
525 526
        $this->tryMethod('dropConstraint', $constraint, $table);
        $this->createConstraint($constraint, $table);
527 528 529
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
530 531 532 533
     * Drops and creates a new index on a table.
     *
     * @param \Doctrine\DBAL\Schema\Index        $index
     * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the index is to be created.
534
     *
Benjamin Morel's avatar
Benjamin Morel committed
535
     * @return void
536
     */
537
    public function dropAndCreateIndex(Index $index, $table)
538
    {
539
        $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table);
540
        $this->createIndex($index, $table);
541 542 543
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
544
     * Drops and creates a new foreign key.
545
     *
Benjamin Morel's avatar
Benjamin Morel committed
546 547 548 549
     * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey An associative array that defines properties of the foreign key to be created.
     * @param \Doctrine\DBAL\Schema\Table|string         $table      The name of the table on which the foreign key is to be created.
     *
     * @return void
550
     */
551
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
552
    {
553 554
        $this->tryMethod('dropForeignKey', $foreignKey, $table);
        $this->createForeignKey($foreignKey, $table);
555 556 557
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
558 559 560
     * Drops and create a new sequence.
     *
     * @param \Doctrine\DBAL\Schema\Sequence $sequence
561
     *
Benjamin Morel's avatar
Benjamin Morel committed
562 563 564
     * @return void
     *
     * @throws \Doctrine\DBAL\ConnectionException If something fails at database level.
565
     */
566
    public function dropAndCreateSequence(Sequence $sequence)
567
    {
Benjamin Eberlei's avatar
Benjamin Eberlei committed
568 569
        $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform));
        $this->createSequence($sequence);
570 571 572
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
573 574 575
     * Drops and creates a new table.
     *
     * @param \Doctrine\DBAL\Schema\Table $table
576
     *
Benjamin Morel's avatar
Benjamin Morel committed
577
     * @return void
578
     */
579
    public function dropAndCreateTable(Table $table)
580
    {
581
        $this->tryMethod('dropTable', $table->getQuotedName($this->_platform));
582
        $this->createTable($table);
583 584 585
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
586
     * Drops and creates a new database.
587 588
     *
     * @param string $database The name of the database to create.
Benjamin Morel's avatar
Benjamin Morel committed
589 590
     *
     * @return void
591 592 593 594 595 596 597 598
     */
    public function dropAndCreateDatabase($database)
    {
        $this->tryMethod('dropDatabase', $database);
        $this->createDatabase($database);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
599 600 601
     * Drops and creates a new view.
     *
     * @param \Doctrine\DBAL\Schema\View $view
602
     *
Benjamin Morel's avatar
Benjamin Morel committed
603
     * @return void
604
     */
605
    public function dropAndCreateView(View $view)
606
    {
607
        $this->tryMethod('dropView', $view->getQuotedName($this->_platform));
608
        $this->createView($view);
609 610
    }

611 612
    /* alterTable() Methods */

romanb's avatar
romanb committed
613
    /**
Benjamin Morel's avatar
Benjamin Morel committed
614
     * Alters an existing tables schema.
romanb's avatar
romanb committed
615
     *
Benjamin Morel's avatar
Benjamin Morel committed
616 617 618
     * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff
     *
     * @return void
619 620 621
     */
    public function alterTable(TableDiff $tableDiff)
    {
622
        $queries = $this->_platform->getAlterTableSQL($tableDiff);
623
        if (is_array($queries) && count($queries)) {
624
            foreach ($queries as $ddlQuery) {
625 626
                $this->_execSql($ddlQuery);
            }
627
        }
628 629
    }

630
    /**
Benjamin Morel's avatar
Benjamin Morel committed
631 632 633 634
     * Renames a given table to another name.
     *
     * @param string $name    The current name of the table.
     * @param string $newName The new name of the table.
635
     *
Benjamin Morel's avatar
Benjamin Morel committed
636
     * @return void
637 638 639
     */
    public function renameTable($name, $newName)
    {
640 641 642
        $tableDiff = new TableDiff($name);
        $tableDiff->newName = $newName;
        $this->alterTable($tableDiff);
643 644
    }

645 646 647 648 649
    /**
     * Methods for filtering return values of list*() methods to convert
     * the native DBMS data definition to a portable Doctrine definition
     */

Benjamin Morel's avatar
Benjamin Morel committed
650 651 652 653 654
    /**
     * @param array $databases
     *
     * @return array
     */
655 656
    protected function _getPortableDatabasesList($databases)
    {
657
        $list = array();
658
        foreach ($databases as $value) {
659 660 661
            if ($value = $this->_getPortableDatabaseDefinition($value)) {
                $list[] = $value;
            }
662
        }
Benjamin Morel's avatar
Benjamin Morel committed
663

664
        return $list;
665 666
    }

667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
    /**
     * Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition.
     *
     * @param array $namespaces The list of namespace names in the native DBMS data definition.
     *
     * @return array
     */
    protected function getPortableNamespacesList(array $namespaces)
    {
        $namespacesList = array();

        foreach ($namespaces as $namespace) {
            $namespacesList[] = $this->getPortableNamespaceDefinition($namespace);
        }

        return $namespacesList;
    }

Benjamin Morel's avatar
Benjamin Morel committed
685 686 687 688 689
    /**
     * @param array $database
     *
     * @return mixed
     */
690 691 692 693 694
    protected function _getPortableDatabaseDefinition($database)
    {
        return $database;
    }

695 696 697 698 699 700 701 702 703 704 705 706
    /**
     * Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition.
     *
     * @param array $namespace The native DBMS namespace definition.
     *
     * @return mixed
     */
    protected function getPortableNamespaceDefinition(array $namespace)
    {
        return $namespace;
    }

Benjamin Morel's avatar
Benjamin Morel committed
707 708 709 710 711
    /**
     * @param array $functions
     *
     * @return array
     */
712 713
    protected function _getPortableFunctionsList($functions)
    {
714
        $list = array();
715
        foreach ($functions as $value) {
716 717 718
            if ($value = $this->_getPortableFunctionDefinition($value)) {
                $list[] = $value;
            }
719
        }
Benjamin Morel's avatar
Benjamin Morel committed
720

721
        return $list;
722 723
    }

Benjamin Morel's avatar
Benjamin Morel committed
724 725 726 727 728
    /**
     * @param array $function
     *
     * @return mixed
     */
729 730 731 732 733
    protected function _getPortableFunctionDefinition($function)
    {
        return $function;
    }

Benjamin Morel's avatar
Benjamin Morel committed
734 735 736 737 738
    /**
     * @param array $triggers
     *
     * @return array
     */
739 740
    protected function _getPortableTriggersList($triggers)
    {
741
        $list = array();
742
        foreach ($triggers as $value) {
743 744 745
            if ($value = $this->_getPortableTriggerDefinition($value)) {
                $list[] = $value;
            }
746
        }
Benjamin Morel's avatar
Benjamin Morel committed
747

748
        return $list;
749 750
    }

Benjamin Morel's avatar
Benjamin Morel committed
751 752 753 754 755
    /**
     * @param array $trigger
     *
     * @return mixed
     */
756 757 758 759 760
    protected function _getPortableTriggerDefinition($trigger)
    {
        return $trigger;
    }

Benjamin Morel's avatar
Benjamin Morel committed
761 762 763 764 765
    /**
     * @param array $sequences
     *
     * @return array
     */
766 767
    protected function _getPortableSequencesList($sequences)
    {
768
        $list = array();
769
        foreach ($sequences as $value) {
770 771 772
            if ($value = $this->_getPortableSequenceDefinition($value)) {
                $list[] = $value;
            }
773
        }
Benjamin Morel's avatar
Benjamin Morel committed
774

775
        return $list;
776 777
    }

778 779
    /**
     * @param array $sequence
Benjamin Morel's avatar
Benjamin Morel committed
780 781 782 783
     *
     * @return \Doctrine\DBAL\Schema\Sequence
     *
     * @throws \Doctrine\DBAL\DBALException
784
     */
785 786
    protected function _getPortableSequenceDefinition($sequence)
    {
787
        throw DBALException::notSupported('Sequences');
788 789
    }

790 791 792 793 794
    /**
     * Independent of the database the keys of the column list result are lowercased.
     *
     * The name of the created column instance however is kept in its case.
     *
Benjamin Morel's avatar
Benjamin Morel committed
795 796 797 798
     * @param string $table        The name of the table.
     * @param string $database
     * @param array  $tableColumns
     *
799 800
     * @return array
     */
801
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
802
    {
803 804
        $eventManager = $this->_platform->getEventManager();

805
        $list = array();
806
        foreach ($tableColumns as $tableColumn) {
807
            $column = null;
808 809 810
            $defaultPrevented = false;

            if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) {
811
                $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn);
812 813 814
                $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs);

                $defaultPrevented = $eventArgs->isDefaultPrevented();
815
                $column = $eventArgs->getColumn();
816 817
            }

818
            if ( ! $defaultPrevented) {
819
                $column = $this->_getPortableTableColumnDefinition($tableColumn);
820 821
            }

822 823 824
            if ($column) {
                $name = strtolower($column->getQuotedName($this->_platform));
                $list[$name] = $column;
825
            }
826
        }
Benjamin Morel's avatar
Benjamin Morel committed
827

828
        return $list;
829 830
    }

831
    /**
Benjamin Morel's avatar
Benjamin Morel committed
832
     * Gets Table Column Definition.
833 834
     *
     * @param array $tableColumn
Benjamin Morel's avatar
Benjamin Morel committed
835 836
     *
     * @return \Doctrine\DBAL\Schema\Column
837 838
     */
    abstract protected function _getPortableTableColumnDefinition($tableColumn);
839

840
    /**
Benjamin Morel's avatar
Benjamin Morel committed
841 842 843 844
     * Aggregates and groups the index results according to the required data result.
     *
     * @param array       $tableIndexRows
     * @param string|null $tableName
845 846 847
     *
     * @return array
     */
848
    protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null)
849
    {
850
        $result = array();
Steve Müller's avatar
Steve Müller committed
851
        foreach ($tableIndexRows as $tableIndex) {
852
            $indexName = $keyName = $tableIndex['key_name'];
Steve Müller's avatar
Steve Müller committed
853
            if ($tableIndex['primary']) {
854 855
                $keyName = 'primary';
            }
856
            $keyName = strtolower($keyName);
857

Steve Müller's avatar
Steve Müller committed
858
            if (!isset($result[$keyName])) {
859 860 861 862 863
                $result[$keyName] = array(
                    'name' => $indexName,
                    'columns' => array($tableIndex['column_name']),
                    'unique' => $tableIndex['non_unique'] ? false : true,
                    'primary' => $tableIndex['primary'],
864
                    'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(),
865
                    'options' => isset($tableIndex['where']) ? array('where' => $tableIndex['where']) : array(),
866 867 868
                );
            } else {
                $result[$keyName]['columns'][] = $tableIndex['column_name'];
869
            }
870
        }
871

872 873
        $eventManager = $this->_platform->getEventManager();

874
        $indexes = array();
Steve Müller's avatar
Steve Müller committed
875
        foreach ($result as $indexKey => $data) {
876 877 878 879 880 881 882 883 884 885 886
            $index = null;
            $defaultPrevented = false;

            if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) {
                $eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn);
                $eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs);

                $defaultPrevented = $eventArgs->isDefaultPrevented();
                $index = $eventArgs->getIndex();
            }

887
            if ( ! $defaultPrevented) {
888
                $index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options']);
889 890 891 892 893
            }

            if ($index) {
                $indexes[$indexKey] = $index;
            }
894 895 896
        }

        return $indexes;
897 898
    }

Benjamin Morel's avatar
Benjamin Morel committed
899 900 901 902 903
    /**
     * @param array $tables
     *
     * @return array
     */
904 905
    protected function _getPortableTablesList($tables)
    {
906
        $list = array();
907
        foreach ($tables as $value) {
908 909 910
            if ($value = $this->_getPortableTableDefinition($value)) {
                $list[] = $value;
            }
911
        }
Benjamin Morel's avatar
Benjamin Morel committed
912

913
        return $list;
914 915
    }

Benjamin Morel's avatar
Benjamin Morel committed
916 917 918 919 920
    /**
     * @param array $table
     *
     * @return array
     */
921 922 923 924 925
    protected function _getPortableTableDefinition($table)
    {
        return $table;
    }

Benjamin Morel's avatar
Benjamin Morel committed
926 927 928 929 930
    /**
     * @param array $users
     *
     * @return array
     */
931 932
    protected function _getPortableUsersList($users)
    {
933
        $list = array();
934
        foreach ($users as $value) {
935 936 937
            if ($value = $this->_getPortableUserDefinition($value)) {
                $list[] = $value;
            }
938
        }
Benjamin Morel's avatar
Benjamin Morel committed
939

940
        return $list;
941 942
    }

Benjamin Morel's avatar
Benjamin Morel committed
943 944 945 946 947
    /**
     * @param array $user
     *
     * @return mixed
     */
948 949 950 951 952
    protected function _getPortableUserDefinition($user)
    {
        return $user;
    }

Benjamin Morel's avatar
Benjamin Morel committed
953 954 955 956
    /**
     * @param array $views
     * @return array
     */
957 958
    protected function _getPortableViewsList($views)
    {
959
        $list = array();
960
        foreach ($views as $value) {
961
            if ($view = $this->_getPortableViewDefinition($value)) {
962
                $viewName = strtolower($view->getQuotedName($this->_platform));
963
                $list[$viewName] = $view;
964
            }
965
        }
Benjamin Morel's avatar
Benjamin Morel committed
966

967
        return $list;
968 969
    }

Benjamin Morel's avatar
Benjamin Morel committed
970 971 972 973 974
    /**
     * @param array $view
     *
     * @return mixed
     */
975 976
    protected function _getPortableViewDefinition($view)
    {
977
        return false;
978 979
    }

Benjamin Morel's avatar
Benjamin Morel committed
980 981 982 983 984
    /**
     * @param array $tableForeignKeys
     *
     * @return array
     */
985 986 987
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
    {
        $list = array();
988
        foreach ($tableForeignKeys as $value) {
989 990 991 992
            if ($value = $this->_getPortableTableForeignKeyDefinition($value)) {
                $list[] = $value;
            }
        }
Benjamin Morel's avatar
Benjamin Morel committed
993

994 995 996
        return $list;
    }

Benjamin Morel's avatar
Benjamin Morel committed
997 998 999 1000 1001
    /**
     * @param array $tableForeignKey
     *
     * @return mixed
     */
1002 1003 1004 1005
    protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
    {
        return $tableForeignKey;
    }
1006

Benjamin Morel's avatar
Benjamin Morel committed
1007 1008 1009 1010 1011
    /**
     * @param array|string $sql
     *
     * @return void
     */
romanb's avatar
romanb committed
1012
    protected function _execSql($sql)
1013 1014
    {
        foreach ((array) $sql as $query) {
1015
            $this->_conn->executeUpdate($query);
romanb's avatar
romanb committed
1016 1017
        }
    }
1018 1019

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1020
     * Creates a schema instance for the current database.
1021
     *
Benjamin Morel's avatar
Benjamin Morel committed
1022
     * @return \Doctrine\DBAL\Schema\Schema
1023 1024 1025
     */
    public function createSchema()
    {
1026 1027 1028 1029 1030 1031
        $namespaces = array();

        if ($this->_platform->supportsSchemas()) {
            $namespaces = $this->listNamespaceNames();
        }

1032
        $sequences = array();
1033

Steve Müller's avatar
Steve Müller committed
1034
        if ($this->_platform->supportsSequences()) {
1035 1036
            $sequences = $this->listSequences();
        }
1037

1038 1039
        $tables = $this->listTables();

1040
        return new Schema($tables, $sequences, $this->createSchemaConfig(), $namespaces);
1041 1042 1043
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1044
     * Creates the configuration for this schema.
1045
     *
Benjamin Morel's avatar
Benjamin Morel committed
1046
     * @return \Doctrine\DBAL\Schema\SchemaConfig
1047 1048 1049 1050 1051
     */
    public function createSchemaConfig()
    {
        $schemaConfig = new SchemaConfig();
        $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength());
1052 1053 1054 1055 1056

        $searchPaths = $this->getSchemaSearchPaths();
        if (isset($searchPaths[0])) {
            $schemaConfig->setName($searchPaths[0]);
        }
1057

1058 1059
        $params = $this->_conn->getParams();
        if (isset($params['defaultTableOptions'])) {
1060
            $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']);
1061 1062
        }

1063
        return $schemaConfig;
1064
    }
1065

1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
    /**
     * The search path for namespaces in the currently connected database.
     *
     * The first entry is usually the default namespace in the Schema. All
     * further namespaces contain tables/sequences which can also be addressed
     * with a short, not full-qualified name.
     *
     * For databases that don't support subschema/namespaces this method
     * returns the name of the currently connected database.
     *
     * @return array
     */
1078 1079 1080 1081 1082
    public function getSchemaSearchPaths()
    {
        return array($this->_conn->getDatabase());
    }

1083 1084 1085
    /**
     * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns
     * the type given as default.
1086
     *
Benjamin Morel's avatar
Benjamin Morel committed
1087 1088 1089
     * @param string $comment
     * @param string $currentType
     *
1090 1091 1092 1093
     * @return string
     */
    public function extractDoctrineTypeFromComment($comment, $currentType)
    {
1094
        if (preg_match("(\(DC2Type:([a-zA-Z0-9_]+)\))", $comment, $match)) {
1095 1096
            $currentType = $match[1];
        }
Benjamin Morel's avatar
Benjamin Morel committed
1097

1098 1099 1100
        return $currentType;
    }

Benjamin Morel's avatar
Benjamin Morel committed
1101 1102 1103 1104 1105 1106
    /**
     * @param string $comment
     * @param string $type
     *
     * @return string
     */
1107 1108 1109 1110
    public function removeDoctrineTypeFromComment($comment, $type)
    {
        return str_replace('(DC2Type:'.$type.')', '', $comment);
    }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
1111
}