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

Michael Moravec's avatar
Michael Moravec committed
3 4
declare(strict_types=1);

5
namespace Doctrine\DBAL\Schema;
romanb's avatar
romanb committed
6

7 8 9
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
10
use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
11
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
12
use Doctrine\DBAL\Events;
13
use Doctrine\DBAL\Exception\DatabaseRequired;
14
use Doctrine\DBAL\Platforms\AbstractPlatform;
15
use Doctrine\DBAL\Platforms\Exception\NotSupported;
16
use Throwable;
17
use function array_filter;
18
use function array_intersect;
19
use function array_map;
20
use function array_shift;
21
use function array_values;
Sergei Morozov's avatar
Sergei Morozov committed
22
use function assert;
23 24 25 26
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
27
use function is_callable;
28 29
use function preg_match;
use function strtolower;
30

romanb's avatar
romanb committed
31
/**
32
 * Base class for schema managers. Schema managers are used to inspect and/or
33
 * modify the database schema/structure.
romanb's avatar
romanb committed
34
 */
35
abstract class AbstractSchemaManager
romanb's avatar
romanb committed
36
{
37
    /**
Benjamin Morel's avatar
Benjamin Morel committed
38
     * Holds instance of the Doctrine connection for this schema manager.
39
     *
40
     * @var Connection
41
     */
romanb's avatar
romanb committed
42 43
    protected $_conn;

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

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

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

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

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

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

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

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

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

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

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

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

romanb's avatar
romanb committed
125
    /**
Benjamin Morel's avatar
Benjamin Morel committed
126 127
     * Lists the available sequences for this connection.
     *
128
     * @return array<int, Sequence>
romanb's avatar
romanb committed
129
     */
130
    public function listSequences(?string $database = null) : array
romanb's avatar
romanb committed
131
    {
132 133 134 135 136
        $database = $this->ensureDatabase(
            $database ?? $this->_conn->getDatabase(),
            __METHOD__
        );

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 148
     * 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
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.
     *
154
     * @return array<string, Column>
romanb's avatar
romanb committed
155
     */
156
    public function listTableColumns(string $table, ?string $database = null) : array
romanb's avatar
romanb committed
157
    {
158 159 160 161
        $database = $this->ensureDatabase(
            $database ?? $this->_conn->getDatabase(),
            __METHOD__
        );
162 163

        $sql = $this->_platform->getListTableColumnsSQL($table, $database);
164 165 166

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

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

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

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

183
        return $this->_getPortableTableIndexesList($tableIndexes, $table);
romanb's avatar
romanb committed
184 185
    }

186
    /**
Benjamin Morel's avatar
Benjamin Morel committed
187
     * Returns true if all the given tables exist.
188
     *
189
     * @param array<int, string> $tableNames
190
     */
191
    public function tablesExist(array $tableNames) : bool
192
    {
193
        $tableNames = array_map('strtolower', $tableNames);
Benjamin Morel's avatar
Benjamin Morel committed
194

195
        return count($tableNames) === count(array_intersect($tableNames, array_map('strtolower', $this->listTableNames())));
196 197
    }

198 199 200 201 202
    public function tableExists(string $tableName) : bool
    {
        return $this->tablesExist([$tableName]);
    }

romanb's avatar
romanb committed
203
    /**
Benjamin Morel's avatar
Benjamin Morel committed
204
     * Returns a list of all tables in the current database.
romanb's avatar
romanb committed
205
     *
206
     * @return array<int, string>
romanb's avatar
romanb committed
207
     */
208
    public function listTableNames() : array
romanb's avatar
romanb committed
209
    {
210
        $sql = $this->_platform->getListTablesSQL();
211

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

215 216
        return $this->filterAssetNames($tableNames);
    }
217

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

233
        return array_values(array_filter($assetNames, $filter));
234 235
    }

236
    /**
Benjamin Morel's avatar
Benjamin Morel committed
237
     * Lists the tables for this connection.
238
     *
239
     * @return array<int, Table>
240
     */
241
    public function listTables() : array
242 243
    {
        $tableNames = $this->listTableNames();
244

245
        $tables = [];
246
        foreach ($tableNames as $tableName) {
247
            $tables[] = $this->listTableDetails($tableName);
248 249 250
        }

        return $tables;
romanb's avatar
romanb committed
251 252
    }

253
    public function listTableDetails(string $tableName) : Table
254
    {
255
        $columns     = $this->listTableColumns($tableName);
256
        $foreignKeys = [];
257

258 259 260
        if ($this->_platform->supportsForeignKeyConstraints()) {
            $foreignKeys = $this->listTableForeignKeys($tableName);
        }
261

262 263
        $indexes = $this->listTableIndexes($tableName);

264
        return new Table($tableName, $columns, $indexes, [], $foreignKeys, []);
265 266
    }

romanb's avatar
romanb committed
267
    /**
Benjamin Morel's avatar
Benjamin Morel committed
268
     * Lists the views this connection has.
romanb's avatar
romanb committed
269
     *
270
     * @return array<string, View>
romanb's avatar
romanb committed
271
     */
272
    public function listViews() : array
romanb's avatar
romanb committed
273
    {
274 275 276 277 278 279 280
        $database = $this->ensureDatabase(
            $this->_conn->getDatabase(),
            __METHOD__
        );

        $sql   = $this->_platform->getListViewsSQL($database);
        $views = $this->_conn->fetchAll($sql);
281 282

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

285
    /**
Benjamin Morel's avatar
Benjamin Morel committed
286 287
     * Lists the foreign keys for the given table.
     *
288
     * @return array<int|string, ForeignKeyConstraint>
289
     */
290
    public function listTableForeignKeys(string $table, ?string $database = null) : array
291
    {
292
        if ($database === null) {
293 294
            $database = $this->_conn->getDatabase();
        }
295
        $sql              = $this->_platform->getListTableForeignKeysSQL($table, $database);
296 297 298 299 300
        $tableForeignKeys = $this->_conn->fetchAll($sql);

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

301 302
    /* drop*() Methods */

romanb's avatar
romanb committed
303
    /**
304
     * Drops a database.
305
     *
306
     * NOTE: You can not drop the database this SchemaManager is currently connected to.
romanb's avatar
romanb committed
307
     */
308
    public function dropDatabase(string $database) : void
romanb's avatar
romanb committed
309
    {
310
        $this->_execSql($this->_platform->getDropDatabaseSQL($database));
romanb's avatar
romanb committed
311 312 313
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
314
     * Drops the given table.
romanb's avatar
romanb committed
315
     */
316
    public function dropTable(string $tableName) : void
romanb's avatar
romanb committed
317
    {
jeroendedauw's avatar
jeroendedauw committed
318
        $this->_execSql($this->_platform->getDropTableSQL($tableName));
romanb's avatar
romanb committed
319 320 321
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
322
     * Drops the index from the given table.
romanb's avatar
romanb committed
323
     *
324 325
     * @param Index|string $index The name of the index.
     * @param Table|string $table The name of the table.
romanb's avatar
romanb committed
326
     */
327
    public function dropIndex($index, $table) : void
romanb's avatar
romanb committed
328
    {
Steve Müller's avatar
Steve Müller committed
329
        if ($index instanceof Index) {
330
            $index = $index->getQuotedName($this->_platform);
331 332
        }

333
        $this->_execSql($this->_platform->getDropIndexSQL($index, $table));
romanb's avatar
romanb committed
334 335 336
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
337 338
     * Drops the constraint from the given table.
     *
339
     * @param Table|string $table The name of the table.
romanb's avatar
romanb committed
340
     */
341
    public function dropConstraint(Constraint $constraint, $table) : void
romanb's avatar
romanb committed
342
    {
343
        $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table));
romanb's avatar
romanb committed
344 345 346
    }

    /**
romanb's avatar
romanb committed
347
     * Drops a foreign key from a table.
romanb's avatar
romanb committed
348
     *
349 350
     * @param ForeignKeyConstraint|string $foreignKey The name of the foreign key.
     * @param Table|string                $table      The name of the table with the foreign key.
romanb's avatar
romanb committed
351
     */
352
    public function dropForeignKey($foreignKey, $table) : void
romanb's avatar
romanb committed
353
    {
354
        $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table));
romanb's avatar
romanb committed
355 356 357
    }

    /**
romanb's avatar
romanb committed
358
     * Drops a sequence with a given name.
romanb's avatar
romanb committed
359
     */
360
    public function dropSequence(string $name) : void
romanb's avatar
romanb committed
361
    {
362
        $this->_execSql($this->_platform->getDropSequenceSQL($name));
romanb's avatar
romanb committed
363 364
    }

365
    /**
Benjamin Morel's avatar
Benjamin Morel committed
366
     * Drops a view.
367
     */
368
    public function dropView(string $name) : void
369
    {
370
        $this->_execSql($this->_platform->getDropViewSQL($name));
371 372 373 374
    }

    /* create*() Methods */

romanb's avatar
romanb committed
375
    /**
romanb's avatar
romanb committed
376
     * Creates a new database.
romanb's avatar
romanb committed
377
     */
378
    public function createDatabase(string $database) : void
romanb's avatar
romanb committed
379
    {
380
        $this->_execSql($this->_platform->getCreateDatabaseSQL($database));
romanb's avatar
romanb committed
381 382 383
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
384
     * Creates a new table.
romanb's avatar
romanb committed
385
     */
386
    public function createTable(Table $table) : void
romanb's avatar
romanb committed
387
    {
388
        $createFlags = AbstractPlatform::CREATE_INDEXES|AbstractPlatform::CREATE_FOREIGNKEYS;
389
        $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags));
romanb's avatar
romanb committed
390 391 392
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
393 394
     * Creates a new sequence.
     *
395
     * @throws ConnectionException If something fails at database level.
romanb's avatar
romanb committed
396
     */
397
    public function createSequence(Sequence $sequence) : void
romanb's avatar
romanb committed
398
    {
399
        $this->_execSql($this->_platform->getCreateSequenceSQL($sequence));
romanb's avatar
romanb committed
400 401 402
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
403 404
     * Creates a constraint on a table.
     *
405
     * @param Table|string $table
406
     */
407
    public function createConstraint(Constraint $constraint, $table) : void
romanb's avatar
romanb committed
408
    {
409
        $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table));
romanb's avatar
romanb committed
410 411 412
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
413
     * Creates a new index on a table.
romanb's avatar
romanb committed
414
     *
415
     * @param Table|string $table The name of the table on which the index is to be created.
romanb's avatar
romanb committed
416
     */
417
    public function createIndex(Index $index, $table) : void
romanb's avatar
romanb committed
418
    {
419
        $this->_execSql($this->_platform->getCreateIndexSQL($index, $table));
romanb's avatar
romanb committed
420 421 422
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
423 424
     * Creates a new foreign key.
     *
425 426
     * @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
427
     */
428
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) : void
romanb's avatar
romanb committed
429
    {
430
        $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table));
romanb's avatar
romanb committed
431 432
    }

