AbstractSchemaManager.php 28.9 KB
Newer Older
romanb's avatar
romanb committed
1 2
<?php

3
namespace Doctrine\DBAL\Schema;
romanb's avatar
romanb committed
4

5 6 7
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
9
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
10
use Doctrine\DBAL\Events;
11
use Doctrine\DBAL\Platforms\AbstractPlatform;
12
use Throwable;
13

14
use function array_filter;
15
use function array_intersect;
16 17
use function array_map;
use function array_values;
Sergei Morozov's avatar
Sergei Morozov committed
18
use function assert;
19 20 21 22
use function call_user_func_array;
use function count;
use function func_get_args;
use function is_array;
Sergei Morozov's avatar
Sergei Morozov committed
23
use function is_callable;
24 25 26
use function preg_match;
use function str_replace;
use function strtolower;
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
 */
32
abstract class AbstractSchemaManager
romanb's avatar
romanb committed
33
{
34
    /**
Benjamin Morel's avatar
Benjamin Morel committed
35
     * Holds instance of the Doctrine connection for this schema manager.
36
     *
37
     * @var Connection
38
     */
romanb's avatar
romanb committed
39 40
    protected $_conn;

41
    /**
Benjamin Morel's avatar
Benjamin Morel committed
42
     * Holds instance of the database platform used for this schema manager.
43
     *
44
     * @var AbstractPlatform
45 46 47 48
     */
    protected $_platform;

    /**
Benjamin Morel's avatar
Benjamin Morel committed
49
     * Constructor. Accepts the Connection instance to manage the schema for.
50
     */
51
    public function __construct(Connection $conn, ?AbstractPlatform $platform = null)
52
    {
53 54
        $this->_conn     = $conn;
        $this->_platform = $platform ?: $this->_conn->getDatabasePlatform();
55 56
    }

57
    /**
Benjamin Morel's avatar
Benjamin Morel committed
58
     * Returns the associated platform.
59
     *
60
     * @return AbstractPlatform
61 62 63 64 65 66
     */
    public function getDatabasePlatform()
    {
        return $this->_platform;
    }

67
    /**
Benjamin Morel's avatar
Benjamin Morel committed
68
     * Tries any method on the schema manager. Normally a method throws an
69 70 71 72 73 74 75 76 77 78 79 80
     * 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()
    {
81
        $args   = func_get_args();
82 83 84 85
        $method = $args[0];
        unset($args[0]);
        $args = array_values($args);

Sergei Morozov's avatar
Sergei Morozov committed
86 87 88
        $callback = [$this, $method];
        assert(is_callable($callback));

89
        try {
Sergei Morozov's avatar
Sergei Morozov committed
90
            return call_user_func_array($callback, $args);
91
        } catch (Throwable $e) {
92 93 94 95
            return false;
        }
    }

romanb's avatar
romanb committed
96
    /**
Benjamin Morel's avatar
Benjamin Morel committed
97
     * Lists the available databases for this connection.
romanb's avatar
romanb committed
98
     *
99
     * @return string[]
romanb's avatar
romanb committed
100 101 102
     */
    public function listDatabases()
    {
103
        $sql = $this->_platform->getListDatabasesSQL();
104 105 106 107

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

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

110 111 112
    /**
     * Returns a list of all namespaces in the current database.
     *
113
     * @return string[]
114 115 116 117 118 119 120 121 122 123
     */
    public function listNamespaceNames()
    {
        $sql = $this->_platform->getListNamespacesSQL();

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

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

romanb's avatar
romanb committed
124
    /**
Benjamin Morel's avatar
Benjamin Morel committed
125 126 127
     * Lists the available sequences for this connection.
     *
     * @param string|null $database
romanb's avatar
romanb committed
128
     *
129
     * @return Sequence[]
romanb's avatar
romanb committed
130
     */
131
    public function listSequences($database = null)
romanb's avatar
romanb committed
132
    {
133
        if ($database === null) {
134 135
            $database = $this->_conn->getDatabase();
        }
Grégoire Paris's avatar
Grégoire Paris committed
136

137
        $sql = $this->_platform->getListSequencesSQL($database);
138 139 140

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

141
        return $this->filterAssetNames($this->_getPortableSequencesList($sequences));
romanb's avatar
romanb committed
142 143 144
    }

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

        $sql = $this->_platform->getListTableColumnsSQL($table, $database);
166 167 168

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

169
        return $this->_getPortableTableColumnList($table, $database, $tableColumns);
romanb's avatar
romanb committed
170 171 172
    }

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

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

187
        return $this->_getPortableTableIndexesList($tableIndexes, $table);
romanb's avatar
romanb committed
188 189
    }

190
    /**
Benjamin Morel's avatar
Benjamin Morel committed
191
     * Returns true if all the given tables exist.
192
     *
193 194
     * The usage of a string $tableNames is deprecated. Pass a one-element array instead.
     *
195
     * @param string|string[] $names
Benjamin Morel's avatar
Benjamin Morel committed
196
     *
197
     * @return bool
198
     */
199
    public function tablesExist($names)
200
    {
201
        $names = array_map('strtolower', (array) $names);
Benjamin Morel's avatar
Benjamin Morel committed
202

203
        return count($names) === count(array_intersect($names, array_map('strtolower', $this->listTableNames())));
204 205
    }

romanb's avatar
romanb committed
206
    /**
Benjamin Morel's avatar
Benjamin Morel committed
207
     * Returns a list of all tables in the current database.
romanb's avatar
romanb committed
208
     *
209
     * @return string[]
romanb's avatar
romanb committed
210
     */
211
    public function listTableNames()
romanb's avatar
romanb committed
212
    {
213
        $sql = $this->_platform->getListTablesSQL();
214

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

218 219
        return $this->filterAssetNames($tableNames);
    }
220

221
    /**
Benjamin Morel's avatar
Benjamin Morel committed
222
     * Filters asset names if they are configured to return only a subset of all
223 224
     * the found elements.
     *
225
     * @param mixed[] $assetNames
Benjamin Morel's avatar
Benjamin Morel committed
226
     *
227
     * @return mixed[]
228 229 230
     */
    protected function filterAssetNames($assetNames)
    {
231 232
        $filter = $this->_conn->getConfiguration()->getSchemaAssetsFilter();
        if (! $filter) {
233 234
            return $assetNames;
        }
Benjamin Morel's avatar
Benjamin Morel committed
235

236
        return array_values(array_filter($assetNames, $filter));
237 238
    }

Benjamin Morel's avatar
Benjamin Morel committed
239
    /**
240 241
     * @deprecated Use Configuration::getSchemaAssetsFilter() instead
     *
Benjamin Morel's avatar
Benjamin Morel committed
242 243
     * @return string|null
     */
244 245 246
    protected function getFilterSchemaAssetsExpression()
    {
        return $this->_conn->getConfiguration()->getFilterSchemaAssetsExpression();
247 248 249
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
250
     * Lists the tables for this connection.
251
     *
252
     * @return Table[]
253 254 255 256
     */
    public function listTables()
    {
        $tableNames = $this->listTableNames();
257

258
        $tables = [];
259
        foreach ($tableNames as $tableName) {
260
            $tables[] = $this->listTableDetails($tableName);
261 262 263
        }

        return $tables;
romanb's avatar
romanb committed
264 265
    }

266
    /**
267
     * @param string $name
Benjamin Morel's avatar
Benjamin Morel committed
268
     *
269
     * @return Table
270
     */
271
    public function listTableDetails($name)
272
    {
273
        $columns     = $this->listTableColumns($name);
274
        $foreignKeys = [];
275
        if ($this->_platform->supportsForeignKeyConstraints()) {
276
            $foreignKeys = $this->listTableForeignKeys($name);
277
        }
Grégoire Paris's avatar
Grégoire Paris committed
278

279
        $indexes = $this->listTableIndexes($name);
280

281
        return new Table($name, $columns, $indexes, $foreignKeys);
282 283
    }

romanb's avatar
romanb committed
284
    /**
Benjamin Morel's avatar
Benjamin Morel committed
285
     * Lists the views this connection has.
romanb's avatar
romanb committed
286
     *
287
     * @return View[]
romanb's avatar
romanb committed
288
     */
289
    public function listViews()
romanb's avatar
romanb committed
290
    {
291
        $database = $this->_conn->getDatabase();
292 293
        $sql      = $this->_platform->getListViewsSQL($database);
        $views    = $this->_conn->fetchAll($sql);
294 295

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

298
    /**
Benjamin Morel's avatar
Benjamin Morel committed
299 300 301 302
     * Lists the foreign keys for the given table.
     *
     * @param string      $table    The name of the table.
     * @param string|null $database
303
     *
304
     * @return ForeignKeyConstraint[]
305 306 307
     */
    public function listTableForeignKeys($table, $database = null)
    {
308
        if ($database === null) {
309 310
            $database = $this->_conn->getDatabase();
        }
Grégoire Paris's avatar
Grégoire Paris committed
311

312
        $sql              = $this->_platform->getListTableForeignKeysSQL($table, $database);
313 314 315 316 317
        $tableForeignKeys = $this->_conn->fetchAll($sql);

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

318 319
    /* drop*() Methods */

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

    /**
Benjamin Morel's avatar
Benjamin Morel committed
335 336
     * Drops the given table.
     *
337
     * @param string $name The name of the table to drop.
romanb's avatar
romanb committed
338
     *
Benjamin Morel's avatar
Benjamin Morel committed
339
     * @return void
romanb's avatar
romanb committed
340
     */
341
    public function dropTable($name)
romanb's avatar
romanb committed
342
    {
343
        $this->_execSql($this->_platform->getDropTableSQL($name));
romanb's avatar
romanb committed
344 345 346
    }

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

360
        $this->_execSql($this->_platform->getDropIndexSQL($index, $table));
romanb's avatar
romanb committed
361 362 363
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
364 365
     * Drops the constraint from the given table.
     *
366
     * @param Table|string $table The name of the table.
romanb's avatar
romanb committed
367
     *
Benjamin Morel's avatar
Benjamin Morel committed
368
     * @return void
romanb's avatar
romanb committed
369
     */
370
    public function dropConstraint(Constraint $constraint, $table)
romanb's avatar
romanb committed
371
    {
372
        $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table));
romanb's avatar
romanb committed
373 374 375
    }

    /**
romanb's avatar
romanb committed
376
     * Drops a foreign key from a table.
romanb's avatar
romanb committed
377
     *
378 379
     * @param ForeignKeyConstraint|string $foreignKey The name of the foreign key.
     * @param Table|string                $table      The name of the table with the foreign key.
Benjamin Morel's avatar
Benjamin Morel committed
380 381
     *
     * @return void
romanb's avatar
romanb committed
382
     */
383
    public function dropForeignKey($foreignKey, $table)
romanb's avatar
romanb committed
384
    {
385
        $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table));
romanb's avatar
romanb committed
386 387 388
    }

    /**
romanb's avatar
romanb committed
389
     * Drops a sequence with a given name.
romanb's avatar
romanb committed
390
     *
romanb's avatar
romanb committed
391
     * @param string $name The name of the sequence to drop.
Benjamin Morel's avatar
Benjamin Morel committed
392 393
     *
     * @return void
romanb's avatar
romanb committed
394
     */
