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