433
    /**
Benjamin Morel's avatar
Benjamin Morel committed
434
     * Creates a new view.
435
     */
436
    public function createView(View $view) : void
437
    {
438
        $this->_execSql($this->_platform->getCreateViewSQL($view->getQuotedName($this->_platform), $view->getSql()));
439 440
    }

441 442
    /* dropAndCreate*() Methods */

443
    /**
Benjamin Morel's avatar
Benjamin Morel committed
444
     * Drops and creates a constraint.
445 446 447
     *
     * @see dropConstraint()
     * @see createConstraint()
Benjamin Morel's avatar
Benjamin Morel committed
448
     *
449
     * @param Table|string $table
450
     */
451
    public function dropAndCreateConstraint(Constraint $constraint, $table) : void
452
    {
453 454
        $this->tryMethod('dropConstraint', $constraint, $table);
        $this->createConstraint($constraint, $table);
455 456 457
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
458 459
     * Drops and creates a new index on a table.
     *
460
     * @param Table|string $table The name of the table on which the index is to be created.
461
     */
462
    public function dropAndCreateIndex(Index $index, $table) : void
463
    {
464
        $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table);
465
        $this->createIndex($index, $table);
466 467 468
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
469
     * Drops and creates a new foreign key.
470
     *
471 472
     * @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.
473
     */
474
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) : void
475
    {
476 477
        $this->tryMethod('dropForeignKey', $foreignKey, $table);
        $this->createForeignKey($foreignKey, $table);
478 479 480
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
481 482
     * Drops and create a new sequence.
     *
483
     * @throws ConnectionException If something fails at database level.
484
     */
485
    public function dropAndCreateSequence(Sequence $sequence) : void
486
    {
Benjamin Eberlei's avatar
Benjamin Eberlei committed
487 488
        $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform));
        $this->createSequence($sequence);