395
    public function dropSequence($name)
romanb's avatar
romanb committed
396
    {
397
        $this->_execSql($this->_platform->getDropSequenceSQL($name));
romanb's avatar
romanb committed
398 399
    }

400
    /**
Benjamin Morel's avatar
Benjamin Morel committed
401
     * Drops a view.
402
     *
Benjamin Morel's avatar
Benjamin Morel committed
403 404 405
     * @param string $name The name of the view.
     *
     * @return void
406 407 408
     */
    public function dropView($name)
    {
409
        $this->_execSql($this->_platform->getDropViewSQL($name));
410 411 412 413
    }

    /* create*() Methods */

romanb's avatar
romanb committed
414
    /**
romanb's avatar
romanb committed
415
     * Creates a new database.
romanb's avatar
romanb committed
416
     *
romanb's avatar
romanb committed
417
     * @param string $database The name of the database to create.
Benjamin Morel's avatar
Benjamin Morel committed
418 419
     *
     * @return void
romanb's avatar
romanb committed
420
     */
romanb's avatar
romanb committed
421
    public function createDatabase($database)
romanb's avatar
romanb committed
422
    {
423
        $this->_execSql($this->_platform->getCreateDatabaseSQL($database));
romanb's avatar
romanb committed
424 425 426
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
427
     * Creates a new table.
romanb's avatar
romanb committed
428
     *
Benjamin Morel's avatar
Benjamin Morel committed
429
     * @return void
romanb's avatar
romanb committed
430
     */
431
    public function createTable(Table $table)
romanb's avatar
romanb committed
432
    {
433
        $createFlags = AbstractPlatform::CREATE_INDEXES | AbstractPlatform::CREATE_FOREIGNKEYS;
434
        $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags));
