1<?php
2
3namespace Doctrine\DBAL\Schema;
4
5use Doctrine\DBAL\Connection;
6use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs;
7use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
8use Doctrine\DBAL\Events;
9use Doctrine\DBAL\Exception;
10use Doctrine\DBAL\Platforms\AbstractPlatform;
11use Doctrine\Deprecations\Deprecation;
12use Throwable;
13
14use function array_filter;
15use function array_intersect;
16use function array_map;
17use function array_values;
18use function assert;
19use function call_user_func_array;
20use function count;
21use function func_get_args;
22use function is_callable;
23use function is_string;
24use function preg_match;
25use function str_replace;
26use function strtolower;
27
28/**
29 * Base class for schema managers. Schema managers are used to inspect and/or
30 * modify the database schema/structure.
31 */
32abstract class AbstractSchemaManager
33{
34    /**
35     * Holds instance of the Doctrine connection for this schema manager.
36     *
37     * @var Connection
38     */
39    protected $_conn;
40
41    /**
42     * Holds instance of the database platform used for this schema manager.
43     *
44     * @var AbstractPlatform
45     */
46    protected $_platform;
47
48    public function __construct(Connection $connection, AbstractPlatform $platform)
49    {
50        $this->_conn     = $connection;
51        $this->_platform = $platform;
52    }
53
54    /**
55     * Returns the associated platform.
56     *
57     * @return AbstractPlatform
58     */
59    public function getDatabasePlatform()
60    {
61        return $this->_platform;
62    }
63
64    /**
65     * Tries any method on the schema manager. Normally a method throws an
66     * exception when your DBMS doesn't support it or if an error occurs.
67     * This method allows you to try and method on your SchemaManager
68     * instance and will return false if it does not work or is not supported.
69     *
70     * <code>
71     * $result = $sm->tryMethod('dropView', 'view_name');
72     * </code>
73     *
74     * @return mixed
75     */
76    public function tryMethod()
77    {
78        $args   = func_get_args();
79        $method = $args[0];
80        unset($args[0]);
81        $args = array_values($args);
82
83        $callback = [$this, $method];
84        assert(is_callable($callback));
85
86        try {
87            return call_user_func_array($callback, $args);
88        } catch (Throwable $e) {
89            return false;
90        }
91    }
92
93    /**
94     * Lists the available databases for this connection.
95     *
96     * @return string[]
97     *
98     * @throws Exception
99     */
100    public function listDatabases()
101    {
102        $sql = $this->_platform->getListDatabasesSQL();
103
104        $databases = $this->_conn->fetchAllAssociative($sql);
105
106        return $this->_getPortableDatabasesList($databases);
107    }
108
109    /**
110     * Returns a list of all namespaces in the current database.
111     *
112     * @deprecated Use {@link listSchemaNames()} instead.
113     *
114     * @return string[]
115     *
116     * @throws Exception
117     */
118    public function listNamespaceNames()
119    {
120        Deprecation::triggerIfCalledFromOutside(
121            'doctrine/dbal',
122            'https://github.com/doctrine/dbal/issues/4503',
123            'AbstractSchemaManager::listNamespaceNames() is deprecated,'
124                . ' use AbstractSchemaManager::listSchemaNames() instead.'
125        );
126
127        $sql = $this->_platform->getListNamespacesSQL();
128
129        $namespaces = $this->_conn->fetchAllAssociative($sql);
130
131        return $this->getPortableNamespacesList($namespaces);
132    }
133
134    /**
135     * Returns a list of the names of all schemata in the current database.
136     *
137     * @return list<string>
138     *
139     * @throws Exception
140     */
141    public function listSchemaNames(): array
142    {
143        throw Exception::notSupported(__METHOD__);
144    }
145
146    /**
147     * Lists the available sequences for this connection.
148     *
149     * @param string|null $database
150     *
151     * @return Sequence[]
152     *
153     * @throws Exception
154     */
155    public function listSequences($database = null)
156    {
157        if ($database === null) {
158            $database = $this->_conn->getDatabase();
159        }
160
161        $sql = $this->_platform->getListSequencesSQL($database);
162
163        $sequences = $this->_conn->fetchAllAssociative($sql);
164
165        return $this->filterAssetNames($this->_getPortableSequencesList($sequences));
166    }
167
168    /**
169     * Lists the columns for a given table.
170     *
171     * In contrast to other libraries and to the old version of Doctrine,
172     * this column definition does try to contain the 'primary' column for
173     * the reason that it is not portable across different RDBMS. Use
174     * {@see listTableIndexes($tableName)} to retrieve the primary key
175     * of a table. Where a RDBMS specifies more details, these are held
176     * in the platformDetails array.
177     *
178     * @param string      $table    The name of the table.
179     * @param string|null $database
180     *
181     * @return Column[]
182     *
183     * @throws Exception
184     */
185    public function listTableColumns($table, $database = null)
186    {
187        if ($database === null) {
188            $database = $this->_conn->getDatabase();
189        }
190
191        $sql = $this->_platform->getListTableColumnsSQL($table, $database);
192
193        $tableColumns = $this->_conn->fetchAllAssociative($sql);
194
195        return $this->_getPortableTableColumnList($table, $database, $tableColumns);
196    }
197
198    /**
199     * Lists the indexes for a given table returning an array of Index instances.
200     *
201     * Keys of the portable indexes list are all lower-cased.
202     *
203     * @param string $table The name of the table.
204     *
205     * @return Index[]
206     *
207     * @throws Exception
208     */
209    public function listTableIndexes($table)
210    {
211        $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase());
212
213        $tableIndexes = $this->_conn->fetchAllAssociative($sql);
214
215        return $this->_getPortableTableIndexesList($tableIndexes, $table);
216    }
217
218    /**
219     * Returns true if all the given tables exist.
220     *
221     * The usage of a string $tableNames is deprecated. Pass a one-element array instead.
222     *
223     * @param string|string[] $names
224     *
225     * @return bool
226     *
227     * @throws Exception
228     */
229    public function tablesExist($names)
230    {
231        if (is_string($names)) {
232            Deprecation::trigger(
233                'doctrine/dbal',
234                'https://github.com/doctrine/dbal/issues/3580',
235                'The usage of a string $tableNames in AbstractSchemaManager::tablesExist() is deprecated. ' .
236                'Pass a one-element array instead.'
237            );
238        }
239
240        $names = array_map('strtolower', (array) $names);
241
242        return count($names) === count(array_intersect($names, array_map('strtolower', $this->listTableNames())));
243    }
244
245    /**
246     * Returns a list of all tables in the current database.
247     *
248     * @return string[]
249     *
250     * @throws Exception
251     */
252    public function listTableNames()
253    {
254        $sql = $this->_platform->getListTablesSQL();
255
256        $tables     = $this->_conn->fetchAllAssociative($sql);
257        $tableNames = $this->_getPortableTablesList($tables);
258
259        return $this->filterAssetNames($tableNames);
260    }
261
262    /**
263     * Filters asset names if they are configured to return only a subset of all
264     * the found elements.
265     *
266     * @param mixed[] $assetNames
267     *
268     * @return mixed[]
269     */
270    protected function filterAssetNames($assetNames)
271    {
272        $filter = $this->_conn->getConfiguration()->getSchemaAssetsFilter();
273        if ($filter === null) {
274            return $assetNames;
275        }
276
277        return array_values(array_filter($assetNames, $filter));
278    }
279
280    /**
281     * Lists the tables for this connection.
282     *
283     * @return Table[]
284     *
285     * @throws Exception
286     */
287    public function listTables()
288    {
289        $tableNames = $this->listTableNames();
290
291        $tables = [];
292        foreach ($tableNames as $tableName) {
293            $tables[] = $this->listTableDetails($tableName);
294        }
295
296        return $tables;
297    }
298
299    /**
300     * @param string $name
301     *
302     * @return Table
303     *
304     * @throws Exception
305     */
306    public function listTableDetails($name)
307    {
308        $columns     = $this->listTableColumns($name);
309        $foreignKeys = [];
310
311        if ($this->_platform->supportsForeignKeyConstraints()) {
312            $foreignKeys = $this->listTableForeignKeys($name);
313        }
314
315        $indexes = $this->listTableIndexes($name);
316
317        return new Table($name, $columns, $indexes, [], $foreignKeys);
318    }
319
320    /**
321     * Lists the views this connection has.
322     *
323     * @return View[]
324     *
325     * @throws Exception
326     */
327    public function listViews()
328    {
329        $database = $this->_conn->getDatabase();
330        $sql      = $this->_platform->getListViewsSQL($database);
331        $views    = $this->_conn->fetchAllAssociative($sql);
332
333        return $this->_getPortableViewsList($views);
334    }
335
336    /**
337     * Lists the foreign keys for the given table.
338     *
339     * @param string      $table    The name of the table.
340     * @param string|null $database
341     *
342     * @return ForeignKeyConstraint[]
343     *
344     * @throws Exception
345     */
346    public function listTableForeignKeys($table, $database = null)
347    {
348        if ($database === null) {
349            $database = $this->_conn->getDatabase();
350        }
351
352        $sql              = $this->_platform->getListTableForeignKeysSQL($table, $database);
353        $tableForeignKeys = $this->_conn->fetchAllAssociative($sql);
354
355        return $this->_getPortableTableForeignKeysList($tableForeignKeys);
356    }
357
358    /* drop*() Methods */
359
360    /**
361     * Drops a database.
362     *
363     * NOTE: You can not drop the database this SchemaManager is currently connected to.
364     *
365     * @param string $database The name of the database to drop.
366     *
367     * @return void
368     *
369     * @throws Exception
370     */
371    public function dropDatabase($database)
372    {
373        $this->_execSql($this->_platform->getDropDatabaseSQL($database));
374    }
375
376    /**
377     * Drops a schema.
378     *
379     * @throws Exception
380     */
381    public function dropSchema(string $schemaName): void
382    {
383        $this->_execSql($this->_platform->getDropSchemaSQL($schemaName));
384    }
385
386    /**
387     * Drops the given table.
388     *
389     * @param string $name The name of the table to drop.
390     *
391     * @return void
392     *
393     * @throws Exception
394     */
395    public function dropTable($name)
396    {
397        $this->_execSql($this->_platform->getDropTableSQL($name));
398    }
399
400    /**
401     * Drops the index from the given table.
402     *
403     * @param Index|string $index The name of the index.
404     * @param Table|string $table The name of the table.
405     *
406     * @return void
407     *
408     * @throws Exception
409     */
410    public function dropIndex($index, $table)
411    {
412        if ($index instanceof Index) {
413            $index = $index->getQuotedName($this->_platform);
414        }
415
416        $this->_execSql($this->_platform->getDropIndexSQL($index, $table));
417    }
418
419    /**
420     * Drops the constraint from the given table.
421     *
422     * @param Table|string $table The name of the table.
423     *
424     * @return void
425     *
426     * @throws Exception
427     */
428    public function dropConstraint(Constraint $constraint, $table)
429    {
430        $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table));
431    }
432
433    /**
434     * Drops a foreign key from a table.
435     *
436     * @param ForeignKeyConstraint|string $foreignKey The name of the foreign key.
437     * @param Table|string                $table      The name of the table with the foreign key.
438     *
439     * @return void
440     *
441     * @throws Exception
442     */
443    public function dropForeignKey($foreignKey, $table)
444    {
445        $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table));
446    }
447
448    /**
449     * Drops a sequence with a given name.
450     *
451     * @param string $name The name of the sequence to drop.
452     *
453     * @return void
454     *
455     * @throws Exception
456     */
457    public function dropSequence($name)
458    {
459        $this->_execSql($this->_platform->getDropSequenceSQL($name));
460    }
461
462    /**
463     * Drops a view.
464     *
465     * @param string $name The name of the view.
466     *
467     * @return void
468     *
469     * @throws Exception
470     */
471    public function dropView($name)
472    {
473        $this->_execSql($this->_platform->getDropViewSQL($name));
474    }
475
476    /* create*() Methods */
477
478    /**
479     * Creates a new database.
480     *
481     * @param string $database The name of the database to create.
482     *
483     * @return void
484     *
485     * @throws Exception
486     */
487    public function createDatabase($database)
488    {
489        $this->_execSql($this->_platform->getCreateDatabaseSQL($database));
490    }
491
492    /**
493     * Creates a new table.
494     *
495     * @return void
496     *
497     * @throws Exception
498     */
499    public function createTable(Table $table)
500    {
501        $createFlags = AbstractPlatform::CREATE_INDEXES | AbstractPlatform::CREATE_FOREIGNKEYS;
502        $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags));
503    }
504
505    /**
506     * Creates a new sequence.
507     *
508     * @param Sequence $sequence
509     *
510     * @return void
511     *
512     * @throws Exception
513     */
514    public function createSequence($sequence)
515    {
516        $this->_execSql($this->_platform->getCreateSequenceSQL($sequence));
517    }
518
519    /**
520     * Creates a constraint on a table.
521     *
522     * @param Table|string $table
523     *
524     * @return void
525     *
526     * @throws Exception
527     */
528    public function createConstraint(Constraint $constraint, $table)
529    {
530        $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table));
531    }
532
533    /**
534     * Creates a new index on a table.
535     *
536     * @param Table|string $table The name of the table on which the index is to be created.
537     *
538     * @return void
539     *
540     * @throws Exception
541     */
542    public function createIndex(Index $index, $table)
543    {
544        $this->_execSql($this->_platform->getCreateIndexSQL($index, $table));
545    }
546
547    /**
548     * Creates a new foreign key.
549     *
550     * @param ForeignKeyConstraint $foreignKey The ForeignKey instance.
551     * @param Table|string         $table      The name of the table on which the foreign key is to be created.
552     *
553     * @return void
554     *
555     * @throws Exception
556     */
557    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
558    {
559        $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table));
560    }
561
562    /**
563     * Creates a new view.
564     *
565     * @return void
566     *
567     * @throws Exception
568     */
569    public function createView(View $view)
570    {
571        $this->_execSql($this->_platform->getCreateViewSQL($view->getQuotedName($this->_platform), $view->getSql()));
572    }
573
574    /* dropAndCreate*() Methods */
575
576    /**
577     * Drops and creates a constraint.
578     *
579     * @see dropConstraint()
580     * @see createConstraint()
581     *
582     * @param Table|string $table
583     *
584     * @return void
585     *
586     * @throws Exception
587     */
588    public function dropAndCreateConstraint(Constraint $constraint, $table)
589    {
590        $this->tryMethod('dropConstraint', $constraint, $table);
591        $this->createConstraint($constraint, $table);
592    }
593
594    /**
595     * Drops and creates a new index on a table.
596     *
597     * @param Table|string $table The name of the table on which the index is to be created.
598     *
599     * @return void
600     *
601     * @throws Exception
602     */
603    public function dropAndCreateIndex(Index $index, $table)
604    {
605        $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table);
606        $this->createIndex($index, $table);
607    }
608
609    /**
610     * Drops and creates a new foreign key.
611     *
612     * @param ForeignKeyConstraint $foreignKey An associative array that defines properties
613     *                                         of the foreign key to be created.
614     * @param Table|string         $table      The name of the table on which the foreign key is to be created.
615     *
616     * @return void
617     *
618     * @throws Exception
619     */
620    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
621    {
622        $this->tryMethod('dropForeignKey', $foreignKey, $table);
623        $this->createForeignKey($foreignKey, $table);
624    }
625
626    /**
627     * Drops and create a new sequence.
628     *
629     * @return void
630     *
631     * @throws Exception
632     */
633    public function dropAndCreateSequence(Sequence $sequence)
634    {
635        $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform));
636        $this->createSequence($sequence);
637    }
638
639    /**
640     * Drops and creates a new table.
641     *
642     * @return void
643     *
644     * @throws Exception
645     */
646    public function dropAndCreateTable(Table $table)
647    {
648        $this->tryMethod('dropTable', $table->getQuotedName($this->_platform));
649        $this->createTable($table);
650    }
651
652    /**
653     * Drops and creates a new database.
654     *
655     * @param string $database The name of the database to create.
656     *
657     * @return void
658     *
659     * @throws Exception
660     */
661    public function dropAndCreateDatabase($database)
662    {
663        $this->tryMethod('dropDatabase', $database);
664        $this->createDatabase($database);
665    }
666
667    /**
668     * Drops and creates a new view.
669     *
670     * @return void
671     *
672     * @throws Exception
673     */
674    public function dropAndCreateView(View $view)
675    {
676        $this->tryMethod('dropView', $view->getQuotedName($this->_platform));
677        $this->createView($view);
678    }
679
680    /* alterTable() Methods */
681
682    /**
683     * Alters an existing tables schema.
684     *
685     * @return void
686     *
687     * @throws Exception
688     */
689    public function alterTable(TableDiff $tableDiff)
690    {
691        foreach ($this->_platform->getAlterTableSQL($tableDiff) as $ddlQuery) {
692            $this->_execSql($ddlQuery);
693        }
694    }
695
696    /**
697     * Renames a given table to another name.
698     *
699     * @param string $name    The current name of the table.
700     * @param string $newName The new name of the table.
701     *
702     * @return void
703     *
704     * @throws Exception
705     */
706    public function renameTable($name, $newName)
707    {
708        $tableDiff          = new TableDiff($name);
709        $tableDiff->newName = $newName;
710        $this->alterTable($tableDiff);
711    }
712
713    /**
714     * Methods for filtering return values of list*() methods to convert
715     * the native DBMS data definition to a portable Doctrine definition
716     */
717
718    /**
719     * @param mixed[] $databases
720     *
721     * @return string[]
722     */
723    protected function _getPortableDatabasesList($databases)
724    {
725        $list = [];
726        foreach ($databases as $value) {
727            $list[] = $this->_getPortableDatabaseDefinition($value);
728        }
729
730        return $list;
731    }
732
733    /**
734     * Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition.
735     *
736     * @deprecated Use {@link listSchemaNames()} instead.
737     *
738     * @param array<int, array<string, mixed>> $namespaces The list of namespace names
739     *                                                     in the native DBMS data definition.
740     *
741     * @return string[]
742     */
743    protected function getPortableNamespacesList(array $namespaces)
744    {
745        Deprecation::triggerIfCalledFromOutside(
746            'doctrine/dbal',
747            'https://github.com/doctrine/dbal/issues/4503',
748            'AbstractSchemaManager::getPortableNamespacesList() is deprecated,'
749                . ' use AbstractSchemaManager::listSchemaNames() instead.'
750        );
751
752        $namespacesList = [];
753
754        foreach ($namespaces as $namespace) {
755            $namespacesList[] = $this->getPortableNamespaceDefinition($namespace);
756        }
757
758        return $namespacesList;
759    }
760
761    /**
762     * @param mixed $database
763     *
764     * @return mixed
765     */
766    protected function _getPortableDatabaseDefinition($database)
767    {
768        return $database;
769    }
770
771    /**
772     * Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition.
773     *
774     * @deprecated Use {@link listSchemaNames()} instead.
775     *
776     * @param array<string, mixed> $namespace The native DBMS namespace definition.
777     *
778     * @return mixed
779     */
780    protected function getPortableNamespaceDefinition(array $namespace)
781    {
782        Deprecation::triggerIfCalledFromOutside(
783            'doctrine/dbal',
784            'https://github.com/doctrine/dbal/issues/4503',
785            'AbstractSchemaManager::getPortableNamespaceDefinition() is deprecated,'
786                . ' use AbstractSchemaManager::listSchemaNames() instead.'
787        );
788
789        return $namespace;
790    }
791
792    /**
793     * @param mixed[][] $triggers
794     *
795     * @return mixed[][]
796     */
797    protected function _getPortableTriggersList($triggers)
798    {
799        $list = [];
800        foreach ($triggers as $value) {
801            $value = $this->_getPortableTriggerDefinition($value);
802
803            if (! $value) {
804                continue;
805            }
806
807            $list[] = $value;
808        }
809
810        return $list;
811    }
812
813    /**
814     * @param mixed[] $trigger
815     *
816     * @return mixed
817     */
818    protected function _getPortableTriggerDefinition($trigger)
819    {
820        return $trigger;
821    }
822
823    /**
824     * @param mixed[][] $sequences
825     *
826     * @return Sequence[]
827     *
828     * @throws Exception
829     */
830    protected function _getPortableSequencesList($sequences)
831    {
832        $list = [];
833
834        foreach ($sequences as $value) {
835            $list[] = $this->_getPortableSequenceDefinition($value);
836        }
837
838        return $list;
839    }
840
841    /**
842     * @param mixed[] $sequence
843     *
844     * @return Sequence
845     *
846     * @throws Exception
847     */
848    protected function _getPortableSequenceDefinition($sequence)
849    {
850        throw Exception::notSupported('Sequences');
851    }
852
853    /**
854     * Independent of the database the keys of the column list result are lowercased.
855     *
856     * The name of the created column instance however is kept in its case.
857     *
858     * @param string    $table        The name of the table.
859     * @param string    $database
860     * @param mixed[][] $tableColumns
861     *
862     * @return Column[]
863     *
864     * @throws Exception
865     */
866    protected function _getPortableTableColumnList($table, $database, $tableColumns)
867    {
868        $eventManager = $this->_platform->getEventManager();
869
870        $list = [];
871        foreach ($tableColumns as $tableColumn) {
872            $column           = null;
873            $defaultPrevented = false;
874
875            if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) {
876                $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn);
877                $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs);
878
879                $defaultPrevented = $eventArgs->isDefaultPrevented();
880                $column           = $eventArgs->getColumn();
881            }
882
883            if (! $defaultPrevented) {
884                $column = $this->_getPortableTableColumnDefinition($tableColumn);
885            }
886
887            if ($column === null) {
888                continue;
889            }
890
891            $name        = strtolower($column->getQuotedName($this->_platform));
892            $list[$name] = $column;
893        }
894
895        return $list;
896    }
897
898    /**
899     * Gets Table Column Definition.
900     *
901     * @param mixed[] $tableColumn
902     *
903     * @return Column
904     *
905     * @throws Exception
906     */
907    abstract protected function _getPortableTableColumnDefinition($tableColumn);
908
909    /**
910     * Aggregates and groups the index results according to the required data result.
911     *
912     * @param mixed[][]   $tableIndexes
913     * @param string|null $tableName
914     *
915     * @return Index[]
916     *
917     * @throws Exception
918     */
919    protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
920    {
921        $result = [];
922        foreach ($tableIndexes as $tableIndex) {
923            $indexName = $keyName = $tableIndex['key_name'];
924            if ($tableIndex['primary']) {
925                $keyName = 'primary';
926            }
927
928            $keyName = strtolower($keyName);
929
930            if (! isset($result[$keyName])) {
931                $options = [
932                    'lengths' => [],
933                ];
934
935                if (isset($tableIndex['where'])) {
936                    $options['where'] = $tableIndex['where'];
937                }
938
939                $result[$keyName] = [
940                    'name' => $indexName,
941                    'columns' => [],
942                    'unique' => ! $tableIndex['non_unique'],
943                    'primary' => $tableIndex['primary'],
944                    'flags' => $tableIndex['flags'] ?? [],
945                    'options' => $options,
946                ];
947            }
948
949            $result[$keyName]['columns'][]            = $tableIndex['column_name'];
950            $result[$keyName]['options']['lengths'][] = $tableIndex['length'] ?? null;
951        }
952
953        $eventManager = $this->_platform->getEventManager();
954
955        $indexes = [];
956        foreach ($result as $indexKey => $data) {
957            $index            = null;
958            $defaultPrevented = false;
959
960            if ($eventManager !== null && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) {
961                $eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn);
962                $eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs);
963
964                $defaultPrevented = $eventArgs->isDefaultPrevented();
965                $index            = $eventArgs->getIndex();
966            }
967
968            if (! $defaultPrevented) {
969                $index = new Index(
970                    $data['name'],
971                    $data['columns'],
972                    $data['unique'],
973                    $data['primary'],
974                    $data['flags'],
975                    $data['options']
976                );
977            }
978
979            if ($index === null) {
980                continue;
981            }
982
983            $indexes[$indexKey] = $index;
984        }
985
986        return $indexes;
987    }
988
989    /**
990     * @param mixed[][] $tables
991     *
992     * @return string[]
993     */
994    protected function _getPortableTablesList($tables)
995    {
996        $list = [];
997        foreach ($tables as $value) {
998            $list[] = $this->_getPortableTableDefinition($value);
999        }
1000
1001        return $list;
1002    }
1003
1004    /**
1005     * @param mixed $table
1006     *
1007     * @return string
1008     */
1009    protected function _getPortableTableDefinition($table)
1010    {
1011        return $table;
1012    }
1013
1014    /**
1015     * @param mixed[][] $users
1016     *
1017     * @return string[][]
1018     */
1019    protected function _getPortableUsersList($users)
1020    {
1021        $list = [];
1022        foreach ($users as $value) {
1023            $list[] = $this->_getPortableUserDefinition($value);
1024        }
1025
1026        return $list;
1027    }
1028
1029    /**
1030     * @param string[] $user
1031     *
1032     * @return string[]
1033     */
1034    protected function _getPortableUserDefinition($user)
1035    {
1036        return $user;
1037    }
1038
1039    /**
1040     * @param mixed[][] $views
1041     *
1042     * @return View[]
1043     */
1044    protected function _getPortableViewsList($views)
1045    {
1046        $list = [];
1047        foreach ($views as $value) {
1048            $view = $this->_getPortableViewDefinition($value);
1049
1050            if ($view === false) {
1051                continue;
1052            }
1053
1054            $viewName        = strtolower($view->getQuotedName($this->_platform));
1055            $list[$viewName] = $view;
1056        }
1057
1058        return $list;
1059    }
1060
1061    /**
1062     * @param mixed[] $view
1063     *
1064     * @return View|false
1065     */
1066    protected function _getPortableViewDefinition($view)
1067    {
1068        return false;
1069    }
1070
1071    /**
1072     * @param mixed[][] $tableForeignKeys
1073     *
1074     * @return ForeignKeyConstraint[]
1075     */
1076    protected function _getPortableTableForeignKeysList($tableForeignKeys)
1077    {
1078        $list = [];
1079
1080        foreach ($tableForeignKeys as $value) {
1081            $list[] = $this->_getPortableTableForeignKeyDefinition($value);
1082        }
1083
1084        return $list;
1085    }
1086
1087    /**
1088     * @param mixed $tableForeignKey
1089     *
1090     * @return ForeignKeyConstraint
1091     */
1092    protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
1093    {
1094        return $tableForeignKey;
1095    }
1096
1097    /**
1098     * @param string[]|string $sql
1099     *
1100     * @return void
1101     *
1102     * @throws Exception
1103     */
1104    protected function _execSql($sql)
1105    {
1106        foreach ((array) $sql as $query) {
1107            $this->_conn->executeStatement($query);
1108        }
1109    }
1110
1111    /**
1112     * Creates a schema instance for the current database.
1113     *
1114     * @return Schema
1115     *
1116     * @throws Exception
1117     */
1118    public function createSchema()
1119    {
1120        $schemaNames = [];
1121
1122        if ($this->_platform->supportsSchemas()) {
1123            $schemaNames = $this->listNamespaceNames();
1124        }
1125
1126        $sequences = [];
1127
1128        if ($this->_platform->supportsSequences()) {
1129            $sequences = $this->listSequences();
1130        }
1131
1132        $tables = $this->listTables();
1133
1134        return new Schema($tables, $sequences, $this->createSchemaConfig(), $schemaNames);
1135    }
1136
1137    /**
1138     * Creates the configuration for this schema.
1139     *
1140     * @return SchemaConfig
1141     *
1142     * @throws Exception
1143     */
1144    public function createSchemaConfig()
1145    {
1146        $schemaConfig = new SchemaConfig();
1147        $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength());
1148
1149        $searchPaths = $this->getSchemaSearchPaths();
1150        if (isset($searchPaths[0])) {
1151            $schemaConfig->setName($searchPaths[0]);
1152        }
1153
1154        $params = $this->_conn->getParams();
1155        if (! isset($params['defaultTableOptions'])) {
1156            $params['defaultTableOptions'] = [];
1157        }
1158
1159        if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) {
1160            $params['defaultTableOptions']['charset'] = $params['charset'];
1161        }
1162
1163        $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']);
1164
1165        return $schemaConfig;
1166    }
1167
1168    /**
1169     * The search path for namespaces in the currently connected database.
1170     *
1171     * The first entry is usually the default namespace in the Schema. All
1172     * further namespaces contain tables/sequences which can also be addressed
1173     * with a short, not full-qualified name.
1174     *
1175     * For databases that don't support subschema/namespaces this method
1176     * returns the name of the currently connected database.
1177     *
1178     * @return string[]
1179     *
1180     * @throws Exception
1181     */
1182    public function getSchemaSearchPaths()
1183    {
1184        $database = $this->_conn->getDatabase();
1185
1186        if ($database !== null) {
1187            return [$database];
1188        }
1189
1190        return [];
1191    }
1192
1193    /**
1194     * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns
1195     * the type given as default.
1196     *
1197     * @param string|null $comment
1198     * @param string      $currentType
1199     *
1200     * @return string
1201     */
1202    public function extractDoctrineTypeFromComment($comment, $currentType)
1203    {
1204        if ($comment !== null && preg_match('(\(DC2Type:(((?!\)).)+)\))', $comment, $match) === 1) {
1205            return $match[1];
1206        }
1207
1208        return $currentType;
1209    }
1210
1211    /**
1212     * @param string|null $comment
1213     * @param string|null $type
1214     *
1215     * @return string|null
1216     */
1217    public function removeDoctrineTypeFromComment($comment, $type)
1218    {
1219        if ($comment === null) {
1220            return null;
1221        }
1222
1223        return str_replace('(DC2Type:' . $type . ')', '', $comment);
1224    }
1225}
1226