489 490 491
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
492
     * Drops and creates a new table.
493
     */
494
    public function dropAndCreateTable(Table $table) : void
495
    {
496
        $this->tryMethod('dropTable', $table->getQuotedName($this->_platform));
497
        $this->createTable($table);
498 499 500
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
501
     * Drops and creates a new database.
502
     */
503
    public function dropAndCreateDatabase(string $database) : void
504 505 506 507 508 509
    {
        $this->tryMethod('dropDatabase', $database);
        $this->createDatabase($database);
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
510
     * Drops and creates a new view.
511
     */
512
    public function dropAndCreateView(View $view) : void
513
    {
514
        $this->tryMethod('dropView', $view->getQuotedName($this->_platform));
515
        $this->createView($view);
516 517
    }

518 519
    /* alterTable() Methods */

romanb's avatar
romanb committed
520
    /**
Benjamin Morel's avatar
Benjamin Morel committed
521
     * Alters an existing tables schema.
522
     */
523
    public function alterTable(TableDiff $tableDiff) : void
524
    {
525
        $queries = $this->_platform->getAlterTableSQL($tableDiff);
526

527 528 529 530 531 532
        if (! is_array($queries) || ! count($queries)) {
            return;
        }

        foreach ($queries as $ddlQuery) {
            $this->_execSql($ddlQuery);
533
        }
534 535
    }

536
    /**
Benjamin Morel's avatar
Benjamin Morel committed
537
     * Renames a given table to another name.
538
     */
539
    public function renameTable(string $name, string $newName) : void
540
    {
541
        $tableDiff          = new TableDiff($name);
542 543
        $tableDiff->newName = $newName;
        $this->alterTable($tableDiff);
544 545
    }

546 547 548 549 550
    /**
     * 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
551
    /**
552
     * @param array<int, mixed> $databases
Benjamin Morel's avatar
Benjamin Morel committed
553
     *
554
     * @return array<int, string>
Benjamin Morel's avatar
Benjamin Morel committed
555
     */
556
    protected function _getPortableDatabasesList(array $databases) : array
557
    {
558
        $list = [];
559
        foreach ($databases as $value) {
560 561 562
            $value = $this->_getPortableDatabaseDefinition($value);

            if (! $value) {
563
                continue;
564
            }
565 566

            $list[] = $value;
567
        }
Benjamin Morel's avatar
Benjamin Morel committed
568

569
        return $list;
570 571
    }

572 573 574
    /**
     * Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition.
     *
575
     * @param array<int, array<int, mixed>> $namespaces The list of namespace names in the native DBMS data definition.
576
     *
577
     * @return array<int, string>
578
     */
579
    protected function getPortableNamespacesList(array $namespaces) : array
580
    {
581
        $namespacesList = [];
582 583 584 585 586 587 588 589

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

        return $namespacesList;
    }

Benjamin Morel's avatar
Benjamin Morel committed
590
    /**
591
     * @param array<string, string> $database
Benjamin Morel's avatar
Benjamin Morel committed
592
     */
593
    protected function _getPortableDatabaseDefinition(array $database) : string
594
    {
595
        return array_shift($database);
596 597
    }

598 599 600
    /**
     * Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition.
     *
601
     * @param array<string|int, mixed> $namespace The native DBMS namespace definition.
Benjamin Morel's avatar
Benjamin Morel committed
602
     */
603
    protected function getPortableNamespaceDefinition(array $namespace) : string
604
    {
605
        return array_shift($namespace);
606 607
    }

Benjamin Morel's avatar
Benjamin Morel committed
608
    /**
609
     * @param array<int, array<int, mixed>> $triggers
Benjamin Morel's avatar
Benjamin Morel committed
610
     *
611
     * @return array<int, string>
Benjamin Morel's avatar
Benjamin Morel committed
612
     */
613
    protected function _getPortableTriggersList(array $triggers)
614
    {
615
        $list = [];
616
        foreach ($triggers as $value) {
617 618 619
            $value = $this->_getPortableTriggerDefinition($value);

            if (! $value) {
620
                continue;
621
            }
622 623

            $list[] = $value;
624
        }
Benjamin Morel's avatar
Benjamin Morel committed
625

626
        return $list;
627 628
    }

Benjamin Morel's avatar
Benjamin Morel committed
629
    /**
630
     * @param array<string|int, mixed> $trigger
Benjamin Morel's avatar
Benjamin Morel committed
631
     */
632
    protected function _getPortableTriggerDefinition(array $trigger) : string
633
    {
634
        return array_shift($trigger);
635 636
    }

Benjamin Morel's avatar
Benjamin Morel committed
637
    /**
638
     * @param array<int, array<string, mixed>> $sequences
Benjamin Morel's avatar
Benjamin Morel committed
639
     *
640
     * @return array<int, Sequence>
Benjamin Morel's avatar
Benjamin Morel committed
641
     */
642
    protected function _getPortableSequencesList(array $sequences) : array
643
    {
644
        $list = [];
645

Sergei Morozov's avatar
Sergei Morozov committed
646 647
        foreach ($sequences as $value) {
            $list[] = $this->_getPortableSequenceDefinition($value);
648
        }
Benjamin Morel's avatar
Benjamin Morel committed
649

650
        return $list;
651 652
    }

653
    /**
654
     * @param array<string, mixed> $sequence
Benjamin Morel's avatar
Benjamin Morel committed
655
     *
656
     * @throws DBALException
657
     */
658
    protected function _getPortableSequenceDefinition(array $sequence) : Sequence
659
    {
660
        throw NotSupported::new('Sequences');
661 662
    }

663 664 665 666 667
    /**
     * 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.
     *
668
     * @param array<int, array<string, mixed>> $tableColumns
Benjamin Morel's avatar
Benjamin Morel committed
669
     *
670
     * @return array<string, Column>
671
     */
672
    protected function _getPortableTableColumnList(string $table, string $database, array $tableColumns) : array
673
    {
674 675
        $eventManager = $this->_platform->getEventManager();

676
        $list = [];
677
        foreach ($tableColumns as $tableColumn) {
678
            $column           = null;
679 680
            $defaultPrevented = false;

681
            if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) {
682
                $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn);
683 684 685
                $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs);

                $defaultPrevented = $eventArgs->isDefaultPrevented();
686
                $column           = $eventArgs->getColumn();
687 688
            }

689
            if (! $defaultPrevented) {
690
                $column = $this->_getPortableTableColumnDefinition($tableColumn);
691 692
            }

693 694
            if (! $column) {
                continue;
695
            }
696 697 698

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

701
        return $list;
702 703
    }