romanb's avatar
romanb committed
435 436 437
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
438 439
     * Creates a new sequence.
     *
440
     * @param Sequence $sequence
romanb's avatar
romanb committed
441
     *
Benjamin Morel's avatar
Benjamin Morel committed
442 443
     * @return void
     *
444
     * @throws ConnectionException If something fails at database level.
romanb's avatar
romanb committed
445
     */
446
    public function createSequence($sequence)
romanb's avatar
romanb committed
447
    {
448
        $this->_execSql($this->_platform->getCreateSequenceSQL($sequence));
romanb's avatar
romanb committed
449 450 451
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
452 453
     * Creates a constraint on a table.
     *
454
     * @param Table|string $table
romanb's avatar
romanb committed
455
     *
Benjamin Morel's avatar
Benjamin Morel committed
456
     * @return void
457 458
     */
    public function createConstraint(Constraint $constraint, $table)
romanb's avatar
romanb committed
459
    {
460
        $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table));
romanb's avatar
romanb committed
461 462 463
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
464
     * Creates a new index on a table.
romanb's avatar
romanb committed
465
     *
466
     * @param Table|string $table The name of the table on which the index is to be created.
Benjamin Morel's avatar
Benjamin Morel committed
467 468
     *
     * @return void
romanb's avatar
romanb committed
469
     */
