AbstractSchemaManager.php 30.7 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 28 29 30 31 32 33 34 35 36 37
use function array_filter;
use function array_map;
use function array_values;
use function call_user_func_array;
use function count;
use function func_get_args;
use function is_array;
use function is_null;
use function preg_match;
use function str_replace;
use function strtolower;
38

romanb's avatar
romanb committed
39
/**
40
 * Base class for schema managers. Schema managers are used to inspect and/or
41
 * modify the database schema/structure.
romanb's avatar
romanb committed
42
 *
Benjamin Morel's avatar
Benjamin Morel committed
43 44 45 46 47 48
 * @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
49
 */
50
abstract class AbstractSchemaManager
romanb's avatar
romanb committed
51
{
52
    /**
Benjamin Morel's avatar
Benjamin Morel committed
53
     * Holds instance of the Doctrine connection for this schema manager.
54
     *
55
     * @var \Doctrine\DBAL\Connection
56
     */
romanb's avatar
romanb committed
57 58
    protected $_conn;

59
    /**
Benjamin Morel's avatar
Benjamin Morel committed
60
     * Holds instance of the database platform used for this schema manager.
61
     *
62
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
63 64 65 66
     */
    protected $_platform;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
67
     * Constructor. Accepts the Connection instance to manage the schema for.
68
     *
Benjamin Morel's avatar
Benjamin Morel committed
69 70
     * @param \Doctrine\DBAL\Connection                      $conn
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform|null $platform
71
     */
72
    public function __construct(\Doctrine\DBAL\Connection $conn, AbstractPlatform $platform = null)
73
    {
74 75
        $this->_conn     = $conn;
        $this->_platform = $platform ?: $this->_conn->getDatabasePlatform();
76 77
    }

78
    /**
Benjamin Morel's avatar
Benjamin Morel committed
79
     * Returns the associated platform.
80
     *
81
     * @return \Doctrine\DBAL\Platforms\AbstractPlatform
82 83 84 85 86 87
     */
    public function getDatabasePlatform()
    {
        return $this->_platform;
    }

88
    /**
Benjamin Morel's avatar
Benjamin Morel committed
89
     * Tries any method on the schema manager. Normally a method throws an
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
     * 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 {
108
            return call_user_func_array([$this, $method], $args);
109 110 111 112 113
        } catch (\Exception $e) {
            return false;
        }
    }

romanb's avatar
romanb committed
114
    /**
Benjamin Morel's avatar
Benjamin Morel committed
115
     * Lists the available databases for this connection.
romanb's avatar
romanb committed
116
     *
Benjamin Morel's avatar
Benjamin Morel committed
117
     * @return array
romanb's avatar
romanb committed
118 119 120
     */
    public function listDatabases()
    {
121
        $sql = $this->_platform->getListDatabasesSQL();
122 123 124 125

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

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

128 129 130 131 132 133 134 135 136 137 138 139 140 141
    /**
     * 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
142
    /**
Benjamin Morel's avatar
Benjamin Morel committed
143 144 145
     * Lists the available sequences for this connection.
     *
     * @param string|null $database
romanb's avatar
romanb committed
146
     *
Benjamin Morel's avatar
Benjamin Morel committed
147
     * @return \Doctrine\DBAL\Schema\Sequence[]
romanb's avatar
romanb committed
148
     */
149
    public function listSequences($database = null)
romanb's avatar
romanb committed
150
    {
151 152 153
        if (is_null($database)) {
            $database = $this->_conn->getDatabase();
        }
154
        $sql = $this->_platform->getListSequencesSQL($database);
155 156 157

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

158
        return $this->filterAssetNames($this->_getPortableSequencesList($sequences));
romanb's avatar
romanb committed
159 160 161
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
162
     * Lists the columns for a given table.
romanb's avatar
romanb committed
163
     *
164 165
     * In contrast to other libraries and to the old version of Doctrine,
     * this column definition does try to contain the 'primary' field for
Possum's avatar
Possum committed
166
     * the reason that it is not portable across different RDBMS. Use
167 168 169 170
     * {@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
171 172 173 174
     * @param string      $table    The name of the table.
     * @param string|null $database
     *
     * @return \Doctrine\DBAL\Schema\Column[]
romanb's avatar
romanb committed
175
     */
176
    public function listTableColumns($table, $database = null)
romanb's avatar
romanb committed
177
    {
178
        if ( ! $database) {
179 180 181 182
            $database = $this->_conn->getDatabase();
        }

        $sql = $this->_platform->getListTableColumnsSQL($table, $database);
183 184 185

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

186
        return $this->_getPortableTableColumnList($table, $database, $tableColumns);
romanb's avatar
romanb committed
187 188 189
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
190
     * Lists the indexes for a given table returning an array of Index instances.
romanb's avatar
romanb committed
191
     *
192 193
     * Keys of the portable indexes list are all lower-cased.
     *
Benjamin Morel's avatar
Benjamin Morel committed
194 195 196
     * @param string $table The name of the table.
     *
     * @return \Doctrine\DBAL\Schema\Index[]
romanb's avatar
romanb committed
197 198 199
     */
    public function listTableIndexes($table)
    {
200
        $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase());
201 202 203

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

204
        return $this->_getPortableTableIndexesList($tableIndexes, $table);
romanb's avatar
romanb committed
205 206
    }

207
    /**
Benjamin Morel's avatar
Benjamin Morel committed
208
     * Returns true if all the given tables exist.
209
     *
210
     * @param array $tableNames
Benjamin Morel's avatar
Benjamin Morel committed
211
     *
212
     * @return bool
213 214 215
     */
    public function tablesExist($tableNames)
    {
216
        $tableNames = array_map('strtolower', (array) $tableNames);
Benjamin Morel's avatar
Benjamin Morel committed
217

218 219 220
        return count($tableNames) == count(\array_intersect($tableNames, array_map('strtolower', $this->listTableNames())));
    }

romanb's avatar
romanb committed
221
    /**
Benjamin Morel's avatar
Benjamin Morel committed
222
     * Returns a list of all tables in the current database.
romanb's avatar
romanb committed
223
     *
224
     * @return array
romanb's avatar
romanb committed
225
     */
226
    public function listTableNames()
romanb's avatar
romanb committed
227
    {
228
        $sql = $this->_platform->getListTablesSQL();
229 230

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

233 234
        return $this->filterAssetNames($tableNames);
    }
235

236
    /**
Benjamin Morel's avatar
Benjamin Morel committed
237
     * Filters asset names if they are configured to return only a subset of all
238 239 240
     * the found elements.
     *
     * @param array $assetNames
Benjamin Morel's avatar
Benjamin Morel committed
241
     *
242 243 244 245 246
     * @return array
     */
    protected function filterAssetNames($assetNames)
    {
        $filterExpr = $this->getFilterSchemaAssetsExpression();
247
        if ( ! $filterExpr) {
248 249
            return $assetNames;
        }
Benjamin Morel's avatar
Benjamin Morel committed
250

251
        return array_values(
252 253
            array_filter($assetNames, function ($assetName) use ($filterExpr) {
                $assetName = ($assetName instanceof AbstractAsset) ? $assetName->getName() : $assetName;
254

255
                return preg_match($filterExpr, $assetName);
256 257 258 259
            })
        );
    }

Benjamin Morel's avatar
Benjamin Morel committed
260 261 262
    /**
     * @return string|null
     */
263 264 265
    protected function getFilterSchemaAssetsExpression()
    {
        return $this->_conn->getConfiguration()->getFilterSchemaAssetsExpression();
266 267 268
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
269
     * Lists the tables for this connection.
270
     *
Benjamin Morel's avatar
Benjamin Morel committed
271
     * @return \Doctrine\DBAL\Schema\Table[]
272 273 274 275
     */
    public function listTables()
    {
        $tableNames = $this->listTableNames();
276

277
        $tables = [];
278
        foreach ($tableNames as $tableName) {
279
            $tables[] = $this->listTableDetails($tableName);
280 281 282
        }

        return $tables;
romanb's avatar
romanb committed
283 284
    }

285
    /**
Benjamin Morel's avatar
Benjamin Morel committed
286 287 288
     * @param string $tableName
     *
     * @return \Doctrine\DBAL\Schema\Table
289 290 291 292
     */
    public function listTableDetails($tableName)
    {
        $columns = $this->listTableColumns($tableName);
293
        $foreignKeys = [];
294 295 296 297 298
        if ($this->_platform->supportsForeignKeyConstraints()) {
            $foreignKeys = $this->listTableForeignKeys($tableName);
        }
        $indexes = $this->listTableIndexes($tableName);

299
        return new Table($tableName, $columns, $indexes, $foreignKeys, false, []);
300 301
    }

romanb's avatar
romanb committed
302
    /**
Benjamin Morel's avatar
Benjamin Morel committed
303
     * Lists the views this connection has.
romanb's avatar
romanb committed
304
     *
Benjamin Morel's avatar
Benjamin Morel committed
305
     * @return \Doctrine\DBAL\Schema\View[]
romanb's avatar
romanb committed
306
     */
307
    public function listViews()
romanb's avatar
romanb committed
308
    {
309
        $database = $this->_conn->getDatabase();
310
        $sql = $this->_platform->getListViewsSQL($database);
311 312 313
        $views = $this->_conn->fetchAll($sql);

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

316
    /**
Benjamin Morel's avatar
Benjamin Morel committed
317 318 319 320
     * Lists the foreign keys for the given table.
     *
     * @param string      $table    The name of the table.
     * @param string|null $database
321
     *
Benjamin Morel's avatar
Benjamin Morel committed
322
     * @return \Doctrine\DBAL\Schema\ForeignKeyConstraint[]
323 324 325 326 327 328
     */
    public function listTableForeignKeys($table, $database = null)
    {
        if (is_null($database)) {
            $database = $this->_conn->getDatabase();
        }
329
        $sql = $this->_platform->getListTableForeignKeysSQL($table, $database);
330 331 332 333 334
        $tableForeignKeys = $this->_conn->fetchAll($sql);

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

335 336
    /* drop*() Methods */

romanb's avatar
romanb committed
337
    /**
338
     * Drops a database.
339
     *
340
     * NOTE: You can not drop the database this SchemaManager is currently connected to.
romanb's avatar
romanb committed
341
     *
Benjamin Morel's avatar
Benjamin Morel committed
342 343 344
     * @param string $database The name of the database to drop.
     *
     * @return void
romanb's avatar
romanb committed
345
     */
346
    public function dropDatabase($database)
romanb's avatar
romanb committed
347
    {
348
        $this->_execSql($this->_platform->getDropDatabaseSQL($database));
romanb's avatar
romanb committed
349 350 351
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
352 353
     * Drops the given table.
     *
jeroendedauw's avatar
jeroendedauw committed
354
     * @param string $tableName The name of the table to drop.
romanb's avatar
romanb committed
355
     *
Benjamin Morel's avatar
Benjamin Morel committed
356
     * @return void
romanb's avatar
romanb committed
357
     */
jeroendedauw's avatar
jeroendedauw committed
358
    public function dropTable($tableName)
romanb's avatar
romanb committed
359
    {
jeroendedauw's avatar
jeroendedauw committed
360
        $this->_execSql($this->_platform->getDropTableSQL($tableName));
romanb's avatar
romanb committed
361 362 363
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
364
     * Drops the index from the given table.
romanb's avatar
romanb committed
365
     *
Benjamin Morel's avatar
Benjamin Morel committed
366 367 368 369
     * @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
370
     */
371
    public function dropIndex($index, $table)
romanb's avatar
romanb committed
372
    {
Steve Müller's avatar
Steve Müller committed
373
        if ($index instanceof Index) {
374
            $index = $index->getQuotedName($this->_platform);
375 376
        }

377
        $this->_execSql($this->_platform->getDropIndexSQL($index, $table));
romanb's avatar
romanb committed
378 379 380
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
381 382 383 384
     * 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
385
     *
Benjamin Morel's avatar
Benjamin Morel committed
386
     * @return void
romanb's avatar
romanb committed
387
     */
388
    public function dropConstraint(Constraint $constraint, $table)
romanb's avatar
romanb committed
389
    {
390
        $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table));
romanb's avatar
romanb committed
391 392 393
    }

    /**
romanb's avatar
romanb committed
394
     * Drops a foreign key from a table.
romanb's avatar
romanb committed
395
     *
Benjamin Morel's avatar
Benjamin Morel committed
396 397 398 399
     * @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
400
     */
401
    public function dropForeignKey($foreignKey, $table)
romanb's avatar
romanb committed
402
    {
403
        $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table));
romanb's avatar
romanb committed
404 405 406
    }

    /**
romanb's avatar
romanb committed
407
     * Drops a sequence with a given name.
romanb's avatar
romanb committed
408
     *
romanb's avatar
romanb committed
409
     * @param string $name The name of the sequence to drop.
Benjamin Morel's avatar
Benjamin Morel committed
410 411
     *
     * @return void
romanb's avatar
romanb committed
412
     */
413
    public function dropSequence($name)
romanb's avatar
romanb committed
414
    {
415
        $this->_execSql($this->_platform->getDropSequenceSQL($name));
romanb's avatar
romanb committed
416 417
    }

418
    /**
Benjamin Morel's avatar
Benjamin Morel committed
419
     * Drops a view.
420
     *
Benjamin Morel's avatar
Benjamin Morel committed
421 422 423
     * @param string $name The name of the view.
     *
     * @return void
424 425 426
     */
    public function dropView($name)
    {
427
        $this->_execSql($this->_platform->getDropViewSQL($name));
428 429 430 431
    }

    /* create*() Methods */

romanb's avatar
romanb committed
432
    /**
romanb's avatar
romanb committed
433
     * Creates a new database.
romanb's avatar
romanb committed
434
     *
romanb's avatar
romanb committed
435
     * @param string $database The name of the database to create.
Benjamin Morel's avatar
Benjamin Morel committed
436 437
     *
     * @return void
romanb's avatar
romanb committed
438
     */
romanb's avatar
romanb committed
439
    public function createDatabase($database)
romanb's avatar
romanb committed
440
    {
441
        $this->_execSql($this->_platform->getCreateDatabaseSQL($database));
romanb's avatar
romanb committed
442 443 444
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
445
     * Creates a new table.
romanb's avatar
romanb committed
446
     *
Benjamin Morel's avatar
Benjamin Morel committed
447 448 449
     * @param \Doctrine\DBAL\Schema\Table $table
     *
     * @return void
romanb's avatar
romanb committed
450
     */
451
    public function createTable(Table $table)
romanb's avatar
romanb committed
452
    {
453
        $createFlags = AbstractPlatform::CREATE_INDEXES|AbstractPlatform::CREATE_FOREIGNKEYS;
454
        $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags));
romanb's avatar
romanb committed
455 456 457
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
458 459 460
     * Creates a new sequence.
     *
     * @param \Doctrine\DBAL\Schema\Sequence $sequence
romanb's avatar
romanb committed
461
     *
Benjamin Morel's avatar
Benjamin Morel committed
462 463 464
     * @return void
     *
     * @throws \Doctrine\DBAL\ConnectionException If something fails at database level.
romanb's avatar
romanb committed
465
     */
466
    public function createSequence($sequence)
romanb's avatar
romanb committed
467
    {
468
        $this->_execSql($this->_platform->getCreateSequenceSQL($sequence));
romanb's avatar
romanb committed
469 470 471
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
472 473 474 475
     * Creates a constraint on a table.
     *
     * @param \Doctrine\DBAL\Schema\Constraint   $constraint
     * @param \Doctrine\DBAL\Schema\Table|string $table
romanb's avatar
romanb committed
476
     *
Benjamin Morel's avatar
Benjamin Morel committed
477
     * @return void
478 479
     */
    public function createConstraint(Constraint $constraint, $table)
romanb's avatar
romanb committed
480
    {
481
        $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table));
romanb's avatar
romanb committed
482 483 484
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
485
     * Creates a new index on a table.
romanb's avatar
romanb committed
486
     *
Benjamin Morel's avatar
Benjamin Morel committed
487 488 489 490
     * @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
491
     */
492
    public function createIndex(Index $index, $table)
romanb's avatar
romanb committed
493
    {
494
        $this->_execSql($this->_platform->getCreateIndexSQL($index, $table));
romanb's avatar
romanb committed
495 496 497
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
498 499 500 501
     * 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
502
     *
Benjamin Morel's avatar
Benjamin Morel committed
503
     * @return void
romanb's avatar
romanb committed
504
     */
505
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
romanb's avatar
romanb committed
506
    {
507
        $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table));
romanb's avatar
romanb committed
508 509
    }

510
    /**
Benjamin Morel's avatar
Benjamin Morel committed
511
     * Creates a new view.
512
     *
Benjamin Morel's avatar
Benjamin Morel committed
513 514 515
     * @param \Doctrine\DBAL\Schema\View $view
     *
     * @return void
516
     */
517
    public function createView(View $view)
518
    {
519
        $this->_execSql($this->_platform->getCreateViewSQL($view->getQuotedName($this->_platform), $view->getSql()));
520 521
    }

522 523
    /* dropAndCreate*() Methods */

524
    /**
Benjamin Morel's avatar
Benjamin Morel committed
525
     * Drops and creates a constraint.
526 527 528
     *
     * @see dropConstraint()
     * @see createConstraint()
Benjamin Morel's avatar
Benjamin Morel committed
529 530 531 532 533
     *
     * @param \Doctrine\DBAL\Schema\Constraint   $constraint
     * @param \Doctrine\DBAL\Schema\Table|string $table
     *
     * @return void
534
     */
535
    public function dropAndCreateConstraint(Constraint $constraint, $table)
536
    {
537 538
        $this->tryMethod('dropConstraint', $constraint, $table);
        $this->createConstraint($constraint, $table);
539 540 541
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
542 543 544 545
     * 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.
546
     *
Benjamin Morel's avatar
Benjamin Morel committed
547
     * @return void
548
     */
549
    public function dropAndCreateIndex(Index $index, $table)
550
    {
551
        $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table);
552
        $this->createIndex($index, $table);
553 554 555
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
556
     * Drops and creates a new foreign key.
557
     *
Benjamin Morel's avatar
Benjamin Morel committed
558 559 560 561
     * @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
562
     */
563
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
564
    {
565 566
        $this->tryMethod('dropForeignKey', $foreignKey, $table);
        $this->createForeignKey($foreignKey, $table);
567 568 569
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
570 571 572
     * Drops and create a new sequence.
     *
     * @param \Doctrine\DBAL\Schema\Sequence $sequence
573
     *
Benjamin Morel's avatar
Benjamin Morel committed
574 575 576
     * @return void
     *
     * @throws \Doctrine\DBAL\ConnectionException If something fails at database level.
577
     */
578
    public function dropAndCreateSequence(Sequence $sequence)
579
    {
Benjamin Eberlei's avatar
Benjamin Eberlei committed
580 581
        $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform));
        $this->createSequence($sequence);
582 583 584
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
585 586 587
     * Drops and creates a new table.
     *
     * @param \Doctrine\DBAL\Schema\Table $table
588
     *
Benjamin Morel's avatar
Benjamin Morel committed
589
     * @return void
590
     */
591
    public function dropAndCreateTable(Table $table)
592
    {
593
        $this->tryMethod('dropTable', $table->getQuotedName($this->_platform));
594
        $this->createTable($table);
595 596 597
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
598
     * Drops and creates a new database.
599 600
     *
     * @param string $database The name of the database to create.
Benjamin Morel's avatar
Benjamin Morel committed
601 602
     *
     * @return void
603 604 605 606 607 608 609 610
     */
    public function dropAndCreateDatabase($database)
    {
        $this->tryMethod('dropDatabase', $database);
        $this->createDatabase($database);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
611 612 613
     * Drops and creates a new view.
     *
     * @param \Doctrine\DBAL\Schema\View $view
614
     *
Benjamin Morel's avatar
Benjamin Morel committed
615
     * @return void
616
     */
617
    public function dropAndCreateView(View $view)
618
    {
619
        $this->tryMethod('dropView', $view->getQuotedName($this->_platform));
620
        $this->createView($view);
621 622
    }

623 624
    /* alterTable() Methods */

romanb's avatar
romanb committed
625
    /**
Benjamin Morel's avatar
Benjamin Morel committed
626
     * Alters an existing tables schema.
romanb's avatar
romanb committed
627
     *
Benjamin Morel's avatar
Benjamin Morel committed
628 629 630
     * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff
     *
     * @return void
631 632 633
     */
    public function alterTable(TableDiff $tableDiff)
    {
634
        $queries = $this->_platform->getAlterTableSQL($tableDiff);
635
        if (is_array($queries) && count($queries)) {
636
            foreach ($queries as $ddlQuery) {
637 638
                $this->_execSql($ddlQuery);
            }
639
        }
640 641
    }

642
    /**
Benjamin Morel's avatar
Benjamin Morel committed
643 644 645 646
     * 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.
647
     *
Benjamin Morel's avatar
Benjamin Morel committed
648
     * @return void
649 650 651
     */
    public function renameTable($name, $newName)
    {
652 653 654
        $tableDiff = new TableDiff($name);
        $tableDiff->newName = $newName;
        $this->alterTable($tableDiff);
655 656
    }

657 658 659 660 661
    /**
     * 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
662 663 664 665 666
    /**
     * @param array $databases
     *
     * @return array
     */
667 668
    protected function _getPortableDatabasesList($databases)
    {
669
        $list = [];
670
        foreach ($databases as $value) {
671 672 673
            if ($value = $this->_getPortableDatabaseDefinition($value)) {
                $list[] = $value;
            }
674
        }
Benjamin Morel's avatar
Benjamin Morel committed
675

676
        return $list;
677 678
    }

679 680 681 682 683 684 685 686 687
    /**
     * 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)
    {
688
        $namespacesList = [];
689 690 691 692 693 694 695 696

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

        return $namespacesList;
    }

Benjamin Morel's avatar
Benjamin Morel committed
697 698 699 700 701
    /**
     * @param array $database
     *
     * @return mixed
     */
702 703 704 705 706
    protected function _getPortableDatabaseDefinition($database)
    {
        return $database;
    }

707 708 709 710 711 712 713 714 715 716 717 718
    /**
     * 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
719 720 721 722 723
    /**
     * @param array $functions
     *
     * @return array
     */
724 725
    protected function _getPortableFunctionsList($functions)
    {
726
        $list = [];
727
        foreach ($functions as $value) {
728 729 730
            if ($value = $this->_getPortableFunctionDefinition($value)) {
                $list[] = $value;
            }
731
        }
Benjamin Morel's avatar
Benjamin Morel committed
732

733
        return $list;
734 735
    }

Benjamin Morel's avatar
Benjamin Morel committed
736 737 738 739 740
    /**
     * @param array $function
     *
     * @return mixed
     */
741 742 743 744 745
    protected function _getPortableFunctionDefinition($function)
    {
        return $function;
    }

Benjamin Morel's avatar
Benjamin Morel committed
746 747 748 749 750
    /**
     * @param array $triggers
     *
     * @return array
     */
751 752
    protected function _getPortableTriggersList($triggers)
    {
753
        $list = [];
754
        foreach ($triggers as $value) {
755 756 757
            if ($value = $this->_getPortableTriggerDefinition($value)) {
                $list[] = $value;
            }
758
        }
Benjamin Morel's avatar
Benjamin Morel committed
759

760
        return $list;
761 762
    }

Benjamin Morel's avatar
Benjamin Morel committed
763 764 765 766 767
    /**
     * @param array $trigger
     *
     * @return mixed
     */
768 769 770 771 772
    protected function _getPortableTriggerDefinition($trigger)
    {
        return $trigger;
    }

Benjamin Morel's avatar
Benjamin Morel committed
773 774 775 776 777
    /**
     * @param array $sequences
     *
     * @return array
     */
778 779
    protected function _getPortableSequencesList($sequences)
    {
780
        $list = [];
781
        foreach ($sequences as $value) {
782 783 784
            if ($value = $this->_getPortableSequenceDefinition($value)) {
                $list[] = $value;
            }
785
        }
Benjamin Morel's avatar
Benjamin Morel committed
786

787
        return $list;
788 789
    }

790 791
    /**
     * @param array $sequence
Benjamin Morel's avatar
Benjamin Morel committed
792 793 794 795
     *
     * @return \Doctrine\DBAL\Schema\Sequence
     *
     * @throws \Doctrine\DBAL\DBALException
796
     */
797 798
    protected function _getPortableSequenceDefinition($sequence)
    {
799
        throw DBALException::notSupported('Sequences');
800 801
    }

802 803 804 805 806
    /**
     * 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
807 808 809 810
     * @param string $table        The name of the table.
     * @param string $database
     * @param array  $tableColumns
     *
811 812
     * @return array
     */
813
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
814
    {
815 816
        $eventManager = $this->_platform->getEventManager();

817
        $list = [];
818
        foreach ($tableColumns as $tableColumn) {
819
            $column = null;
820 821 822
            $defaultPrevented = false;

            if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) {
823
                $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn);
824 825 826
                $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs);

                $defaultPrevented = $eventArgs->isDefaultPrevented();
827
                $column = $eventArgs->getColumn();
828 829
            }

830
            if ( ! $defaultPrevented) {
831
                $column = $this->_getPortableTableColumnDefinition($tableColumn);
832 833
            }

834 835 836
            if ($column) {
                $name = strtolower($column->getQuotedName($this->_platform));
                $list[$name] = $column;
837
            }
838
        }
Benjamin Morel's avatar
Benjamin Morel committed
839

840
        return $list;
841 842
    }

843
    /**
Benjamin Morel's avatar
Benjamin Morel committed
844
     * Gets Table Column Definition.
845 846
     *
     * @param array $tableColumn
Benjamin Morel's avatar
Benjamin Morel committed
847 848
     *
     * @return \Doctrine\DBAL\Schema\Column
849 850
     */
    abstract protected function _getPortableTableColumnDefinition($tableColumn);
851

852
    /**
Benjamin Morel's avatar
Benjamin Morel committed
853 854 855 856
     * Aggregates and groups the index results according to the required data result.
     *
     * @param array       $tableIndexRows
     * @param string|null $tableName
857 858 859
     *
     * @return array
     */
860
    protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null)
861
    {
862
        $result = [];
Steve Müller's avatar
Steve Müller committed
863
        foreach ($tableIndexRows as $tableIndex) {
864
            $indexName = $keyName = $tableIndex['key_name'];
Steve Müller's avatar
Steve Müller committed
865
            if ($tableIndex['primary']) {
866 867
                $keyName = 'primary';
            }
868
            $keyName = strtolower($keyName);
869

Steve Müller's avatar
Steve Müller committed
870
            if (!isset($result[$keyName])) {
871
                $result[$keyName] = [
872
                    'name' => $indexName,
873
                    'columns' => [$tableIndex['column_name']],
874 875
                    'unique' => $tableIndex['non_unique'] ? false : true,
                    'primary' => $tableIndex['primary'],
876
                    'flags' => $tableIndex['flags'] ?? [],
877 878
                    'options' => isset($tableIndex['where']) ? ['where' => $tableIndex['where']] : [],
                ];
879 880
            } else {
                $result[$keyName]['columns'][] = $tableIndex['column_name'];
881
            }
882
        }
883

884 885
        $eventManager = $this->_platform->getEventManager();

886
        $indexes = [];
Steve Müller's avatar
Steve Müller committed
887
        foreach ($result as $indexKey => $data) {
888 889 890 891 892 893 894 895 896 897 898
            $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();
            }

899
            if ( ! $defaultPrevented) {
900
                $index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options']);
901 902 903 904 905
            }

            if ($index) {
                $indexes[$indexKey] = $index;
            }
906 907 908
        }

        return $indexes;
909 910
    }

Benjamin Morel's avatar
Benjamin Morel committed
911 912 913 914 915
    /**
     * @param array $tables
     *
     * @return array
     */
916 917
    protected function _getPortableTablesList($tables)
    {
918
        $list = [];
919
        foreach ($tables as $value) {
920 921 922
            if ($value = $this->_getPortableTableDefinition($value)) {
                $list[] = $value;
            }
923
        }
Benjamin Morel's avatar
Benjamin Morel committed
924

925
        return $list;
926 927
    }

Benjamin Morel's avatar
Benjamin Morel committed
928 929 930 931 932
    /**
     * @param array $table
     *
     * @return array
     */
933 934 935 936 937
    protected function _getPortableTableDefinition($table)
    {
        return $table;
    }

Benjamin Morel's avatar
Benjamin Morel committed
938 939 940 941 942
    /**
     * @param array $users
     *
     * @return array
     */
943 944
    protected function _getPortableUsersList($users)
    {
945
        $list = [];
946
        foreach ($users as $value) {
947 948 949
            if ($value = $this->_getPortableUserDefinition($value)) {
                $list[] = $value;
            }
950
        }
Benjamin Morel's avatar
Benjamin Morel committed
951

952
        return $list;
953 954
    }

Benjamin Morel's avatar
Benjamin Morel committed
955 956 957 958 959
    /**
     * @param array $user
     *
     * @return mixed
     */
960 961 962 963 964
    protected function _getPortableUserDefinition($user)
    {
        return $user;
    }

Benjamin Morel's avatar
Benjamin Morel committed
965 966
    /**
     * @param array $views
967
     *
Benjamin Morel's avatar
Benjamin Morel committed
968 969
     * @return array
     */
970 971
    protected function _getPortableViewsList($views)
    {
972
        $list = [];
973
        foreach ($views as $value) {
974
            if ($view = $this->_getPortableViewDefinition($value)) {
975
                $viewName = strtolower($view->getQuotedName($this->_platform));
976
                $list[$viewName] = $view;
977
            }
978
        }
Benjamin Morel's avatar
Benjamin Morel committed
979

980
        return $list;
981 982
    }

Benjamin Morel's avatar
Benjamin Morel committed
983 984 985 986 987
    /**
     * @param array $view
     *
     * @return mixed
     */
988 989
    protected function _getPortableViewDefinition($view)
    {
990
        return false;
991 992
    }

Benjamin Morel's avatar
Benjamin Morel committed
993 994 995 996 997
    /**
     * @param array $tableForeignKeys
     *
     * @return array
     */
998 999
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
    {
1000
        $list = [];
1001
        foreach ($tableForeignKeys as $value) {
1002 1003 1004 1005
            if ($value = $this->_getPortableTableForeignKeyDefinition($value)) {
                $list[] = $value;
            }
        }
Benjamin Morel's avatar
Benjamin Morel committed
1006

1007 1008 1009
        return $list;
    }

Benjamin Morel's avatar
Benjamin Morel committed
1010 1011 1012 1013 1014
    /**
     * @param array $tableForeignKey
     *
     * @return mixed
     */
1015 1016 1017 1018
    protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
    {
        return $tableForeignKey;
    }
1019

Benjamin Morel's avatar
Benjamin Morel committed
1020 1021 1022 1023 1024
    /**
     * @param array|string $sql
     *
     * @return void
     */
romanb's avatar
romanb committed
1025
    protected function _execSql($sql)
1026 1027
    {
        foreach ((array) $sql as $query) {
1028
            $this->_conn->executeUpdate($query);
romanb's avatar
romanb committed
1029 1030
        }
    }
1031 1032

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1033
     * Creates a schema instance for the current database.
1034
     *
Benjamin Morel's avatar
Benjamin Morel committed
1035
     * @return \Doctrine\DBAL\Schema\Schema
1036 1037 1038
     */
    public function createSchema()
    {
1039
        $namespaces = [];
1040 1041 1042 1043 1044

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

1045
        $sequences = [];
1046

Steve Müller's avatar
Steve Müller committed
1047
        if ($this->_platform->supportsSequences()) {
1048 1049
            $sequences = $this->listSequences();
        }
1050

1051 1052
        $tables = $this->listTables();

1053
        return new Schema($tables, $sequences, $this->createSchemaConfig(), $namespaces);
1054 1055 1056
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1057
     * Creates the configuration for this schema.
1058
     *
Benjamin Morel's avatar
Benjamin Morel committed
1059
     * @return \Doctrine\DBAL\Schema\SchemaConfig
1060 1061 1062 1063 1064
     */
    public function createSchemaConfig()
    {
        $schemaConfig = new SchemaConfig();
        $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength());
1065 1066 1067 1068 1069

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

1071
        $params = $this->_conn->getParams();
1072 1073
        if (! isset($params['defaultTableOptions'])) {
            $params['defaultTableOptions'] = [];
1074
        }
1075 1076 1077 1078
        if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) {
            $params['defaultTableOptions']['charset'] = $params['charset'];
        }
        $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']);
1079

1080
        return $schemaConfig;
1081
    }
1082

1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
    /**
     * 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
     */
1095 1096
    public function getSchemaSearchPaths()
    {
1097
        return [$this->_conn->getDatabase()];
1098 1099
    }

1100 1101 1102
    /**
     * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns
     * the type given as default.
1103
     *
Benjamin Morel's avatar
Benjamin Morel committed
1104 1105 1106
     * @param string $comment
     * @param string $currentType
     *
1107 1108 1109 1110
     * @return string
     */
    public function extractDoctrineTypeFromComment($comment, $currentType)
    {
1111
        if (preg_match("(\(DC2Type:(((?!\)).)+)\))", $comment, $match)) {
1112 1113
            $currentType = $match[1];
        }
Benjamin Morel's avatar
Benjamin Morel committed
1114

1115 1116 1117
        return $currentType;
    }

Benjamin Morel's avatar
Benjamin Morel committed
1118 1119 1120 1121 1122 1123
    /**
     * @param string $comment
     * @param string $type
     *
     * @return string
     */
1124 1125 1126 1127
    public function removeDoctrineTypeFromComment($comment, $type)
    {
        return str_replace('(DC2Type:'.$type.')', '', $comment);
    }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
1128
}