704
    /**
Benjamin Morel's avatar
Benjamin Morel committed
705
     * Gets Table Column Definition.
706
     *
707
     * @param array<string, mixed> $tableColumn
708
     */
709
    abstract protected function _getPortableTableColumnDefinition(array $tableColumn) : Column;
710

711
    /**
Benjamin Morel's avatar
Benjamin Morel committed
712 713
     * Aggregates and groups the index results according to the required data result.
     *
714
     * @param array<int, array<string, mixed>> $tableIndexRows
715
     *
716
     * @return array<string, Index>
717
     */
718
    protected function _getPortableTableIndexesList(array $tableIndexRows, string $tableName) : array
719
    {
720
        $result = [];
Steve Müller's avatar
Steve Müller committed
721
        foreach ($tableIndexRows as $tableIndex) {
722
            $indexName = $keyName = $tableIndex['key_name'];
Steve Müller's avatar
Steve Müller committed
723
            if ($tableIndex['primary']) {
724 725
                $keyName = 'primary';
            }
726
            $keyName = strtolower($keyName);
727

728
            if (! isset($result[$keyName])) {
729 730 731 732 733 734 735 736
                $options = [
                    'lengths' => [],
                ];

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

737
                $result[$keyName] = [
738
                    'name' => $indexName,
739
                    'columns' => [],
740
                    'unique' => ! $tableIndex['non_unique'],
741
                    'primary' => $tableIndex['primary'],
742
                    'flags' => $tableIndex['flags'] ?? [],
743
                    'options' => $options,
744
                ];
745
            }
746 747 748

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

751 752
        $eventManager = $this->_platform->getEventManager();

753
        $indexes = [];
Steve Müller's avatar
Steve Müller committed
754
        foreach ($result as $indexKey => $data) {
755
            $index            = null;
756 757
            $defaultPrevented = false;

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

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

766
            if (! $defaultPrevented) {
767
                $index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options']);
768 769
            }

770 771
            if (! $index) {
                continue;
772
            }
773 774

            $indexes[$indexKey] = $index;
775 776 777
        }

        return $indexes;
778 779
    }

Benjamin Morel's avatar
Benjamin Morel committed
780
    /**
781
     * @param array<int, array<string, mixed>> $tables
Benjamin Morel's avatar
Benjamin Morel committed
782
     *
783
     * @return array<int, string>
Benjamin Morel's avatar
Benjamin Morel committed
784
     */
785
    protected function _getPortableTablesList(array $tables) : array
786
    {
787
        $list = [];
788
        foreach ($tables as $value) {
789 790 791
            $value = $this->_getPortableTableDefinition($value);

            if (! $value) {
792
                continue;
793
            }
794 795

            $list[] = $value;
796
        }
Benjamin Morel's avatar
Benjamin Morel committed
797

798
        return $list;
799 800
    }

Benjamin Morel's avatar
Benjamin Morel committed
801
    /**
802
     * @param array<string, string> $table
Benjamin Morel's avatar
Benjamin Morel committed
803
     */
804
    protected function _getPortableTableDefinition(array $table) : string
805
    {
806
        return array_shift($table);
807 808
    }

Benjamin Morel's avatar
Benjamin Morel committed
809
    /**
810
     * @param array<int, array<string, mixed>> $users
Benjamin Morel's avatar
Benjamin Morel committed
811
     *
812
     * @return array<int, array<string, mixed>>
Benjamin Morel's avatar
Benjamin Morel committed
813
     */
814
    protected function _getPortableUsersList(array $users) : array
815
    {
816
        $list = [];
817
        foreach ($users as $value) {
818 819 820
            $value = $this->_getPortableUserDefinition($value);

            if (! $value) {
821
                continue;
822
            }
823 824

            $list[] = $value;
825
        }
Benjamin Morel's avatar
Benjamin Morel committed
826

827
        return $list;
828 829
    }

Benjamin Morel's avatar
Benjamin Morel committed
830
    /**
831
     * @param array<string, mixed> $user
Benjamin Morel's avatar
Benjamin Morel committed
832
     *
833
     * @return array<string, mixed>
Benjamin Morel's avatar
Benjamin Morel committed
834
     */
835
    protected function _getPortableUserDefinition(array $user) : array
836 837 838 839
    {
        return $user;
    }

Benjamin Morel's avatar
Benjamin Morel committed
840
    /**
841
     * @param array<int, array<string, mixed>> $views
842
     *
843
     * @return array<string, View>
Benjamin Morel's avatar
Benjamin Morel committed
844
     */
845
    protected function _getPortableViewsList(array $views) : array
846
    {
847
        $list = [];
848
        foreach ($views as $value) {
849 850 851
            $view        = $this->_getPortableViewDefinition($value);
            $name        = strtolower($view->getQuotedName($this->_platform));
            $list[$name] = $view;
852
        }
Benjamin Morel's avatar
Benjamin Morel committed
853

854
        return $list;
855 856
    }

Benjamin Morel's avatar
Benjamin Morel committed
857
    /**
858
     * @param array<string, mixed> $view
Benjamin Morel's avatar
Benjamin Morel committed
859
     */
860
    protected function _getPortableViewDefinition(array $view) : View
861
    {
862
        throw NotSupported::new('Views');
863 864
    }

Benjamin Morel's avatar
Benjamin Morel committed
865
    /**
866
     * @param array<int|string, array<string, mixed>> $tableForeignKeys
Benjamin Morel's avatar
Benjamin Morel committed
867
     *
868
     * @return array<int, ForeignKeyConstraint>
Benjamin Morel's avatar
Benjamin Morel committed
869
     */
870
    protected function _getPortableTableForeignKeysList(array $tableForeignKeys) : array
871
    {
872
        $list = [];
873

Sergei Morozov's avatar
Sergei Morozov committed
874 875
        foreach ($tableForeignKeys as $value) {
            $list[] = $this->_getPortableTableForeignKeyDefinition($value);
876
        }
Benjamin Morel's avatar
Benjamin Morel committed
877

878 879 880
        return $list;
    }

Benjamin Morel's avatar
Benjamin Morel committed
881
    /**
882
     * @param array<string, mixed> $tableForeignKey
Benjamin Morel's avatar
Benjamin Morel committed
883
     */
884
    protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey) : ForeignKeyConstraint