470
    public function createIndex(Index $index, $table)
romanb's avatar
romanb committed
471
    {
472
        $this->_execSql($this->_platform->getCreateIndexSQL($index, $table));
romanb's avatar
romanb committed
473 474 475
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
476 477
     * Creates a new foreign key.
     *
478 479
     * @param ForeignKeyConstraint $foreignKey The ForeignKey instance.
     * @param Table|string         $table      The name of the table on which the foreign key is to be created.
romanb's avatar
romanb committed
480
     *
Benjamin Morel's avatar
Benjamin Morel committed
481
     * @return void
romanb's avatar
romanb committed
482
     */
483
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
romanb's avatar
romanb committed
484
    {
485
        $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table));
romanb's avatar
romanb committed
486 487
    }

488
    /**
Benjamin Morel's avatar
Benjamin Morel committed
489
     * Creates a new view.
490
     *
Benjamin Morel's avatar
Benjamin Morel committed
491
     * @return void
492
     */
493
    public function createView(View $view)
494
    {
495
        $this->_execSql($this->_platform->getCreateViewSQL($view->getQuotedName($this->_platform), $view->getSql()));
496 497
    }

498 499
    /* dropAndCreate*() Methods */

500
    /**
Benjamin Morel's avatar
Benjamin Morel committed
501
     * Drops and creates a constraint.
502 503 504
     *
     * @see dropConstraint()
     * @see createConstraint()
Benjamin Morel's avatar
Benjamin Morel committed
505
     *
506
     * @param Table|string $table
Benjamin Morel's avatar
Benjamin Morel committed
507 508
     *
     * @return void
509
     */
510
    public function dropAndCreateConstraint(Constraint $constraint, $table)
