summaryrefslogtreecommitdiff
path: root/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php')
-rw-r--r--vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php864
1 files changed, 864 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php b/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php
new file mode 100644
index 0000000..9730797
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/AbstractSchemaManager.php
@@ -0,0 +1,864 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Exception\DatabaseRequired;
10use Doctrine\DBAL\Platforms\AbstractPlatform;
11use Doctrine\DBAL\Platforms\Exception\NotSupported;
12use Doctrine\DBAL\Result;
13use Doctrine\DBAL\Schema\Exception\TableDoesNotExist;
14
15use function array_filter;
16use function array_intersect;
17use function array_map;
18use function array_values;
19use function count;
20use function strtolower;
21
22/**
23 * Base class for schema managers. Schema managers are used to inspect and/or
24 * modify the database schema/structure.
25 *
26 * @template-covariant T of AbstractPlatform
27 */
28abstract class AbstractSchemaManager
29{
30 /** @param T $platform */
31 public function __construct(protected Connection $connection, protected AbstractPlatform $platform)
32 {
33 }
34
35 /**
36 * Lists the available databases for this connection.
37 *
38 * @return array<int, string>
39 *
40 * @throws Exception
41 */
42 public function listDatabases(): array
43 {
44 return array_map(function (array $row): string {
45 return $this->_getPortableDatabaseDefinition($row);
46 }, $this->connection->fetchAllAssociative(
47 $this->platform->getListDatabasesSQL(),
48 ));
49 }
50
51 /**
52 * Returns a list of the names of all schemata in the current database.
53 *
54 * @return list<string>
55 *
56 * @throws Exception
57 */
58 public function listSchemaNames(): array
59 {
60 throw NotSupported::new(__METHOD__);
61 }
62
63 /**
64 * Lists the available sequences for this connection.
65 *
66 * @return array<int, Sequence>
67 *
68 * @throws Exception
69 */
70 public function listSequences(): array
71 {
72 return $this->filterAssetNames(
73 array_map(function (array $row): Sequence {
74 return $this->_getPortableSequenceDefinition($row);
75 }, $this->connection->fetchAllAssociative(
76 $this->platform->getListSequencesSQL(
77 $this->getDatabase(__METHOD__),
78 ),
79 )),
80 );
81 }
82
83 /**
84 * Lists the columns for a given table.
85 *
86 * In contrast to other libraries and to the old version of Doctrine,
87 * this column definition does try to contain the 'primary' column for
88 * the reason that it is not portable across different RDBMS. Use
89 * {@see listTableIndexes($tableName)} to retrieve the primary key
90 * of a table. Where a RDBMS specifies more details, these are held
91 * in the platformDetails array.
92 *
93 * @return array<string, Column>
94 *
95 * @throws Exception
96 */
97 public function listTableColumns(string $table): array
98 {
99 $database = $this->getDatabase(__METHOD__);
100
101 return $this->_getPortableTableColumnList(
102 $table,
103 $database,
104 $this->selectTableColumns($database, $this->normalizeName($table))
105 ->fetchAllAssociative(),
106 );
107 }
108
109 /**
110 * Lists the indexes for a given table returning an array of Index instances.
111 *
112 * Keys of the portable indexes list are all lower-cased.
113 *
114 * @return array<string, Index>
115 *
116 * @throws Exception
117 */
118 public function listTableIndexes(string $table): array
119 {
120 $database = $this->getDatabase(__METHOD__);
121 $table = $this->normalizeName($table);
122
123 return $this->_getPortableTableIndexesList(
124 $this->selectIndexColumns(
125 $database,
126 $table,
127 )->fetchAllAssociative(),
128 $table,
129 );
130 }
131
132 /**
133 * Returns true if all the given tables exist.
134 *
135 * @param array<int, string> $names
136 *
137 * @throws Exception
138 */
139 public function tablesExist(array $names): bool
140 {
141 $names = array_map('strtolower', $names);
142
143 return count($names) === count(array_intersect($names, array_map('strtolower', $this->listTableNames())));
144 }
145
146 public function tableExists(string $tableName): bool
147 {
148 return $this->tablesExist([$tableName]);
149 }
150
151 /**
152 * Returns a list of all tables in the current database.
153 *
154 * @return array<int, string>
155 *
156 * @throws Exception
157 */
158 public function listTableNames(): array
159 {
160 return $this->filterAssetNames(
161 array_map(function (array $row): string {
162 return $this->_getPortableTableDefinition($row);
163 }, $this->selectTableNames(
164 $this->getDatabase(__METHOD__),
165 )->fetchAllAssociative()),
166 );
167 }
168
169 /**
170 * Filters asset names if they are configured to return only a subset of all
171 * the found elements.
172 *
173 * @param array<int, mixed> $assetNames
174 *
175 * @return array<int, mixed>
176 */
177 private function filterAssetNames(array $assetNames): array
178 {
179 $filter = $this->connection->getConfiguration()->getSchemaAssetsFilter();
180
181 return array_values(array_filter($assetNames, $filter));
182 }
183
184 /**
185 * Lists the tables for this connection.
186 *
187 * @return list<Table>
188 *
189 * @throws Exception
190 */
191 public function listTables(): array
192 {
193 $database = $this->getDatabase(__METHOD__);
194
195 $tableColumnsByTable = $this->fetchTableColumnsByTable($database);
196 $indexColumnsByTable = $this->fetchIndexColumnsByTable($database);
197 $foreignKeyColumnsByTable = $this->fetchForeignKeyColumnsByTable($database);
198 $tableOptionsByTable = $this->fetchTableOptionsByTable($database);
199
200 $filter = $this->connection->getConfiguration()->getSchemaAssetsFilter();
201 $tables = [];
202
203 foreach ($tableColumnsByTable as $tableName => $tableColumns) {
204 if (! $filter($tableName)) {
205 continue;
206 }
207
208 $tables[] = new Table(
209 $tableName,
210 $this->_getPortableTableColumnList($tableName, $database, $tableColumns),
211 $this->_getPortableTableIndexesList($indexColumnsByTable[$tableName] ?? [], $tableName),
212 [],
213 $this->_getPortableTableForeignKeysList($foreignKeyColumnsByTable[$tableName] ?? []),
214 $tableOptionsByTable[$tableName] ?? [],
215 );
216 }
217
218 return $tables;
219 }
220
221 /**
222 * An extension point for those platforms where case sensitivity of the object name depends on whether it's quoted.
223 *
224 * Such platforms should convert a possibly quoted name into a value of the corresponding case.
225 */
226 protected function normalizeName(string $name): string
227 {
228 $identifier = new Identifier($name);
229
230 return $identifier->getName();
231 }
232
233 /**
234 * Selects names of tables in the specified database.
235 *
236 * @throws Exception
237 */
238 abstract protected function selectTableNames(string $databaseName): Result;
239
240 /**
241 * Selects definitions of table columns in the specified database. If the table name is specified, narrows down
242 * the selection to this table.
243 *
244 * @throws Exception
245 */
246 abstract protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result;
247
248 /**
249 * Selects definitions of index columns in the specified database. If the table name is specified, narrows down
250 * the selection to this table.
251 *
252 * @throws Exception
253 */
254 abstract protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result;
255
256 /**
257 * Selects definitions of foreign key columns in the specified database. If the table name is specified,
258 * narrows down the selection to this table.
259 *
260 * @throws Exception
261 */
262 abstract protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result;
263
264 /**
265 * Fetches definitions of table columns in the specified database and returns them grouped by table name.
266 *
267 * @return array<string,list<array<string,mixed>>>
268 *
269 * @throws Exception
270 */
271 protected function fetchTableColumnsByTable(string $databaseName): array
272 {
273 return $this->fetchAllAssociativeGrouped($this->selectTableColumns($databaseName));
274 }
275
276 /**
277 * Fetches definitions of index columns in the specified database and returns them grouped by table name.
278 *
279 * @return array<string,list<array<string,mixed>>>
280 *
281 * @throws Exception
282 */
283 protected function fetchIndexColumnsByTable(string $databaseName): array
284 {
285 return $this->fetchAllAssociativeGrouped($this->selectIndexColumns($databaseName));
286 }
287
288 /**
289 * Fetches definitions of foreign key columns in the specified database and returns them grouped by table name.
290 *
291 * @return array<string, list<array<string, mixed>>>
292 *
293 * @throws Exception
294 */
295 protected function fetchForeignKeyColumnsByTable(string $databaseName): array
296 {
297 return $this->fetchAllAssociativeGrouped(
298 $this->selectForeignKeyColumns($databaseName),
299 );
300 }
301
302 /**
303 * Fetches table options for the tables in the specified database and returns them grouped by table name.
304 * If the table name is specified, narrows down the selection to this table.
305 *
306 * @return array<string,array<string,mixed>>
307 *
308 * @throws Exception
309 */
310 abstract protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array;
311
312 /**
313 * Introspects the table with the given name.
314 *
315 * @throws Exception
316 */
317 public function introspectTable(string $name): Table
318 {
319 $columns = $this->listTableColumns($name);
320
321 if ($columns === []) {
322 throw TableDoesNotExist::new($name);
323 }
324
325 return new Table(
326 $name,
327 $columns,
328 $this->listTableIndexes($name),
329 [],
330 $this->listTableForeignKeys($name),
331 $this->getTableOptions($name),
332 );
333 }
334
335 /**
336 * Lists the views this connection has.
337 *
338 * @return list<View>
339 *
340 * @throws Exception
341 */
342 public function listViews(): array
343 {
344 return array_map(function (array $row): View {
345 return $this->_getPortableViewDefinition($row);
346 }, $this->connection->fetchAllAssociative(
347 $this->platform->getListViewsSQL(
348 $this->getDatabase(__METHOD__),
349 ),
350 ));
351 }
352
353 /**
354 * Lists the foreign keys for the given table.
355 *
356 * @return array<int|string, ForeignKeyConstraint>
357 *
358 * @throws Exception
359 */
360 public function listTableForeignKeys(string $table): array
361 {
362 $database = $this->getDatabase(__METHOD__);
363
364 return $this->_getPortableTableForeignKeysList(
365 $this->selectForeignKeyColumns(
366 $database,
367 $this->normalizeName($table),
368 )->fetchAllAssociative(),
369 );
370 }
371
372 /**
373 * @return array<string, mixed>
374 *
375 * @throws Exception
376 */
377 private function getTableOptions(string $name): array
378 {
379 $normalizedName = $this->normalizeName($name);
380
381 return $this->fetchTableOptionsByTable(
382 $this->getDatabase(__METHOD__),
383 $normalizedName,
384 )[$normalizedName] ?? [];
385 }
386
387 /* drop*() Methods */
388
389 /**
390 * Drops a database.
391 *
392 * NOTE: You can not drop the database this SchemaManager is currently connected to.
393 *
394 * @throws Exception
395 */
396 public function dropDatabase(string $database): void
397 {
398 $this->connection->executeStatement(
399 $this->platform->getDropDatabaseSQL($database),
400 );
401 }
402
403 /**
404 * Drops a schema.
405 *
406 * @throws Exception
407 */
408 public function dropSchema(string $schemaName): void
409 {
410 $this->connection->executeStatement(
411 $this->platform->getDropSchemaSQL($schemaName),
412 );
413 }
414
415 /**
416 * Drops the given table.
417 *
418 * @throws Exception
419 */
420 public function dropTable(string $name): void
421 {
422 $this->connection->executeStatement(
423 $this->platform->getDropTableSQL($name),
424 );
425 }
426
427 /**
428 * Drops the index from the given table.
429 *
430 * @throws Exception
431 */
432 public function dropIndex(string $index, string $table): void
433 {
434 $this->connection->executeStatement(
435 $this->platform->getDropIndexSQL($index, $table),
436 );
437 }
438
439 /**
440 * Drops a foreign key from a table.
441 *
442 * @throws Exception
443 */
444 public function dropForeignKey(string $name, string $table): void
445 {
446 $this->connection->executeStatement(
447 $this->platform->getDropForeignKeySQL($name, $table),
448 );
449 }
450
451 /**
452 * Drops a sequence with a given name.
453 *
454 * @throws Exception
455 */
456 public function dropSequence(string $name): void
457 {
458 $this->connection->executeStatement(
459 $this->platform->getDropSequenceSQL($name),
460 );
461 }
462
463 /**
464 * Drops the unique constraint from the given table.
465 *
466 * @throws Exception
467 */
468 public function dropUniqueConstraint(string $name, string $tableName): void
469 {
470 $this->connection->executeStatement(
471 $this->platform->getDropUniqueConstraintSQL($name, $tableName),
472 );
473 }
474
475 /**
476 * Drops a view.
477 *
478 * @throws Exception
479 */
480 public function dropView(string $name): void
481 {
482 $this->connection->executeStatement(
483 $this->platform->getDropViewSQL($name),
484 );
485 }
486
487 /* create*() Methods */
488
489 /** @throws Exception */
490 public function createSchemaObjects(Schema $schema): void
491 {
492 $this->executeStatements($schema->toSql($this->platform));
493 }
494
495 /**
496 * Creates a new database.
497 *
498 * @throws Exception
499 */
500 public function createDatabase(string $database): void
501 {
502 $this->connection->executeStatement(
503 $this->platform->getCreateDatabaseSQL($database),
504 );
505 }
506
507 /**
508 * Creates a new table.
509 *
510 * @throws Exception
511 */
512 public function createTable(Table $table): void
513 {
514 $this->executeStatements($this->platform->getCreateTableSQL($table));
515 }
516
517 /**
518 * Creates a new sequence.
519 *
520 * @throws Exception
521 */
522 public function createSequence(Sequence $sequence): void
523 {
524 $this->connection->executeStatement(
525 $this->platform->getCreateSequenceSQL($sequence),
526 );
527 }
528
529 /**
530 * Creates a new index on a table.
531 *
532 * @param string $table The name of the table on which the index is to be created.
533 *
534 * @throws Exception
535 */
536 public function createIndex(Index $index, string $table): void
537 {
538 $this->connection->executeStatement(
539 $this->platform->getCreateIndexSQL($index, $table),
540 );
541 }
542
543 /**
544 * Creates a new foreign key.
545 *
546 * @param ForeignKeyConstraint $foreignKey The ForeignKey instance.
547 * @param string $table The name of the table on which the foreign key is to be created.
548 *
549 * @throws Exception
550 */
551 public function createForeignKey(ForeignKeyConstraint $foreignKey, string $table): void
552 {
553 $this->connection->executeStatement(
554 $this->platform->getCreateForeignKeySQL($foreignKey, $table),
555 );
556 }
557
558 /**
559 * Creates a unique constraint on a table.
560 *
561 * @throws Exception
562 */
563 public function createUniqueConstraint(UniqueConstraint $uniqueConstraint, string $tableName): void
564 {
565 $this->connection->executeStatement(
566 $this->platform->getCreateUniqueConstraintSQL($uniqueConstraint, $tableName),
567 );
568 }
569
570 /**
571 * Creates a new view.
572 *
573 * @throws Exception
574 */
575 public function createView(View $view): void
576 {
577 $this->connection->executeStatement(
578 $this->platform->getCreateViewSQL(
579 $view->getQuotedName($this->platform),
580 $view->getSql(),
581 ),
582 );
583 }
584
585 /** @throws Exception */
586 public function dropSchemaObjects(Schema $schema): void
587 {
588 $this->executeStatements($schema->toDropSql($this->platform));
589 }
590
591 /**
592 * Alters an existing schema.
593 *
594 * @throws Exception
595 */
596 public function alterSchema(SchemaDiff $schemaDiff): void
597 {
598 $this->executeStatements($this->platform->getAlterSchemaSQL($schemaDiff));
599 }
600
601 /**
602 * Migrates an existing schema to a new schema.
603 *
604 * @throws Exception
605 */
606 public function migrateSchema(Schema $newSchema): void
607 {
608 $schemaDiff = $this->createComparator()
609 ->compareSchemas($this->introspectSchema(), $newSchema);
610
611 $this->alterSchema($schemaDiff);
612 }
613
614 /* alterTable() Methods */
615
616 /**
617 * Alters an existing tables schema.
618 *
619 * @throws Exception
620 */
621 public function alterTable(TableDiff $tableDiff): void
622 {
623 $this->executeStatements($this->platform->getAlterTableSQL($tableDiff));
624 }
625
626 /**
627 * Renames a given table to another name.
628 *
629 * @throws Exception
630 */
631 public function renameTable(string $name, string $newName): void
632 {
633 $this->connection->executeStatement(
634 $this->platform->getRenameTableSQL($name, $newName),
635 );
636 }
637
638 /**
639 * Methods for filtering return values of list*() methods to convert
640 * the native DBMS data definition to a portable Doctrine definition
641 */
642
643 /** @param array<string, string> $database */
644 protected function _getPortableDatabaseDefinition(array $database): string
645 {
646 throw NotSupported::new(__METHOD__);
647 }
648
649 /** @param array<string, mixed> $sequence */
650 protected function _getPortableSequenceDefinition(array $sequence): Sequence
651 {
652 throw NotSupported::new(__METHOD__);
653 }
654
655 /**
656 * Independent of the database the keys of the column list result are lowercased.
657 *
658 * The name of the created column instance however is kept in its case.
659 *
660 * @param array<int, array<string, mixed>> $tableColumns
661 *
662 * @return array<string, Column>
663 *
664 * @throws Exception
665 */
666 protected function _getPortableTableColumnList(string $table, string $database, array $tableColumns): array
667 {
668 $list = [];
669 foreach ($tableColumns as $tableColumn) {
670 $column = $this->_getPortableTableColumnDefinition($tableColumn);
671
672 $name = strtolower($column->getQuotedName($this->platform));
673 $list[$name] = $column;
674 }
675
676 return $list;
677 }
678
679 /**
680 * Gets Table Column Definition.
681 *
682 * @param array<string, mixed> $tableColumn
683 *
684 * @throws Exception
685 */
686 abstract protected function _getPortableTableColumnDefinition(array $tableColumn): Column;
687
688 /**
689 * Aggregates and groups the index results according to the required data result.
690 *
691 * @param array<int, array<string, mixed>> $tableIndexes
692 *
693 * @return array<string, Index>
694 *
695 * @throws Exception
696 */
697 protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
698 {
699 $result = [];
700 foreach ($tableIndexes as $tableIndex) {
701 $indexName = $keyName = $tableIndex['key_name'];
702 if ($tableIndex['primary']) {
703 $keyName = 'primary';
704 }
705
706 $keyName = strtolower($keyName);
707
708 if (! isset($result[$keyName])) {
709 $options = [
710 'lengths' => [],
711 ];
712
713 if (isset($tableIndex['where'])) {
714 $options['where'] = $tableIndex['where'];
715 }
716
717 $result[$keyName] = [
718 'name' => $indexName,
719 'columns' => [],
720 'unique' => ! $tableIndex['non_unique'],
721 'primary' => $tableIndex['primary'],
722 'flags' => $tableIndex['flags'] ?? [],
723 'options' => $options,
724 ];
725 }
726
727 $result[$keyName]['columns'][] = $tableIndex['column_name'];
728 $result[$keyName]['options']['lengths'][] = $tableIndex['length'] ?? null;
729 }
730
731 $indexes = [];
732 foreach ($result as $indexKey => $data) {
733 $indexes[$indexKey] = new Index(
734 $data['name'],
735 $data['columns'],
736 $data['unique'],
737 $data['primary'],
738 $data['flags'],
739 $data['options'],
740 );
741 }
742
743 return $indexes;
744 }
745
746 /** @param array<string, string> $table */
747 abstract protected function _getPortableTableDefinition(array $table): string;
748
749 /** @param array<string, mixed> $view */
750 abstract protected function _getPortableViewDefinition(array $view): View;
751
752 /**
753 * @param array<int|string, array<string, mixed>> $tableForeignKeys
754 *
755 * @return array<int, ForeignKeyConstraint>
756 */
757 protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array
758 {
759 $list = [];
760
761 foreach ($tableForeignKeys as $value) {
762 $list[] = $this->_getPortableTableForeignKeyDefinition($value);
763 }
764
765 return $list;
766 }
767
768 /** @param array<string, mixed> $tableForeignKey */
769 abstract protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint;
770
771 /**
772 * @param array<int, string> $sql
773 *
774 * @throws Exception
775 */
776 private function executeStatements(array $sql): void
777 {
778 foreach ($sql as $query) {
779 $this->connection->executeStatement($query);
780 }
781 }
782
783 /**
784 * Returns a {@see Schema} instance representing the current database schema.
785 *
786 * @throws Exception
787 */
788 public function introspectSchema(): Schema
789 {
790 $schemaNames = [];
791
792 if ($this->platform->supportsSchemas()) {
793 $schemaNames = $this->listSchemaNames();
794 }
795
796 $sequences = [];
797
798 if ($this->platform->supportsSequences()) {
799 $sequences = $this->listSequences();
800 }
801
802 $tables = $this->listTables();
803
804 return new Schema($tables, $sequences, $this->createSchemaConfig(), $schemaNames);
805 }
806
807 /**
808 * Creates the configuration for this schema.
809 *
810 * @throws Exception
811 */
812 public function createSchemaConfig(): SchemaConfig
813 {
814 $schemaConfig = new SchemaConfig();
815 $schemaConfig->setMaxIdentifierLength($this->platform->getMaxIdentifierLength());
816
817 $params = $this->connection->getParams();
818 if (! isset($params['defaultTableOptions'])) {
819 $params['defaultTableOptions'] = [];
820 }
821
822 if (! isset($params['defaultTableOptions']['charset']) && isset($params['charset'])) {
823 $params['defaultTableOptions']['charset'] = $params['charset'];
824 }
825
826 $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']);
827
828 return $schemaConfig;
829 }
830
831 /** @throws Exception */
832 private function getDatabase(string $methodName): string
833 {
834 $database = $this->connection->getDatabase();
835
836 if ($database === null) {
837 throw DatabaseRequired::new($methodName);
838 }
839
840 return $database;
841 }
842
843 public function createComparator(): Comparator
844 {
845 return new Comparator($this->platform);
846 }
847
848 /**
849 * @return array<string,list<array<string,mixed>>>
850 *
851 * @throws Exception
852 */
853 private function fetchAllAssociativeGrouped(Result $result): array
854 {
855 $data = [];
856
857 foreach ($result->fetchAllAssociative() as $row) {
858 $tableName = $this->_getPortableTableDefinition($row);
859 $data[$tableName][] = $row;
860 }
861
862 return $data;
863 }
864}