885
    {
886
        throw NotSupported::new('ForeignKey');
887
    }
888

Benjamin Morel's avatar
Benjamin Morel committed
889
    /**
890
     * @param array<int, string>|string $sql
Benjamin Morel's avatar
Benjamin Morel committed
891
     */
892
    protected function _execSql($sql) : void
893 894
    {
        foreach ((array) $sql as $query) {
895
            $this->_conn->executeUpdate($query);
romanb's avatar
romanb committed
896 897
        }
    }
898 899

    /**
Benjamin Morel's avatar
Benjamin Morel committed
900
     * Creates a schema instance for the current database.
901
     */
902
    public function createSchema() : Schema
903
    {
904
        $namespaces = [];
905 906 907 908 909

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

910
        $sequences = [];
911

Steve Müller's avatar
Steve Müller committed
912
        if ($this->_platform->supportsSequences()) {
913 914
            $sequences = $this->listSequences();
        }
915

916 917
        $tables = $this->listTables();

918
        return new Schema($tables, $sequences, $this->createSchemaConfig(), $namespaces);
919 920 921
    }

    /**
Benjamin Morel's avatar
Benjamin Morel committed
922
     * Creates the configuration for this schema.
923
     */
924
    public function createSchemaConfig() : SchemaConfig
925 926 927
    {
        $schemaConfig = new SchemaConfig();
        $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength());