511
    {
512 513
        $this->tryMethod('dropConstraint', $constraint, $table);
        $this->createConstraint($constraint, $table);
514 515 516
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
517 518
     * Drops and creates a new index on a table.
     *
519
     * @param Table|string $table The name of the table on which the index is to be created.
520
     *
Benjamin Morel's avatar
Benjamin Morel committed
521
     * @return void
522
     */
523
    public function dropAndCreateIndex(Index $index, $table)
524
    {
525
        $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table);
526
        $this->createIndex($index, $table);
527 528 529
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
530
     * Drops and creates a new foreign key.
531
     *
532 533
     * @param ForeignKeyConstraint $foreignKey An associative array that defines properties of the foreign key to be created.
     * @param Table|string         $table      The name of the table on which the foreign key is to be created.
Benjamin Morel's avatar
Benjamin Morel committed
534 535
     *
     * @return void
536
     */
537
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
538
    {
539 540
        $this->tryMethod('dropForeignKey', $foreignKey, $table);
        $this->createForeignKey($foreignKey, $table);
541 542 543
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
544 545 546 547
     * Drops and create a new sequence.
     *
     * @return void
     *
548
     * @throws ConnectionException If something fails at database level.
549
     */
550
    public function dropAndCreateSequence(Sequence $sequence)
551
    {
Benjamin Eberlei's avatar
Benjamin Eberlei committed
552 553
        $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform));
        $this->createSequence($sequence);
554 555 556
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
557 558 559
     * Drops and creates a new table.
     *
     * @return void
560
     */
561
    public function dropAndCreateTable(Table $table)
562
    {
563
        $this->tryMethod('dropTable', $table->getQuotedName($this->_platform));
564
        $this->createTable($table);
565 566 567
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
568
     * Drops and creates a new database.
569 570
     *
     * @param string $database The name of the database to create.
Benjamin Morel's avatar
Benjamin Morel committed
571 572
     *
     * @return void
573 574 575 576 577 578 579 580
     */
    public function dropAndCreateDatabase($database)
    {
        $this->tryMethod('dropDatabase', $database);
        $this->createDatabase($database);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
581 582 583
     * Drops and creates a new view.
     *
     * @return void
584
     */
585
    public function dropAndCreateView(View $view)
586
    {
587
        $this->tryMethod('dropView', $view->getQuotedName($this->_platform));
588
        $this->createView($view);
589 590
    }

591 592
    /* alterTable() Methods */

romanb's avatar
romanb committed
593
    /**
Benjamin Morel's avatar
Benjamin Morel committed
594
     * Alters an existing tables schema.
romanb's avatar
romanb committed
595
     *
Benjamin Morel's avatar
Benjamin Morel committed
596
     * @return void
597 598 599
     */
    public function alterTable(TableDiff $tableDiff)
    {
600
        $queries = $this->_platform->getAlterTableSQL($tableDiff);
601 602 603 604 605 606
        if (! is_array($queries) || ! count($queries)) {
            return;
        }

        foreach ($queries as $ddlQuery) {
            $this->_execSql($ddlQuery);
607
        }
608 609
    }

610
    /**
Benjamin Morel's avatar
Benjamin Morel committed
611 612 613 614
     * 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.
615
     *
Benjamin Morel's avatar
Benjamin Morel committed
616
     * @return void
617 618 619
     */
    public function renameTable($name, $newName)
    {
620
        $tableDiff          = new TableDiff($name);
621 622
        $tableDiff->newName = $newName;
        $this->alterTable($tableDiff);
623 624
    }

625 626 627 628 629
    /**
     * 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
630
    /**
631
     * @param mixed[] $databases
Benjamin Morel's avatar
Benjamin Morel committed
632
     *
633
     * @return string[]
Benjamin Morel's avatar
Benjamin Morel committed
634
     */
635 636
    protected function _getPortableDatabasesList($databases)
    {
637
        $list = [];
638
        foreach ($databases as $value) {
639 640 641
            $value = $this->_getPortableDatabaseDefinition($value);

            if (! $value) {
642
                continue;
643
            }
644 645

            $list[] = $value;
646
        }
Benjamin Morel's avatar
Benjamin Morel committed
647

648
        return $list;
649 650
    }

651 652 653
    /**
     * Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition.
     *
654
     * @param mixed[][] $namespaces The list of namespace names in the native DBMS data definition.
655
     *
656
     * @return string[]
657 658 659
     */
    protected function getPortableNamespacesList(array $namespaces)
    {
660
        $namespacesList = [];
661 662 663 664 665 666 667 668

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

        return $namespacesList;
    }

Benjamin Morel's avatar
Benjamin Morel committed
669
    /**
670
     * @param mixed $database
Benjamin Morel's avatar
Benjamin Morel committed
671 672 673
     *
     * @return mixed
     */
674 675 676 677 678
    protected function _getPortableDatabaseDefinition($database)
    {
        return $database;
    }

679 680 681
    /**
     * Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition.
     *
682
     * @param mixed[] $namespace The native DBMS namespace definition.
683 684 685 686 687 688 689 690
     *
     * @return mixed
     */
    protected function getPortableNamespaceDefinition(array $namespace)
    {
        return $namespace;
    }

Benjamin Morel's avatar
Benjamin Morel committed
691
    /**
692 693
     * @deprecated
     *
694
     * @param mixed[][] $functions
Benjamin Morel's avatar
Benjamin Morel committed
695
     *
696
     * @return mixed[][]
Benjamin Morel's avatar
Benjamin Morel committed
697
     */
698 699
    protected function _getPortableFunctionsList($functions)
    {
700
        $list = [];
701
        foreach ($functions as $value) {
702 703 704
            $value = $this->_getPortableFunctionDefinition($value);

            if (! $value) {
705
                continue;
706
            }
707 708

            $list[] = $value;
709
        }
Benjamin Morel's avatar
Benjamin Morel committed
710

711
        return $list;
712 713
    }

Benjamin Morel's avatar
Benjamin Morel committed
714
    /**
715 716
     * @deprecated
     *
717
     * @param mixed[] $function
Benjamin Morel's avatar
Benjamin Morel committed
718 719 720
     *
     * @return mixed
     */
721 722 723 724 725
    protected function _getPortableFunctionDefinition($function)
    {
        return $function;
    }

Benjamin Morel's avatar
Benjamin Morel committed
726
    /**
727
     * @param mixed[][] $triggers
Benjamin Morel's avatar
Benjamin Morel committed
728
     *
729
     * @return mixed[][]
Benjamin Morel's avatar
Benjamin Morel committed
730
     */
731 732
    protected function _getPortableTriggersList($triggers)
    {
733
        $list = [];
734
        foreach ($triggers as $value) {
735 736 737
            $value = $this->_getPortableTriggerDefinition($value);

            if (! $value) {
738
                continue;
739
            }
740 741

            $list[] = $value;
742
        }
Benjamin Morel's avatar
Benjamin Morel committed
743

744
        return $list;
745 746
    }

Benjamin Morel's avatar
Benjamin Morel committed
747
    /**
748
     * @param mixed[] $trigger
Benjamin Morel's avatar
Benjamin Morel committed
749 750 751
     *
     * @return mixed
     */
752 753 754 755 756
    protected function _getPortableTriggerDefinition($trigger)
    {
        return $trigger;
    }

Benjamin Morel's avatar
Benjamin Morel committed
757
    /**
758
     * @param mixed[][] $sequences
Benjamin Morel's avatar
Benjamin Morel committed
759
     *
760
     * @return Sequence[]
Benjamin Morel's avatar
Benjamin Morel committed
761
     */
762 763
    protected function _getPortableSequencesList($sequences)
    {
764
        $list = [];
765

Sergei Morozov's avatar
Sergei Morozov committed
766 767
        foreach ($sequences as $value) {
            $list[] = $this->_getPortableSequenceDefinition($value);
768
        }
Benjamin Morel's avatar
Benjamin Morel committed
769

770
        return $list;
771 772
    }

773
    /**
774
     * @param mixed[] $sequence
Benjamin Morel's avatar
Benjamin Morel committed
775
     *
776
     * @return Sequence
Benjamin Morel's avatar
Benjamin Morel committed
777
     *
778
     * @throws DBALException
779
     */
780 781
    protected function _getPortableSequenceDefinition($sequence)
    {
782
        throw DBALException::notSupported('Sequences');
783 784
    }

785 786 787 788 789
    /**
     * 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.
     *
790 791 792
     * @param string    $table        The name of the table.
     * @param string    $database
     * @param mixed[][] $tableColumns
Benjamin Morel's avatar
Benjamin Morel committed
793
     *
794
     * @return Column[]
795
     */
796
    protected function _getPortableTableColumnList($table, $database, $tableColumns)
797
    {
798 799
        $eventManager = $this->_platform->getEventManager();

800
        $list = [];
801
        foreach ($tableColumns as $tableColumn) {
802
            $column           = null;
803 804
            $defaultPrevented = false;

805
            if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) {
806
                $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn);
807 808 809
                $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs);

                $defaultPrevented = $eventArgs->isDefaultPrevented();
810
                $column           = $eventArgs->getColumn();
811 812
            }

813
            if (! $defaultPrevented) {
814
                $column = $this->_getPortableTableColumnDefinition($tableColumn);
815 816
            }

817 818
            if (! $column) {
                continue;
819
            }
820 821 822

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

825
        return $list;
826 827
    }

828
    /**
Benjamin Morel's avatar
Benjamin Morel committed
829
     * Gets Table Column Definition.
830
     *
831
     * @param mixed[] $tableColumn
Benjamin Morel's avatar
Benjamin Morel committed
832
     *
833
     * @return Column
834 835
     */
    abstract protected function _getPortableTableColumnDefinition($tableColumn);
836

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

854
            $keyName = strtolower($keyName);
855

856
            if (! isset($result[$keyName])) {
857 858 859 860 861 862 863 864
                $options = [
                    'lengths' => [],
                ];

                if (isset($tableIndex['where'])) {
                    $options['where'] = $tableIndex['where'];
                }

865
                $result[$keyName] = [
866
                    'name' => $indexName,
867
                    'columns' => [],
868
                    'unique' => ! $tableIndex['non_unique'],
869
                    'primary' => $tableIndex['primary'],
870
                    'flags' => $tableIndex['flags'] ?? [],
871
                    'options' => $options,
872
                ];
873
            }
874 875 876

            $result[$keyName]['columns'][]            = $tableIndex['column_name'];
            $result[$keyName]['options']['lengths'][] = $tableIndex['length'] ?? null;
877
        }
878

879 880
        $eventManager = $this->_platform->getEventManager();

881
        $indexes = [];
Steve Müller's avatar
Steve Müller committed
882
        foreach ($result as $indexKey => $data) {
883
            $index            = null;
884 885
            $defaultPrevented = false;

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

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

894
            if (! $defaultPrevented) {
895
                $index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options']);
896 897
            }

898 899
            if (! $index) {
                continue;
900
            }
901 902

            $indexes[$indexKey] = $index;
903 904 905
        }

        return $indexes;
906 907
    }

Benjamin Morel's avatar
Benjamin Morel committed
908
    /**
909
     * @param mixed[][] $tables
Benjamin Morel's avatar
Benjamin Morel committed
910
     *
911
     * @return string[]
Benjamin Morel's avatar
Benjamin Morel committed
912
     */
913 914
    protected function _getPortableTablesList($tables)
    {
915
        $list = [];
916
        foreach ($tables as $value) {
917 918 919
            $value = $this->_getPortableTableDefinition($value);

            if (! $value) {
920
                continue;
921
            }
922 923

            $list[] = $value;
924
        }
Benjamin Morel's avatar
Benjamin Morel committed
925

926
        return $list;
927 928
    }

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

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

            if (! $value) {
951
                continue;
952
            }
953 954

            $list[] = $value;