928 929 930 931 932

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

934
        $params = $this->_conn->getParams();
935 936
        if (! isset($params['defaultTableOptions'])) {
            $params['defaultTableOptions'] = [];
937
        }
938 939 940 941
        if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) {
            $params['defaultTableOptions']['charset'] = $params['charset'];
        }
        $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']);
942

943
        return $schemaConfig;
944
    }
945

946 947 948 949 950 951 952 953 954 955
    /**
     * 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.
     *
956
     * @return array<int, string>
957
     */
958
    public function getSchemaSearchPaths() : array
959
    {
960 961 962 963 964 965 966
        $database = $this->_conn->getDatabase();

        if ($database !== null) {
            return [$database];
        }

        return [];
967 968
    }

969
    /**
970 971
     * Given a table comment this method tries to extract a type hint for Doctrine Type. If the type hint is found,
     * it's removed from the comment.
972
     *
973
     * @return string|null The extracted Doctrine type or NULL of the type hint was not found.
Benjamin Morel's avatar
Benjamin Morel committed
974
     */
975
    final protected function extractDoctrineTypeFromComment(?string &$comment) : ?string
976
    {
977
        if ($comment === null || ! preg_match('/(.*)\(DC2Type:(((?!\)).)+)\)(.*)/', $comment, $match)) {
Sergei Morozov's avatar
Sergei Morozov committed
978 979 980
            return null;
        }

981 982 983
        $comment = $match[1] . $match[4];

        return $match[2];
984
    }
985 986 987 988 989 990 991 992 993 994 995 996

    /**
     * @throws DatabaseRequired
     */
    private function ensureDatabase(?string $database, string $methodName) : string
    {
        if ($database === null) {
            throw DatabaseRequired::new($methodName);
        }

        return $database;
    }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
997
}