955
        }
Benjamin Morel's avatar
Benjamin Morel committed
956

957
        return $list;
958 959
    }

Benjamin Morel's avatar
Benjamin Morel committed
960
    /**
961
     * @param string[] $user
Benjamin Morel's avatar
Benjamin Morel committed
962
     *
963
     * @return string[]
Benjamin Morel's avatar
Benjamin Morel committed
964
     */
965 966 967 968 969
    protected function _getPortableUserDefinition($user)
    {
        return $user;
    }

Benjamin Morel's avatar
Benjamin Morel committed
970
    /**
971
     * @param mixed[][] $views
972
     *
973
     * @return View[]
Benjamin Morel's avatar
Benjamin Morel committed
974
     */
975 976
    protected function _getPortableViewsList($views)
    {
977
        $list = [];
978
        foreach ($views as $value) {
979 980 981
            $view = $this->_getPortableViewDefinition($value);

            if (! $view) {
982
                continue;
983
            }
984 985 986

            $viewName        = strtolower($view->getQuotedName($this->_platform));
            $list[$viewName] = $view;
987
        }
Benjamin Morel's avatar
Benjamin Morel committed
988

989
        return $list;
990 991
    }

Benjamin Morel's avatar
Benjamin Morel committed
992
    /**
993
     * @param mixed[] $view
Benjamin Morel's avatar
Benjamin Morel committed
994
     *
995
     * @return View|false
Benjamin Morel's avatar
Benjamin Morel committed
996
     */
997 998
    protected function _getPortableViewDefinition($view)
    {
999
        return false;
1000 1001
    }

Benjamin Morel's avatar
Benjamin Morel committed
1002
    /**
1003
     * @param mixed[][] $tableForeignKeys
Benjamin Morel's avatar
Benjamin Morel committed
1004
     *
1005
     * @return ForeignKeyConstraint[]
Benjamin Morel's avatar
Benjamin Morel committed
1006
     */
1007 1008
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
    {
1009
        $list = [];
1010

Sergei Morozov's avatar
Sergei Morozov committed
1011 1012
        foreach ($tableForeignKeys as $value) {
            $list[] = $this->_getPortableTableForeignKeyDefinition($value);
1013
        }
Benjamin Morel's avatar
Benjamin Morel committed
1014

1015 1016 1017
        return $list;
    }

Benjamin Morel's avatar
Benjamin Morel committed
1018
    /**
1019
     * @param mixed $tableForeignKey
Benjamin Morel's avatar
Benjamin Morel committed
1020
     *
1021
     * @return ForeignKeyConstraint
Benjamin Morel's avatar
Benjamin Morel committed
1022
     */
1023 1024 1025 1026
    protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
    {
        return $tableForeignKey;
    }
1027

Benjamin Morel's avatar
Benjamin Morel committed
1028
    /**
1029
     * @param string[]|string $sql
Benjamin Morel's avatar
Benjamin Morel committed
1030 1031 1032
     *
     * @return void
     */
romanb's avatar
romanb committed
1033
    protected function _execSql($sql)
1034 1035
    {
        foreach ((array) $sql as $query) {
1036
            $this->_conn->executeUpdate($query);
romanb's avatar
romanb committed
1037 1038
        }
    }
1039 1040

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1041
     * Creates a schema instance for the current database.
1042
     *
1043
     * @return Schema
1044 1045 1046
     */
    public function createSchema()
    {
1047
        $namespaces = [];
1048 1049 1050 1051 1052

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

1053
        $sequences = [];
1054

Steve Müller's avatar
Steve Müller committed
1055
        if ($this->_platform->supportsSequences()) {
1056 1057
            $sequences = $this->listSequences();
        }
1058

1059 1060
        $tables = $this->listTables();

1061
        return new Schema($tables, $sequences, $this->createSchemaConfig(), $namespaces);
1062 1063 1064
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
1065
     * Creates the configuration for this schema.
1066
     *
1067
     * @return SchemaConfig
1068 1069 1070 1071 1072
     */
    public function createSchemaConfig()
    {
        $schemaConfig = new SchemaConfig();
        $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength());
1073 1074 1075 1076 1077

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

1079
        $params = $this->_conn->getParams();
1080 1081
        if (! isset($params['defaultTableOptions'])) {
            $params['defaultTableOptions'] = [];
1082
        }
Grégoire Paris's avatar
Grégoire Paris committed
1083

1084 1085 1086
        if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) {
            $params['defaultTableOptions']['charset'] = $params['charset'];
        }
Grégoire Paris's avatar
Grégoire Paris committed
1087

1088
        $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']);
1089

1090
        return $schemaConfig;
1091
    }
1092

1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
    /**
     * 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.
     *
1103
     * @return string[]
1104
     */
1105 1106
    public function getSchemaSearchPaths()
    {
1107
        return [$this->_conn->getDatabase()];
1108 1109
    }

1110 1111 1112
    /**
     * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns
     * the type given as default.
1113
     *
Sergei Morozov's avatar
Sergei Morozov committed
1114 1115
     * @param string|null $comment
     * @param string      $currentType
Benjamin Morel's avatar
Benjamin Morel committed
1116
     *
1117 1118 1119 1120
     * @return string
     */
    public function extractDoctrineTypeFromComment($comment, $currentType)
    {
Sergei Morozov's avatar
Sergei Morozov committed
1121 1122
        if ($comment !== null && preg_match('(\(DC2Type:(((?!\)).)+)\))', $comment, $match)) {
            return $match[1];
1123
        }
Benjamin Morel's avatar
Benjamin Morel committed
1124

1125 1126 1127
        return $currentType;
    }

Benjamin Morel's avatar
Benjamin Morel committed
1128
    /**
Sergei Morozov's avatar
Sergei Morozov committed
1129 1130
     * @param string|null $comment
     * @param string|null $type
Benjamin Morel's avatar
Benjamin Morel committed
1131
     *
Sergei Morozov's avatar
Sergei Morozov committed
1132
     * @return string|null
Benjamin Morel's avatar
Benjamin Morel committed
1133
     */
1134 1135
    public function removeDoctrineTypeFromComment($comment, $type)
    {
Sergei Morozov's avatar
Sergei Morozov committed
1136 1137 1138 1139
        if ($comment === null) {
            return null;
        }

1140
        return str_replace('(DC2Type:' . $type . ')', '', $comment);
1141
    }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
1142
}