diff options
Diffstat (limited to 'vendor/doctrine/dbal/src/Schema')
39 files changed, 7569 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/Schema/AbstractAsset.php b/vendor/doctrine/dbal/src/Schema/AbstractAsset.php new file mode 100644 index 0000000..de5b56f --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/AbstractAsset.php | |||
@@ -0,0 +1,157 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
8 | |||
9 | use function array_map; | ||
10 | use function crc32; | ||
11 | use function dechex; | ||
12 | use function explode; | ||
13 | use function implode; | ||
14 | use function str_contains; | ||
15 | use function str_replace; | ||
16 | use function strtolower; | ||
17 | use function strtoupper; | ||
18 | use function substr; | ||
19 | |||
20 | /** | ||
21 | * The abstract asset allows to reset the name of all assets without publishing this to the public userland. | ||
22 | * | ||
23 | * This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables | ||
24 | * array($tableName => Table($tableName)); if you want to rename the table, you have to make sure this does not get | ||
25 | * recreated during schema migration. | ||
26 | */ | ||
27 | abstract class AbstractAsset | ||
28 | { | ||
29 | protected string $_name = ''; | ||
30 | |||
31 | /** | ||
32 | * Namespace of the asset. If none isset the default namespace is assumed. | ||
33 | */ | ||
34 | protected ?string $_namespace = null; | ||
35 | |||
36 | protected bool $_quoted = false; | ||
37 | |||
38 | /** | ||
39 | * Sets the name of this asset. | ||
40 | */ | ||
41 | protected function _setName(string $name): void | ||
42 | { | ||
43 | if ($this->isIdentifierQuoted($name)) { | ||
44 | $this->_quoted = true; | ||
45 | $name = $this->trimQuotes($name); | ||
46 | } | ||
47 | |||
48 | if (str_contains($name, '.')) { | ||
49 | $parts = explode('.', $name); | ||
50 | $this->_namespace = $parts[0]; | ||
51 | $name = $parts[1]; | ||
52 | } | ||
53 | |||
54 | $this->_name = $name; | ||
55 | } | ||
56 | |||
57 | /** | ||
58 | * Is this asset in the default namespace? | ||
59 | */ | ||
60 | public function isInDefaultNamespace(string $defaultNamespaceName): bool | ||
61 | { | ||
62 | return $this->_namespace === $defaultNamespaceName || $this->_namespace === null; | ||
63 | } | ||
64 | |||
65 | /** | ||
66 | * Gets the namespace name of this asset. | ||
67 | * | ||
68 | * If NULL is returned this means the default namespace is used. | ||
69 | */ | ||
70 | public function getNamespaceName(): ?string | ||
71 | { | ||
72 | return $this->_namespace; | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * The shortest name is stripped of the default namespace. All other | ||
77 | * namespaced elements are returned as full-qualified names. | ||
78 | */ | ||
79 | public function getShortestName(?string $defaultNamespaceName): string | ||
80 | { | ||
81 | $shortestName = $this->getName(); | ||
82 | if ($this->_namespace === $defaultNamespaceName) { | ||
83 | $shortestName = $this->_name; | ||
84 | } | ||
85 | |||
86 | return strtolower($shortestName); | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Checks if this asset's name is quoted. | ||
91 | */ | ||
92 | public function isQuoted(): bool | ||
93 | { | ||
94 | return $this->_quoted; | ||
95 | } | ||
96 | |||
97 | /** | ||
98 | * Checks if this identifier is quoted. | ||
99 | */ | ||
100 | protected function isIdentifierQuoted(string $identifier): bool | ||
101 | { | ||
102 | return isset($identifier[0]) && ($identifier[0] === '`' || $identifier[0] === '"' || $identifier[0] === '['); | ||
103 | } | ||
104 | |||
105 | /** | ||
106 | * Trim quotes from the identifier. | ||
107 | */ | ||
108 | protected function trimQuotes(string $identifier): string | ||
109 | { | ||
110 | return str_replace(['`', '"', '[', ']'], '', $identifier); | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Returns the name of this schema asset. | ||
115 | */ | ||
116 | public function getName(): string | ||
117 | { | ||
118 | if ($this->_namespace !== null) { | ||
119 | return $this->_namespace . '.' . $this->_name; | ||
120 | } | ||
121 | |||
122 | return $this->_name; | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * Gets the quoted representation of this asset but only if it was defined with one. Otherwise | ||
127 | * return the plain unquoted value as inserted. | ||
128 | */ | ||
129 | public function getQuotedName(AbstractPlatform $platform): string | ||
130 | { | ||
131 | $keywords = $platform->getReservedKeywordsList(); | ||
132 | $parts = explode('.', $this->getName()); | ||
133 | foreach ($parts as $k => $v) { | ||
134 | $parts[$k] = $this->_quoted || $keywords->isKeyword($v) ? $platform->quoteIdentifier($v) : $v; | ||
135 | } | ||
136 | |||
137 | return implode('.', $parts); | ||
138 | } | ||
139 | |||
140 | /** | ||
141 | * Generates an identifier from a list of column names obeying a certain string length. | ||
142 | * | ||
143 | * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars, | ||
144 | * however building idents automatically for foreign keys, composite keys or such can easily create | ||
145 | * very long names. | ||
146 | * | ||
147 | * @param array<int, string> $columnNames | ||
148 | */ | ||
149 | protected function _generateIdentifierName(array $columnNames, string $prefix = '', int $maxSize = 30): string | ||
150 | { | ||
151 | $hash = implode('', array_map(static function ($column): string { | ||
152 | return dechex(crc32($column)); | ||
153 | }, $columnNames)); | ||
154 | |||
155 | return strtoupper(substr($prefix . '_' . $hash, 0, $maxSize)); | ||
156 | } | ||
157 | } | ||
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 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Connection; | ||
8 | use Doctrine\DBAL\Exception; | ||
9 | use Doctrine\DBAL\Exception\DatabaseRequired; | ||
10 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
11 | use Doctrine\DBAL\Platforms\Exception\NotSupported; | ||
12 | use Doctrine\DBAL\Result; | ||
13 | use Doctrine\DBAL\Schema\Exception\TableDoesNotExist; | ||
14 | |||
15 | use function array_filter; | ||
16 | use function array_intersect; | ||
17 | use function array_map; | ||
18 | use function array_values; | ||
19 | use function count; | ||
20 | use 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 | */ | ||
28 | abstract 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 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Column.php b/vendor/doctrine/dbal/src/Schema/Column.php new file mode 100644 index 0000000..8963cd7 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Column.php | |||
@@ -0,0 +1,252 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\Exception\UnknownColumnOption; | ||
8 | use Doctrine\DBAL\Types\Type; | ||
9 | |||
10 | use function array_merge; | ||
11 | use function method_exists; | ||
12 | |||
13 | /** | ||
14 | * Object representation of a database column. | ||
15 | */ | ||
16 | class Column extends AbstractAsset | ||
17 | { | ||
18 | protected Type $_type; | ||
19 | |||
20 | protected ?int $_length = null; | ||
21 | |||
22 | protected ?int $_precision = null; | ||
23 | |||
24 | protected int $_scale = 0; | ||
25 | |||
26 | protected bool $_unsigned = false; | ||
27 | |||
28 | protected bool $_fixed = false; | ||
29 | |||
30 | protected bool $_notnull = true; | ||
31 | |||
32 | protected mixed $_default = null; | ||
33 | |||
34 | protected bool $_autoincrement = false; | ||
35 | |||
36 | /** @var array<string, mixed> */ | ||
37 | protected array $_platformOptions = []; | ||
38 | |||
39 | protected ?string $_columnDefinition = null; | ||
40 | |||
41 | protected string $_comment = ''; | ||
42 | |||
43 | /** | ||
44 | * Creates a new Column. | ||
45 | * | ||
46 | * @param array<string, mixed> $options | ||
47 | */ | ||
48 | public function __construct(string $name, Type $type, array $options = []) | ||
49 | { | ||
50 | $this->_setName($name); | ||
51 | $this->setType($type); | ||
52 | $this->setOptions($options); | ||
53 | } | ||
54 | |||
55 | /** @param array<string, mixed> $options */ | ||
56 | public function setOptions(array $options): self | ||
57 | { | ||
58 | foreach ($options as $name => $value) { | ||
59 | $method = 'set' . $name; | ||
60 | |||
61 | if (! method_exists($this, $method)) { | ||
62 | throw UnknownColumnOption::new($name); | ||
63 | } | ||
64 | |||
65 | $this->$method($value); | ||
66 | } | ||
67 | |||
68 | return $this; | ||
69 | } | ||
70 | |||
71 | public function setType(Type $type): self | ||
72 | { | ||
73 | $this->_type = $type; | ||
74 | |||
75 | return $this; | ||
76 | } | ||
77 | |||
78 | public function setLength(?int $length): self | ||
79 | { | ||
80 | $this->_length = $length; | ||
81 | |||
82 | return $this; | ||
83 | } | ||
84 | |||
85 | public function setPrecision(?int $precision): self | ||
86 | { | ||
87 | $this->_precision = $precision; | ||
88 | |||
89 | return $this; | ||
90 | } | ||
91 | |||
92 | public function setScale(int $scale): self | ||
93 | { | ||
94 | $this->_scale = $scale; | ||
95 | |||
96 | return $this; | ||
97 | } | ||
98 | |||
99 | public function setUnsigned(bool $unsigned): self | ||
100 | { | ||
101 | $this->_unsigned = $unsigned; | ||
102 | |||
103 | return $this; | ||
104 | } | ||
105 | |||
106 | public function setFixed(bool $fixed): self | ||
107 | { | ||
108 | $this->_fixed = $fixed; | ||
109 | |||
110 | return $this; | ||
111 | } | ||
112 | |||
113 | public function setNotnull(bool $notnull): self | ||
114 | { | ||
115 | $this->_notnull = $notnull; | ||
116 | |||
117 | return $this; | ||
118 | } | ||
119 | |||
120 | public function setDefault(mixed $default): self | ||
121 | { | ||
122 | $this->_default = $default; | ||
123 | |||
124 | return $this; | ||
125 | } | ||
126 | |||
127 | /** @param array<string, mixed> $platformOptions */ | ||
128 | public function setPlatformOptions(array $platformOptions): self | ||
129 | { | ||
130 | $this->_platformOptions = $platformOptions; | ||
131 | |||
132 | return $this; | ||
133 | } | ||
134 | |||
135 | public function setPlatformOption(string $name, mixed $value): self | ||
136 | { | ||
137 | $this->_platformOptions[$name] = $value; | ||
138 | |||
139 | return $this; | ||
140 | } | ||
141 | |||
142 | public function setColumnDefinition(?string $value): self | ||
143 | { | ||
144 | $this->_columnDefinition = $value; | ||
145 | |||
146 | return $this; | ||
147 | } | ||
148 | |||
149 | public function getType(): Type | ||
150 | { | ||
151 | return $this->_type; | ||
152 | } | ||
153 | |||
154 | public function getLength(): ?int | ||
155 | { | ||
156 | return $this->_length; | ||
157 | } | ||
158 | |||
159 | public function getPrecision(): ?int | ||
160 | { | ||
161 | return $this->_precision; | ||
162 | } | ||
163 | |||
164 | public function getScale(): int | ||
165 | { | ||
166 | return $this->_scale; | ||
167 | } | ||
168 | |||
169 | public function getUnsigned(): bool | ||
170 | { | ||
171 | return $this->_unsigned; | ||
172 | } | ||
173 | |||
174 | public function getFixed(): bool | ||
175 | { | ||
176 | return $this->_fixed; | ||
177 | } | ||
178 | |||
179 | public function getNotnull(): bool | ||
180 | { | ||
181 | return $this->_notnull; | ||
182 | } | ||
183 | |||
184 | public function getDefault(): mixed | ||
185 | { | ||
186 | return $this->_default; | ||
187 | } | ||
188 | |||
189 | /** @return array<string, mixed> */ | ||
190 | public function getPlatformOptions(): array | ||
191 | { | ||
192 | return $this->_platformOptions; | ||
193 | } | ||
194 | |||
195 | public function hasPlatformOption(string $name): bool | ||
196 | { | ||
197 | return isset($this->_platformOptions[$name]); | ||
198 | } | ||
199 | |||
200 | public function getPlatformOption(string $name): mixed | ||
201 | { | ||
202 | return $this->_platformOptions[$name]; | ||
203 | } | ||
204 | |||
205 | public function getColumnDefinition(): ?string | ||
206 | { | ||
207 | return $this->_columnDefinition; | ||
208 | } | ||
209 | |||
210 | public function getAutoincrement(): bool | ||
211 | { | ||
212 | return $this->_autoincrement; | ||
213 | } | ||
214 | |||
215 | public function setAutoincrement(bool $flag): self | ||
216 | { | ||
217 | $this->_autoincrement = $flag; | ||
218 | |||
219 | return $this; | ||
220 | } | ||
221 | |||
222 | public function setComment(string $comment): self | ||
223 | { | ||
224 | $this->_comment = $comment; | ||
225 | |||
226 | return $this; | ||
227 | } | ||
228 | |||
229 | public function getComment(): string | ||
230 | { | ||
231 | return $this->_comment; | ||
232 | } | ||
233 | |||
234 | /** @return array<string, mixed> */ | ||
235 | public function toArray(): array | ||
236 | { | ||
237 | return array_merge([ | ||
238 | 'name' => $this->_name, | ||
239 | 'type' => $this->_type, | ||
240 | 'default' => $this->_default, | ||
241 | 'notnull' => $this->_notnull, | ||
242 | 'length' => $this->_length, | ||
243 | 'precision' => $this->_precision, | ||
244 | 'scale' => $this->_scale, | ||
245 | 'fixed' => $this->_fixed, | ||
246 | 'unsigned' => $this->_unsigned, | ||
247 | 'autoincrement' => $this->_autoincrement, | ||
248 | 'columnDefinition' => $this->_columnDefinition, | ||
249 | 'comment' => $this->_comment, | ||
250 | ], $this->_platformOptions); | ||
251 | } | ||
252 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/ColumnDiff.php b/vendor/doctrine/dbal/src/Schema/ColumnDiff.php new file mode 100644 index 0000000..3e4950a --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/ColumnDiff.php | |||
@@ -0,0 +1,106 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | /** | ||
8 | * Represents the change of a column. | ||
9 | */ | ||
10 | class ColumnDiff | ||
11 | { | ||
12 | /** @internal The diff can be only instantiated by a {@see Comparator}. */ | ||
13 | public function __construct(private readonly Column $oldColumn, private readonly Column $newColumn) | ||
14 | { | ||
15 | } | ||
16 | |||
17 | public function getOldColumn(): Column | ||
18 | { | ||
19 | return $this->oldColumn; | ||
20 | } | ||
21 | |||
22 | public function getNewColumn(): Column | ||
23 | { | ||
24 | return $this->newColumn; | ||
25 | } | ||
26 | |||
27 | public function hasTypeChanged(): bool | ||
28 | { | ||
29 | return $this->newColumn->getType()::class !== $this->oldColumn->getType()::class; | ||
30 | } | ||
31 | |||
32 | public function hasLengthChanged(): bool | ||
33 | { | ||
34 | return $this->hasPropertyChanged(static function (Column $column): ?int { | ||
35 | return $column->getLength(); | ||
36 | }); | ||
37 | } | ||
38 | |||
39 | public function hasPrecisionChanged(): bool | ||
40 | { | ||
41 | return $this->hasPropertyChanged(static function (Column $column): ?int { | ||
42 | return $column->getPrecision(); | ||
43 | }); | ||
44 | } | ||
45 | |||
46 | public function hasScaleChanged(): bool | ||
47 | { | ||
48 | return $this->hasPropertyChanged(static function (Column $column): int { | ||
49 | return $column->getScale(); | ||
50 | }); | ||
51 | } | ||
52 | |||
53 | public function hasUnsignedChanged(): bool | ||
54 | { | ||
55 | return $this->hasPropertyChanged(static function (Column $column): bool { | ||
56 | return $column->getUnsigned(); | ||
57 | }); | ||
58 | } | ||
59 | |||
60 | public function hasFixedChanged(): bool | ||
61 | { | ||
62 | return $this->hasPropertyChanged(static function (Column $column): bool { | ||
63 | return $column->getFixed(); | ||
64 | }); | ||
65 | } | ||
66 | |||
67 | public function hasNotNullChanged(): bool | ||
68 | { | ||
69 | return $this->hasPropertyChanged(static function (Column $column): bool { | ||
70 | return $column->getNotnull(); | ||
71 | }); | ||
72 | } | ||
73 | |||
74 | public function hasDefaultChanged(): bool | ||
75 | { | ||
76 | $oldDefault = $this->oldColumn->getDefault(); | ||
77 | $newDefault = $this->newColumn->getDefault(); | ||
78 | |||
79 | // Null values need to be checked additionally as they tell whether to create or drop a default value. | ||
80 | // null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation. | ||
81 | if (($newDefault === null) xor ($oldDefault === null)) { | ||
82 | return true; | ||
83 | } | ||
84 | |||
85 | return $newDefault != $oldDefault; | ||
86 | } | ||
87 | |||
88 | public function hasAutoIncrementChanged(): bool | ||
89 | { | ||
90 | return $this->hasPropertyChanged(static function (Column $column): bool { | ||
91 | return $column->getAutoincrement(); | ||
92 | }); | ||
93 | } | ||
94 | |||
95 | public function hasCommentChanged(): bool | ||
96 | { | ||
97 | return $this->hasPropertyChanged(static function (Column $column): string { | ||
98 | return $column->getComment(); | ||
99 | }); | ||
100 | } | ||
101 | |||
102 | private function hasPropertyChanged(callable $property): bool | ||
103 | { | ||
104 | return $property($this->newColumn) !== $property($this->oldColumn); | ||
105 | } | ||
106 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Comparator.php b/vendor/doctrine/dbal/src/Schema/Comparator.php new file mode 100644 index 0000000..4c60c07 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Comparator.php | |||
@@ -0,0 +1,417 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
8 | |||
9 | use function array_map; | ||
10 | use function assert; | ||
11 | use function count; | ||
12 | use function strtolower; | ||
13 | |||
14 | /** | ||
15 | * Compares two Schemas and return an instance of SchemaDiff. | ||
16 | */ | ||
17 | class Comparator | ||
18 | { | ||
19 | /** @internal The comparator can be only instantiated by a schema manager. */ | ||
20 | public function __construct(private readonly AbstractPlatform $platform) | ||
21 | { | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * Returns the differences between the schemas. | ||
26 | */ | ||
27 | public function compareSchemas(Schema $oldSchema, Schema $newSchema): SchemaDiff | ||
28 | { | ||
29 | $createdSchemas = []; | ||
30 | $droppedSchemas = []; | ||
31 | $createdTables = []; | ||
32 | $alteredTables = []; | ||
33 | $droppedTables = []; | ||
34 | $createdSequences = []; | ||
35 | $alteredSequences = []; | ||
36 | $droppedSequences = []; | ||
37 | |||
38 | foreach ($newSchema->getNamespaces() as $newNamespace) { | ||
39 | if ($oldSchema->hasNamespace($newNamespace)) { | ||
40 | continue; | ||
41 | } | ||
42 | |||
43 | $createdSchemas[] = $newNamespace; | ||
44 | } | ||
45 | |||
46 | foreach ($oldSchema->getNamespaces() as $oldNamespace) { | ||
47 | if ($newSchema->hasNamespace($oldNamespace)) { | ||
48 | continue; | ||
49 | } | ||
50 | |||
51 | $droppedSchemas[] = $oldNamespace; | ||
52 | } | ||
53 | |||
54 | foreach ($newSchema->getTables() as $newTable) { | ||
55 | $newTableName = $newTable->getShortestName($newSchema->getName()); | ||
56 | if (! $oldSchema->hasTable($newTableName)) { | ||
57 | $createdTables[] = $newSchema->getTable($newTableName); | ||
58 | } else { | ||
59 | $tableDiff = $this->compareTables( | ||
60 | $oldSchema->getTable($newTableName), | ||
61 | $newSchema->getTable($newTableName), | ||
62 | ); | ||
63 | |||
64 | if (! $tableDiff->isEmpty()) { | ||
65 | $alteredTables[] = $tableDiff; | ||
66 | } | ||
67 | } | ||
68 | } | ||
69 | |||
70 | // Check if there are tables removed | ||
71 | foreach ($oldSchema->getTables() as $oldTable) { | ||
72 | $oldTableName = $oldTable->getShortestName($oldSchema->getName()); | ||
73 | |||
74 | $oldTable = $oldSchema->getTable($oldTableName); | ||
75 | if ($newSchema->hasTable($oldTableName)) { | ||
76 | continue; | ||
77 | } | ||
78 | |||
79 | $droppedTables[] = $oldTable; | ||
80 | } | ||
81 | |||
82 | foreach ($newSchema->getSequences() as $newSequence) { | ||
83 | $newSequenceName = $newSequence->getShortestName($newSchema->getName()); | ||
84 | if (! $oldSchema->hasSequence($newSequenceName)) { | ||
85 | if (! $this->isAutoIncrementSequenceInSchema($oldSchema, $newSequence)) { | ||
86 | $createdSequences[] = $newSequence; | ||
87 | } | ||
88 | } else { | ||
89 | if ($this->diffSequence($newSequence, $oldSchema->getSequence($newSequenceName))) { | ||
90 | $alteredSequences[] = $newSchema->getSequence($newSequenceName); | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | |||
95 | foreach ($oldSchema->getSequences() as $oldSequence) { | ||
96 | if ($this->isAutoIncrementSequenceInSchema($newSchema, $oldSequence)) { | ||
97 | continue; | ||
98 | } | ||
99 | |||
100 | $oldSequenceName = $oldSequence->getShortestName($oldSchema->getName()); | ||
101 | |||
102 | if ($newSchema->hasSequence($oldSequenceName)) { | ||
103 | continue; | ||
104 | } | ||
105 | |||
106 | $droppedSequences[] = $oldSequence; | ||
107 | } | ||
108 | |||
109 | return new SchemaDiff( | ||
110 | $createdSchemas, | ||
111 | $droppedSchemas, | ||
112 | $createdTables, | ||
113 | $alteredTables, | ||
114 | $droppedTables, | ||
115 | $createdSequences, | ||
116 | $alteredSequences, | ||
117 | $droppedSequences, | ||
118 | ); | ||
119 | } | ||
120 | |||
121 | private function isAutoIncrementSequenceInSchema(Schema $schema, Sequence $sequence): bool | ||
122 | { | ||
123 | foreach ($schema->getTables() as $table) { | ||
124 | if ($sequence->isAutoIncrementsFor($table)) { | ||
125 | return true; | ||
126 | } | ||
127 | } | ||
128 | |||
129 | return false; | ||
130 | } | ||
131 | |||
132 | public function diffSequence(Sequence $sequence1, Sequence $sequence2): bool | ||
133 | { | ||
134 | if ($sequence1->getAllocationSize() !== $sequence2->getAllocationSize()) { | ||
135 | return true; | ||
136 | } | ||
137 | |||
138 | return $sequence1->getInitialValue() !== $sequence2->getInitialValue(); | ||
139 | } | ||
140 | |||
141 | /** | ||
142 | * Compares the tables and returns the difference between them. | ||
143 | */ | ||
144 | public function compareTables(Table $oldTable, Table $newTable): TableDiff | ||
145 | { | ||
146 | $addedColumns = []; | ||
147 | $modifiedColumns = []; | ||
148 | $droppedColumns = []; | ||
149 | $addedIndexes = []; | ||
150 | $modifiedIndexes = []; | ||
151 | $droppedIndexes = []; | ||
152 | $addedForeignKeys = []; | ||
153 | $modifiedForeignKeys = []; | ||
154 | $droppedForeignKeys = []; | ||
155 | |||
156 | $oldColumns = $oldTable->getColumns(); | ||
157 | $newColumns = $newTable->getColumns(); | ||
158 | |||
159 | // See if all the columns in the old table exist in the new table | ||
160 | foreach ($newColumns as $newColumn) { | ||
161 | $newColumnName = strtolower($newColumn->getName()); | ||
162 | |||
163 | if ($oldTable->hasColumn($newColumnName)) { | ||
164 | continue; | ||
165 | } | ||
166 | |||
167 | $addedColumns[$newColumnName] = $newColumn; | ||
168 | } | ||
169 | |||
170 | // See if there are any removed columns in the new table | ||
171 | foreach ($oldColumns as $oldColumn) { | ||
172 | $oldColumnName = strtolower($oldColumn->getName()); | ||
173 | |||
174 | // See if column is removed in the new table. | ||
175 | if (! $newTable->hasColumn($oldColumnName)) { | ||
176 | $droppedColumns[$oldColumnName] = $oldColumn; | ||
177 | |||
178 | continue; | ||
179 | } | ||
180 | |||
181 | $newColumn = $newTable->getColumn($oldColumnName); | ||
182 | |||
183 | if ($this->columnsEqual($oldColumn, $newColumn)) { | ||
184 | continue; | ||
185 | } | ||
186 | |||
187 | $modifiedColumns[] = new ColumnDiff($oldColumn, $newColumn); | ||
188 | } | ||
189 | |||
190 | $renamedColumns = $this->detectRenamedColumns($addedColumns, $droppedColumns); | ||
191 | |||
192 | $oldIndexes = $oldTable->getIndexes(); | ||
193 | $newIndexes = $newTable->getIndexes(); | ||
194 | |||
195 | // See if all the indexes from the old table exist in the new one | ||
196 | foreach ($newIndexes as $newIndexName => $newIndex) { | ||
197 | if (($newIndex->isPrimary() && $oldTable->getPrimaryKey() !== null) || $oldTable->hasIndex($newIndexName)) { | ||
198 | continue; | ||
199 | } | ||
200 | |||
201 | $addedIndexes[$newIndexName] = $newIndex; | ||
202 | } | ||
203 | |||
204 | // See if there are any removed indexes in the new table | ||
205 | foreach ($oldIndexes as $oldIndexName => $oldIndex) { | ||
206 | // See if the index is removed in the new table. | ||
207 | if ( | ||
208 | ($oldIndex->isPrimary() && $newTable->getPrimaryKey() === null) || | ||
209 | ! $oldIndex->isPrimary() && ! $newTable->hasIndex($oldIndexName) | ||
210 | ) { | ||
211 | $droppedIndexes[$oldIndexName] = $oldIndex; | ||
212 | |||
213 | continue; | ||
214 | } | ||
215 | |||
216 | // See if index has changed in the new table. | ||
217 | $newIndex = $oldIndex->isPrimary() ? $newTable->getPrimaryKey() : $newTable->getIndex($oldIndexName); | ||
218 | assert($newIndex instanceof Index); | ||
219 | |||
220 | if (! $this->diffIndex($oldIndex, $newIndex)) { | ||
221 | continue; | ||
222 | } | ||
223 | |||
224 | $modifiedIndexes[] = $newIndex; | ||
225 | } | ||
226 | |||
227 | $renamedIndexes = $this->detectRenamedIndexes($addedIndexes, $droppedIndexes); | ||
228 | |||
229 | $oldForeignKeys = $oldTable->getForeignKeys(); | ||
230 | $newForeignKeys = $newTable->getForeignKeys(); | ||
231 | |||
232 | foreach ($oldForeignKeys as $oldKey => $oldForeignKey) { | ||
233 | foreach ($newForeignKeys as $newKey => $newForeignKey) { | ||
234 | if ($this->diffForeignKey($oldForeignKey, $newForeignKey) === false) { | ||
235 | unset($oldForeignKeys[$oldKey], $newForeignKeys[$newKey]); | ||
236 | } else { | ||
237 | if (strtolower($oldForeignKey->getName()) === strtolower($newForeignKey->getName())) { | ||
238 | $modifiedForeignKeys[] = $newForeignKey; | ||
239 | |||
240 | unset($oldForeignKeys[$oldKey], $newForeignKeys[$newKey]); | ||
241 | } | ||
242 | } | ||
243 | } | ||
244 | } | ||
245 | |||
246 | foreach ($oldForeignKeys as $oldForeignKey) { | ||
247 | $droppedForeignKeys[] = $oldForeignKey; | ||
248 | } | ||
249 | |||
250 | foreach ($newForeignKeys as $newForeignKey) { | ||
251 | $addedForeignKeys[] = $newForeignKey; | ||
252 | } | ||
253 | |||
254 | return new TableDiff( | ||
255 | $oldTable, | ||
256 | $addedColumns, | ||
257 | $modifiedColumns, | ||
258 | $droppedColumns, | ||
259 | $renamedColumns, | ||
260 | $addedIndexes, | ||
261 | $modifiedIndexes, | ||
262 | $droppedIndexes, | ||
263 | $renamedIndexes, | ||
264 | $addedForeignKeys, | ||
265 | $modifiedForeignKeys, | ||
266 | $droppedForeignKeys, | ||
267 | ); | ||
268 | } | ||
269 | |||
270 | /** | ||
271 | * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop | ||
272 | * however ambiguities between different possibilities should not lead to renaming at all. | ||
273 | * | ||
274 | * @param array<string,Column> $addedColumns | ||
275 | * @param array<string,Column> $removedColumns | ||
276 | * | ||
277 | * @return array<string,Column> | ||
278 | */ | ||
279 | private function detectRenamedColumns(array &$addedColumns, array &$removedColumns): array | ||
280 | { | ||
281 | $candidatesByName = []; | ||
282 | |||
283 | foreach ($addedColumns as $addedColumnName => $addedColumn) { | ||
284 | foreach ($removedColumns as $removedColumn) { | ||
285 | if (! $this->columnsEqual($addedColumn, $removedColumn)) { | ||
286 | continue; | ||
287 | } | ||
288 | |||
289 | $candidatesByName[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName]; | ||
290 | } | ||
291 | } | ||
292 | |||
293 | $renamedColumns = []; | ||
294 | |||
295 | foreach ($candidatesByName as $candidates) { | ||
296 | if (count($candidates) !== 1) { | ||
297 | continue; | ||
298 | } | ||
299 | |||
300 | [$removedColumn, $addedColumn] = $candidates[0]; | ||
301 | $removedColumnName = $removedColumn->getName(); | ||
302 | $addedColumnName = strtolower($addedColumn->getName()); | ||
303 | |||
304 | if (isset($renamedColumns[$removedColumnName])) { | ||
305 | continue; | ||
306 | } | ||
307 | |||
308 | $renamedColumns[$removedColumnName] = $addedColumn; | ||
309 | unset( | ||
310 | $addedColumns[$addedColumnName], | ||
311 | $removedColumns[strtolower($removedColumnName)], | ||
312 | ); | ||
313 | } | ||
314 | |||
315 | return $renamedColumns; | ||
316 | } | ||
317 | |||
318 | /** | ||
319 | * Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop | ||
320 | * however ambiguities between different possibilities should not lead to renaming at all. | ||
321 | * | ||
322 | * @param array<string,Index> $addedIndexes | ||
323 | * @param array<string,Index> $removedIndexes | ||
324 | * | ||
325 | * @return array<string,Index> | ||
326 | */ | ||
327 | private function detectRenamedIndexes(array &$addedIndexes, array &$removedIndexes): array | ||
328 | { | ||
329 | $candidatesByName = []; | ||
330 | |||
331 | // Gather possible rename candidates by comparing each added and removed index based on semantics. | ||
332 | foreach ($addedIndexes as $addedIndexName => $addedIndex) { | ||
333 | foreach ($removedIndexes as $removedIndex) { | ||
334 | if ($this->diffIndex($addedIndex, $removedIndex)) { | ||
335 | continue; | ||
336 | } | ||
337 | |||
338 | $candidatesByName[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName]; | ||
339 | } | ||
340 | } | ||
341 | |||
342 | $renamedIndexes = []; | ||
343 | |||
344 | foreach ($candidatesByName as $candidates) { | ||
345 | // If the current rename candidate contains exactly one semantically equal index, | ||
346 | // we can safely rename it. | ||
347 | // Otherwise, it is unclear if a rename action is really intended, | ||
348 | // therefore we let those ambiguous indexes be added/dropped. | ||
349 | if (count($candidates) !== 1) { | ||
350 | continue; | ||
351 | } | ||
352 | |||
353 | [$removedIndex, $addedIndex] = $candidates[0]; | ||
354 | |||
355 | $removedIndexName = strtolower($removedIndex->getName()); | ||
356 | $addedIndexName = strtolower($addedIndex->getName()); | ||
357 | |||
358 | if (isset($renamedIndexes[$removedIndexName])) { | ||
359 | continue; | ||
360 | } | ||
361 | |||
362 | $renamedIndexes[$removedIndexName] = $addedIndex; | ||
363 | unset( | ||
364 | $addedIndexes[$addedIndexName], | ||
365 | $removedIndexes[$removedIndexName], | ||
366 | ); | ||
367 | } | ||
368 | |||
369 | return $renamedIndexes; | ||
370 | } | ||
371 | |||
372 | protected function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2): bool | ||
373 | { | ||
374 | if ( | ||
375 | array_map('strtolower', $key1->getUnquotedLocalColumns()) | ||
376 | !== array_map('strtolower', $key2->getUnquotedLocalColumns()) | ||
377 | ) { | ||
378 | return true; | ||
379 | } | ||
380 | |||
381 | if ( | ||
382 | array_map('strtolower', $key1->getUnquotedForeignColumns()) | ||
383 | !== array_map('strtolower', $key2->getUnquotedForeignColumns()) | ||
384 | ) { | ||
385 | return true; | ||
386 | } | ||
387 | |||
388 | if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) { | ||
389 | return true; | ||
390 | } | ||
391 | |||
392 | if ($key1->onUpdate() !== $key2->onUpdate()) { | ||
393 | return true; | ||
394 | } | ||
395 | |||
396 | return $key1->onDelete() !== $key2->onDelete(); | ||
397 | } | ||
398 | |||
399 | /** | ||
400 | * Compares the definitions of the given columns | ||
401 | */ | ||
402 | protected function columnsEqual(Column $column1, Column $column2): bool | ||
403 | { | ||
404 | return $this->platform->columnsEqual($column1, $column2); | ||
405 | } | ||
406 | |||
407 | /** | ||
408 | * Finds the difference between the indexes $index1 and $index2. | ||
409 | * | ||
410 | * Compares $index1 with $index2 and returns true if there are any | ||
411 | * differences or false in case there are no differences. | ||
412 | */ | ||
413 | protected function diffIndex(Index $index1, Index $index2): bool | ||
414 | { | ||
415 | return ! ($index1->isFulfilledBy($index2) && $index2->isFulfilledBy($index1)); | ||
416 | } | ||
417 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/DB2SchemaManager.php b/vendor/doctrine/dbal/src/Schema/DB2SchemaManager.php new file mode 100644 index 0000000..f2ed089 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/DB2SchemaManager.php | |||
@@ -0,0 +1,371 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Exception; | ||
8 | use Doctrine\DBAL\Platforms\DB2Platform; | ||
9 | use Doctrine\DBAL\Result; | ||
10 | use Doctrine\DBAL\Types\Type; | ||
11 | use Doctrine\DBAL\Types\Types; | ||
12 | |||
13 | use function array_change_key_case; | ||
14 | use function implode; | ||
15 | use function preg_match; | ||
16 | use function str_replace; | ||
17 | use function strpos; | ||
18 | use function strtolower; | ||
19 | use function strtoupper; | ||
20 | use function substr; | ||
21 | |||
22 | use const CASE_LOWER; | ||
23 | |||
24 | /** | ||
25 | * IBM Db2 Schema Manager. | ||
26 | * | ||
27 | * @extends AbstractSchemaManager<DB2Platform> | ||
28 | */ | ||
29 | class DB2SchemaManager extends AbstractSchemaManager | ||
30 | { | ||
31 | /** | ||
32 | * {@inheritDoc} | ||
33 | * | ||
34 | * @throws Exception | ||
35 | */ | ||
36 | protected function _getPortableTableColumnDefinition(array $tableColumn): Column | ||
37 | { | ||
38 | $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); | ||
39 | |||
40 | $length = $precision = $default = null; | ||
41 | $scale = 0; | ||
42 | $fixed = false; | ||
43 | |||
44 | if ($tableColumn['default'] !== null && $tableColumn['default'] !== 'NULL') { | ||
45 | $default = $tableColumn['default']; | ||
46 | |||
47 | if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) { | ||
48 | $default = str_replace("''", "'", $matches[1]); | ||
49 | } | ||
50 | } | ||
51 | |||
52 | $type = $this->platform->getDoctrineTypeMapping($tableColumn['typename']); | ||
53 | |||
54 | switch (strtolower($tableColumn['typename'])) { | ||
55 | case 'varchar': | ||
56 | if ($tableColumn['codepage'] === 0) { | ||
57 | $type = Types::BINARY; | ||
58 | } | ||
59 | |||
60 | $length = $tableColumn['length']; | ||
61 | break; | ||
62 | |||
63 | case 'character': | ||
64 | if ($tableColumn['codepage'] === 0) { | ||
65 | $type = Types::BINARY; | ||
66 | } | ||
67 | |||
68 | $length = $tableColumn['length']; | ||
69 | $fixed = true; | ||
70 | break; | ||
71 | |||
72 | case 'clob': | ||
73 | $length = $tableColumn['length']; | ||
74 | break; | ||
75 | |||
76 | case 'decimal': | ||
77 | case 'double': | ||
78 | case 'real': | ||
79 | $scale = $tableColumn['scale']; | ||
80 | $precision = $tableColumn['length']; | ||
81 | break; | ||
82 | } | ||
83 | |||
84 | $options = [ | ||
85 | 'length' => $length, | ||
86 | 'unsigned' => false, | ||
87 | 'fixed' => $fixed, | ||
88 | 'default' => $default, | ||
89 | 'autoincrement' => (bool) $tableColumn['autoincrement'], | ||
90 | 'notnull' => $tableColumn['nulls'] === 'N', | ||
91 | 'platformOptions' => [], | ||
92 | ]; | ||
93 | |||
94 | if (isset($tableColumn['comment'])) { | ||
95 | $options['comment'] = $tableColumn['comment']; | ||
96 | } | ||
97 | |||
98 | if ($scale !== null && $precision !== null) { | ||
99 | $options['scale'] = $scale; | ||
100 | $options['precision'] = $precision; | ||
101 | } | ||
102 | |||
103 | return new Column($tableColumn['colname'], Type::getType($type), $options); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * {@inheritDoc} | ||
108 | */ | ||
109 | protected function _getPortableTableDefinition(array $table): string | ||
110 | { | ||
111 | $table = array_change_key_case($table, CASE_LOWER); | ||
112 | |||
113 | return $table['name']; | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * {@inheritDoc} | ||
118 | */ | ||
119 | protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array | ||
120 | { | ||
121 | foreach ($tableIndexes as &$tableIndexRow) { | ||
122 | $tableIndexRow = array_change_key_case($tableIndexRow, CASE_LOWER); | ||
123 | $tableIndexRow['primary'] = (bool) $tableIndexRow['primary']; | ||
124 | } | ||
125 | |||
126 | return parent::_getPortableTableIndexesList($tableIndexes, $tableName); | ||
127 | } | ||
128 | |||
129 | /** | ||
130 | * {@inheritDoc} | ||
131 | */ | ||
132 | protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint | ||
133 | { | ||
134 | return new ForeignKeyConstraint( | ||
135 | $tableForeignKey['local_columns'], | ||
136 | $tableForeignKey['foreign_table'], | ||
137 | $tableForeignKey['foreign_columns'], | ||
138 | $tableForeignKey['name'], | ||
139 | $tableForeignKey['options'], | ||
140 | ); | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * {@inheritDoc} | ||
145 | */ | ||
146 | protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array | ||
147 | { | ||
148 | $foreignKeys = []; | ||
149 | |||
150 | foreach ($tableForeignKeys as $tableForeignKey) { | ||
151 | $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); | ||
152 | |||
153 | if (! isset($foreignKeys[$tableForeignKey['index_name']])) { | ||
154 | $foreignKeys[$tableForeignKey['index_name']] = [ | ||
155 | 'local_columns' => [$tableForeignKey['local_column']], | ||
156 | 'foreign_table' => $tableForeignKey['foreign_table'], | ||
157 | 'foreign_columns' => [$tableForeignKey['foreign_column']], | ||
158 | 'name' => $tableForeignKey['index_name'], | ||
159 | 'options' => [ | ||
160 | 'onUpdate' => $tableForeignKey['on_update'], | ||
161 | 'onDelete' => $tableForeignKey['on_delete'], | ||
162 | ], | ||
163 | ]; | ||
164 | } else { | ||
165 | $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column']; | ||
166 | $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column']; | ||
167 | } | ||
168 | } | ||
169 | |||
170 | return parent::_getPortableTableForeignKeysList($foreignKeys); | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * {@inheritDoc} | ||
175 | */ | ||
176 | protected function _getPortableViewDefinition(array $view): View | ||
177 | { | ||
178 | $view = array_change_key_case($view, CASE_LOWER); | ||
179 | |||
180 | $sql = ''; | ||
181 | $pos = strpos($view['text'], ' AS '); | ||
182 | |||
183 | if ($pos !== false) { | ||
184 | $sql = substr($view['text'], $pos + 4); | ||
185 | } | ||
186 | |||
187 | return new View($view['name'], $sql); | ||
188 | } | ||
189 | |||
190 | protected function normalizeName(string $name): string | ||
191 | { | ||
192 | $identifier = new Identifier($name); | ||
193 | |||
194 | return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name); | ||
195 | } | ||
196 | |||
197 | protected function selectTableNames(string $databaseName): Result | ||
198 | { | ||
199 | $sql = <<<'SQL' | ||
200 | SELECT NAME | ||
201 | FROM SYSIBM.SYSTABLES | ||
202 | WHERE TYPE = 'T' | ||
203 | AND CREATOR = ? | ||
204 | SQL; | ||
205 | |||
206 | return $this->connection->executeQuery($sql, [$databaseName]); | ||
207 | } | ||
208 | |||
209 | protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result | ||
210 | { | ||
211 | $sql = 'SELECT'; | ||
212 | |||
213 | if ($tableName === null) { | ||
214 | $sql .= ' C.TABNAME AS NAME,'; | ||
215 | } | ||
216 | |||
217 | $sql .= <<<'SQL' | ||
218 | C.COLNAME, | ||
219 | C.TYPENAME, | ||
220 | C.CODEPAGE, | ||
221 | C.NULLS, | ||
222 | C.LENGTH, | ||
223 | C.SCALE, | ||
224 | C.REMARKS AS COMMENT, | ||
225 | CASE | ||
226 | WHEN C.GENERATED = 'D' THEN 1 | ||
227 | ELSE 0 | ||
228 | END AS AUTOINCREMENT, | ||
229 | C.DEFAULT | ||
230 | FROM SYSCAT.COLUMNS C | ||
231 | JOIN SYSCAT.TABLES AS T | ||
232 | ON T.TABSCHEMA = C.TABSCHEMA | ||
233 | AND T.TABNAME = C.TABNAME | ||
234 | SQL; | ||
235 | |||
236 | $conditions = ['C.TABSCHEMA = ?', "T.TYPE = 'T'"]; | ||
237 | $params = [$databaseName]; | ||
238 | |||
239 | if ($tableName !== null) { | ||
240 | $conditions[] = 'C.TABNAME = ?'; | ||
241 | $params[] = $tableName; | ||
242 | } | ||
243 | |||
244 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.TABNAME, C.COLNO'; | ||
245 | |||
246 | return $this->connection->executeQuery($sql, $params); | ||
247 | } | ||
248 | |||
249 | protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result | ||
250 | { | ||
251 | $sql = 'SELECT'; | ||
252 | |||
253 | if ($tableName === null) { | ||
254 | $sql .= ' IDX.TABNAME AS NAME,'; | ||
255 | } | ||
256 | |||
257 | $sql .= <<<'SQL' | ||
258 | IDX.INDNAME AS KEY_NAME, | ||
259 | IDXCOL.COLNAME AS COLUMN_NAME, | ||
260 | CASE | ||
261 | WHEN IDX.UNIQUERULE = 'P' THEN 1 | ||
262 | ELSE 0 | ||
263 | END AS PRIMARY, | ||
264 | CASE | ||
265 | WHEN IDX.UNIQUERULE = 'D' THEN 1 | ||
266 | ELSE 0 | ||
267 | END AS NON_UNIQUE | ||
268 | FROM SYSCAT.INDEXES AS IDX | ||
269 | JOIN SYSCAT.TABLES AS T | ||
270 | ON IDX.TABSCHEMA = T.TABSCHEMA AND IDX.TABNAME = T.TABNAME | ||
271 | JOIN SYSCAT.INDEXCOLUSE AS IDXCOL | ||
272 | ON IDX.INDSCHEMA = IDXCOL.INDSCHEMA AND IDX.INDNAME = IDXCOL.INDNAME | ||
273 | SQL; | ||
274 | |||
275 | $conditions = ['IDX.TABSCHEMA = ?', "T.TYPE = 'T'"]; | ||
276 | $params = [$databaseName]; | ||
277 | |||
278 | if ($tableName !== null) { | ||
279 | $conditions[] = 'IDX.TABNAME = ?'; | ||
280 | $params[] = $tableName; | ||
281 | } | ||
282 | |||
283 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ'; | ||
284 | |||
285 | return $this->connection->executeQuery($sql, $params); | ||
286 | } | ||
287 | |||
288 | protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result | ||
289 | { | ||
290 | $sql = 'SELECT'; | ||
291 | |||
292 | if ($tableName === null) { | ||
293 | $sql .= ' R.TABNAME AS NAME,'; | ||
294 | } | ||
295 | |||
296 | $sql .= <<<'SQL' | ||
297 | FKCOL.COLNAME AS LOCAL_COLUMN, | ||
298 | R.REFTABNAME AS FOREIGN_TABLE, | ||
299 | PKCOL.COLNAME AS FOREIGN_COLUMN, | ||
300 | R.CONSTNAME AS INDEX_NAME, | ||
301 | CASE | ||
302 | WHEN R.UPDATERULE = 'R' THEN 'RESTRICT' | ||
303 | END AS ON_UPDATE, | ||
304 | CASE | ||
305 | WHEN R.DELETERULE = 'C' THEN 'CASCADE' | ||
306 | WHEN R.DELETERULE = 'N' THEN 'SET NULL' | ||
307 | WHEN R.DELETERULE = 'R' THEN 'RESTRICT' | ||
308 | END AS ON_DELETE | ||
309 | FROM SYSCAT.REFERENCES AS R | ||
310 | JOIN SYSCAT.TABLES AS T | ||
311 | ON T.TABSCHEMA = R.TABSCHEMA | ||
312 | AND T.TABNAME = R.TABNAME | ||
313 | JOIN SYSCAT.KEYCOLUSE AS FKCOL | ||
314 | ON FKCOL.CONSTNAME = R.CONSTNAME | ||
315 | AND FKCOL.TABSCHEMA = R.TABSCHEMA | ||
316 | AND FKCOL.TABNAME = R.TABNAME | ||
317 | JOIN SYSCAT.KEYCOLUSE AS PKCOL | ||
318 | ON PKCOL.CONSTNAME = R.REFKEYNAME | ||
319 | AND PKCOL.TABSCHEMA = R.REFTABSCHEMA | ||
320 | AND PKCOL.TABNAME = R.REFTABNAME | ||
321 | AND PKCOL.COLSEQ = FKCOL.COLSEQ | ||
322 | SQL; | ||
323 | |||
324 | $conditions = ['R.TABSCHEMA = ?', "T.TYPE = 'T'"]; | ||
325 | $params = [$databaseName]; | ||
326 | |||
327 | if ($tableName !== null) { | ||
328 | $conditions[] = 'R.TABNAME = ?'; | ||
329 | $params[] = $tableName; | ||
330 | } | ||
331 | |||
332 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY R.CONSTNAME, FKCOL.COLSEQ'; | ||
333 | |||
334 | return $this->connection->executeQuery($sql, $params); | ||
335 | } | ||
336 | |||
337 | /** | ||
338 | * {@inheritDoc} | ||
339 | */ | ||
340 | protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array | ||
341 | { | ||
342 | $sql = 'SELECT NAME, REMARKS'; | ||
343 | |||
344 | $conditions = []; | ||
345 | $params = []; | ||
346 | |||
347 | if ($tableName !== null) { | ||
348 | $conditions[] = 'NAME = ?'; | ||
349 | $params[] = $tableName; | ||
350 | } | ||
351 | |||
352 | $sql .= ' FROM SYSIBM.SYSTABLES'; | ||
353 | |||
354 | if ($conditions !== []) { | ||
355 | $sql .= ' WHERE ' . implode(' AND ', $conditions); | ||
356 | } | ||
357 | |||
358 | /** @var array<string,array<string,mixed>> $metadata */ | ||
359 | $metadata = $this->connection->executeQuery($sql, $params) | ||
360 | ->fetchAllAssociativeIndexed(); | ||
361 | |||
362 | $tableOptions = []; | ||
363 | foreach ($metadata as $table => $data) { | ||
364 | $data = array_change_key_case($data, CASE_LOWER); | ||
365 | |||
366 | $tableOptions[$table] = ['comment' => $data['remarks']]; | ||
367 | } | ||
368 | |||
369 | return $tableOptions; | ||
370 | } | ||
371 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php b/vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php new file mode 100644 index 0000000..efba87f --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/DefaultSchemaManagerFactory.php | |||
@@ -0,0 +1,20 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Connection; | ||
8 | use Doctrine\DBAL\Exception; | ||
9 | |||
10 | /** | ||
11 | * A schema manager factory that returns the default schema manager for the given platform. | ||
12 | */ | ||
13 | final class DefaultSchemaManagerFactory implements SchemaManagerFactory | ||
14 | { | ||
15 | /** @throws Exception If the platform does not support creating schema managers yet. */ | ||
16 | public function createSchemaManager(Connection $connection): AbstractSchemaManager | ||
17 | { | ||
18 | return $connection->getDatabasePlatform()->createSchemaManager($connection); | ||
19 | } | ||
20 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php new file mode 100644 index 0000000..53daac9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/ColumnAlreadyExists.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class ColumnAlreadyExists extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $tableName, string $columnName): self | ||
16 | { | ||
17 | return new self(sprintf('The column "%s" on table "%s" already exists.', $columnName, $tableName)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/ColumnDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/ColumnDoesNotExist.php new file mode 100644 index 0000000..cb1cedf --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/ColumnDoesNotExist.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class ColumnDoesNotExist extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $columnName, string $table): self | ||
16 | { | ||
17 | return new self(sprintf('There is no column with name "%s" on table "%s".', $columnName, $table)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/ForeignKeyDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/ForeignKeyDoesNotExist.php new file mode 100644 index 0000000..3e4c6c6 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/ForeignKeyDoesNotExist.php | |||
@@ -0,0 +1,21 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class ForeignKeyDoesNotExist extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $foreignKeyName, string $table): self | ||
16 | { | ||
17 | return new self( | ||
18 | sprintf('There exists no foreign key with the name "%s" on table "%s".', $foreignKeyName, $table), | ||
19 | ); | ||
20 | } | ||
21 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/IndexAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/IndexAlreadyExists.php new file mode 100644 index 0000000..e8592be --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/IndexAlreadyExists.php | |||
@@ -0,0 +1,21 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class IndexAlreadyExists extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $indexName, string $table): self | ||
16 | { | ||
17 | return new self( | ||
18 | sprintf('An index with name "%s" was already defined on table "%s".', $indexName, $table), | ||
19 | ); | ||
20 | } | ||
21 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/IndexDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/IndexDoesNotExist.php new file mode 100644 index 0000000..ee25fd8 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/IndexDoesNotExist.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class IndexDoesNotExist extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $indexName, string $table): self | ||
16 | { | ||
17 | return new self(sprintf('Index "%s" does not exist on table "%s".', $indexName, $table)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/IndexNameInvalid.php b/vendor/doctrine/dbal/src/Schema/Exception/IndexNameInvalid.php new file mode 100644 index 0000000..d295eb8 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/IndexNameInvalid.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use InvalidArgumentException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class IndexNameInvalid extends InvalidArgumentException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $indexName): self | ||
16 | { | ||
17 | return new self(sprintf('Invalid index name "%s" given, has to be [a-zA-Z0-9_].', $indexName)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/InvalidTableName.php b/vendor/doctrine/dbal/src/Schema/Exception/InvalidTableName.php new file mode 100644 index 0000000..3b5d89d --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/InvalidTableName.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use InvalidArgumentException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class InvalidTableName extends InvalidArgumentException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $tableName): self | ||
16 | { | ||
17 | return new self(sprintf('Invalid table name specified "%s".', $tableName)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php new file mode 100644 index 0000000..4109af5 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/NamespaceAlreadyExists.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class NamespaceAlreadyExists extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $namespaceName): self | ||
16 | { | ||
17 | return new self(sprintf('The namespace with name "%s" already exists.', $namespaceName)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/SequenceAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/SequenceAlreadyExists.php new file mode 100644 index 0000000..d374f27 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/SequenceAlreadyExists.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class SequenceAlreadyExists extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $sequenceName): self | ||
16 | { | ||
17 | return new self(sprintf('The sequence "%s" already exists.', $sequenceName)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/SequenceDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/SequenceDoesNotExist.php new file mode 100644 index 0000000..fa98cee --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/SequenceDoesNotExist.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class SequenceDoesNotExist extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $sequenceName): self | ||
16 | { | ||
17 | return new self(sprintf('There exists no sequence with the name "%s".', $sequenceName)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/TableAlreadyExists.php b/vendor/doctrine/dbal/src/Schema/Exception/TableAlreadyExists.php new file mode 100644 index 0000000..b978dbc --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/TableAlreadyExists.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class TableAlreadyExists extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $tableName): self | ||
16 | { | ||
17 | return new self(sprintf('The table with name "%s" already exists.', $tableName)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/TableDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/TableDoesNotExist.php new file mode 100644 index 0000000..8c66a11 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/TableDoesNotExist.php | |||
@@ -0,0 +1,19 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class TableDoesNotExist extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $tableName): self | ||
16 | { | ||
17 | return new self(sprintf('There is no table with name "%s" in the schema.', $tableName)); | ||
18 | } | ||
19 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/UniqueConstraintDoesNotExist.php b/vendor/doctrine/dbal/src/Schema/Exception/UniqueConstraintDoesNotExist.php new file mode 100644 index 0000000..3ae5aaa --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/UniqueConstraintDoesNotExist.php | |||
@@ -0,0 +1,21 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use LogicException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class UniqueConstraintDoesNotExist extends LogicException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $constraintName, string $table): self | ||
16 | { | ||
17 | return new self( | ||
18 | sprintf('There exists no unique constraint with the name "%s" on table "%s".', $constraintName, $table), | ||
19 | ); | ||
20 | } | ||
21 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Exception/UnknownColumnOption.php b/vendor/doctrine/dbal/src/Schema/Exception/UnknownColumnOption.php new file mode 100644 index 0000000..a97ee76 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Exception/UnknownColumnOption.php | |||
@@ -0,0 +1,21 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\SchemaException; | ||
8 | use InvalidArgumentException; | ||
9 | |||
10 | use function sprintf; | ||
11 | |||
12 | /** @psalm-immutable */ | ||
13 | final class UnknownColumnOption extends InvalidArgumentException implements SchemaException | ||
14 | { | ||
15 | public static function new(string $name): self | ||
16 | { | ||
17 | return new self( | ||
18 | sprintf('The "%s" column option is not supported.', $name), | ||
19 | ); | ||
20 | } | ||
21 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/ForeignKeyConstraint.php b/vendor/doctrine/dbal/src/Schema/ForeignKeyConstraint.php new file mode 100644 index 0000000..bb5ef7f --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/ForeignKeyConstraint.php | |||
@@ -0,0 +1,291 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
8 | |||
9 | use function array_keys; | ||
10 | use function array_map; | ||
11 | use function strrpos; | ||
12 | use function strtolower; | ||
13 | use function strtoupper; | ||
14 | use function substr; | ||
15 | |||
16 | /** | ||
17 | * An abstraction class for a foreign key constraint. | ||
18 | */ | ||
19 | class ForeignKeyConstraint extends AbstractAsset | ||
20 | { | ||
21 | /** | ||
22 | * Asset identifier instances of the referencing table column names the foreign key constraint is associated with. | ||
23 | * | ||
24 | * @var array<string, Identifier> | ||
25 | */ | ||
26 | protected array $_localColumnNames; | ||
27 | |||
28 | /** | ||
29 | * Table or asset identifier instance of the referenced table name the foreign key constraint is associated with. | ||
30 | */ | ||
31 | protected Identifier $_foreignTableName; | ||
32 | |||
33 | /** | ||
34 | * Asset identifier instances of the referenced table column names the foreign key constraint is associated with. | ||
35 | * | ||
36 | * @var array<string, Identifier> | ||
37 | */ | ||
38 | protected array $_foreignColumnNames; | ||
39 | |||
40 | /** | ||
41 | * Initializes the foreign key constraint. | ||
42 | * | ||
43 | * @param array<int, string> $localColumnNames Names of the referencing table columns. | ||
44 | * @param string $foreignTableName Referenced table. | ||
45 | * @param array<int, string> $foreignColumnNames Names of the referenced table columns. | ||
46 | * @param string $name Name of the foreign key constraint. | ||
47 | * @param array<string, mixed> $options Options associated with the foreign key constraint. | ||
48 | */ | ||
49 | public function __construct( | ||
50 | array $localColumnNames, | ||
51 | string $foreignTableName, | ||
52 | array $foreignColumnNames, | ||
53 | string $name = '', | ||
54 | protected array $options = [], | ||
55 | ) { | ||
56 | $this->_setName($name); | ||
57 | |||
58 | $this->_localColumnNames = $this->createIdentifierMap($localColumnNames); | ||
59 | $this->_foreignTableName = new Identifier($foreignTableName); | ||
60 | |||
61 | $this->_foreignColumnNames = $this->createIdentifierMap($foreignColumnNames); | ||
62 | } | ||
63 | |||
64 | /** | ||
65 | * @param array<int, string> $names | ||
66 | * | ||
67 | * @return array<string, Identifier> | ||
68 | */ | ||
69 | private function createIdentifierMap(array $names): array | ||
70 | { | ||
71 | $identifiers = []; | ||
72 | |||
73 | foreach ($names as $name) { | ||
74 | $identifiers[$name] = new Identifier($name); | ||
75 | } | ||
76 | |||
77 | return $identifiers; | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * Returns the names of the referencing table columns | ||
82 | * the foreign key constraint is associated with. | ||
83 | * | ||
84 | * @return array<int, string> | ||
85 | */ | ||
86 | public function getLocalColumns(): array | ||
87 | { | ||
88 | return array_keys($this->_localColumnNames); | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * Returns the quoted representation of the referencing table column names | ||
93 | * the foreign key constraint is associated with. | ||
94 | * | ||
95 | * But only if they were defined with one or the referencing table column name | ||
96 | * is a keyword reserved by the platform. | ||
97 | * Otherwise the plain unquoted value as inserted is returned. | ||
98 | * | ||
99 | * @param AbstractPlatform $platform The platform to use for quotation. | ||
100 | * | ||
101 | * @return array<int, string> | ||
102 | */ | ||
103 | public function getQuotedLocalColumns(AbstractPlatform $platform): array | ||
104 | { | ||
105 | $columns = []; | ||
106 | |||
107 | foreach ($this->_localColumnNames as $column) { | ||
108 | $columns[] = $column->getQuotedName($platform); | ||
109 | } | ||
110 | |||
111 | return $columns; | ||
112 | } | ||
113 | |||
114 | /** | ||
115 | * Returns unquoted representation of local table column names for comparison with other FK | ||
116 | * | ||
117 | * @return array<int, string> | ||
118 | */ | ||
119 | public function getUnquotedLocalColumns(): array | ||
120 | { | ||
121 | return array_map($this->trimQuotes(...), $this->getLocalColumns()); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * Returns unquoted representation of foreign table column names for comparison with other FK | ||
126 | * | ||
127 | * @return array<int, string> | ||
128 | */ | ||
129 | public function getUnquotedForeignColumns(): array | ||
130 | { | ||
131 | return array_map($this->trimQuotes(...), $this->getForeignColumns()); | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * Returns the name of the referenced table | ||
136 | * the foreign key constraint is associated with. | ||
137 | */ | ||
138 | public function getForeignTableName(): string | ||
139 | { | ||
140 | return $this->_foreignTableName->getName(); | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * Returns the non-schema qualified foreign table name. | ||
145 | */ | ||
146 | public function getUnqualifiedForeignTableName(): string | ||
147 | { | ||
148 | $name = $this->_foreignTableName->getName(); | ||
149 | $position = strrpos($name, '.'); | ||
150 | |||
151 | if ($position !== false) { | ||
152 | $name = substr($name, $position + 1); | ||
153 | } | ||
154 | |||
155 | return strtolower($name); | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Returns the quoted representation of the referenced table name | ||
160 | * the foreign key constraint is associated with. | ||
161 | * | ||
162 | * But only if it was defined with one or the referenced table name | ||
163 | * is a keyword reserved by the platform. | ||
164 | * Otherwise the plain unquoted value as inserted is returned. | ||
165 | * | ||
166 | * @param AbstractPlatform $platform The platform to use for quotation. | ||
167 | */ | ||
168 | public function getQuotedForeignTableName(AbstractPlatform $platform): string | ||
169 | { | ||
170 | return $this->_foreignTableName->getQuotedName($platform); | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * Returns the names of the referenced table columns | ||
175 | * the foreign key constraint is associated with. | ||
176 | * | ||
177 | * @return array<int, string> | ||
178 | */ | ||
179 | public function getForeignColumns(): array | ||
180 | { | ||
181 | return array_keys($this->_foreignColumnNames); | ||
182 | } | ||
183 | |||
184 | /** | ||
185 | * Returns the quoted representation of the referenced table column names | ||
186 | * the foreign key constraint is associated with. | ||
187 | * | ||
188 | * But only if they were defined with one or the referenced table column name | ||
189 | * is a keyword reserved by the platform. | ||
190 | * Otherwise the plain unquoted value as inserted is returned. | ||
191 | * | ||
192 | * @param AbstractPlatform $platform The platform to use for quotation. | ||
193 | * | ||
194 | * @return array<int, string> | ||
195 | */ | ||
196 | public function getQuotedForeignColumns(AbstractPlatform $platform): array | ||
197 | { | ||
198 | $columns = []; | ||
199 | |||
200 | foreach ($this->_foreignColumnNames as $column) { | ||
201 | $columns[] = $column->getQuotedName($platform); | ||
202 | } | ||
203 | |||
204 | return $columns; | ||
205 | } | ||
206 | |||
207 | /** | ||
208 | * Returns whether or not a given option | ||
209 | * is associated with the foreign key constraint. | ||
210 | */ | ||
211 | public function hasOption(string $name): bool | ||
212 | { | ||
213 | return isset($this->options[$name]); | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * Returns an option associated with the foreign key constraint. | ||
218 | */ | ||
219 | public function getOption(string $name): mixed | ||
220 | { | ||
221 | return $this->options[$name]; | ||
222 | } | ||
223 | |||
224 | /** | ||
225 | * Returns the options associated with the foreign key constraint. | ||
226 | * | ||
227 | * @return array<string, mixed> | ||
228 | */ | ||
229 | public function getOptions(): array | ||
230 | { | ||
231 | return $this->options; | ||
232 | } | ||
233 | |||
234 | /** | ||
235 | * Returns the referential action for UPDATE operations | ||
236 | * on the referenced table the foreign key constraint is associated with. | ||
237 | */ | ||
238 | public function onUpdate(): ?string | ||
239 | { | ||
240 | return $this->onEvent('onUpdate'); | ||
241 | } | ||
242 | |||
243 | /** | ||
244 | * Returns the referential action for DELETE operations | ||
245 | * on the referenced table the foreign key constraint is associated with. | ||
246 | */ | ||
247 | public function onDelete(): ?string | ||
248 | { | ||
249 | return $this->onEvent('onDelete'); | ||
250 | } | ||
251 | |||
252 | /** | ||
253 | * Returns the referential action for a given database operation | ||
254 | * on the referenced table the foreign key constraint is associated with. | ||
255 | * | ||
256 | * @param string $event Name of the database operation/event to return the referential action for. | ||
257 | */ | ||
258 | private function onEvent(string $event): ?string | ||
259 | { | ||
260 | if (isset($this->options[$event])) { | ||
261 | $onEvent = strtoupper($this->options[$event]); | ||
262 | |||
263 | if ($onEvent !== 'NO ACTION' && $onEvent !== 'RESTRICT') { | ||
264 | return $onEvent; | ||
265 | } | ||
266 | } | ||
267 | |||
268 | return null; | ||
269 | } | ||
270 | |||
271 | /** | ||
272 | * Checks whether this foreign key constraint intersects the given index columns. | ||
273 | * | ||
274 | * Returns `true` if at least one of this foreign key's local columns | ||
275 | * matches one of the given index's columns, `false` otherwise. | ||
276 | * | ||
277 | * @param Index $index The index to be checked against. | ||
278 | */ | ||
279 | public function intersectsIndexColumns(Index $index): bool | ||
280 | { | ||
281 | foreach ($index->getColumns() as $indexColumn) { | ||
282 | foreach ($this->_localColumnNames as $localColumn) { | ||
283 | if (strtolower($indexColumn) === strtolower($localColumn->getName())) { | ||
284 | return true; | ||
285 | } | ||
286 | } | ||
287 | } | ||
288 | |||
289 | return false; | ||
290 | } | ||
291 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Identifier.php b/vendor/doctrine/dbal/src/Schema/Identifier.php new file mode 100644 index 0000000..c3c84a7 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Identifier.php | |||
@@ -0,0 +1,29 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | /** | ||
8 | * An abstraction class for an asset identifier. | ||
9 | * | ||
10 | * Wraps identifier names like column names in indexes / foreign keys | ||
11 | * in an abstract class for proper quotation capabilities. | ||
12 | */ | ||
13 | class Identifier extends AbstractAsset | ||
14 | { | ||
15 | /** | ||
16 | * @param string $identifier Identifier name to wrap. | ||
17 | * @param bool $quote Whether to force quoting the given identifier. | ||
18 | */ | ||
19 | public function __construct(string $identifier, bool $quote = false) | ||
20 | { | ||
21 | $this->_setName($identifier); | ||
22 | |||
23 | if (! $quote || $this->_quoted) { | ||
24 | return; | ||
25 | } | ||
26 | |||
27 | $this->_setName('"' . $this->getName() . '"'); | ||
28 | } | ||
29 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Index.php b/vendor/doctrine/dbal/src/Schema/Index.php new file mode 100644 index 0000000..1755654 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Index.php | |||
@@ -0,0 +1,314 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
8 | |||
9 | use function array_filter; | ||
10 | use function array_keys; | ||
11 | use function array_map; | ||
12 | use function array_search; | ||
13 | use function array_shift; | ||
14 | use function count; | ||
15 | use function strtolower; | ||
16 | |||
17 | class Index extends AbstractAsset | ||
18 | { | ||
19 | /** | ||
20 | * Asset identifier instances of the column names the index is associated with. | ||
21 | * | ||
22 | * @var array<string, Identifier> | ||
23 | */ | ||
24 | protected array $_columns = []; | ||
25 | |||
26 | protected bool $_isUnique = false; | ||
27 | |||
28 | protected bool $_isPrimary = false; | ||
29 | |||
30 | /** | ||
31 | * Platform specific flags for indexes. | ||
32 | * | ||
33 | * @var array<string, true> | ||
34 | */ | ||
35 | protected array $_flags = []; | ||
36 | |||
37 | /** | ||
38 | * @param array<int, string> $columns | ||
39 | * @param array<int, string> $flags | ||
40 | * @param array<string, mixed> $options | ||
41 | */ | ||
42 | public function __construct( | ||
43 | ?string $name, | ||
44 | array $columns, | ||
45 | bool $isUnique = false, | ||
46 | bool $isPrimary = false, | ||
47 | array $flags = [], | ||
48 | private readonly array $options = [], | ||
49 | ) { | ||
50 | $isUnique = $isUnique || $isPrimary; | ||
51 | |||
52 | if ($name !== null) { | ||
53 | $this->_setName($name); | ||
54 | } | ||
55 | |||
56 | $this->_isUnique = $isUnique; | ||
57 | $this->_isPrimary = $isPrimary; | ||
58 | |||
59 | foreach ($columns as $column) { | ||
60 | $this->_addColumn($column); | ||
61 | } | ||
62 | |||
63 | foreach ($flags as $flag) { | ||
64 | $this->addFlag($flag); | ||
65 | } | ||
66 | } | ||
67 | |||
68 | protected function _addColumn(string $column): void | ||
69 | { | ||
70 | $this->_columns[$column] = new Identifier($column); | ||
71 | } | ||
72 | |||
73 | /** | ||
74 | * Returns the names of the referencing table columns the constraint is associated with. | ||
75 | * | ||
76 | * @return list<string> | ||
77 | */ | ||
78 | public function getColumns(): array | ||
79 | { | ||
80 | return array_keys($this->_columns); | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * Returns the quoted representation of the column names the constraint is associated with. | ||
85 | * | ||
86 | * But only if they were defined with one or a column name | ||
87 | * is a keyword reserved by the platform. | ||
88 | * Otherwise, the plain unquoted value as inserted is returned. | ||
89 | * | ||
90 | * @param AbstractPlatform $platform The platform to use for quotation. | ||
91 | * | ||
92 | * @return list<string> | ||
93 | */ | ||
94 | public function getQuotedColumns(AbstractPlatform $platform): array | ||
95 | { | ||
96 | $subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths') | ||
97 | ? $this->getOption('lengths') : []; | ||
98 | |||
99 | $columns = []; | ||
100 | |||
101 | foreach ($this->_columns as $column) { | ||
102 | $length = array_shift($subParts); | ||
103 | |||
104 | $quotedColumn = $column->getQuotedName($platform); | ||
105 | |||
106 | if ($length !== null) { | ||
107 | $quotedColumn .= '(' . $length . ')'; | ||
108 | } | ||
109 | |||
110 | $columns[] = $quotedColumn; | ||
111 | } | ||
112 | |||
113 | return $columns; | ||
114 | } | ||
115 | |||
116 | /** @return array<int, string> */ | ||
117 | public function getUnquotedColumns(): array | ||
118 | { | ||
119 | return array_map($this->trimQuotes(...), $this->getColumns()); | ||
120 | } | ||
121 | |||
122 | /** | ||
123 | * Is the index neither unique nor primary key? | ||
124 | */ | ||
125 | public function isSimpleIndex(): bool | ||
126 | { | ||
127 | return ! $this->_isPrimary && ! $this->_isUnique; | ||
128 | } | ||
129 | |||
130 | public function isUnique(): bool | ||
131 | { | ||
132 | return $this->_isUnique; | ||
133 | } | ||
134 | |||
135 | public function isPrimary(): bool | ||
136 | { | ||
137 | return $this->_isPrimary; | ||
138 | } | ||
139 | |||
140 | public function hasColumnAtPosition(string $name, int $pos = 0): bool | ||
141 | { | ||
142 | $name = $this->trimQuotes(strtolower($name)); | ||
143 | $indexColumns = array_map('strtolower', $this->getUnquotedColumns()); | ||
144 | |||
145 | return array_search($name, $indexColumns, true) === $pos; | ||
146 | } | ||
147 | |||
148 | /** | ||
149 | * Checks if this index exactly spans the given column names in the correct order. | ||
150 | * | ||
151 | * @param array<int, string> $columnNames | ||
152 | */ | ||
153 | public function spansColumns(array $columnNames): bool | ||
154 | { | ||
155 | $columns = $this->getColumns(); | ||
156 | $numberOfColumns = count($columns); | ||
157 | $sameColumns = true; | ||
158 | |||
159 | for ($i = 0; $i < $numberOfColumns; $i++) { | ||
160 | if ( | ||
161 | isset($columnNames[$i]) | ||
162 | && $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i])) | ||
163 | ) { | ||
164 | continue; | ||
165 | } | ||
166 | |||
167 | $sameColumns = false; | ||
168 | } | ||
169 | |||
170 | return $sameColumns; | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * Checks if the other index already fulfills all the indexing and constraint needs of the current one. | ||
175 | */ | ||
176 | public function isFulfilledBy(Index $other): bool | ||
177 | { | ||
178 | // allow the other index to be equally large only. It being larger is an option | ||
179 | // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo) | ||
180 | if (count($other->getColumns()) !== count($this->getColumns())) { | ||
181 | return false; | ||
182 | } | ||
183 | |||
184 | // Check if columns are the same, and even in the same order | ||
185 | $sameColumns = $this->spansColumns($other->getColumns()); | ||
186 | |||
187 | if ($sameColumns) { | ||
188 | if (! $this->samePartialIndex($other)) { | ||
189 | return false; | ||
190 | } | ||
191 | |||
192 | if (! $this->hasSameColumnLengths($other)) { | ||
193 | return false; | ||
194 | } | ||
195 | |||
196 | if (! $this->isUnique() && ! $this->isPrimary()) { | ||
197 | // this is a special case: If the current key is neither primary or unique, any unique or | ||
198 | // primary key will always have the same effect for the index and there cannot be any constraint | ||
199 | // overlaps. This means a primary or unique index can always fulfill the requirements of just an | ||
200 | // index that has no constraints. | ||
201 | return true; | ||
202 | } | ||
203 | |||
204 | if ($other->isPrimary() !== $this->isPrimary()) { | ||
205 | return false; | ||
206 | } | ||
207 | |||
208 | return $other->isUnique() === $this->isUnique(); | ||
209 | } | ||
210 | |||
211 | return false; | ||
212 | } | ||
213 | |||
214 | /** | ||
215 | * Detects if the other index is a non-unique, non primary index that can be overwritten by this one. | ||
216 | */ | ||
217 | public function overrules(Index $other): bool | ||
218 | { | ||
219 | if ($other->isPrimary()) { | ||
220 | return false; | ||
221 | } | ||
222 | |||
223 | if ($this->isSimpleIndex() && $other->isUnique()) { | ||
224 | return false; | ||
225 | } | ||
226 | |||
227 | return $this->spansColumns($other->getColumns()) | ||
228 | && ($this->isPrimary() || $this->isUnique()) | ||
229 | && $this->samePartialIndex($other); | ||
230 | } | ||
231 | |||
232 | /** | ||
233 | * Returns platform specific flags for indexes. | ||
234 | * | ||
235 | * @return array<int, string> | ||
236 | */ | ||
237 | public function getFlags(): array | ||
238 | { | ||
239 | return array_keys($this->_flags); | ||
240 | } | ||
241 | |||
242 | /** | ||
243 | * Adds Flag for an index that translates to platform specific handling. | ||
244 | * | ||
245 | * @example $index->addFlag('CLUSTERED') | ||
246 | */ | ||
247 | public function addFlag(string $flag): self | ||
248 | { | ||
249 | $this->_flags[strtolower($flag)] = true; | ||
250 | |||
251 | return $this; | ||
252 | } | ||
253 | |||
254 | /** | ||
255 | * Does this index have a specific flag? | ||
256 | */ | ||
257 | public function hasFlag(string $flag): bool | ||
258 | { | ||
259 | return isset($this->_flags[strtolower($flag)]); | ||
260 | } | ||
261 | |||
262 | /** | ||
263 | * Removes a flag. | ||
264 | */ | ||
265 | public function removeFlag(string $flag): void | ||
266 | { | ||
267 | unset($this->_flags[strtolower($flag)]); | ||
268 | } | ||
269 | |||
270 | public function hasOption(string $name): bool | ||
271 | { | ||
272 | return isset($this->options[strtolower($name)]); | ||
273 | } | ||
274 | |||
275 | public function getOption(string $name): mixed | ||
276 | { | ||
277 | return $this->options[strtolower($name)]; | ||
278 | } | ||
279 | |||
280 | /** @return array<string, mixed> */ | ||
281 | public function getOptions(): array | ||
282 | { | ||
283 | return $this->options; | ||
284 | } | ||
285 | |||
286 | /** | ||
287 | * Return whether the two indexes have the same partial index | ||
288 | */ | ||
289 | private function samePartialIndex(Index $other): bool | ||
290 | { | ||
291 | if ( | ||
292 | $this->hasOption('where') | ||
293 | && $other->hasOption('where') | ||
294 | && $this->getOption('where') === $other->getOption('where') | ||
295 | ) { | ||
296 | return true; | ||
297 | } | ||
298 | |||
299 | return ! $this->hasOption('where') && ! $other->hasOption('where'); | ||
300 | } | ||
301 | |||
302 | /** | ||
303 | * Returns whether the index has the same column lengths as the other | ||
304 | */ | ||
305 | private function hasSameColumnLengths(self $other): bool | ||
306 | { | ||
307 | $filter = static function (?int $length): bool { | ||
308 | return $length !== null; | ||
309 | }; | ||
310 | |||
311 | return array_filter($this->options['lengths'] ?? [], $filter) | ||
312 | === array_filter($other->options['lengths'] ?? [], $filter); | ||
313 | } | ||
314 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php b/vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php new file mode 100644 index 0000000..249be13 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/MySQLSchemaManager.php | |||
@@ -0,0 +1,548 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Exception; | ||
8 | use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; | ||
9 | use Doctrine\DBAL\Platforms\MariaDBPlatform; | ||
10 | use Doctrine\DBAL\Platforms\MySQL; | ||
11 | use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider\CachingCharsetMetadataProvider; | ||
12 | use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider\ConnectionCharsetMetadataProvider; | ||
13 | use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\CachingCollationMetadataProvider; | ||
14 | use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\ConnectionCollationMetadataProvider; | ||
15 | use Doctrine\DBAL\Platforms\MySQL\DefaultTableOptions; | ||
16 | use Doctrine\DBAL\Result; | ||
17 | use Doctrine\DBAL\Types\Type; | ||
18 | |||
19 | use function array_change_key_case; | ||
20 | use function assert; | ||
21 | use function explode; | ||
22 | use function implode; | ||
23 | use function is_string; | ||
24 | use function preg_match; | ||
25 | use function str_contains; | ||
26 | use function strtok; | ||
27 | use function strtolower; | ||
28 | use function strtr; | ||
29 | |||
30 | use const CASE_LOWER; | ||
31 | |||
32 | /** | ||
33 | * Schema manager for the MySQL RDBMS. | ||
34 | * | ||
35 | * @extends AbstractSchemaManager<AbstractMySQLPlatform> | ||
36 | */ | ||
37 | class MySQLSchemaManager extends AbstractSchemaManager | ||
38 | { | ||
39 | /** @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences */ | ||
40 | private const MARIADB_ESCAPE_SEQUENCES = [ | ||
41 | '\\0' => "\0", | ||
42 | "\\'" => "'", | ||
43 | '\\"' => '"', | ||
44 | '\\b' => "\b", | ||
45 | '\\n' => "\n", | ||
46 | '\\r' => "\r", | ||
47 | '\\t' => "\t", | ||
48 | '\\Z' => "\x1a", | ||
49 | '\\\\' => '\\', | ||
50 | '\\%' => '%', | ||
51 | '\\_' => '_', | ||
52 | |||
53 | // Internally, MariaDB escapes single quotes using the standard syntax | ||
54 | "''" => "'", | ||
55 | ]; | ||
56 | |||
57 | private ?DefaultTableOptions $defaultTableOptions = null; | ||
58 | |||
59 | /** | ||
60 | * {@inheritDoc} | ||
61 | */ | ||
62 | protected function _getPortableTableDefinition(array $table): string | ||
63 | { | ||
64 | return $table['TABLE_NAME']; | ||
65 | } | ||
66 | |||
67 | /** | ||
68 | * {@inheritDoc} | ||
69 | */ | ||
70 | protected function _getPortableViewDefinition(array $view): View | ||
71 | { | ||
72 | return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']); | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * {@inheritDoc} | ||
77 | */ | ||
78 | protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array | ||
79 | { | ||
80 | foreach ($tableIndexes as $k => $v) { | ||
81 | $v = array_change_key_case($v, CASE_LOWER); | ||
82 | if ($v['key_name'] === 'PRIMARY') { | ||
83 | $v['primary'] = true; | ||
84 | } else { | ||
85 | $v['primary'] = false; | ||
86 | } | ||
87 | |||
88 | if (str_contains($v['index_type'], 'FULLTEXT')) { | ||
89 | $v['flags'] = ['FULLTEXT']; | ||
90 | } elseif (str_contains($v['index_type'], 'SPATIAL')) { | ||
91 | $v['flags'] = ['SPATIAL']; | ||
92 | } | ||
93 | |||
94 | // Ignore prohibited prefix `length` for spatial index | ||
95 | if (! str_contains($v['index_type'], 'SPATIAL')) { | ||
96 | $v['length'] = isset($v['sub_part']) ? (int) $v['sub_part'] : null; | ||
97 | } | ||
98 | |||
99 | $tableIndexes[$k] = $v; | ||
100 | } | ||
101 | |||
102 | return parent::_getPortableTableIndexesList($tableIndexes, $tableName); | ||
103 | } | ||
104 | |||
105 | /** | ||
106 | * {@inheritDoc} | ||
107 | */ | ||
108 | protected function _getPortableDatabaseDefinition(array $database): string | ||
109 | { | ||
110 | return $database['Database']; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * {@inheritDoc} | ||
115 | */ | ||
116 | protected function _getPortableTableColumnDefinition(array $tableColumn): Column | ||
117 | { | ||
118 | $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); | ||
119 | |||
120 | $dbType = strtolower($tableColumn['type']); | ||
121 | $dbType = strtok($dbType, '(), '); | ||
122 | assert(is_string($dbType)); | ||
123 | |||
124 | $length = $tableColumn['length'] ?? strtok('(), '); | ||
125 | |||
126 | $fixed = false; | ||
127 | |||
128 | if (! isset($tableColumn['name'])) { | ||
129 | $tableColumn['name'] = ''; | ||
130 | } | ||
131 | |||
132 | $scale = 0; | ||
133 | $precision = null; | ||
134 | |||
135 | $type = $this->platform->getDoctrineTypeMapping($dbType); | ||
136 | |||
137 | switch ($dbType) { | ||
138 | case 'char': | ||
139 | case 'binary': | ||
140 | $fixed = true; | ||
141 | break; | ||
142 | |||
143 | case 'float': | ||
144 | case 'double': | ||
145 | case 'real': | ||
146 | case 'numeric': | ||
147 | case 'decimal': | ||
148 | if ( | ||
149 | preg_match( | ||
150 | '([A-Za-z]+\(([0-9]+),([0-9]+)\))', | ||
151 | $tableColumn['type'], | ||
152 | $match, | ||
153 | ) === 1 | ||
154 | ) { | ||
155 | $precision = (int) $match[1]; | ||
156 | $scale = (int) $match[2]; | ||
157 | $length = null; | ||
158 | } | ||
159 | |||
160 | break; | ||
161 | |||
162 | case 'tinytext': | ||
163 | $length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYTEXT; | ||
164 | break; | ||
165 | |||
166 | case 'text': | ||
167 | $length = AbstractMySQLPlatform::LENGTH_LIMIT_TEXT; | ||
168 | break; | ||
169 | |||
170 | case 'mediumtext': | ||
171 | $length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT; | ||
172 | break; | ||
173 | |||
174 | case 'tinyblob': | ||
175 | $length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYBLOB; | ||
176 | break; | ||
177 | |||
178 | case 'blob': | ||
179 | $length = AbstractMySQLPlatform::LENGTH_LIMIT_BLOB; | ||
180 | break; | ||
181 | |||
182 | case 'mediumblob': | ||
183 | $length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB; | ||
184 | break; | ||
185 | |||
186 | case 'tinyint': | ||
187 | case 'smallint': | ||
188 | case 'mediumint': | ||
189 | case 'int': | ||
190 | case 'integer': | ||
191 | case 'bigint': | ||
192 | case 'year': | ||
193 | $length = null; | ||
194 | break; | ||
195 | } | ||
196 | |||
197 | if ($this->platform instanceof MariaDBPlatform) { | ||
198 | $columnDefault = $this->getMariaDBColumnDefault($this->platform, $tableColumn['default']); | ||
199 | } else { | ||
200 | $columnDefault = $tableColumn['default']; | ||
201 | } | ||
202 | |||
203 | $options = [ | ||
204 | 'length' => $length !== null ? (int) $length : null, | ||
205 | 'unsigned' => str_contains($tableColumn['type'], 'unsigned'), | ||
206 | 'fixed' => $fixed, | ||
207 | 'default' => $columnDefault, | ||
208 | 'notnull' => $tableColumn['null'] !== 'YES', | ||
209 | 'scale' => $scale, | ||
210 | 'precision' => $precision, | ||
211 | 'autoincrement' => str_contains($tableColumn['extra'], 'auto_increment'), | ||
212 | ]; | ||
213 | |||
214 | if (isset($tableColumn['comment'])) { | ||
215 | $options['comment'] = $tableColumn['comment']; | ||
216 | } | ||
217 | |||
218 | $column = new Column($tableColumn['field'], Type::getType($type), $options); | ||
219 | |||
220 | if (isset($tableColumn['characterset'])) { | ||
221 | $column->setPlatformOption('charset', $tableColumn['characterset']); | ||
222 | } | ||
223 | |||
224 | if (isset($tableColumn['collation'])) { | ||
225 | $column->setPlatformOption('collation', $tableColumn['collation']); | ||
226 | } | ||
227 | |||
228 | return $column; | ||
229 | } | ||
230 | |||
231 | /** | ||
232 | * Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers. | ||
233 | * | ||
234 | * - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted | ||
235 | * to distinguish them from expressions (see MDEV-10134). | ||
236 | * - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema | ||
237 | * as current_timestamp(), currdate(), currtime() | ||
238 | * - Quoted 'NULL' is not enforced by Maria, it is technically possible to have | ||
239 | * null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053) | ||
240 | * - \' is always stored as '' in information_schema (normalized) | ||
241 | * | ||
242 | * @link https://mariadb.com/kb/en/library/information-schema-columns-table/ | ||
243 | * @link https://jira.mariadb.org/browse/MDEV-13132 | ||
244 | * | ||
245 | * @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7 | ||
246 | */ | ||
247 | private function getMariaDBColumnDefault(MariaDBPlatform $platform, ?string $columnDefault): ?string | ||
248 | { | ||
249 | if ($columnDefault === 'NULL' || $columnDefault === null) { | ||
250 | return null; | ||
251 | } | ||
252 | |||
253 | if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches) === 1) { | ||
254 | return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES); | ||
255 | } | ||
256 | |||
257 | return match ($columnDefault) { | ||
258 | 'current_timestamp()' => $platform->getCurrentTimestampSQL(), | ||
259 | 'curdate()' => $platform->getCurrentDateSQL(), | ||
260 | 'curtime()' => $platform->getCurrentTimeSQL(), | ||
261 | default => $columnDefault, | ||
262 | }; | ||
263 | } | ||
264 | |||
265 | /** | ||
266 | * {@inheritDoc} | ||
267 | */ | ||
268 | protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array | ||
269 | { | ||
270 | $list = []; | ||
271 | foreach ($tableForeignKeys as $value) { | ||
272 | $value = array_change_key_case($value, CASE_LOWER); | ||
273 | if (! isset($list[$value['constraint_name']])) { | ||
274 | if (! isset($value['delete_rule']) || $value['delete_rule'] === 'RESTRICT') { | ||
275 | $value['delete_rule'] = null; | ||
276 | } | ||
277 | |||
278 | if (! isset($value['update_rule']) || $value['update_rule'] === 'RESTRICT') { | ||
279 | $value['update_rule'] = null; | ||
280 | } | ||
281 | |||
282 | $list[$value['constraint_name']] = [ | ||
283 | 'name' => $value['constraint_name'], | ||
284 | 'local' => [], | ||
285 | 'foreign' => [], | ||
286 | 'foreignTable' => $value['referenced_table_name'], | ||
287 | 'onDelete' => $value['delete_rule'], | ||
288 | 'onUpdate' => $value['update_rule'], | ||
289 | ]; | ||
290 | } | ||
291 | |||
292 | $list[$value['constraint_name']]['local'][] = $value['column_name']; | ||
293 | $list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name']; | ||
294 | } | ||
295 | |||
296 | return parent::_getPortableTableForeignKeysList($list); | ||
297 | } | ||
298 | |||
299 | /** | ||
300 | * {@inheritDoc} | ||
301 | */ | ||
302 | protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint | ||
303 | { | ||
304 | return new ForeignKeyConstraint( | ||
305 | $tableForeignKey['local'], | ||
306 | $tableForeignKey['foreignTable'], | ||
307 | $tableForeignKey['foreign'], | ||
308 | $tableForeignKey['name'], | ||
309 | [ | ||
310 | 'onDelete' => $tableForeignKey['onDelete'], | ||
311 | 'onUpdate' => $tableForeignKey['onUpdate'], | ||
312 | ], | ||
313 | ); | ||
314 | } | ||
315 | |||
316 | /** @throws Exception */ | ||
317 | public function createComparator(): Comparator | ||
318 | { | ||
319 | return new MySQL\Comparator( | ||
320 | $this->platform, | ||
321 | new CachingCharsetMetadataProvider( | ||
322 | new ConnectionCharsetMetadataProvider($this->connection), | ||
323 | ), | ||
324 | new CachingCollationMetadataProvider( | ||
325 | new ConnectionCollationMetadataProvider($this->connection), | ||
326 | ), | ||
327 | $this->getDefaultTableOptions(), | ||
328 | ); | ||
329 | } | ||
330 | |||
331 | protected function selectTableNames(string $databaseName): Result | ||
332 | { | ||
333 | $sql = <<<'SQL' | ||
334 | SELECT TABLE_NAME | ||
335 | FROM information_schema.TABLES | ||
336 | WHERE TABLE_SCHEMA = ? | ||
337 | AND TABLE_TYPE = 'BASE TABLE' | ||
338 | ORDER BY TABLE_NAME | ||
339 | SQL; | ||
340 | |||
341 | return $this->connection->executeQuery($sql, [$databaseName]); | ||
342 | } | ||
343 | |||
344 | protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result | ||
345 | { | ||
346 | $columnTypeSQL = $this->platform->getColumnTypeSQLSnippet('c', $databaseName); | ||
347 | |||
348 | $sql = 'SELECT'; | ||
349 | |||
350 | if ($tableName === null) { | ||
351 | $sql .= ' c.TABLE_NAME,'; | ||
352 | } | ||
353 | |||
354 | $sql .= <<<SQL | ||
355 | c.COLUMN_NAME AS field, | ||
356 | $columnTypeSQL AS type, | ||
357 | c.IS_NULLABLE AS `null`, | ||
358 | c.COLUMN_KEY AS `key`, | ||
359 | c.COLUMN_DEFAULT AS `default`, | ||
360 | c.EXTRA, | ||
361 | c.COLUMN_COMMENT AS comment, | ||
362 | c.CHARACTER_SET_NAME AS characterset, | ||
363 | c.COLLATION_NAME AS collation | ||
364 | FROM information_schema.COLUMNS c | ||
365 | INNER JOIN information_schema.TABLES t | ||
366 | ON t.TABLE_NAME = c.TABLE_NAME | ||
367 | SQL; | ||
368 | |||
369 | // The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition | ||
370 | // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions | ||
371 | // caused by https://bugs.mysql.com/bug.php?id=81347 | ||
372 | $conditions = ['c.TABLE_SCHEMA = ?', 't.TABLE_SCHEMA = ?', "t.TABLE_TYPE = 'BASE TABLE'"]; | ||
373 | $params = [$databaseName, $databaseName]; | ||
374 | |||
375 | if ($tableName !== null) { | ||
376 | $conditions[] = 't.TABLE_NAME = ?'; | ||
377 | $params[] = $tableName; | ||
378 | } | ||
379 | |||
380 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY ORDINAL_POSITION'; | ||
381 | |||
382 | return $this->connection->executeQuery($sql, $params); | ||
383 | } | ||
384 | |||
385 | protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result | ||
386 | { | ||
387 | $sql = 'SELECT'; | ||
388 | |||
389 | if ($tableName === null) { | ||
390 | $sql .= ' TABLE_NAME,'; | ||
391 | } | ||
392 | |||
393 | $sql .= <<<'SQL' | ||
394 | NON_UNIQUE AS Non_Unique, | ||
395 | INDEX_NAME AS Key_name, | ||
396 | COLUMN_NAME AS Column_Name, | ||
397 | SUB_PART AS Sub_Part, | ||
398 | INDEX_TYPE AS Index_Type | ||
399 | FROM information_schema.STATISTICS | ||
400 | SQL; | ||
401 | |||
402 | $conditions = ['TABLE_SCHEMA = ?']; | ||
403 | $params = [$databaseName]; | ||
404 | |||
405 | if ($tableName !== null) { | ||
406 | $conditions[] = 'TABLE_NAME = ?'; | ||
407 | $params[] = $tableName; | ||
408 | } | ||
409 | |||
410 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY SEQ_IN_INDEX'; | ||
411 | |||
412 | return $this->connection->executeQuery($sql, $params); | ||
413 | } | ||
414 | |||
415 | protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result | ||
416 | { | ||
417 | $sql = 'SELECT DISTINCT'; | ||
418 | |||
419 | if ($tableName === null) { | ||
420 | $sql .= ' k.TABLE_NAME,'; | ||
421 | } | ||
422 | |||
423 | $sql .= <<<'SQL' | ||
424 | k.CONSTRAINT_NAME, | ||
425 | k.COLUMN_NAME, | ||
426 | k.REFERENCED_TABLE_NAME, | ||
427 | k.REFERENCED_COLUMN_NAME, | ||
428 | k.ORDINAL_POSITION, | ||
429 | c.UPDATE_RULE, | ||
430 | c.DELETE_RULE | ||
431 | FROM information_schema.key_column_usage k | ||
432 | INNER JOIN information_schema.referential_constraints c | ||
433 | ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME | ||
434 | AND c.TABLE_NAME = k.TABLE_NAME | ||
435 | SQL; | ||
436 | |||
437 | $conditions = ['k.TABLE_SCHEMA = ?']; | ||
438 | $params = [$databaseName]; | ||
439 | |||
440 | if ($tableName !== null) { | ||
441 | $conditions[] = 'k.TABLE_NAME = ?'; | ||
442 | $params[] = $tableName; | ||
443 | } | ||
444 | |||
445 | // The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition | ||
446 | // in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions | ||
447 | // caused by https://bugs.mysql.com/bug.php?id=81347 | ||
448 | $conditions[] = 'c.CONSTRAINT_SCHEMA = ?'; | ||
449 | $params[] = $databaseName; | ||
450 | |||
451 | $conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL'; | ||
452 | |||
453 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY k.ORDINAL_POSITION'; | ||
454 | |||
455 | return $this->connection->executeQuery($sql, $params); | ||
456 | } | ||
457 | |||
458 | /** | ||
459 | * {@inheritDoc} | ||
460 | */ | ||
461 | protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array | ||
462 | { | ||
463 | // MariaDB-10.10.1 added FULL_COLLATION_NAME to the information_schema.COLLATION_CHARACTER_SET_APPLICABILITY. | ||
464 | // A base collation like uca1400_ai_ci can refer to multiple character sets. The value in the | ||
465 | // information_schema.TABLES.TABLE_COLLATION corresponds to the full collation name. | ||
466 | // The MariaDB executable comment syntax with version, /*M!101001, is exclusively executed on | ||
467 | // MariaDB-10.10.1+ servers for backwards compatibility, and compatiblity to MySQL servers. | ||
468 | $sql = <<<'SQL' | ||
469 | SELECT t.TABLE_NAME, | ||
470 | t.ENGINE, | ||
471 | t.AUTO_INCREMENT, | ||
472 | t.TABLE_COMMENT, | ||
473 | t.CREATE_OPTIONS, | ||
474 | t.TABLE_COLLATION, | ||
475 | ccsa.CHARACTER_SET_NAME | ||
476 | FROM information_schema.TABLES t | ||
477 | INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY ccsa | ||
478 | ON /*M!101001 ccsa.FULL_COLLATION_NAME = t.TABLE_COLLATION OR */ | ||
479 | ccsa.COLLATION_NAME = t.TABLE_COLLATION | ||
480 | SQL; | ||
481 | |||
482 | $conditions = ['t.TABLE_SCHEMA = ?']; | ||
483 | $params = [$databaseName]; | ||
484 | |||
485 | if ($tableName !== null) { | ||
486 | $conditions[] = 't.TABLE_NAME = ?'; | ||
487 | $params[] = $tableName; | ||
488 | } | ||
489 | |||
490 | $conditions[] = "t.TABLE_TYPE = 'BASE TABLE'"; | ||
491 | |||
492 | $sql .= ' WHERE ' . implode(' AND ', $conditions); | ||
493 | |||
494 | /** @var array<string,array<string,mixed>> $metadata */ | ||
495 | $metadata = $this->connection->executeQuery($sql, $params) | ||
496 | ->fetchAllAssociativeIndexed(); | ||
497 | |||
498 | $tableOptions = []; | ||
499 | foreach ($metadata as $table => $data) { | ||
500 | $data = array_change_key_case($data, CASE_LOWER); | ||
501 | |||
502 | $tableOptions[$table] = [ | ||
503 | 'engine' => $data['engine'], | ||
504 | 'collation' => $data['table_collation'], | ||
505 | 'charset' => $data['character_set_name'], | ||
506 | 'autoincrement' => $data['auto_increment'], | ||
507 | 'comment' => $data['table_comment'], | ||
508 | 'create_options' => $this->parseCreateOptions($data['create_options']), | ||
509 | ]; | ||
510 | } | ||
511 | |||
512 | return $tableOptions; | ||
513 | } | ||
514 | |||
515 | /** @return array<string, string>|array<string, true> */ | ||
516 | private function parseCreateOptions(?string $string): array | ||
517 | { | ||
518 | $options = []; | ||
519 | |||
520 | if ($string === null || $string === '') { | ||
521 | return $options; | ||
522 | } | ||
523 | |||
524 | foreach (explode(' ', $string) as $pair) { | ||
525 | $parts = explode('=', $pair, 2); | ||
526 | |||
527 | $options[$parts[0]] = $parts[1] ?? true; | ||
528 | } | ||
529 | |||
530 | return $options; | ||
531 | } | ||
532 | |||
533 | /** @throws Exception */ | ||
534 | private function getDefaultTableOptions(): DefaultTableOptions | ||
535 | { | ||
536 | if ($this->defaultTableOptions === null) { | ||
537 | $row = $this->connection->fetchNumeric( | ||
538 | 'SELECT @@character_set_database, @@collation_database', | ||
539 | ); | ||
540 | |||
541 | assert($row !== false); | ||
542 | |||
543 | $this->defaultTableOptions = new DefaultTableOptions(...$row); | ||
544 | } | ||
545 | |||
546 | return $this->defaultTableOptions; | ||
547 | } | ||
548 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php b/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php new file mode 100644 index 0000000..f973eaa --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php | |||
@@ -0,0 +1,475 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Exception; | ||
8 | use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException; | ||
9 | use Doctrine\DBAL\Platforms\OraclePlatform; | ||
10 | use Doctrine\DBAL\Result; | ||
11 | use Doctrine\DBAL\Types\Type; | ||
12 | |||
13 | use function array_change_key_case; | ||
14 | use function array_key_exists; | ||
15 | use function array_values; | ||
16 | use function assert; | ||
17 | use function implode; | ||
18 | use function is_string; | ||
19 | use function preg_match; | ||
20 | use function str_contains; | ||
21 | use function str_replace; | ||
22 | use function str_starts_with; | ||
23 | use function strtolower; | ||
24 | use function strtoupper; | ||
25 | use function trim; | ||
26 | |||
27 | use const CASE_LOWER; | ||
28 | |||
29 | /** | ||
30 | * Oracle Schema Manager. | ||
31 | * | ||
32 | * @extends AbstractSchemaManager<OraclePlatform> | ||
33 | */ | ||
34 | class OracleSchemaManager extends AbstractSchemaManager | ||
35 | { | ||
36 | /** | ||
37 | * {@inheritDoc} | ||
38 | */ | ||
39 | protected function _getPortableViewDefinition(array $view): View | ||
40 | { | ||
41 | $view = array_change_key_case($view, CASE_LOWER); | ||
42 | |||
43 | return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']); | ||
44 | } | ||
45 | |||
46 | /** | ||
47 | * {@inheritDoc} | ||
48 | */ | ||
49 | protected function _getPortableTableDefinition(array $table): string | ||
50 | { | ||
51 | $table = array_change_key_case($table, CASE_LOWER); | ||
52 | |||
53 | return $this->getQuotedIdentifierName($table['table_name']); | ||
54 | } | ||
55 | |||
56 | /** | ||
57 | * {@inheritDoc} | ||
58 | * | ||
59 | * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html | ||
60 | */ | ||
61 | protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array | ||
62 | { | ||
63 | $indexBuffer = []; | ||
64 | foreach ($tableIndexes as $tableIndex) { | ||
65 | $tableIndex = array_change_key_case($tableIndex, CASE_LOWER); | ||
66 | |||
67 | $keyName = strtolower($tableIndex['name']); | ||
68 | $buffer = []; | ||
69 | |||
70 | if ($tableIndex['is_primary'] === 'P') { | ||
71 | $keyName = 'primary'; | ||
72 | $buffer['primary'] = true; | ||
73 | $buffer['non_unique'] = false; | ||
74 | } else { | ||
75 | $buffer['primary'] = false; | ||
76 | $buffer['non_unique'] = ! $tableIndex['is_unique']; | ||
77 | } | ||
78 | |||
79 | $buffer['key_name'] = $keyName; | ||
80 | $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']); | ||
81 | $indexBuffer[] = $buffer; | ||
82 | } | ||
83 | |||
84 | return parent::_getPortableTableIndexesList($indexBuffer, $tableName); | ||
85 | } | ||
86 | |||
87 | /** | ||
88 | * {@inheritDoc} | ||
89 | */ | ||
90 | protected function _getPortableTableColumnDefinition(array $tableColumn): Column | ||
91 | { | ||
92 | $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); | ||
93 | |||
94 | $dbType = strtolower($tableColumn['data_type']); | ||
95 | if (str_starts_with($dbType, 'timestamp(')) { | ||
96 | if (str_contains($dbType, 'with time zone')) { | ||
97 | $dbType = 'timestamptz'; | ||
98 | } else { | ||
99 | $dbType = 'timestamp'; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | $length = $precision = null; | ||
104 | $scale = 0; | ||
105 | $fixed = false; | ||
106 | |||
107 | if (! isset($tableColumn['column_name'])) { | ||
108 | $tableColumn['column_name'] = ''; | ||
109 | } | ||
110 | |||
111 | assert(array_key_exists('data_default', $tableColumn)); | ||
112 | |||
113 | // Default values returned from database sometimes have trailing spaces. | ||
114 | if (is_string($tableColumn['data_default'])) { | ||
115 | $tableColumn['data_default'] = trim($tableColumn['data_default']); | ||
116 | } | ||
117 | |||
118 | if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') { | ||
119 | $tableColumn['data_default'] = null; | ||
120 | } | ||
121 | |||
122 | if ($tableColumn['data_default'] !== null) { | ||
123 | // Default values returned from database are represented as literal expressions | ||
124 | if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches) === 1) { | ||
125 | $tableColumn['data_default'] = str_replace("''", "'", $matches[1]); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | if ($tableColumn['data_precision'] !== null) { | ||
130 | $precision = (int) $tableColumn['data_precision']; | ||
131 | } | ||
132 | |||
133 | if ($tableColumn['data_scale'] !== null) { | ||
134 | $scale = (int) $tableColumn['data_scale']; | ||
135 | } | ||
136 | |||
137 | $type = $this->platform->getDoctrineTypeMapping($dbType); | ||
138 | |||
139 | switch ($dbType) { | ||
140 | case 'number': | ||
141 | if ($precision === 20 && $scale === 0) { | ||
142 | $type = 'bigint'; | ||
143 | } elseif ($precision === 5 && $scale === 0) { | ||
144 | $type = 'smallint'; | ||
145 | } elseif ($precision === 1 && $scale === 0) { | ||
146 | $type = 'boolean'; | ||
147 | } elseif ($scale > 0) { | ||
148 | $type = 'decimal'; | ||
149 | } | ||
150 | |||
151 | break; | ||
152 | |||
153 | case 'varchar': | ||
154 | case 'varchar2': | ||
155 | case 'nvarchar2': | ||
156 | $length = (int) $tableColumn['char_length']; | ||
157 | break; | ||
158 | |||
159 | case 'raw': | ||
160 | $length = (int) $tableColumn['data_length']; | ||
161 | $fixed = true; | ||
162 | break; | ||
163 | |||
164 | case 'char': | ||
165 | case 'nchar': | ||
166 | $length = (int) $tableColumn['char_length']; | ||
167 | $fixed = true; | ||
168 | break; | ||
169 | } | ||
170 | |||
171 | $options = [ | ||
172 | 'notnull' => $tableColumn['nullable'] === 'N', | ||
173 | 'fixed' => $fixed, | ||
174 | 'default' => $tableColumn['data_default'], | ||
175 | 'length' => $length, | ||
176 | 'precision' => $precision, | ||
177 | 'scale' => $scale, | ||
178 | ]; | ||
179 | |||
180 | if (isset($tableColumn['comments'])) { | ||
181 | $options['comment'] = $tableColumn['comments']; | ||
182 | } | ||
183 | |||
184 | return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options); | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * {@inheritDoc} | ||
189 | */ | ||
190 | protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array | ||
191 | { | ||
192 | $list = []; | ||
193 | foreach ($tableForeignKeys as $value) { | ||
194 | $value = array_change_key_case($value, CASE_LOWER); | ||
195 | if (! isset($list[$value['constraint_name']])) { | ||
196 | if ($value['delete_rule'] === 'NO ACTION') { | ||
197 | $value['delete_rule'] = null; | ||
198 | } | ||
199 | |||
200 | $list[$value['constraint_name']] = [ | ||
201 | 'name' => $this->getQuotedIdentifierName($value['constraint_name']), | ||
202 | 'local' => [], | ||
203 | 'foreign' => [], | ||
204 | 'foreignTable' => $value['references_table'], | ||
205 | 'onDelete' => $value['delete_rule'], | ||
206 | ]; | ||
207 | } | ||
208 | |||
209 | $localColumn = $this->getQuotedIdentifierName($value['local_column']); | ||
210 | $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']); | ||
211 | |||
212 | $list[$value['constraint_name']]['local'][$value['position']] = $localColumn; | ||
213 | $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn; | ||
214 | } | ||
215 | |||
216 | return parent::_getPortableTableForeignKeysList($list); | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * {@inheritDoc} | ||
221 | */ | ||
222 | protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint | ||
223 | { | ||
224 | return new ForeignKeyConstraint( | ||
225 | array_values($tableForeignKey['local']), | ||
226 | $this->getQuotedIdentifierName($tableForeignKey['foreignTable']), | ||
227 | array_values($tableForeignKey['foreign']), | ||
228 | $this->getQuotedIdentifierName($tableForeignKey['name']), | ||
229 | ['onDelete' => $tableForeignKey['onDelete']], | ||
230 | ); | ||
231 | } | ||
232 | |||
233 | /** | ||
234 | * {@inheritDoc} | ||
235 | */ | ||
236 | protected function _getPortableSequenceDefinition(array $sequence): Sequence | ||
237 | { | ||
238 | $sequence = array_change_key_case($sequence, CASE_LOWER); | ||
239 | |||
240 | return new Sequence( | ||
241 | $this->getQuotedIdentifierName($sequence['sequence_name']), | ||
242 | (int) $sequence['increment_by'], | ||
243 | (int) $sequence['min_value'], | ||
244 | ); | ||
245 | } | ||
246 | |||
247 | /** | ||
248 | * {@inheritDoc} | ||
249 | */ | ||
250 | protected function _getPortableDatabaseDefinition(array $database): string | ||
251 | { | ||
252 | $database = array_change_key_case($database, CASE_LOWER); | ||
253 | |||
254 | return $database['username']; | ||
255 | } | ||
256 | |||
257 | public function createDatabase(string $database): void | ||
258 | { | ||
259 | $statement = $this->platform->getCreateDatabaseSQL($database); | ||
260 | |||
261 | $params = $this->connection->getParams(); | ||
262 | |||
263 | if (isset($params['password'])) { | ||
264 | $statement .= ' IDENTIFIED BY ' . $params['password']; | ||
265 | } | ||
266 | |||
267 | $this->connection->executeStatement($statement); | ||
268 | |||
269 | $statement = 'GRANT DBA TO ' . $database; | ||
270 | $this->connection->executeStatement($statement); | ||
271 | } | ||
272 | |||
273 | /** @throws Exception */ | ||
274 | protected function dropAutoincrement(string $table): bool | ||
275 | { | ||
276 | $sql = $this->platform->getDropAutoincrementSql($table); | ||
277 | foreach ($sql as $query) { | ||
278 | $this->connection->executeStatement($query); | ||
279 | } | ||
280 | |||
281 | return true; | ||
282 | } | ||
283 | |||
284 | public function dropTable(string $name): void | ||
285 | { | ||
286 | try { | ||
287 | $this->dropAutoincrement($name); | ||
288 | } catch (DatabaseObjectNotFoundException) { | ||
289 | } | ||
290 | |||
291 | parent::dropTable($name); | ||
292 | } | ||
293 | |||
294 | /** | ||
295 | * Returns the quoted representation of the given identifier name. | ||
296 | * | ||
297 | * Quotes non-uppercase identifiers explicitly to preserve case | ||
298 | * and thus make references to the particular identifier work. | ||
299 | */ | ||
300 | private function getQuotedIdentifierName(string $identifier): string | ||
301 | { | ||
302 | if (preg_match('/[a-z]/', $identifier) === 1) { | ||
303 | return $this->platform->quoteIdentifier($identifier); | ||
304 | } | ||
305 | |||
306 | return $identifier; | ||
307 | } | ||
308 | |||
309 | protected function selectTableNames(string $databaseName): Result | ||
310 | { | ||
311 | $sql = <<<'SQL' | ||
312 | SELECT TABLE_NAME | ||
313 | FROM ALL_TABLES | ||
314 | WHERE OWNER = :OWNER | ||
315 | ORDER BY TABLE_NAME | ||
316 | SQL; | ||
317 | |||
318 | return $this->connection->executeQuery($sql, ['OWNER' => $databaseName]); | ||
319 | } | ||
320 | |||
321 | protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result | ||
322 | { | ||
323 | $sql = 'SELECT'; | ||
324 | |||
325 | if ($tableName === null) { | ||
326 | $sql .= ' C.TABLE_NAME,'; | ||
327 | } | ||
328 | |||
329 | $sql .= <<<'SQL' | ||
330 | C.COLUMN_NAME, | ||
331 | C.DATA_TYPE, | ||
332 | C.DATA_DEFAULT, | ||
333 | C.DATA_PRECISION, | ||
334 | C.DATA_SCALE, | ||
335 | C.CHAR_LENGTH, | ||
336 | C.DATA_LENGTH, | ||
337 | C.NULLABLE, | ||
338 | D.COMMENTS | ||
339 | FROM ALL_TAB_COLUMNS C | ||
340 | INNER JOIN ALL_TABLES T | ||
341 | ON T.OWNER = C.OWNER | ||
342 | AND T.TABLE_NAME = C.TABLE_NAME | ||
343 | LEFT JOIN ALL_COL_COMMENTS D | ||
344 | ON D.OWNER = C.OWNER | ||
345 | AND D.TABLE_NAME = C.TABLE_NAME | ||
346 | AND D.COLUMN_NAME = C.COLUMN_NAME | ||
347 | SQL; | ||
348 | |||
349 | $conditions = ['C.OWNER = :OWNER']; | ||
350 | $params = ['OWNER' => $databaseName]; | ||
351 | |||
352 | if ($tableName !== null) { | ||
353 | $conditions[] = 'C.TABLE_NAME = :TABLE_NAME'; | ||
354 | $params['TABLE_NAME'] = $tableName; | ||
355 | } | ||
356 | |||
357 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.COLUMN_ID'; | ||
358 | |||
359 | return $this->connection->executeQuery($sql, $params); | ||
360 | } | ||
361 | |||
362 | protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result | ||
363 | { | ||
364 | $sql = 'SELECT'; | ||
365 | |||
366 | if ($tableName === null) { | ||
367 | $sql .= ' IND_COL.TABLE_NAME,'; | ||
368 | } | ||
369 | |||
370 | $sql .= <<<'SQL' | ||
371 | IND_COL.INDEX_NAME AS NAME, | ||
372 | IND.INDEX_TYPE AS TYPE, | ||
373 | DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE, | ||
374 | IND_COL.COLUMN_NAME, | ||
375 | IND_COL.COLUMN_POSITION AS COLUMN_POS, | ||
376 | CON.CONSTRAINT_TYPE AS IS_PRIMARY | ||
377 | FROM ALL_IND_COLUMNS IND_COL | ||
378 | LEFT JOIN ALL_INDEXES IND | ||
379 | ON IND.OWNER = IND_COL.INDEX_OWNER | ||
380 | AND IND.INDEX_NAME = IND_COL.INDEX_NAME | ||
381 | LEFT JOIN ALL_CONSTRAINTS CON | ||
382 | ON CON.OWNER = IND_COL.INDEX_OWNER | ||
383 | AND CON.INDEX_NAME = IND_COL.INDEX_NAME | ||
384 | SQL; | ||
385 | |||
386 | $conditions = ['IND_COL.INDEX_OWNER = :OWNER']; | ||
387 | $params = ['OWNER' => $databaseName]; | ||
388 | |||
389 | if ($tableName !== null) { | ||
390 | $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME'; | ||
391 | $params['TABLE_NAME'] = $tableName; | ||
392 | } | ||
393 | |||
394 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME' | ||
395 | . ', IND_COL.COLUMN_POSITION'; | ||
396 | |||
397 | return $this->connection->executeQuery($sql, $params); | ||
398 | } | ||
399 | |||
400 | protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result | ||
401 | { | ||
402 | $sql = 'SELECT'; | ||
403 | |||
404 | if ($tableName === null) { | ||
405 | $sql .= ' COLS.TABLE_NAME,'; | ||
406 | } | ||
407 | |||
408 | $sql .= <<<'SQL' | ||
409 | ALC.CONSTRAINT_NAME, | ||
410 | ALC.DELETE_RULE, | ||
411 | COLS.COLUMN_NAME LOCAL_COLUMN, | ||
412 | COLS.POSITION, | ||
413 | R_COLS.TABLE_NAME REFERENCES_TABLE, | ||
414 | R_COLS.COLUMN_NAME FOREIGN_COLUMN | ||
415 | FROM ALL_CONS_COLUMNS COLS | ||
416 | LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME | ||
417 | LEFT JOIN ALL_CONS_COLUMNS R_COLS ON R_COLS.OWNER = ALC.R_OWNER AND | ||
418 | R_COLS.CONSTRAINT_NAME = ALC.R_CONSTRAINT_NAME AND | ||
419 | R_COLS.POSITION = COLS.POSITION | ||
420 | SQL; | ||
421 | |||
422 | $conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER']; | ||
423 | $params = ['OWNER' => $databaseName]; | ||
424 | |||
425 | if ($tableName !== null) { | ||
426 | $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME'; | ||
427 | $params['TABLE_NAME'] = $tableName; | ||
428 | } | ||
429 | |||
430 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME' | ||
431 | . ', COLS.POSITION'; | ||
432 | |||
433 | return $this->connection->executeQuery($sql, $params); | ||
434 | } | ||
435 | |||
436 | /** | ||
437 | * {@inheritDoc} | ||
438 | */ | ||
439 | protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array | ||
440 | { | ||
441 | $sql = 'SELECT TABLE_NAME, COMMENTS'; | ||
442 | |||
443 | $conditions = ['OWNER = :OWNER']; | ||
444 | $params = ['OWNER' => $databaseName]; | ||
445 | |||
446 | if ($tableName !== null) { | ||
447 | $conditions[] = 'TABLE_NAME = :TABLE_NAME'; | ||
448 | $params['TABLE_NAME'] = $tableName; | ||
449 | } | ||
450 | |||
451 | $sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions); | ||
452 | |||
453 | /** @var array<string,array<string,mixed>> $metadata */ | ||
454 | $metadata = $this->connection->executeQuery($sql, $params) | ||
455 | ->fetchAllAssociativeIndexed(); | ||
456 | |||
457 | $tableOptions = []; | ||
458 | foreach ($metadata as $table => $data) { | ||
459 | $data = array_change_key_case($data, CASE_LOWER); | ||
460 | |||
461 | $tableOptions[$table] = [ | ||
462 | 'comment' => $data['comments'], | ||
463 | ]; | ||
464 | } | ||
465 | |||
466 | return $tableOptions; | ||
467 | } | ||
468 | |||
469 | protected function normalizeName(string $name): string | ||
470 | { | ||
471 | $identifier = new Identifier($name); | ||
472 | |||
473 | return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name); | ||
474 | } | ||
475 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php b/vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php new file mode 100644 index 0000000..9af16c9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/PostgreSQLSchemaManager.php | |||
@@ -0,0 +1,572 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Exception; | ||
8 | use Doctrine\DBAL\Platforms\PostgreSQLPlatform; | ||
9 | use Doctrine\DBAL\Result; | ||
10 | use Doctrine\DBAL\Types\JsonType; | ||
11 | use Doctrine\DBAL\Types\Type; | ||
12 | |||
13 | use function array_change_key_case; | ||
14 | use function array_key_exists; | ||
15 | use function array_map; | ||
16 | use function array_merge; | ||
17 | use function assert; | ||
18 | use function explode; | ||
19 | use function implode; | ||
20 | use function in_array; | ||
21 | use function is_string; | ||
22 | use function preg_match; | ||
23 | use function sprintf; | ||
24 | use function str_contains; | ||
25 | use function str_replace; | ||
26 | use function strtolower; | ||
27 | use function trim; | ||
28 | |||
29 | use const CASE_LOWER; | ||
30 | |||
31 | /** | ||
32 | * PostgreSQL Schema Manager. | ||
33 | * | ||
34 | * @extends AbstractSchemaManager<PostgreSQLPlatform> | ||
35 | */ | ||
36 | class PostgreSQLSchemaManager extends AbstractSchemaManager | ||
37 | { | ||
38 | private ?string $currentSchema = null; | ||
39 | |||
40 | /** | ||
41 | * {@inheritDoc} | ||
42 | */ | ||
43 | public function listSchemaNames(): array | ||
44 | { | ||
45 | return $this->connection->fetchFirstColumn( | ||
46 | <<<'SQL' | ||
47 | SELECT schema_name | ||
48 | FROM information_schema.schemata | ||
49 | WHERE schema_name NOT LIKE 'pg\_%' | ||
50 | AND schema_name != 'information_schema' | ||
51 | SQL, | ||
52 | ); | ||
53 | } | ||
54 | |||
55 | public function createSchemaConfig(): SchemaConfig | ||
56 | { | ||
57 | $config = parent::createSchemaConfig(); | ||
58 | |||
59 | $config->setName($this->getCurrentSchema()); | ||
60 | |||
61 | return $config; | ||
62 | } | ||
63 | |||
64 | /** | ||
65 | * Returns the name of the current schema. | ||
66 | * | ||
67 | * @throws Exception | ||
68 | */ | ||
69 | protected function getCurrentSchema(): ?string | ||
70 | { | ||
71 | return $this->currentSchema ??= $this->determineCurrentSchema(); | ||
72 | } | ||
73 | |||
74 | /** | ||
75 | * Determines the name of the current schema. | ||
76 | * | ||
77 | * @throws Exception | ||
78 | */ | ||
79 | protected function determineCurrentSchema(): string | ||
80 | { | ||
81 | $currentSchema = $this->connection->fetchOne('SELECT current_schema()'); | ||
82 | assert(is_string($currentSchema)); | ||
83 | |||
84 | return $currentSchema; | ||
85 | } | ||
86 | |||
87 | /** | ||
88 | * {@inheritDoc} | ||
89 | */ | ||
90 | protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint | ||
91 | { | ||
92 | $onUpdate = null; | ||
93 | $onDelete = null; | ||
94 | |||
95 | if ( | ||
96 | preg_match( | ||
97 | '(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', | ||
98 | $tableForeignKey['condef'], | ||
99 | $match, | ||
100 | ) === 1 | ||
101 | ) { | ||
102 | $onUpdate = $match[1]; | ||
103 | } | ||
104 | |||
105 | if ( | ||
106 | preg_match( | ||
107 | '(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', | ||
108 | $tableForeignKey['condef'], | ||
109 | $match, | ||
110 | ) === 1 | ||
111 | ) { | ||
112 | $onDelete = $match[1]; | ||
113 | } | ||
114 | |||
115 | $result = preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values); | ||
116 | assert($result === 1); | ||
117 | |||
118 | // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get | ||
119 | // the idea to trim them here. | ||
120 | $localColumns = array_map('trim', explode(',', $values[1])); | ||
121 | $foreignColumns = array_map('trim', explode(',', $values[3])); | ||
122 | $foreignTable = $values[2]; | ||
123 | |||
124 | return new ForeignKeyConstraint( | ||
125 | $localColumns, | ||
126 | $foreignTable, | ||
127 | $foreignColumns, | ||
128 | $tableForeignKey['conname'], | ||
129 | ['onUpdate' => $onUpdate, 'onDelete' => $onDelete], | ||
130 | ); | ||
131 | } | ||
132 | |||
133 | /** | ||
134 | * {@inheritDoc} | ||
135 | */ | ||
136 | protected function _getPortableViewDefinition(array $view): View | ||
137 | { | ||
138 | return new View($view['schemaname'] . '.' . $view['viewname'], $view['definition']); | ||
139 | } | ||
140 | |||
141 | /** | ||
142 | * {@inheritDoc} | ||
143 | */ | ||
144 | protected function _getPortableTableDefinition(array $table): string | ||
145 | { | ||
146 | $currentSchema = $this->getCurrentSchema(); | ||
147 | |||
148 | if ($table['schema_name'] === $currentSchema) { | ||
149 | return $table['table_name']; | ||
150 | } | ||
151 | |||
152 | return $table['schema_name'] . '.' . $table['table_name']; | ||
153 | } | ||
154 | |||
155 | /** | ||
156 | * {@inheritDoc} | ||
157 | * | ||
158 | * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html | ||
159 | */ | ||
160 | protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array | ||
161 | { | ||
162 | $buffer = []; | ||
163 | foreach ($tableIndexes as $row) { | ||
164 | $colNumbers = array_map('intval', explode(' ', $row['indkey'])); | ||
165 | $columnNameSql = sprintf( | ||
166 | 'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC', | ||
167 | $row['indrelid'], | ||
168 | implode(' ,', $colNumbers), | ||
169 | ); | ||
170 | |||
171 | $indexColumns = $this->connection->fetchAllAssociative($columnNameSql); | ||
172 | |||
173 | // required for getting the order of the columns right. | ||
174 | foreach ($colNumbers as $colNum) { | ||
175 | foreach ($indexColumns as $colRow) { | ||
176 | if ($colNum !== $colRow['attnum']) { | ||
177 | continue; | ||
178 | } | ||
179 | |||
180 | $buffer[] = [ | ||
181 | 'key_name' => $row['relname'], | ||
182 | 'column_name' => trim($colRow['attname']), | ||
183 | 'non_unique' => ! $row['indisunique'], | ||
184 | 'primary' => $row['indisprimary'], | ||
185 | 'where' => $row['where'], | ||
186 | ]; | ||
187 | } | ||
188 | } | ||
189 | } | ||
190 | |||
191 | return parent::_getPortableTableIndexesList($buffer, $tableName); | ||
192 | } | ||
193 | |||
194 | /** | ||
195 | * {@inheritDoc} | ||
196 | */ | ||
197 | protected function _getPortableDatabaseDefinition(array $database): string | ||
198 | { | ||
199 | return $database['datname']; | ||
200 | } | ||
201 | |||
202 | /** | ||
203 | * {@inheritDoc} | ||
204 | */ | ||
205 | protected function _getPortableSequenceDefinition(array $sequence): Sequence | ||
206 | { | ||
207 | if ($sequence['schemaname'] !== 'public') { | ||
208 | $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname']; | ||
209 | } else { | ||
210 | $sequenceName = $sequence['relname']; | ||
211 | } | ||
212 | |||
213 | return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']); | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * {@inheritDoc} | ||
218 | */ | ||
219 | protected function _getPortableTableColumnDefinition(array $tableColumn): Column | ||
220 | { | ||
221 | $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); | ||
222 | |||
223 | $length = null; | ||
224 | |||
225 | if ( | ||
226 | in_array(strtolower($tableColumn['type']), ['varchar', 'bpchar'], true) | ||
227 | && preg_match('/\((\d*)\)/', $tableColumn['complete_type'], $matches) === 1 | ||
228 | ) { | ||
229 | $length = (int) $matches[1]; | ||
230 | } | ||
231 | |||
232 | $autoincrement = $tableColumn['attidentity'] === 'd'; | ||
233 | |||
234 | $matches = []; | ||
235 | |||
236 | assert(array_key_exists('default', $tableColumn)); | ||
237 | assert(array_key_exists('complete_type', $tableColumn)); | ||
238 | |||
239 | if ($tableColumn['default'] !== null) { | ||
240 | if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches) === 1) { | ||
241 | $tableColumn['default'] = $matches[1]; | ||
242 | } elseif (preg_match('/^NULL::/', $tableColumn['default']) === 1) { | ||
243 | $tableColumn['default'] = null; | ||
244 | } | ||
245 | } | ||
246 | |||
247 | if ($length === -1 && isset($tableColumn['atttypmod'])) { | ||
248 | $length = $tableColumn['atttypmod'] - 4; | ||
249 | } | ||
250 | |||
251 | if ((int) $length <= 0) { | ||
252 | $length = null; | ||
253 | } | ||
254 | |||
255 | $fixed = false; | ||
256 | |||
257 | if (! isset($tableColumn['name'])) { | ||
258 | $tableColumn['name'] = ''; | ||
259 | } | ||
260 | |||
261 | $precision = null; | ||
262 | $scale = 0; | ||
263 | $jsonb = null; | ||
264 | |||
265 | $dbType = strtolower($tableColumn['type']); | ||
266 | if ( | ||
267 | $tableColumn['domain_type'] !== null | ||
268 | && $tableColumn['domain_type'] !== '' | ||
269 | && ! $this->platform->hasDoctrineTypeMappingFor($tableColumn['type']) | ||
270 | ) { | ||
271 | $dbType = strtolower($tableColumn['domain_type']); | ||
272 | $tableColumn['complete_type'] = $tableColumn['domain_complete_type']; | ||
273 | } | ||
274 | |||
275 | $type = $this->platform->getDoctrineTypeMapping($dbType); | ||
276 | |||
277 | switch ($dbType) { | ||
278 | case 'smallint': | ||
279 | case 'int2': | ||
280 | case 'int': | ||
281 | case 'int4': | ||
282 | case 'integer': | ||
283 | case 'bigint': | ||
284 | case 'int8': | ||
285 | $length = null; | ||
286 | break; | ||
287 | |||
288 | case 'bool': | ||
289 | case 'boolean': | ||
290 | if ($tableColumn['default'] === 'true') { | ||
291 | $tableColumn['default'] = true; | ||
292 | } | ||
293 | |||
294 | if ($tableColumn['default'] === 'false') { | ||
295 | $tableColumn['default'] = false; | ||
296 | } | ||
297 | |||
298 | $length = null; | ||
299 | break; | ||
300 | |||
301 | case 'json': | ||
302 | case 'text': | ||
303 | case '_varchar': | ||
304 | case 'varchar': | ||
305 | $tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']); | ||
306 | break; | ||
307 | |||
308 | case 'char': | ||
309 | case 'bpchar': | ||
310 | $fixed = true; | ||
311 | break; | ||
312 | |||
313 | case 'float': | ||
314 | case 'float4': | ||
315 | case 'float8': | ||
316 | case 'double': | ||
317 | case 'double precision': | ||
318 | case 'real': | ||
319 | case 'decimal': | ||
320 | case 'money': | ||
321 | case 'numeric': | ||
322 | if ( | ||
323 | preg_match( | ||
324 | '([A-Za-z]+\(([0-9]+),([0-9]+)\))', | ||
325 | $tableColumn['complete_type'], | ||
326 | $match, | ||
327 | ) === 1 | ||
328 | ) { | ||
329 | $precision = (int) $match[1]; | ||
330 | $scale = (int) $match[2]; | ||
331 | $length = null; | ||
332 | } | ||
333 | |||
334 | break; | ||
335 | |||
336 | case 'year': | ||
337 | $length = null; | ||
338 | break; | ||
339 | |||
340 | // PostgreSQL 9.4+ only | ||
341 | case 'jsonb': | ||
342 | $jsonb = true; | ||
343 | break; | ||
344 | } | ||
345 | |||
346 | if ( | ||
347 | is_string($tableColumn['default']) && preg_match( | ||
348 | "('([^']+)'::)", | ||
349 | $tableColumn['default'], | ||
350 | $match, | ||
351 | ) === 1 | ||
352 | ) { | ||
353 | $tableColumn['default'] = $match[1]; | ||
354 | } | ||
355 | |||
356 | $options = [ | ||
357 | 'length' => $length, | ||
358 | 'notnull' => (bool) $tableColumn['isnotnull'], | ||
359 | 'default' => $tableColumn['default'], | ||
360 | 'precision' => $precision, | ||
361 | 'scale' => $scale, | ||
362 | 'fixed' => $fixed, | ||
363 | 'autoincrement' => $autoincrement, | ||
364 | ]; | ||
365 | |||
366 | if (isset($tableColumn['comment'])) { | ||
367 | $options['comment'] = $tableColumn['comment']; | ||
368 | } | ||
369 | |||
370 | $column = new Column($tableColumn['field'], Type::getType($type), $options); | ||
371 | |||
372 | if (! empty($tableColumn['collation'])) { | ||
373 | $column->setPlatformOption('collation', $tableColumn['collation']); | ||
374 | } | ||
375 | |||
376 | if ($column->getType() instanceof JsonType) { | ||
377 | $column->setPlatformOption('jsonb', $jsonb); | ||
378 | } | ||
379 | |||
380 | return $column; | ||
381 | } | ||
382 | |||
383 | /** | ||
384 | * Parses a default value expression as given by PostgreSQL | ||
385 | */ | ||
386 | private function parseDefaultExpression(?string $default): ?string | ||
387 | { | ||
388 | if ($default === null) { | ||
389 | return $default; | ||
390 | } | ||
391 | |||
392 | return str_replace("''", "'", $default); | ||
393 | } | ||
394 | |||
395 | protected function selectTableNames(string $databaseName): Result | ||
396 | { | ||
397 | $sql = <<<'SQL' | ||
398 | SELECT quote_ident(table_name) AS table_name, | ||
399 | table_schema AS schema_name | ||
400 | FROM information_schema.tables | ||
401 | WHERE table_catalog = ? | ||
402 | AND table_schema NOT LIKE 'pg\_%' | ||
403 | AND table_schema != 'information_schema' | ||
404 | AND table_name != 'geometry_columns' | ||
405 | AND table_name != 'spatial_ref_sys' | ||
406 | AND table_type = 'BASE TABLE' | ||
407 | SQL; | ||
408 | |||
409 | return $this->connection->executeQuery($sql, [$databaseName]); | ||
410 | } | ||
411 | |||
412 | protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result | ||
413 | { | ||
414 | $sql = 'SELECT'; | ||
415 | |||
416 | if ($tableName === null) { | ||
417 | $sql .= ' c.relname AS table_name, n.nspname AS schema_name,'; | ||
418 | } | ||
419 | |||
420 | $sql .= <<<'SQL' | ||
421 | a.attnum, | ||
422 | quote_ident(a.attname) AS field, | ||
423 | t.typname AS type, | ||
424 | format_type(a.atttypid, a.atttypmod) AS complete_type, | ||
425 | (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation, | ||
426 | (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, | ||
427 | (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM | ||
428 | pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, | ||
429 | a.attnotnull AS isnotnull, | ||
430 | a.attidentity, | ||
431 | (SELECT 't' | ||
432 | FROM pg_index | ||
433 | WHERE c.oid = pg_index.indrelid | ||
434 | AND pg_index.indkey[0] = a.attnum | ||
435 | AND pg_index.indisprimary = 't' | ||
436 | ) AS pri, | ||
437 | (SELECT pg_get_expr(adbin, adrelid) | ||
438 | FROM pg_attrdef | ||
439 | WHERE c.oid = pg_attrdef.adrelid | ||
440 | AND pg_attrdef.adnum=a.attnum | ||
441 | ) AS default, | ||
442 | (SELECT pg_description.description | ||
443 | FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid | ||
444 | ) AS comment | ||
445 | FROM pg_attribute a | ||
446 | INNER JOIN pg_class c | ||
447 | ON c.oid = a.attrelid | ||
448 | INNER JOIN pg_type t | ||
449 | ON t.oid = a.atttypid | ||
450 | INNER JOIN pg_namespace n | ||
451 | ON n.oid = c.relnamespace | ||
452 | LEFT JOIN pg_depend d | ||
453 | ON d.objid = c.oid | ||
454 | AND d.deptype = 'e' | ||
455 | AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class') | ||
456 | SQL; | ||
457 | |||
458 | $conditions = array_merge([ | ||
459 | 'a.attnum > 0', | ||
460 | "c.relkind = 'r'", | ||
461 | 'd.refobjid IS NULL', | ||
462 | ], $this->buildQueryConditions($tableName)); | ||
463 | |||
464 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY a.attnum'; | ||
465 | |||
466 | return $this->connection->executeQuery($sql); | ||
467 | } | ||
468 | |||
469 | protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result | ||
470 | { | ||
471 | $sql = 'SELECT'; | ||
472 | |||
473 | if ($tableName === null) { | ||
474 | $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; | ||
475 | } | ||
476 | |||
477 | $sql .= <<<'SQL' | ||
478 | quote_ident(ic.relname) AS relname, | ||
479 | i.indisunique, | ||
480 | i.indisprimary, | ||
481 | i.indkey, | ||
482 | i.indrelid, | ||
483 | pg_get_expr(indpred, indrelid) AS "where" | ||
484 | FROM pg_index i | ||
485 | JOIN pg_class AS tc ON tc.oid = i.indrelid | ||
486 | JOIN pg_namespace tn ON tn.oid = tc.relnamespace | ||
487 | JOIN pg_class AS ic ON ic.oid = i.indexrelid | ||
488 | WHERE ic.oid IN ( | ||
489 | SELECT indexrelid | ||
490 | FROM pg_index i, pg_class c, pg_namespace n | ||
491 | SQL; | ||
492 | |||
493 | $conditions = array_merge([ | ||
494 | 'c.oid = i.indrelid', | ||
495 | 'c.relnamespace = n.oid', | ||
496 | ], $this->buildQueryConditions($tableName)); | ||
497 | |||
498 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ')'; | ||
499 | |||
500 | return $this->connection->executeQuery($sql); | ||
501 | } | ||
502 | |||
503 | protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result | ||
504 | { | ||
505 | $sql = 'SELECT'; | ||
506 | |||
507 | if ($tableName === null) { | ||
508 | $sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,'; | ||
509 | } | ||
510 | |||
511 | $sql .= <<<'SQL' | ||
512 | quote_ident(r.conname) as conname, | ||
513 | pg_get_constraintdef(r.oid, true) as condef | ||
514 | FROM pg_constraint r | ||
515 | JOIN pg_class AS tc ON tc.oid = r.conrelid | ||
516 | JOIN pg_namespace tn ON tn.oid = tc.relnamespace | ||
517 | WHERE r.conrelid IN | ||
518 | ( | ||
519 | SELECT c.oid | ||
520 | FROM pg_class c, pg_namespace n | ||
521 | SQL; | ||
522 | |||
523 | $conditions = array_merge(['n.oid = c.relnamespace'], $this->buildQueryConditions($tableName)); | ||
524 | |||
525 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ") AND r.contype = 'f'"; | ||
526 | |||
527 | return $this->connection->executeQuery($sql); | ||
528 | } | ||
529 | |||
530 | /** | ||
531 | * {@inheritDoc} | ||
532 | */ | ||
533 | protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array | ||
534 | { | ||
535 | $sql = <<<'SQL' | ||
536 | SELECT c.relname, | ||
537 | CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged, | ||
538 | obj_description(c.oid, 'pg_class') AS comment | ||
539 | FROM pg_class c | ||
540 | INNER JOIN pg_namespace n | ||
541 | ON n.oid = c.relnamespace | ||
542 | SQL; | ||
543 | |||
544 | $conditions = array_merge(["c.relkind = 'r'"], $this->buildQueryConditions($tableName)); | ||
545 | |||
546 | $sql .= ' WHERE ' . implode(' AND ', $conditions); | ||
547 | |||
548 | return $this->connection->fetchAllAssociativeIndexed($sql); | ||
549 | } | ||
550 | |||
551 | /** @return list<string> */ | ||
552 | private function buildQueryConditions(?string $tableName): array | ||
553 | { | ||
554 | $conditions = []; | ||
555 | |||
556 | if ($tableName !== null) { | ||
557 | if (str_contains($tableName, '.')) { | ||
558 | [$schemaName, $tableName] = explode('.', $tableName); | ||
559 | $conditions[] = 'n.nspname = ' . $this->platform->quoteStringLiteral($schemaName); | ||
560 | } else { | ||
561 | $conditions[] = 'n.nspname = ANY(current_schemas(false))'; | ||
562 | } | ||
563 | |||
564 | $identifier = new Identifier($tableName); | ||
565 | $conditions[] = 'c.relname = ' . $this->platform->quoteStringLiteral($identifier->getName()); | ||
566 | } | ||
567 | |||
568 | $conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')"; | ||
569 | |||
570 | return $conditions; | ||
571 | } | ||
572 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php b/vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php new file mode 100644 index 0000000..e0a74ce --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SQLServerSchemaManager.php | |||
@@ -0,0 +1,498 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Exception; | ||
8 | use Doctrine\DBAL\Platforms\SQLServer; | ||
9 | use Doctrine\DBAL\Platforms\SQLServerPlatform; | ||
10 | use Doctrine\DBAL\Result; | ||
11 | use Doctrine\DBAL\Types\Type; | ||
12 | |||
13 | use function array_change_key_case; | ||
14 | use function assert; | ||
15 | use function explode; | ||
16 | use function implode; | ||
17 | use function is_string; | ||
18 | use function preg_match; | ||
19 | use function sprintf; | ||
20 | use function str_contains; | ||
21 | use function str_replace; | ||
22 | use function strtok; | ||
23 | |||
24 | use const CASE_LOWER; | ||
25 | |||
26 | /** | ||
27 | * SQL Server Schema Manager. | ||
28 | * | ||
29 | * @extends AbstractSchemaManager<SQLServerPlatform> | ||
30 | */ | ||
31 | class SQLServerSchemaManager extends AbstractSchemaManager | ||
32 | { | ||
33 | private ?string $databaseCollation = null; | ||
34 | |||
35 | /** | ||
36 | * {@inheritDoc} | ||
37 | */ | ||
38 | public function listSchemaNames(): array | ||
39 | { | ||
40 | return $this->connection->fetchFirstColumn( | ||
41 | <<<'SQL' | ||
42 | SELECT name | ||
43 | FROM sys.schemas | ||
44 | WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys') | ||
45 | SQL, | ||
46 | ); | ||
47 | } | ||
48 | |||
49 | /** | ||
50 | * {@inheritDoc} | ||
51 | */ | ||
52 | protected function _getPortableSequenceDefinition(array $sequence): Sequence | ||
53 | { | ||
54 | return new Sequence($sequence['name'], (int) $sequence['increment'], (int) $sequence['start_value']); | ||
55 | } | ||
56 | |||
57 | /** | ||
58 | * {@inheritDoc} | ||
59 | */ | ||
60 | protected function _getPortableTableColumnDefinition(array $tableColumn): Column | ||
61 | { | ||
62 | $dbType = strtok($tableColumn['type'], '(), '); | ||
63 | assert(is_string($dbType)); | ||
64 | |||
65 | $length = (int) $tableColumn['length']; | ||
66 | |||
67 | $precision = null; | ||
68 | |||
69 | $scale = 0; | ||
70 | $fixed = false; | ||
71 | |||
72 | if (! isset($tableColumn['name'])) { | ||
73 | $tableColumn['name'] = ''; | ||
74 | } | ||
75 | |||
76 | if ($tableColumn['scale'] !== null) { | ||
77 | $scale = (int) $tableColumn['scale']; | ||
78 | } | ||
79 | |||
80 | if ($tableColumn['precision'] !== null) { | ||
81 | $precision = (int) $tableColumn['precision']; | ||
82 | } | ||
83 | |||
84 | switch ($dbType) { | ||
85 | case 'nchar': | ||
86 | case 'ntext': | ||
87 | // Unicode data requires 2 bytes per character | ||
88 | $length /= 2; | ||
89 | break; | ||
90 | |||
91 | case 'nvarchar': | ||
92 | if ($length === -1) { | ||
93 | break; | ||
94 | } | ||
95 | |||
96 | // Unicode data requires 2 bytes per character | ||
97 | $length /= 2; | ||
98 | break; | ||
99 | |||
100 | case 'varchar': | ||
101 | // TEXT type is returned as VARCHAR(MAX) with a length of -1 | ||
102 | if ($length === -1) { | ||
103 | $dbType = 'text'; | ||
104 | } | ||
105 | |||
106 | break; | ||
107 | |||
108 | case 'varbinary': | ||
109 | if ($length === -1) { | ||
110 | $dbType = 'blob'; | ||
111 | } | ||
112 | |||
113 | break; | ||
114 | } | ||
115 | |||
116 | if ($dbType === 'char' || $dbType === 'nchar' || $dbType === 'binary') { | ||
117 | $fixed = true; | ||
118 | } | ||
119 | |||
120 | $type = $this->platform->getDoctrineTypeMapping($dbType); | ||
121 | |||
122 | $options = [ | ||
123 | 'fixed' => $fixed, | ||
124 | 'notnull' => (bool) $tableColumn['notnull'], | ||
125 | 'scale' => $scale, | ||
126 | 'precision' => $precision, | ||
127 | 'autoincrement' => (bool) $tableColumn['autoincrement'], | ||
128 | ]; | ||
129 | |||
130 | if (isset($tableColumn['comment'])) { | ||
131 | $options['comment'] = $tableColumn['comment']; | ||
132 | } | ||
133 | |||
134 | if ($length !== 0 && ($type === 'text' || $type === 'string' || $type === 'binary')) { | ||
135 | $options['length'] = $length; | ||
136 | } | ||
137 | |||
138 | $column = new Column($tableColumn['name'], Type::getType($type), $options); | ||
139 | |||
140 | if ($tableColumn['default'] !== null) { | ||
141 | $default = $this->parseDefaultExpression($tableColumn['default']); | ||
142 | |||
143 | $column->setDefault($default); | ||
144 | $column->setPlatformOption( | ||
145 | SQLServerPlatform::OPTION_DEFAULT_CONSTRAINT_NAME, | ||
146 | $tableColumn['df_name'], | ||
147 | ); | ||
148 | } | ||
149 | |||
150 | if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') { | ||
151 | $column->setPlatformOption('collation', $tableColumn['collation']); | ||
152 | } | ||
153 | |||
154 | return $column; | ||
155 | } | ||
156 | |||
157 | private function parseDefaultExpression(string $value): ?string | ||
158 | { | ||
159 | while (preg_match('/^\((.*)\)$/s', $value, $matches)) { | ||
160 | $value = $matches[1]; | ||
161 | } | ||
162 | |||
163 | if ($value === 'NULL') { | ||
164 | return null; | ||
165 | } | ||
166 | |||
167 | if (preg_match('/^\'(.*)\'$/s', $value, $matches) === 1) { | ||
168 | $value = str_replace("''", "'", $matches[1]); | ||
169 | } | ||
170 | |||
171 | if ($value === 'getdate()') { | ||
172 | return $this->platform->getCurrentTimestampSQL(); | ||
173 | } | ||
174 | |||
175 | return $value; | ||
176 | } | ||
177 | |||
178 | /** | ||
179 | * {@inheritDoc} | ||
180 | */ | ||
181 | protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array | ||
182 | { | ||
183 | $foreignKeys = []; | ||
184 | |||
185 | foreach ($tableForeignKeys as $tableForeignKey) { | ||
186 | $name = $tableForeignKey['ForeignKey']; | ||
187 | |||
188 | if (! isset($foreignKeys[$name])) { | ||
189 | $foreignKeys[$name] = [ | ||
190 | 'local_columns' => [$tableForeignKey['ColumnName']], | ||
191 | 'foreign_table' => $tableForeignKey['ReferenceTableName'], | ||
192 | 'foreign_columns' => [$tableForeignKey['ReferenceColumnName']], | ||
193 | 'name' => $name, | ||
194 | 'options' => [ | ||
195 | 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']), | ||
196 | 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']), | ||
197 | ], | ||
198 | ]; | ||
199 | } else { | ||
200 | $foreignKeys[$name]['local_columns'][] = $tableForeignKey['ColumnName']; | ||
201 | $foreignKeys[$name]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName']; | ||
202 | } | ||
203 | } | ||
204 | |||
205 | return parent::_getPortableTableForeignKeysList($foreignKeys); | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * {@inheritDoc} | ||
210 | */ | ||
211 | protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array | ||
212 | { | ||
213 | foreach ($tableIndexes as &$tableIndex) { | ||
214 | $tableIndex['non_unique'] = (bool) $tableIndex['non_unique']; | ||
215 | $tableIndex['primary'] = (bool) $tableIndex['primary']; | ||
216 | $tableIndex['flags'] = $tableIndex['flags'] ? [$tableIndex['flags']] : null; | ||
217 | } | ||
218 | |||
219 | return parent::_getPortableTableIndexesList($tableIndexes, $tableName); | ||
220 | } | ||
221 | |||
222 | /** | ||
223 | * {@inheritDoc} | ||
224 | */ | ||
225 | protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint | ||
226 | { | ||
227 | return new ForeignKeyConstraint( | ||
228 | $tableForeignKey['local_columns'], | ||
229 | $tableForeignKey['foreign_table'], | ||
230 | $tableForeignKey['foreign_columns'], | ||
231 | $tableForeignKey['name'], | ||
232 | $tableForeignKey['options'], | ||
233 | ); | ||
234 | } | ||
235 | |||
236 | /** | ||
237 | * {@inheritDoc} | ||
238 | */ | ||
239 | protected function _getPortableTableDefinition(array $table): string | ||
240 | { | ||
241 | if ($table['schema_name'] !== 'dbo') { | ||
242 | return $table['schema_name'] . '.' . $table['table_name']; | ||
243 | } | ||
244 | |||
245 | return $table['table_name']; | ||
246 | } | ||
247 | |||
248 | /** | ||
249 | * {@inheritDoc} | ||
250 | */ | ||
251 | protected function _getPortableDatabaseDefinition(array $database): string | ||
252 | { | ||
253 | return $database['name']; | ||
254 | } | ||
255 | |||
256 | /** | ||
257 | * {@inheritDoc} | ||
258 | */ | ||
259 | protected function _getPortableViewDefinition(array $view): View | ||
260 | { | ||
261 | return new View($view['name'], $view['definition']); | ||
262 | } | ||
263 | |||
264 | /** @throws Exception */ | ||
265 | public function createComparator(): Comparator | ||
266 | { | ||
267 | return new SQLServer\Comparator($this->platform, $this->getDatabaseCollation()); | ||
268 | } | ||
269 | |||
270 | /** @throws Exception */ | ||
271 | private function getDatabaseCollation(): string | ||
272 | { | ||
273 | if ($this->databaseCollation === null) { | ||
274 | $databaseCollation = $this->connection->fetchOne( | ||
275 | 'SELECT collation_name FROM sys.databases WHERE name = ' | ||
276 | . $this->platform->getCurrentDatabaseExpression(), | ||
277 | ); | ||
278 | |||
279 | // a database is always selected, even if omitted in the connection parameters | ||
280 | assert(is_string($databaseCollation)); | ||
281 | |||
282 | $this->databaseCollation = $databaseCollation; | ||
283 | } | ||
284 | |||
285 | return $this->databaseCollation; | ||
286 | } | ||
287 | |||
288 | protected function selectTableNames(string $databaseName): Result | ||
289 | { | ||
290 | // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams | ||
291 | $sql = <<<'SQL' | ||
292 | SELECT name AS table_name, | ||
293 | SCHEMA_NAME(schema_id) AS schema_name | ||
294 | FROM sys.objects | ||
295 | WHERE type = 'U' | ||
296 | AND name != 'sysdiagrams' | ||
297 | ORDER BY name | ||
298 | SQL; | ||
299 | |||
300 | return $this->connection->executeQuery($sql); | ||
301 | } | ||
302 | |||
303 | protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result | ||
304 | { | ||
305 | $sql = 'SELECT'; | ||
306 | |||
307 | if ($tableName === null) { | ||
308 | $sql .= ' obj.name AS table_name, scm.name AS schema_name,'; | ||
309 | } | ||
310 | |||
311 | $sql .= <<<'SQL' | ||
312 | col.name, | ||
313 | type.name AS type, | ||
314 | col.max_length AS length, | ||
315 | ~col.is_nullable AS notnull, | ||
316 | def.definition AS [default], | ||
317 | def.name AS df_name, | ||
318 | col.scale, | ||
319 | col.precision, | ||
320 | col.is_identity AS autoincrement, | ||
321 | col.collation_name AS collation, | ||
322 | -- CAST avoids driver error for sql_variant type | ||
323 | CAST(prop.value AS NVARCHAR(MAX)) AS comment | ||
324 | FROM sys.columns AS col | ||
325 | JOIN sys.types AS type | ||
326 | ON col.user_type_id = type.user_type_id | ||
327 | JOIN sys.objects AS obj | ||
328 | ON col.object_id = obj.object_id | ||
329 | JOIN sys.schemas AS scm | ||
330 | ON obj.schema_id = scm.schema_id | ||
331 | LEFT JOIN sys.default_constraints def | ||
332 | ON col.default_object_id = def.object_id | ||
333 | AND col.object_id = def.parent_object_id | ||
334 | LEFT JOIN sys.extended_properties AS prop | ||
335 | ON obj.object_id = prop.major_id | ||
336 | AND col.column_id = prop.minor_id | ||
337 | AND prop.name = 'MS_Description' | ||
338 | SQL; | ||
339 | |||
340 | // The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams | ||
341 | $conditions = ["obj.type = 'U'", "obj.name != 'sysdiagrams'"]; | ||
342 | $params = []; | ||
343 | |||
344 | if ($tableName !== null) { | ||
345 | $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'obj.name'); | ||
346 | } | ||
347 | |||
348 | $sql .= ' WHERE ' . implode(' AND ', $conditions); | ||
349 | |||
350 | return $this->connection->executeQuery($sql, $params); | ||
351 | } | ||
352 | |||
353 | protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result | ||
354 | { | ||
355 | $sql = 'SELECT'; | ||
356 | |||
357 | if ($tableName === null) { | ||
358 | $sql .= ' tbl.name AS table_name, scm.name AS schema_name,'; | ||
359 | } | ||
360 | |||
361 | $sql .= <<<'SQL' | ||
362 | idx.name AS key_name, | ||
363 | col.name AS column_name, | ||
364 | ~idx.is_unique AS non_unique, | ||
365 | idx.is_primary_key AS [primary], | ||
366 | CASE idx.type | ||
367 | WHEN '1' THEN 'clustered' | ||
368 | WHEN '2' THEN 'nonclustered' | ||
369 | ELSE NULL | ||
370 | END AS flags | ||
371 | FROM sys.tables AS tbl | ||
372 | JOIN sys.schemas AS scm | ||
373 | ON tbl.schema_id = scm.schema_id | ||
374 | JOIN sys.indexes AS idx | ||
375 | ON tbl.object_id = idx.object_id | ||
376 | JOIN sys.index_columns AS idxcol | ||
377 | ON idx.object_id = idxcol.object_id | ||
378 | AND idx.index_id = idxcol.index_id | ||
379 | JOIN sys.columns AS col | ||
380 | ON idxcol.object_id = col.object_id | ||
381 | AND idxcol.column_id = col.column_id | ||
382 | SQL; | ||
383 | |||
384 | $conditions = []; | ||
385 | $params = []; | ||
386 | |||
387 | if ($tableName !== null) { | ||
388 | $conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name'); | ||
389 | $sql .= ' WHERE ' . implode(' AND ', $conditions); | ||
390 | } | ||
391 | |||
392 | $sql .= ' ORDER BY idx.index_id, idxcol.key_ordinal'; | ||
393 | |||
394 | return $this->connection->executeQuery($sql, $params); | ||
395 | } | ||
396 | |||
397 | protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result | ||
398 | { | ||
399 | $sql = 'SELECT'; | ||
400 | |||
401 | if ($tableName === null) { | ||
402 | $sql .= ' OBJECT_NAME (f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,'; | ||
403 | } | ||
404 | |||
405 | $sql .= <<<'SQL' | ||
406 | f.name AS ForeignKey, | ||
407 | SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, | ||
408 | OBJECT_NAME (f.parent_object_id) AS TableName, | ||
409 | COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, | ||
410 | SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, | ||
411 | OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, | ||
412 | COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, | ||
413 | f.delete_referential_action_desc, | ||
414 | f.update_referential_action_desc | ||
415 | FROM sys.foreign_keys AS f | ||
416 | INNER JOIN sys.foreign_key_columns AS fc | ||
417 | INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id | ||
418 | ON f.OBJECT_ID = fc.constraint_object_id | ||
419 | SQL; | ||
420 | |||
421 | $conditions = []; | ||
422 | $params = []; | ||
423 | |||
424 | if ($tableName !== null) { | ||
425 | $conditions[] = $this->getTableWhereClause( | ||
426 | $tableName, | ||
427 | 'SCHEMA_NAME(f.schema_id)', | ||
428 | 'OBJECT_NAME(f.parent_object_id)', | ||
429 | ); | ||
430 | |||
431 | $sql .= ' WHERE ' . implode(' AND ', $conditions); | ||
432 | } | ||
433 | |||
434 | $sql .= ' ORDER BY fc.constraint_column_id'; | ||
435 | |||
436 | return $this->connection->executeQuery($sql, $params); | ||
437 | } | ||
438 | |||
439 | /** | ||
440 | * {@inheritDoc} | ||
441 | */ | ||
442 | protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array | ||
443 | { | ||
444 | $sql = <<<'SQL' | ||
445 | SELECT | ||
446 | tbl.name, | ||
447 | p.value AS [table_comment] | ||
448 | FROM | ||
449 | sys.tables AS tbl | ||
450 | INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1 | ||
451 | SQL; | ||
452 | |||
453 | $conditions = ["SCHEMA_NAME(tbl.schema_id) = N'dbo'", "p.name = N'MS_Description'"]; | ||
454 | $params = []; | ||
455 | |||
456 | if ($tableName !== null) { | ||
457 | $conditions[] = "tbl.name = N'" . $tableName . "'"; | ||
458 | } | ||
459 | |||
460 | $sql .= ' WHERE ' . implode(' AND ', $conditions); | ||
461 | |||
462 | /** @var array<string,array<string,mixed>> $metadata */ | ||
463 | $metadata = $this->connection->executeQuery($sql, $params) | ||
464 | ->fetchAllAssociativeIndexed(); | ||
465 | |||
466 | $tableOptions = []; | ||
467 | foreach ($metadata as $table => $data) { | ||
468 | $data = array_change_key_case($data, CASE_LOWER); | ||
469 | |||
470 | $tableOptions[$table] = [ | ||
471 | 'comment' => $data['table_comment'], | ||
472 | ]; | ||
473 | } | ||
474 | |||
475 | return $tableOptions; | ||
476 | } | ||
477 | |||
478 | /** | ||
479 | * Returns the where clause to filter schema and table name in a query. | ||
480 | * | ||
481 | * @param string $table The full qualified name of the table. | ||
482 | * @param string $schemaColumn The name of the column to compare the schema to in the where clause. | ||
483 | * @param string $tableColumn The name of the column to compare the table to in the where clause. | ||
484 | */ | ||
485 | private function getTableWhereClause(string $table, string $schemaColumn, string $tableColumn): string | ||
486 | { | ||
487 | if (str_contains($table, '.')) { | ||
488 | [$schema, $table] = explode('.', $table); | ||
489 | $schema = $this->platform->quoteStringLiteral($schema); | ||
490 | $table = $this->platform->quoteStringLiteral($table); | ||
491 | } else { | ||
492 | $schema = 'SCHEMA_NAME()'; | ||
493 | $table = $this->platform->quoteStringLiteral($table); | ||
494 | } | ||
495 | |||
496 | return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); | ||
497 | } | ||
498 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/SQLiteSchemaManager.php b/vendor/doctrine/dbal/src/Schema/SQLiteSchemaManager.php new file mode 100644 index 0000000..c001c25 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SQLiteSchemaManager.php | |||
@@ -0,0 +1,620 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Exception; | ||
8 | use Doctrine\DBAL\Platforms\SQLite; | ||
9 | use Doctrine\DBAL\Platforms\SQLitePlatform; | ||
10 | use Doctrine\DBAL\Result; | ||
11 | use Doctrine\DBAL\Types\StringType; | ||
12 | use Doctrine\DBAL\Types\TextType; | ||
13 | use Doctrine\DBAL\Types\Type; | ||
14 | |||
15 | use function array_change_key_case; | ||
16 | use function array_merge; | ||
17 | use function assert; | ||
18 | use function count; | ||
19 | use function implode; | ||
20 | use function is_string; | ||
21 | use function preg_match; | ||
22 | use function preg_match_all; | ||
23 | use function preg_quote; | ||
24 | use function preg_replace; | ||
25 | use function rtrim; | ||
26 | use function str_contains; | ||
27 | use function str_replace; | ||
28 | use function str_starts_with; | ||
29 | use function strcasecmp; | ||
30 | use function strtolower; | ||
31 | use function trim; | ||
32 | use function usort; | ||
33 | |||
34 | use const CASE_LOWER; | ||
35 | |||
36 | /** | ||
37 | * SQLite SchemaManager. | ||
38 | * | ||
39 | * @extends AbstractSchemaManager<SQLitePlatform> | ||
40 | */ | ||
41 | class SQLiteSchemaManager extends AbstractSchemaManager | ||
42 | { | ||
43 | /** | ||
44 | * {@inheritDoc} | ||
45 | */ | ||
46 | protected function fetchForeignKeyColumnsByTable(string $databaseName): array | ||
47 | { | ||
48 | $columnsByTable = parent::fetchForeignKeyColumnsByTable($databaseName); | ||
49 | |||
50 | if (count($columnsByTable) > 0) { | ||
51 | foreach ($columnsByTable as $table => $columns) { | ||
52 | $columnsByTable[$table] = $this->addDetailsToTableForeignKeyColumns($table, $columns); | ||
53 | } | ||
54 | } | ||
55 | |||
56 | return $columnsByTable; | ||
57 | } | ||
58 | |||
59 | public function createForeignKey(ForeignKeyConstraint $foreignKey, string $table): void | ||
60 | { | ||
61 | $table = $this->introspectTable($table); | ||
62 | |||
63 | $this->alterTable(new TableDiff($table, [], [], [], [], [], [], [], [], [$foreignKey], [], [])); | ||
64 | } | ||
65 | |||
66 | public function dropForeignKey(string $name, string $table): void | ||
67 | { | ||
68 | $table = $this->introspectTable($table); | ||
69 | |||
70 | $foreignKey = $table->getForeignKey($name); | ||
71 | |||
72 | $this->alterTable(new TableDiff($table, [], [], [], [], [], [], [], [], [], [], [$foreignKey])); | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * {@inheritDoc} | ||
77 | */ | ||
78 | public function listTableForeignKeys(string $table): array | ||
79 | { | ||
80 | $table = $this->normalizeName($table); | ||
81 | |||
82 | $columns = $this->selectForeignKeyColumns('main', $table) | ||
83 | ->fetchAllAssociative(); | ||
84 | |||
85 | if (count($columns) > 0) { | ||
86 | $columns = $this->addDetailsToTableForeignKeyColumns($table, $columns); | ||
87 | } | ||
88 | |||
89 | return $this->_getPortableTableForeignKeysList($columns); | ||
90 | } | ||
91 | |||
92 | /** | ||
93 | * {@inheritDoc} | ||
94 | */ | ||
95 | protected function _getPortableTableDefinition(array $table): string | ||
96 | { | ||
97 | return $table['table_name']; | ||
98 | } | ||
99 | |||
100 | /** | ||
101 | * {@inheritDoc} | ||
102 | * | ||
103 | * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html | ||
104 | */ | ||
105 | protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array | ||
106 | { | ||
107 | $indexBuffer = []; | ||
108 | |||
109 | // fetch primary | ||
110 | $indexArray = $this->connection->fetchAllAssociative('SELECT * FROM PRAGMA_TABLE_INFO (?)', [$tableName]); | ||
111 | |||
112 | usort( | ||
113 | $indexArray, | ||
114 | /** | ||
115 | * @param array<string,mixed> $a | ||
116 | * @param array<string,mixed> $b | ||
117 | */ | ||
118 | static function (array $a, array $b): int { | ||
119 | if ($a['pk'] === $b['pk']) { | ||
120 | return $a['cid'] - $b['cid']; | ||
121 | } | ||
122 | |||
123 | return $a['pk'] - $b['pk']; | ||
124 | }, | ||
125 | ); | ||
126 | |||
127 | foreach ($indexArray as $indexColumnRow) { | ||
128 | if ($indexColumnRow['pk'] === 0 || $indexColumnRow['pk'] === '0') { | ||
129 | continue; | ||
130 | } | ||
131 | |||
132 | $indexBuffer[] = [ | ||
133 | 'key_name' => 'primary', | ||
134 | 'primary' => true, | ||
135 | 'non_unique' => false, | ||
136 | 'column_name' => $indexColumnRow['name'], | ||
137 | ]; | ||
138 | } | ||
139 | |||
140 | // fetch regular indexes | ||
141 | foreach ($tableIndexes as $tableIndex) { | ||
142 | // Ignore indexes with reserved names, e.g. autoindexes | ||
143 | if (str_starts_with($tableIndex['name'], 'sqlite_')) { | ||
144 | continue; | ||
145 | } | ||
146 | |||
147 | $keyName = $tableIndex['name']; | ||
148 | $idx = []; | ||
149 | $idx['key_name'] = $keyName; | ||
150 | $idx['primary'] = false; | ||
151 | $idx['non_unique'] = ! $tableIndex['unique']; | ||
152 | |||
153 | $indexArray = $this->connection->fetchAllAssociative('SELECT * FROM PRAGMA_INDEX_INFO (?)', [$keyName]); | ||
154 | |||
155 | foreach ($indexArray as $indexColumnRow) { | ||
156 | $idx['column_name'] = $indexColumnRow['name']; | ||
157 | $indexBuffer[] = $idx; | ||
158 | } | ||
159 | } | ||
160 | |||
161 | return parent::_getPortableTableIndexesList($indexBuffer, $tableName); | ||
162 | } | ||
163 | |||
164 | /** | ||
165 | * {@inheritDoc} | ||
166 | */ | ||
167 | protected function _getPortableTableColumnList(string $table, string $database, array $tableColumns): array | ||
168 | { | ||
169 | $list = parent::_getPortableTableColumnList($table, $database, $tableColumns); | ||
170 | |||
171 | // find column with autoincrement | ||
172 | $autoincrementColumn = null; | ||
173 | $autoincrementCount = 0; | ||
174 | |||
175 | foreach ($tableColumns as $tableColumn) { | ||
176 | if ($tableColumn['pk'] === 0 || $tableColumn['pk'] === '0') { | ||
177 | continue; | ||
178 | } | ||
179 | |||
180 | $autoincrementCount++; | ||
181 | if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') { | ||
182 | continue; | ||
183 | } | ||
184 | |||
185 | $autoincrementColumn = $tableColumn['name']; | ||
186 | } | ||
187 | |||
188 | if ($autoincrementCount === 1 && $autoincrementColumn !== null) { | ||
189 | foreach ($list as $column) { | ||
190 | if ($autoincrementColumn !== $column->getName()) { | ||
191 | continue; | ||
192 | } | ||
193 | |||
194 | $column->setAutoincrement(true); | ||
195 | } | ||
196 | } | ||
197 | |||
198 | // inspect column collation and comments | ||
199 | $createSql = $this->getCreateTableSQL($table); | ||
200 | |||
201 | foreach ($list as $columnName => $column) { | ||
202 | $type = $column->getType(); | ||
203 | |||
204 | if ($type instanceof StringType || $type instanceof TextType) { | ||
205 | $column->setPlatformOption( | ||
206 | 'collation', | ||
207 | $this->parseColumnCollationFromSQL($columnName, $createSql) ?? 'BINARY', | ||
208 | ); | ||
209 | } | ||
210 | |||
211 | $comment = $this->parseColumnCommentFromSQL($columnName, $createSql); | ||
212 | |||
213 | $column->setComment($comment); | ||
214 | } | ||
215 | |||
216 | return $list; | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * {@inheritDoc} | ||
221 | */ | ||
222 | protected function _getPortableTableColumnDefinition(array $tableColumn): Column | ||
223 | { | ||
224 | preg_match('/^([^()]*)\\s*(\\(((\\d+)(,\\s*(\\d+))?)\\))?/', $tableColumn['type'], $matches); | ||
225 | |||
226 | $dbType = trim(strtolower($matches[1])); | ||
227 | |||
228 | $length = $precision = $unsigned = null; | ||
229 | $fixed = $unsigned = false; | ||
230 | $scale = 0; | ||
231 | |||
232 | if (count($matches) >= 6) { | ||
233 | $precision = (int) $matches[4]; | ||
234 | $scale = (int) $matches[6]; | ||
235 | } elseif (count($matches) >= 4) { | ||
236 | $length = (int) $matches[4]; | ||
237 | } | ||
238 | |||
239 | if (str_contains($dbType, ' unsigned')) { | ||
240 | $dbType = str_replace(' unsigned', '', $dbType); | ||
241 | $unsigned = true; | ||
242 | } | ||
243 | |||
244 | $type = $this->platform->getDoctrineTypeMapping($dbType); | ||
245 | $default = $tableColumn['dflt_value']; | ||
246 | if ($default === 'NULL') { | ||
247 | $default = null; | ||
248 | } | ||
249 | |||
250 | if ($default !== null) { | ||
251 | // SQLite returns the default value as a literal expression, so we need to parse it | ||
252 | if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) { | ||
253 | $default = str_replace("''", "'", $matches[1]); | ||
254 | } | ||
255 | } | ||
256 | |||
257 | $notnull = (bool) $tableColumn['notnull']; | ||
258 | |||
259 | if (! isset($tableColumn['name'])) { | ||
260 | $tableColumn['name'] = ''; | ||
261 | } | ||
262 | |||
263 | if ($dbType === 'char') { | ||
264 | $fixed = true; | ||
265 | } | ||
266 | |||
267 | $options = [ | ||
268 | 'length' => $length, | ||
269 | 'unsigned' => $unsigned, | ||
270 | 'fixed' => $fixed, | ||
271 | 'notnull' => $notnull, | ||
272 | 'default' => $default, | ||
273 | 'precision' => $precision, | ||
274 | 'scale' => $scale, | ||
275 | ]; | ||
276 | |||
277 | return new Column($tableColumn['name'], Type::getType($type), $options); | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * {@inheritDoc} | ||
282 | */ | ||
283 | protected function _getPortableViewDefinition(array $view): View | ||
284 | { | ||
285 | return new View($view['name'], $view['sql']); | ||
286 | } | ||
287 | |||
288 | /** | ||
289 | * {@inheritDoc} | ||
290 | */ | ||
291 | protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array | ||
292 | { | ||
293 | $list = []; | ||
294 | foreach ($tableForeignKeys as $value) { | ||
295 | $value = array_change_key_case($value, CASE_LOWER); | ||
296 | $id = $value['id']; | ||
297 | if (! isset($list[$id])) { | ||
298 | if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') { | ||
299 | $value['on_delete'] = null; | ||
300 | } | ||
301 | |||
302 | if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') { | ||
303 | $value['on_update'] = null; | ||
304 | } | ||
305 | |||
306 | $list[$id] = [ | ||
307 | 'name' => $value['constraint_name'], | ||
308 | 'local' => [], | ||
309 | 'foreign' => [], | ||
310 | 'foreignTable' => $value['table'], | ||
311 | 'onDelete' => $value['on_delete'], | ||
312 | 'onUpdate' => $value['on_update'], | ||
313 | 'deferrable' => $value['deferrable'], | ||
314 | 'deferred' => $value['deferred'], | ||
315 | ]; | ||
316 | } | ||
317 | |||
318 | $list[$id]['local'][] = $value['from']; | ||
319 | |||
320 | if ($value['to'] === null) { | ||
321 | // Inferring a shorthand form for the foreign key constraint, where the "to" field is empty. | ||
322 | // @see https://www.sqlite.org/foreignkeys.html#fk_indexes. | ||
323 | $foreignTableIndexes = $this->_getPortableTableIndexesList([], $value['table']); | ||
324 | |||
325 | if (! isset($foreignTableIndexes['primary'])) { | ||
326 | continue; | ||
327 | } | ||
328 | |||
329 | $list[$id]['foreign'] = [...$list[$id]['foreign'], ...$foreignTableIndexes['primary']->getColumns()]; | ||
330 | |||
331 | continue; | ||
332 | } | ||
333 | |||
334 | $list[$id]['foreign'][] = $value['to']; | ||
335 | } | ||
336 | |||
337 | return parent::_getPortableTableForeignKeysList($list); | ||
338 | } | ||
339 | |||
340 | /** | ||
341 | * {@inheritDoc} | ||
342 | */ | ||
343 | protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint | ||
344 | { | ||
345 | return new ForeignKeyConstraint( | ||
346 | $tableForeignKey['local'], | ||
347 | $tableForeignKey['foreignTable'], | ||
348 | $tableForeignKey['foreign'], | ||
349 | $tableForeignKey['name'], | ||
350 | [ | ||
351 | 'onDelete' => $tableForeignKey['onDelete'], | ||
352 | 'onUpdate' => $tableForeignKey['onUpdate'], | ||
353 | 'deferrable' => $tableForeignKey['deferrable'], | ||
354 | 'deferred' => $tableForeignKey['deferred'], | ||
355 | ], | ||
356 | ); | ||
357 | } | ||
358 | |||
359 | private function parseColumnCollationFromSQL(string $column, string $sql): ?string | ||
360 | { | ||
361 | $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' | ||
362 | . preg_quote($this->platform->quoteSingleIdentifier($column)) | ||
363 | . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is'; | ||
364 | |||
365 | if (preg_match($pattern, $sql, $match) !== 1) { | ||
366 | return null; | ||
367 | } | ||
368 | |||
369 | return $match[1]; | ||
370 | } | ||
371 | |||
372 | private function parseTableCommentFromSQL(string $table, string $sql): ?string | ||
373 | { | ||
374 | $pattern = '/\s* # Allow whitespace characters at start of line | ||
375 | CREATE\sTABLE # Match "CREATE TABLE" | ||
376 | (?:\W"' . preg_quote($this->platform->quoteSingleIdentifier($table), '/') . '"\W|\W' . preg_quote($table, '/') | ||
377 | . '\W) # Match table name (quoted and unquoted) | ||
378 | ( # Start capture | ||
379 | (?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s) | ||
380 | )/ix'; | ||
381 | |||
382 | if (preg_match($pattern, $sql, $match) !== 1) { | ||
383 | return null; | ||
384 | } | ||
385 | |||
386 | $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n")); | ||
387 | |||
388 | return $comment === '' ? null : $comment; | ||
389 | } | ||
390 | |||
391 | private function parseColumnCommentFromSQL(string $column, string $sql): string | ||
392 | { | ||
393 | $pattern = '{[\s(,](?:\W' . preg_quote($this->platform->quoteSingleIdentifier($column)) | ||
394 | . '\W|\W' . preg_quote($column) . '\W)(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i'; | ||
395 | |||
396 | if (preg_match($pattern, $sql, $match) !== 1) { | ||
397 | return ''; | ||
398 | } | ||
399 | |||
400 | $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n")); | ||
401 | assert(is_string($comment)); | ||
402 | |||
403 | return $comment; | ||
404 | } | ||
405 | |||
406 | /** @throws Exception */ | ||
407 | private function getCreateTableSQL(string $table): string | ||
408 | { | ||
409 | $sql = $this->connection->fetchOne( | ||
410 | <<<'SQL' | ||
411 | SELECT sql | ||
412 | FROM ( | ||
413 | SELECT * | ||
414 | FROM sqlite_master | ||
415 | UNION ALL | ||
416 | SELECT * | ||
417 | FROM sqlite_temp_master | ||
418 | ) | ||
419 | WHERE type = 'table' | ||
420 | AND name = ? | ||
421 | SQL | ||
422 | , | ||
423 | [$table], | ||
424 | ); | ||
425 | |||
426 | if ($sql !== false) { | ||
427 | return $sql; | ||
428 | } | ||
429 | |||
430 | return ''; | ||
431 | } | ||
432 | |||
433 | /** | ||
434 | * @param list<array<string,mixed>> $columns | ||
435 | * | ||
436 | * @return list<array<string,mixed>> | ||
437 | * | ||
438 | * @throws Exception | ||
439 | */ | ||
440 | private function addDetailsToTableForeignKeyColumns(string $table, array $columns): array | ||
441 | { | ||
442 | $foreignKeyDetails = $this->getForeignKeyDetails($table); | ||
443 | $foreignKeyCount = count($foreignKeyDetails); | ||
444 | |||
445 | foreach ($columns as $i => $column) { | ||
446 | // SQLite identifies foreign keys in reverse order of appearance in SQL | ||
447 | $columns[$i] = array_merge($column, $foreignKeyDetails[$foreignKeyCount - $column['id'] - 1]); | ||
448 | } | ||
449 | |||
450 | return $columns; | ||
451 | } | ||
452 | |||
453 | /** | ||
454 | * @return list<array<string, mixed>> | ||
455 | * | ||
456 | * @throws Exception | ||
457 | */ | ||
458 | private function getForeignKeyDetails(string $table): array | ||
459 | { | ||
460 | $createSql = $this->getCreateTableSQL($table); | ||
461 | |||
462 | if ( | ||
463 | preg_match_all( | ||
464 | '# | ||
465 | (?:CONSTRAINT\s+(\S+)\s+)? | ||
466 | (?:FOREIGN\s+KEY[^)]+\)\s*)? | ||
467 | REFERENCES\s+\S+\s*(?:\([^)]+\))? | ||
468 | (?: | ||
469 | [^,]*? | ||
470 | (NOT\s+DEFERRABLE|DEFERRABLE) | ||
471 | (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))? | ||
472 | )?#isx', | ||
473 | $createSql, | ||
474 | $match, | ||
475 | ) === 0 | ||
476 | ) { | ||
477 | return []; | ||
478 | } | ||
479 | |||
480 | $names = $match[1]; | ||
481 | $deferrable = $match[2]; | ||
482 | $deferred = $match[3]; | ||
483 | $details = []; | ||
484 | |||
485 | for ($i = 0, $count = count($match[0]); $i < $count; $i++) { | ||
486 | $details[] = [ | ||
487 | 'constraint_name' => $names[$i] ?? '', | ||
488 | 'deferrable' => isset($deferrable[$i]) && strcasecmp($deferrable[$i], 'deferrable') === 0, | ||
489 | 'deferred' => isset($deferred[$i]) && strcasecmp($deferred[$i], 'deferred') === 0, | ||
490 | ]; | ||
491 | } | ||
492 | |||
493 | return $details; | ||
494 | } | ||
495 | |||
496 | public function createComparator(): Comparator | ||
497 | { | ||
498 | return new SQLite\Comparator($this->platform); | ||
499 | } | ||
500 | |||
501 | protected function selectTableNames(string $databaseName): Result | ||
502 | { | ||
503 | $sql = <<<'SQL' | ||
504 | SELECT name AS table_name | ||
505 | FROM sqlite_master | ||
506 | WHERE type = 'table' | ||
507 | AND name != 'sqlite_sequence' | ||
508 | AND name != 'geometry_columns' | ||
509 | AND name != 'spatial_ref_sys' | ||
510 | UNION ALL | ||
511 | SELECT name | ||
512 | FROM sqlite_temp_master | ||
513 | WHERE type = 'table' | ||
514 | ORDER BY name | ||
515 | SQL; | ||
516 | |||
517 | return $this->connection->executeQuery($sql); | ||
518 | } | ||
519 | |||
520 | protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result | ||
521 | { | ||
522 | $sql = <<<'SQL' | ||
523 | SELECT t.name AS table_name, | ||
524 | c.* | ||
525 | FROM sqlite_master t | ||
526 | JOIN pragma_table_info(t.name) c | ||
527 | SQL; | ||
528 | |||
529 | $conditions = [ | ||
530 | "t.type = 'table'", | ||
531 | "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", | ||
532 | ]; | ||
533 | $params = []; | ||
534 | |||
535 | if ($tableName !== null) { | ||
536 | $conditions[] = 't.name = ?'; | ||
537 | $params[] = str_replace('.', '__', $tableName); | ||
538 | } | ||
539 | |||
540 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, c.cid'; | ||
541 | |||
542 | return $this->connection->executeQuery($sql, $params); | ||
543 | } | ||
544 | |||
545 | protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result | ||
546 | { | ||
547 | $sql = <<<'SQL' | ||
548 | SELECT t.name AS table_name, | ||
549 | i.* | ||
550 | FROM sqlite_master t | ||
551 | JOIN pragma_index_list(t.name) i | ||
552 | SQL; | ||
553 | |||
554 | $conditions = [ | ||
555 | "t.type = 'table'", | ||
556 | "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", | ||
557 | ]; | ||
558 | $params = []; | ||
559 | |||
560 | if ($tableName !== null) { | ||
561 | $conditions[] = 't.name = ?'; | ||
562 | $params[] = str_replace('.', '__', $tableName); | ||
563 | } | ||
564 | |||
565 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, i.seq'; | ||
566 | |||
567 | return $this->connection->executeQuery($sql, $params); | ||
568 | } | ||
569 | |||
570 | protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result | ||
571 | { | ||
572 | $sql = <<<'SQL' | ||
573 | SELECT t.name AS table_name, | ||
574 | p.* | ||
575 | FROM sqlite_master t | ||
576 | JOIN pragma_foreign_key_list(t.name) p | ||
577 | ON p."seq" != '-1' | ||
578 | SQL; | ||
579 | |||
580 | $conditions = [ | ||
581 | "t.type = 'table'", | ||
582 | "t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')", | ||
583 | ]; | ||
584 | $params = []; | ||
585 | |||
586 | if ($tableName !== null) { | ||
587 | $conditions[] = 't.name = ?'; | ||
588 | $params[] = str_replace('.', '__', $tableName); | ||
589 | } | ||
590 | |||
591 | $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.id DESC, p.seq'; | ||
592 | |||
593 | return $this->connection->executeQuery($sql, $params); | ||
594 | } | ||
595 | |||
596 | /** | ||
597 | * {@inheritDoc} | ||
598 | */ | ||
599 | protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array | ||
600 | { | ||
601 | if ($tableName === null) { | ||
602 | $tables = $this->listTableNames(); | ||
603 | } else { | ||
604 | $tables = [$tableName]; | ||
605 | } | ||
606 | |||
607 | $tableOptions = []; | ||
608 | foreach ($tables as $table) { | ||
609 | $comment = $this->parseTableCommentFromSQL($table, $this->getCreateTableSQL($table)); | ||
610 | |||
611 | if ($comment === null) { | ||
612 | continue; | ||
613 | } | ||
614 | |||
615 | $tableOptions[$table]['comment'] = $comment; | ||
616 | } | ||
617 | |||
618 | return $tableOptions; | ||
619 | } | ||
620 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Schema.php b/vendor/doctrine/dbal/src/Schema/Schema.php new file mode 100644 index 0000000..25fe4a3 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Schema.php | |||
@@ -0,0 +1,374 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Exception; | ||
8 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
9 | use Doctrine\DBAL\Schema\Exception\NamespaceAlreadyExists; | ||
10 | use Doctrine\DBAL\Schema\Exception\SequenceAlreadyExists; | ||
11 | use Doctrine\DBAL\Schema\Exception\SequenceDoesNotExist; | ||
12 | use Doctrine\DBAL\Schema\Exception\TableAlreadyExists; | ||
13 | use Doctrine\DBAL\Schema\Exception\TableDoesNotExist; | ||
14 | use Doctrine\DBAL\SQL\Builder\CreateSchemaObjectsSQLBuilder; | ||
15 | use Doctrine\DBAL\SQL\Builder\DropSchemaObjectsSQLBuilder; | ||
16 | |||
17 | use function array_values; | ||
18 | use function str_contains; | ||
19 | use function strtolower; | ||
20 | |||
21 | /** | ||
22 | * Object representation of a database schema. | ||
23 | * | ||
24 | * Different vendors have very inconsistent naming with regard to the concept | ||
25 | * of a "schema". Doctrine understands a schema as the entity that conceptually | ||
26 | * wraps a set of database objects such as tables, sequences, indexes and | ||
27 | * foreign keys that belong to each other into a namespace. A Doctrine Schema | ||
28 | * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more | ||
29 | * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL. | ||
30 | * | ||
31 | * Every asset in the doctrine schema has a name. A name consists of either a | ||
32 | * namespace.local name pair or just a local unqualified name. | ||
33 | * | ||
34 | * The abstraction layer that covers a PostgreSQL schema is the namespace of an | ||
35 | * database object (asset). A schema can have a name, which will be used as | ||
36 | * default namespace for the unqualified database objects that are created in | ||
37 | * the schema. | ||
38 | * | ||
39 | * In the case of MySQL where cross-database queries are allowed this leads to | ||
40 | * databases being "misinterpreted" as namespaces. This is intentional, however | ||
41 | * the CREATE/DROP SQL visitors will just filter this queries and do not | ||
42 | * execute them. Only the queries for the currently connected database are | ||
43 | * executed. | ||
44 | */ | ||
45 | class Schema extends AbstractAsset | ||
46 | { | ||
47 | /** | ||
48 | * The namespaces in this schema. | ||
49 | * | ||
50 | * @var array<string, string> | ||
51 | */ | ||
52 | private array $namespaces = []; | ||
53 | |||
54 | /** @var array<string, Table> */ | ||
55 | protected array $_tables = []; | ||
56 | |||
57 | /** @var array<string, Sequence> */ | ||
58 | protected array $_sequences = []; | ||
59 | |||
60 | protected SchemaConfig $_schemaConfig; | ||
61 | |||
62 | /** | ||
63 | * @param array<Table> $tables | ||
64 | * @param array<Sequence> $sequences | ||
65 | * @param array<string> $namespaces | ||
66 | */ | ||
67 | public function __construct( | ||
68 | array $tables = [], | ||
69 | array $sequences = [], | ||
70 | ?SchemaConfig $schemaConfig = null, | ||
71 | array $namespaces = [], | ||
72 | ) { | ||
73 | $schemaConfig ??= new SchemaConfig(); | ||
74 | |||
75 | $this->_schemaConfig = $schemaConfig; | ||
76 | |||
77 | $name = $schemaConfig->getName(); | ||
78 | |||
79 | if ($name !== null) { | ||
80 | $this->_setName($name); | ||
81 | } | ||
82 | |||
83 | foreach ($namespaces as $namespace) { | ||
84 | $this->createNamespace($namespace); | ||
85 | } | ||
86 | |||
87 | foreach ($tables as $table) { | ||
88 | $this->_addTable($table); | ||
89 | } | ||
90 | |||
91 | foreach ($sequences as $sequence) { | ||
92 | $this->_addSequence($sequence); | ||
93 | } | ||
94 | } | ||
95 | |||
96 | protected function _addTable(Table $table): void | ||
97 | { | ||
98 | $namespaceName = $table->getNamespaceName(); | ||
99 | $tableName = $this->normalizeName($table); | ||
100 | |||
101 | if (isset($this->_tables[$tableName])) { | ||
102 | throw TableAlreadyExists::new($tableName); | ||
103 | } | ||
104 | |||
105 | if ( | ||
106 | $namespaceName !== null | ||
107 | && ! $table->isInDefaultNamespace($this->getName()) | ||
108 | && ! $this->hasNamespace($namespaceName) | ||
109 | ) { | ||
110 | $this->createNamespace($namespaceName); | ||
111 | } | ||
112 | |||
113 | $this->_tables[$tableName] = $table; | ||
114 | $table->setSchemaConfig($this->_schemaConfig); | ||
115 | } | ||
116 | |||
117 | protected function _addSequence(Sequence $sequence): void | ||
118 | { | ||
119 | $namespaceName = $sequence->getNamespaceName(); | ||
120 | $seqName = $this->normalizeName($sequence); | ||
121 | |||
122 | if (isset($this->_sequences[$seqName])) { | ||
123 | throw SequenceAlreadyExists::new($seqName); | ||
124 | } | ||
125 | |||
126 | if ( | ||
127 | $namespaceName !== null | ||
128 | && ! $sequence->isInDefaultNamespace($this->getName()) | ||
129 | && ! $this->hasNamespace($namespaceName) | ||
130 | ) { | ||
131 | $this->createNamespace($namespaceName); | ||
132 | } | ||
133 | |||
134 | $this->_sequences[$seqName] = $sequence; | ||
135 | } | ||
136 | |||
137 | /** | ||
138 | * Returns the namespaces of this schema. | ||
139 | * | ||
140 | * @return list<string> A list of namespace names. | ||
141 | */ | ||
142 | public function getNamespaces(): array | ||
143 | { | ||
144 | return array_values($this->namespaces); | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * Gets all tables of this schema. | ||
149 | * | ||
150 | * @return list<Table> | ||
151 | */ | ||
152 | public function getTables(): array | ||
153 | { | ||
154 | return array_values($this->_tables); | ||
155 | } | ||
156 | |||
157 | public function getTable(string $name): Table | ||
158 | { | ||
159 | $name = $this->getFullQualifiedAssetName($name); | ||
160 | if (! isset($this->_tables[$name])) { | ||
161 | throw TableDoesNotExist::new($name); | ||
162 | } | ||
163 | |||
164 | return $this->_tables[$name]; | ||
165 | } | ||
166 | |||
167 | private function getFullQualifiedAssetName(string $name): string | ||
168 | { | ||
169 | $name = $this->getUnquotedAssetName($name); | ||
170 | |||
171 | if (! str_contains($name, '.')) { | ||
172 | $name = $this->getName() . '.' . $name; | ||
173 | } | ||
174 | |||
175 | return strtolower($name); | ||
176 | } | ||
177 | |||
178 | /** | ||
179 | * The normalized name is qualified and lower-cased. Lower-casing is | ||
180 | * actually wrong, but we have to do it to keep our sanity. If you are | ||
181 | * using database objects that only differentiate in the casing (FOO vs | ||
182 | * Foo) then you will NOT be able to use Doctrine Schema abstraction. | ||
183 | * | ||
184 | * Every non-namespaced element is prefixed with this schema name. | ||
185 | */ | ||
186 | private function normalizeName(AbstractAsset $asset): string | ||
187 | { | ||
188 | $name = $asset->getName(); | ||
189 | |||
190 | if ($asset->getNamespaceName() === null) { | ||
191 | $name = $this->getName() . '.' . $name; | ||
192 | } | ||
193 | |||
194 | return strtolower($name); | ||
195 | } | ||
196 | |||
197 | /** | ||
198 | * Returns the unquoted representation of a given asset name. | ||
199 | */ | ||
200 | private function getUnquotedAssetName(string $assetName): string | ||
201 | { | ||
202 | if ($this->isIdentifierQuoted($assetName)) { | ||
203 | return $this->trimQuotes($assetName); | ||
204 | } | ||
205 | |||
206 | return $assetName; | ||
207 | } | ||
208 | |||
209 | /** | ||
210 | * Does this schema have a namespace with the given name? | ||
211 | */ | ||
212 | public function hasNamespace(string $name): bool | ||
213 | { | ||
214 | $name = strtolower($this->getUnquotedAssetName($name)); | ||
215 | |||
216 | return isset($this->namespaces[$name]); | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * Does this schema have a table with the given name? | ||
221 | */ | ||
222 | public function hasTable(string $name): bool | ||
223 | { | ||
224 | $name = $this->getFullQualifiedAssetName($name); | ||
225 | |||
226 | return isset($this->_tables[$name]); | ||
227 | } | ||
228 | |||
229 | public function hasSequence(string $name): bool | ||
230 | { | ||
231 | $name = $this->getFullQualifiedAssetName($name); | ||
232 | |||
233 | return isset($this->_sequences[$name]); | ||
234 | } | ||
235 | |||
236 | public function getSequence(string $name): Sequence | ||
237 | { | ||
238 | $name = $this->getFullQualifiedAssetName($name); | ||
239 | if (! $this->hasSequence($name)) { | ||
240 | throw SequenceDoesNotExist::new($name); | ||
241 | } | ||
242 | |||
243 | return $this->_sequences[$name]; | ||
244 | } | ||
245 | |||
246 | /** @return list<Sequence> */ | ||
247 | public function getSequences(): array | ||
248 | { | ||
249 | return array_values($this->_sequences); | ||
250 | } | ||
251 | |||
252 | /** | ||
253 | * Creates a new namespace. | ||
254 | * | ||
255 | * @return $this | ||
256 | */ | ||
257 | public function createNamespace(string $name): self | ||
258 | { | ||
259 | $unquotedName = strtolower($this->getUnquotedAssetName($name)); | ||
260 | |||
261 | if (isset($this->namespaces[$unquotedName])) { | ||
262 | throw NamespaceAlreadyExists::new($unquotedName); | ||
263 | } | ||
264 | |||
265 | $this->namespaces[$unquotedName] = $name; | ||
266 | |||
267 | return $this; | ||
268 | } | ||
269 | |||
270 | /** | ||
271 | * Creates a new table. | ||
272 | */ | ||
273 | public function createTable(string $name): Table | ||
274 | { | ||
275 | $table = new Table($name); | ||
276 | $this->_addTable($table); | ||
277 | |||
278 | foreach ($this->_schemaConfig->getDefaultTableOptions() as $option => $value) { | ||
279 | $table->addOption($option, $value); | ||
280 | } | ||
281 | |||
282 | return $table; | ||
283 | } | ||
284 | |||
285 | /** | ||
286 | * Renames a table. | ||
287 | * | ||
288 | * @return $this | ||
289 | */ | ||
290 | public function renameTable(string $oldName, string $newName): self | ||
291 | { | ||
292 | $table = $this->getTable($oldName); | ||
293 | $table->_setName($newName); | ||
294 | |||
295 | $this->dropTable($oldName); | ||
296 | $this->_addTable($table); | ||
297 | |||
298 | return $this; | ||
299 | } | ||
300 | |||
301 | /** | ||
302 | * Drops a table from the schema. | ||
303 | * | ||
304 | * @return $this | ||
305 | */ | ||
306 | public function dropTable(string $name): self | ||
307 | { | ||
308 | $name = $this->getFullQualifiedAssetName($name); | ||
309 | $this->getTable($name); | ||
310 | unset($this->_tables[$name]); | ||
311 | |||
312 | return $this; | ||
313 | } | ||
314 | |||
315 | /** | ||
316 | * Creates a new sequence. | ||
317 | */ | ||
318 | public function createSequence(string $name, int $allocationSize = 1, int $initialValue = 1): Sequence | ||
319 | { | ||
320 | $seq = new Sequence($name, $allocationSize, $initialValue); | ||
321 | $this->_addSequence($seq); | ||
322 | |||
323 | return $seq; | ||
324 | } | ||
325 | |||
326 | /** @return $this */ | ||
327 | public function dropSequence(string $name): self | ||
328 | { | ||
329 | $name = $this->getFullQualifiedAssetName($name); | ||
330 | unset($this->_sequences[$name]); | ||
331 | |||
332 | return $this; | ||
333 | } | ||
334 | |||
335 | /** | ||
336 | * Returns an array of necessary SQL queries to create the schema on the given platform. | ||
337 | * | ||
338 | * @return list<string> | ||
339 | * | ||
340 | * @throws Exception | ||
341 | */ | ||
342 | public function toSql(AbstractPlatform $platform): array | ||
343 | { | ||
344 | $builder = new CreateSchemaObjectsSQLBuilder($platform); | ||
345 | |||
346 | return $builder->buildSQL($this); | ||
347 | } | ||
348 | |||
349 | /** | ||
350 | * Return an array of necessary SQL queries to drop the schema on the given platform. | ||
351 | * | ||
352 | * @return list<string> | ||
353 | */ | ||
354 | public function toDropSql(AbstractPlatform $platform): array | ||
355 | { | ||
356 | $builder = new DropSchemaObjectsSQLBuilder($platform); | ||
357 | |||
358 | return $builder->buildSQL($this); | ||
359 | } | ||
360 | |||
361 | /** | ||
362 | * Cloning a Schema triggers a deep clone of all related assets. | ||
363 | */ | ||
364 | public function __clone() | ||
365 | { | ||
366 | foreach ($this->_tables as $k => $table) { | ||
367 | $this->_tables[$k] = clone $table; | ||
368 | } | ||
369 | |||
370 | foreach ($this->_sequences as $k => $sequence) { | ||
371 | $this->_sequences[$k] = clone $sequence; | ||
372 | } | ||
373 | } | ||
374 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/SchemaConfig.php b/vendor/doctrine/dbal/src/Schema/SchemaConfig.php new file mode 100644 index 0000000..86cc84f --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SchemaConfig.php | |||
@@ -0,0 +1,61 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | /** | ||
8 | * Configuration for a Schema. | ||
9 | */ | ||
10 | class SchemaConfig | ||
11 | { | ||
12 | protected int $maxIdentifierLength = 63; | ||
13 | |||
14 | protected ?string $name = null; | ||
15 | |||
16 | /** @var array<string, mixed> */ | ||
17 | protected array $defaultTableOptions = []; | ||
18 | |||
19 | public function setMaxIdentifierLength(int $length): void | ||
20 | { | ||
21 | $this->maxIdentifierLength = $length; | ||
22 | } | ||
23 | |||
24 | public function getMaxIdentifierLength(): int | ||
25 | { | ||
26 | return $this->maxIdentifierLength; | ||
27 | } | ||
28 | |||
29 | /** | ||
30 | * Gets the default namespace of schema objects. | ||
31 | */ | ||
32 | public function getName(): ?string | ||
33 | { | ||
34 | return $this->name; | ||
35 | } | ||
36 | |||
37 | /** | ||
38 | * Sets the default namespace name of schema objects. | ||
39 | */ | ||
40 | public function setName(?string $name): void | ||
41 | { | ||
42 | $this->name = $name; | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Gets the default options that are passed to Table instances created with | ||
47 | * Schema#createTable(). | ||
48 | * | ||
49 | * @return array<string, mixed> | ||
50 | */ | ||
51 | public function getDefaultTableOptions(): array | ||
52 | { | ||
53 | return $this->defaultTableOptions; | ||
54 | } | ||
55 | |||
56 | /** @param array<string, mixed> $defaultTableOptions */ | ||
57 | public function setDefaultTableOptions(array $defaultTableOptions): void | ||
58 | { | ||
59 | $this->defaultTableOptions = $defaultTableOptions; | ||
60 | } | ||
61 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/SchemaDiff.php b/vendor/doctrine/dbal/src/Schema/SchemaDiff.php new file mode 100644 index 0000000..28c014d --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SchemaDiff.php | |||
@@ -0,0 +1,109 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use function array_filter; | ||
8 | use function count; | ||
9 | |||
10 | /** | ||
11 | * Differences between two schemas. | ||
12 | */ | ||
13 | class SchemaDiff | ||
14 | { | ||
15 | /** @var array<TableDiff> */ | ||
16 | private readonly array $alteredTables; | ||
17 | |||
18 | /** | ||
19 | * Constructs an SchemaDiff object. | ||
20 | * | ||
21 | * @internal The diff can be only instantiated by a {@see Comparator}. | ||
22 | * | ||
23 | * @param array<string> $createdSchemas | ||
24 | * @param array<string> $droppedSchemas | ||
25 | * @param array<Table> $createdTables | ||
26 | * @param array<TableDiff> $alteredTables | ||
27 | * @param array<Table> $droppedTables | ||
28 | * @param array<Sequence> $createdSequences | ||
29 | * @param array<Sequence> $alteredSequences | ||
30 | * @param array<Sequence> $droppedSequences | ||
31 | */ | ||
32 | public function __construct( | ||
33 | private readonly array $createdSchemas, | ||
34 | private readonly array $droppedSchemas, | ||
35 | private readonly array $createdTables, | ||
36 | array $alteredTables, | ||
37 | private readonly array $droppedTables, | ||
38 | private readonly array $createdSequences, | ||
39 | private readonly array $alteredSequences, | ||
40 | private readonly array $droppedSequences, | ||
41 | ) { | ||
42 | $this->alteredTables = array_filter($alteredTables, static function (TableDiff $diff): bool { | ||
43 | return ! $diff->isEmpty(); | ||
44 | }); | ||
45 | } | ||
46 | |||
47 | /** @return array<string> */ | ||
48 | public function getCreatedSchemas(): array | ||
49 | { | ||
50 | return $this->createdSchemas; | ||
51 | } | ||
52 | |||
53 | /** @return array<string> */ | ||
54 | public function getDroppedSchemas(): array | ||
55 | { | ||
56 | return $this->droppedSchemas; | ||
57 | } | ||
58 | |||
59 | /** @return array<Table> */ | ||
60 | public function getCreatedTables(): array | ||
61 | { | ||
62 | return $this->createdTables; | ||
63 | } | ||
64 | |||
65 | /** @return array<TableDiff> */ | ||
66 | public function getAlteredTables(): array | ||
67 | { | ||
68 | return $this->alteredTables; | ||
69 | } | ||
70 | |||
71 | /** @return array<Table> */ | ||
72 | public function getDroppedTables(): array | ||
73 | { | ||
74 | return $this->droppedTables; | ||
75 | } | ||
76 | |||
77 | /** @return array<Sequence> */ | ||
78 | public function getCreatedSequences(): array | ||
79 | { | ||
80 | return $this->createdSequences; | ||
81 | } | ||
82 | |||
83 | /** @return array<Sequence> */ | ||
84 | public function getAlteredSequences(): array | ||
85 | { | ||
86 | return $this->alteredSequences; | ||
87 | } | ||
88 | |||
89 | /** @return array<Sequence> */ | ||
90 | public function getDroppedSequences(): array | ||
91 | { | ||
92 | return $this->droppedSequences; | ||
93 | } | ||
94 | |||
95 | /** | ||
96 | * Returns whether the diff is empty (contains no changes). | ||
97 | */ | ||
98 | public function isEmpty(): bool | ||
99 | { | ||
100 | return count($this->createdSchemas) === 0 | ||
101 | && count($this->droppedSchemas) === 0 | ||
102 | && count($this->createdTables) === 0 | ||
103 | && count($this->alteredTables) === 0 | ||
104 | && count($this->droppedTables) === 0 | ||
105 | && count($this->createdSequences) === 0 | ||
106 | && count($this->alteredSequences) === 0 | ||
107 | && count($this->droppedSequences) === 0; | ||
108 | } | ||
109 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/SchemaException.php b/vendor/doctrine/dbal/src/Schema/SchemaException.php new file mode 100644 index 0000000..43dd2ad --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SchemaException.php | |||
@@ -0,0 +1,12 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Exception; | ||
8 | |||
9 | /** @psalm-immutable */ | ||
10 | interface SchemaException extends Exception | ||
11 | { | ||
12 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/SchemaManagerFactory.php b/vendor/doctrine/dbal/src/Schema/SchemaManagerFactory.php new file mode 100644 index 0000000..37c32e0 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/SchemaManagerFactory.php | |||
@@ -0,0 +1,17 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Connection; | ||
8 | |||
9 | /** | ||
10 | * Creates a schema manager for the given connection. | ||
11 | * | ||
12 | * This interface is an extension point for applications that need to override schema managers. | ||
13 | */ | ||
14 | interface SchemaManagerFactory | ||
15 | { | ||
16 | public function createSchemaManager(Connection $connection): AbstractSchemaManager; | ||
17 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Sequence.php b/vendor/doctrine/dbal/src/Schema/Sequence.php new file mode 100644 index 0000000..32a5e67 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Sequence.php | |||
@@ -0,0 +1,98 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use function count; | ||
8 | use function sprintf; | ||
9 | |||
10 | /** | ||
11 | * Sequence structure. | ||
12 | */ | ||
13 | class Sequence extends AbstractAsset | ||
14 | { | ||
15 | protected int $allocationSize = 1; | ||
16 | |||
17 | protected int $initialValue = 1; | ||
18 | |||
19 | public function __construct( | ||
20 | string $name, | ||
21 | int $allocationSize = 1, | ||
22 | int $initialValue = 1, | ||
23 | protected ?int $cache = null, | ||
24 | ) { | ||
25 | $this->_setName($name); | ||
26 | $this->setAllocationSize($allocationSize); | ||
27 | $this->setInitialValue($initialValue); | ||
28 | } | ||
29 | |||
30 | public function getAllocationSize(): int | ||
31 | { | ||
32 | return $this->allocationSize; | ||
33 | } | ||
34 | |||
35 | public function getInitialValue(): int | ||
36 | { | ||
37 | return $this->initialValue; | ||
38 | } | ||
39 | |||
40 | public function getCache(): ?int | ||
41 | { | ||
42 | return $this->cache; | ||
43 | } | ||
44 | |||
45 | public function setAllocationSize(int $allocationSize): self | ||
46 | { | ||
47 | $this->allocationSize = $allocationSize; | ||
48 | |||
49 | return $this; | ||
50 | } | ||
51 | |||
52 | public function setInitialValue(int $initialValue): self | ||
53 | { | ||
54 | $this->initialValue = $initialValue; | ||
55 | |||
56 | return $this; | ||
57 | } | ||
58 | |||
59 | public function setCache(int $cache): self | ||
60 | { | ||
61 | $this->cache = $cache; | ||
62 | |||
63 | return $this; | ||
64 | } | ||
65 | |||
66 | /** | ||
67 | * Checks if this sequence is an autoincrement sequence for a given table. | ||
68 | * | ||
69 | * This is used inside the comparator to not report sequences as missing, | ||
70 | * when the "from" schema implicitly creates the sequences. | ||
71 | */ | ||
72 | public function isAutoIncrementsFor(Table $table): bool | ||
73 | { | ||
74 | $primaryKey = $table->getPrimaryKey(); | ||
75 | |||
76 | if ($primaryKey === null) { | ||
77 | return false; | ||
78 | } | ||
79 | |||
80 | $pkColumns = $primaryKey->getColumns(); | ||
81 | |||
82 | if (count($pkColumns) !== 1) { | ||
83 | return false; | ||
84 | } | ||
85 | |||
86 | $column = $table->getColumn($pkColumns[0]); | ||
87 | |||
88 | if (! $column->getAutoincrement()) { | ||
89 | return false; | ||
90 | } | ||
91 | |||
92 | $sequenceName = $this->getShortestName($table->getNamespaceName()); | ||
93 | $tableName = $table->getShortestName($table->getNamespaceName()); | ||
94 | $tableSequenceName = sprintf('%s_%s_seq', $tableName, $column->getShortestName($table->getNamespaceName())); | ||
95 | |||
96 | return $tableSequenceName === $sequenceName; | ||
97 | } | ||
98 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/Table.php b/vendor/doctrine/dbal/src/Schema/Table.php new file mode 100644 index 0000000..cc7f04d --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/Table.php | |||
@@ -0,0 +1,753 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\Exception\ColumnAlreadyExists; | ||
8 | use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist; | ||
9 | use Doctrine\DBAL\Schema\Exception\ForeignKeyDoesNotExist; | ||
10 | use Doctrine\DBAL\Schema\Exception\IndexAlreadyExists; | ||
11 | use Doctrine\DBAL\Schema\Exception\IndexDoesNotExist; | ||
12 | use Doctrine\DBAL\Schema\Exception\IndexNameInvalid; | ||
13 | use Doctrine\DBAL\Schema\Exception\InvalidTableName; | ||
14 | use Doctrine\DBAL\Schema\Exception\UniqueConstraintDoesNotExist; | ||
15 | use Doctrine\DBAL\Types\Type; | ||
16 | |||
17 | use function array_merge; | ||
18 | use function array_values; | ||
19 | use function in_array; | ||
20 | use function is_string; | ||
21 | use function preg_match; | ||
22 | use function strtolower; | ||
23 | |||
24 | /** | ||
25 | * Object Representation of a table. | ||
26 | */ | ||
27 | class Table extends AbstractAsset | ||
28 | { | ||
29 | /** @var Column[] */ | ||
30 | protected array $_columns = []; | ||
31 | |||
32 | /** @var Index[] */ | ||
33 | private array $implicitIndexes = []; | ||
34 | |||
35 | /** @var Index[] */ | ||
36 | protected array $_indexes = []; | ||
37 | |||
38 | protected ?string $_primaryKeyName = null; | ||
39 | |||
40 | /** @var UniqueConstraint[] */ | ||
41 | protected array $uniqueConstraints = []; | ||
42 | |||
43 | /** @var ForeignKeyConstraint[] */ | ||
44 | protected array $_fkConstraints = []; | ||
45 | |||
46 | /** @var mixed[] */ | ||
47 | protected array $_options = [ | ||
48 | 'create_options' => [], | ||
49 | ]; | ||
50 | |||
51 | protected ?SchemaConfig $_schemaConfig = null; | ||
52 | |||
53 | /** | ||
54 | * @param array<Column> $columns | ||
55 | * @param array<Index> $indexes | ||
56 | * @param array<UniqueConstraint> $uniqueConstraints | ||
57 | * @param array<ForeignKeyConstraint> $fkConstraints | ||
58 | * @param array<string, mixed> $options | ||
59 | */ | ||
60 | public function __construct( | ||
61 | string $name, | ||
62 | array $columns = [], | ||
63 | array $indexes = [], | ||
64 | array $uniqueConstraints = [], | ||
65 | array $fkConstraints = [], | ||
66 | array $options = [], | ||
67 | ) { | ||
68 | if ($name === '') { | ||
69 | throw InvalidTableName::new($name); | ||
70 | } | ||
71 | |||
72 | $this->_setName($name); | ||
73 | |||
74 | foreach ($columns as $column) { | ||
75 | $this->_addColumn($column); | ||
76 | } | ||
77 | |||
78 | foreach ($indexes as $idx) { | ||
79 | $this->_addIndex($idx); | ||
80 | } | ||
81 | |||
82 | foreach ($uniqueConstraints as $uniqueConstraint) { | ||
83 | $this->_addUniqueConstraint($uniqueConstraint); | ||
84 | } | ||
85 | |||
86 | foreach ($fkConstraints as $fkConstraint) { | ||
87 | $this->_addForeignKeyConstraint($fkConstraint); | ||
88 | } | ||
89 | |||
90 | $this->_options = array_merge($this->_options, $options); | ||
91 | } | ||
92 | |||
93 | public function setSchemaConfig(SchemaConfig $schemaConfig): void | ||
94 | { | ||
95 | $this->_schemaConfig = $schemaConfig; | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * Sets the Primary Key. | ||
100 | * | ||
101 | * @param array<int, string> $columnNames | ||
102 | */ | ||
103 | public function setPrimaryKey(array $columnNames, ?string $indexName = null): self | ||
104 | { | ||
105 | if ($indexName === null) { | ||
106 | $indexName = 'primary'; | ||
107 | } | ||
108 | |||
109 | $this->_addIndex($this->_createIndex($columnNames, $indexName, true, true)); | ||
110 | |||
111 | foreach ($columnNames as $columnName) { | ||
112 | $column = $this->getColumn($columnName); | ||
113 | $column->setNotnull(true); | ||
114 | } | ||
115 | |||
116 | return $this; | ||
117 | } | ||
118 | |||
119 | /** | ||
120 | * @param array<int, string> $columnNames | ||
121 | * @param array<int, string> $flags | ||
122 | * @param array<string, mixed> $options | ||
123 | */ | ||
124 | public function addUniqueConstraint( | ||
125 | array $columnNames, | ||
126 | ?string $indexName = null, | ||
127 | array $flags = [], | ||
128 | array $options = [], | ||
129 | ): self { | ||
130 | $indexName ??= $this->_generateIdentifierName( | ||
131 | array_merge([$this->getName()], $columnNames), | ||
132 | 'uniq', | ||
133 | $this->_getMaxIdentifierLength(), | ||
134 | ); | ||
135 | |||
136 | return $this->_addUniqueConstraint($this->_createUniqueConstraint($columnNames, $indexName, $flags, $options)); | ||
137 | } | ||
138 | |||
139 | /** | ||
140 | * @param array<int, string> $columnNames | ||
141 | * @param array<int, string> $flags | ||
142 | * @param array<string, mixed> $options | ||
143 | */ | ||
144 | public function addIndex( | ||
145 | array $columnNames, | ||
146 | ?string $indexName = null, | ||
147 | array $flags = [], | ||
148 | array $options = [], | ||
149 | ): self { | ||
150 | $indexName ??= $this->_generateIdentifierName( | ||
151 | array_merge([$this->getName()], $columnNames), | ||
152 | 'idx', | ||
153 | $this->_getMaxIdentifierLength(), | ||
154 | ); | ||
155 | |||
156 | return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options)); | ||
157 | } | ||
158 | |||
159 | /** | ||
160 | * Drops the primary key from this table. | ||
161 | */ | ||
162 | public function dropPrimaryKey(): void | ||
163 | { | ||
164 | if ($this->_primaryKeyName === null) { | ||
165 | return; | ||
166 | } | ||
167 | |||
168 | $this->dropIndex($this->_primaryKeyName); | ||
169 | $this->_primaryKeyName = null; | ||
170 | } | ||
171 | |||
172 | /** | ||
173 | * Drops an index from this table. | ||
174 | */ | ||
175 | public function dropIndex(string $name): void | ||
176 | { | ||
177 | $name = $this->normalizeIdentifier($name); | ||
178 | |||
179 | if (! $this->hasIndex($name)) { | ||
180 | throw IndexDoesNotExist::new($name, $this->_name); | ||
181 | } | ||
182 | |||
183 | unset($this->_indexes[$name]); | ||
184 | } | ||
185 | |||
186 | /** | ||
187 | * @param array<int, string> $columnNames | ||
188 | * @param array<string, mixed> $options | ||
189 | */ | ||
190 | public function addUniqueIndex(array $columnNames, ?string $indexName = null, array $options = []): self | ||
191 | { | ||
192 | $indexName ??= $this->_generateIdentifierName( | ||
193 | array_merge([$this->getName()], $columnNames), | ||
194 | 'uniq', | ||
195 | $this->_getMaxIdentifierLength(), | ||
196 | ); | ||
197 | |||
198 | return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options)); | ||
199 | } | ||
200 | |||
201 | /** | ||
202 | * Renames an index. | ||
203 | * | ||
204 | * @param string $oldName The name of the index to rename from. | ||
205 | * @param string|null $newName The name of the index to rename to. If null is given, the index name | ||
206 | * will be auto-generated. | ||
207 | */ | ||
208 | public function renameIndex(string $oldName, ?string $newName = null): self | ||
209 | { | ||
210 | $oldName = $this->normalizeIdentifier($oldName); | ||
211 | $normalizedNewName = $this->normalizeIdentifier($newName); | ||
212 | |||
213 | if ($oldName === $normalizedNewName) { | ||
214 | return $this; | ||
215 | } | ||
216 | |||
217 | if (! $this->hasIndex($oldName)) { | ||
218 | throw IndexDoesNotExist::new($oldName, $this->_name); | ||
219 | } | ||
220 | |||
221 | if ($this->hasIndex($normalizedNewName)) { | ||
222 | throw IndexAlreadyExists::new($normalizedNewName, $this->_name); | ||
223 | } | ||
224 | |||
225 | $oldIndex = $this->_indexes[$oldName]; | ||
226 | |||
227 | if ($oldIndex->isPrimary()) { | ||
228 | $this->dropPrimaryKey(); | ||
229 | |||
230 | return $this->setPrimaryKey($oldIndex->getColumns(), $newName ?? null); | ||
231 | } | ||
232 | |||
233 | unset($this->_indexes[$oldName]); | ||
234 | |||
235 | if ($oldIndex->isUnique()) { | ||
236 | return $this->addUniqueIndex($oldIndex->getColumns(), $newName, $oldIndex->getOptions()); | ||
237 | } | ||
238 | |||
239 | return $this->addIndex($oldIndex->getColumns(), $newName, $oldIndex->getFlags(), $oldIndex->getOptions()); | ||
240 | } | ||
241 | |||
242 | /** | ||
243 | * Checks if an index begins in the order of the given columns. | ||
244 | * | ||
245 | * @param array<int, string> $columnNames | ||
246 | */ | ||
247 | public function columnsAreIndexed(array $columnNames): bool | ||
248 | { | ||
249 | foreach ($this->getIndexes() as $index) { | ||
250 | if ($index->spansColumns($columnNames)) { | ||
251 | return true; | ||
252 | } | ||
253 | } | ||
254 | |||
255 | return false; | ||
256 | } | ||
257 | |||
258 | /** @param array<string, mixed> $options */ | ||
259 | public function addColumn(string $name, string $typeName, array $options = []): Column | ||
260 | { | ||
261 | $column = new Column($name, Type::getType($typeName), $options); | ||
262 | |||
263 | $this->_addColumn($column); | ||
264 | |||
265 | return $column; | ||
266 | } | ||
267 | |||
268 | /** @param array<string, mixed> $options */ | ||
269 | public function modifyColumn(string $name, array $options): self | ||
270 | { | ||
271 | $column = $this->getColumn($name); | ||
272 | $column->setOptions($options); | ||
273 | |||
274 | return $this; | ||
275 | } | ||
276 | |||
277 | /** | ||
278 | * Drops a Column from the Table. | ||
279 | */ | ||
280 | public function dropColumn(string $name): self | ||
281 | { | ||
282 | $name = $this->normalizeIdentifier($name); | ||
283 | |||
284 | unset($this->_columns[$name]); | ||
285 | |||
286 | return $this; | ||
287 | } | ||
288 | |||
289 | /** | ||
290 | * Adds a foreign key constraint. | ||
291 | * | ||
292 | * Name is inferred from the local columns. | ||
293 | * | ||
294 | * @param array<int, string> $localColumnNames | ||
295 | * @param array<int, string> $foreignColumnNames | ||
296 | * @param array<string, mixed> $options | ||
297 | */ | ||
298 | public function addForeignKeyConstraint( | ||
299 | string $foreignTableName, | ||
300 | array $localColumnNames, | ||
301 | array $foreignColumnNames, | ||
302 | array $options = [], | ||
303 | ?string $name = null, | ||
304 | ): self { | ||
305 | $name ??= $this->_generateIdentifierName( | ||
306 | array_merge([$this->getName()], $localColumnNames), | ||
307 | 'fk', | ||
308 | $this->_getMaxIdentifierLength(), | ||
309 | ); | ||
310 | |||
311 | foreach ($localColumnNames as $columnName) { | ||
312 | if (! $this->hasColumn($columnName)) { | ||
313 | throw ColumnDoesNotExist::new($columnName, $this->_name); | ||
314 | } | ||
315 | } | ||
316 | |||
317 | $constraint = new ForeignKeyConstraint( | ||
318 | $localColumnNames, | ||
319 | $foreignTableName, | ||
320 | $foreignColumnNames, | ||
321 | $name, | ||
322 | $options, | ||
323 | ); | ||
324 | |||
325 | return $this->_addForeignKeyConstraint($constraint); | ||
326 | } | ||
327 | |||
328 | public function addOption(string $name, mixed $value): self | ||
329 | { | ||
330 | $this->_options[$name] = $value; | ||
331 | |||
332 | return $this; | ||
333 | } | ||
334 | |||
335 | /** | ||
336 | * Returns whether this table has a foreign key constraint with the given name. | ||
337 | */ | ||
338 | public function hasForeignKey(string $name): bool | ||
339 | { | ||
340 | $name = $this->normalizeIdentifier($name); | ||
341 | |||
342 | return isset($this->_fkConstraints[$name]); | ||
343 | } | ||
344 | |||
345 | /** | ||
346 | * Returns the foreign key constraint with the given name. | ||
347 | */ | ||
348 | public function getForeignKey(string $name): ForeignKeyConstraint | ||
349 | { | ||
350 | $name = $this->normalizeIdentifier($name); | ||
351 | |||
352 | if (! $this->hasForeignKey($name)) { | ||
353 | throw ForeignKeyDoesNotExist::new($name, $this->_name); | ||
354 | } | ||
355 | |||
356 | return $this->_fkConstraints[$name]; | ||
357 | } | ||
358 | |||
359 | /** | ||
360 | * Removes the foreign key constraint with the given name. | ||
361 | */ | ||
362 | public function removeForeignKey(string $name): void | ||
363 | { | ||
364 | $name = $this->normalizeIdentifier($name); | ||
365 | |||
366 | if (! $this->hasForeignKey($name)) { | ||
367 | throw ForeignKeyDoesNotExist::new($name, $this->_name); | ||
368 | } | ||
369 | |||
370 | unset($this->_fkConstraints[$name]); | ||
371 | } | ||
372 | |||
373 | /** | ||
374 | * Returns whether this table has a unique constraint with the given name. | ||
375 | */ | ||
376 | public function hasUniqueConstraint(string $name): bool | ||
377 | { | ||
378 | $name = $this->normalizeIdentifier($name); | ||
379 | |||
380 | return isset($this->uniqueConstraints[$name]); | ||
381 | } | ||
382 | |||
383 | /** | ||
384 | * Returns the unique constraint with the given name. | ||
385 | */ | ||
386 | public function getUniqueConstraint(string $name): UniqueConstraint | ||
387 | { | ||
388 | $name = $this->normalizeIdentifier($name); | ||
389 | |||
390 | if (! $this->hasUniqueConstraint($name)) { | ||
391 | throw UniqueConstraintDoesNotExist::new($name, $this->_name); | ||
392 | } | ||
393 | |||
394 | return $this->uniqueConstraints[$name]; | ||
395 | } | ||
396 | |||
397 | /** | ||
398 | * Removes the unique constraint with the given name. | ||
399 | */ | ||
400 | public function removeUniqueConstraint(string $name): void | ||
401 | { | ||
402 | $name = $this->normalizeIdentifier($name); | ||
403 | |||
404 | if (! $this->hasUniqueConstraint($name)) { | ||
405 | throw UniqueConstraintDoesNotExist::new($name, $this->_name); | ||
406 | } | ||
407 | |||
408 | unset($this->uniqueConstraints[$name]); | ||
409 | } | ||
410 | |||
411 | /** | ||
412 | * Returns the list of table columns. | ||
413 | * | ||
414 | * @return list<Column> | ||
415 | */ | ||
416 | public function getColumns(): array | ||
417 | { | ||
418 | return array_values($this->_columns); | ||
419 | } | ||
420 | |||
421 | /** | ||
422 | * Returns whether this table has a Column with the given name. | ||
423 | */ | ||
424 | public function hasColumn(string $name): bool | ||
425 | { | ||
426 | $name = $this->normalizeIdentifier($name); | ||
427 | |||
428 | return isset($this->_columns[$name]); | ||
429 | } | ||
430 | |||
431 | /** | ||
432 | * Returns the Column with the given name. | ||
433 | */ | ||
434 | public function getColumn(string $name): Column | ||
435 | { | ||
436 | $name = $this->normalizeIdentifier($name); | ||
437 | |||
438 | if (! $this->hasColumn($name)) { | ||
439 | throw ColumnDoesNotExist::new($name, $this->_name); | ||
440 | } | ||
441 | |||
442 | return $this->_columns[$name]; | ||
443 | } | ||
444 | |||
445 | /** | ||
446 | * Returns the primary key. | ||
447 | */ | ||
448 | public function getPrimaryKey(): ?Index | ||
449 | { | ||
450 | if ($this->_primaryKeyName !== null) { | ||
451 | return $this->getIndex($this->_primaryKeyName); | ||
452 | } | ||
453 | |||
454 | return null; | ||
455 | } | ||
456 | |||
457 | /** | ||
458 | * Returns whether this table has an Index with the given name. | ||
459 | */ | ||
460 | public function hasIndex(string $name): bool | ||
461 | { | ||
462 | $name = $this->normalizeIdentifier($name); | ||
463 | |||
464 | return isset($this->_indexes[$name]); | ||
465 | } | ||
466 | |||
467 | /** | ||
468 | * Returns the Index with the given name. | ||
469 | */ | ||
470 | public function getIndex(string $name): Index | ||
471 | { | ||
472 | $name = $this->normalizeIdentifier($name); | ||
473 | |||
474 | if (! $this->hasIndex($name)) { | ||
475 | throw IndexDoesNotExist::new($name, $this->_name); | ||
476 | } | ||
477 | |||
478 | return $this->_indexes[$name]; | ||
479 | } | ||
480 | |||
481 | /** @return array<string, Index> */ | ||
482 | public function getIndexes(): array | ||
483 | { | ||
484 | return $this->_indexes; | ||
485 | } | ||
486 | |||
487 | /** | ||
488 | * Returns the unique constraints. | ||
489 | * | ||
490 | * @return array<string, UniqueConstraint> | ||
491 | */ | ||
492 | public function getUniqueConstraints(): array | ||
493 | { | ||
494 | return $this->uniqueConstraints; | ||
495 | } | ||
496 | |||
497 | /** | ||
498 | * Returns the foreign key constraints. | ||
499 | * | ||
500 | * @return array<string, ForeignKeyConstraint> | ||
501 | */ | ||
502 | public function getForeignKeys(): array | ||
503 | { | ||
504 | return $this->_fkConstraints; | ||
505 | } | ||
506 | |||
507 | public function hasOption(string $name): bool | ||
508 | { | ||
509 | return isset($this->_options[$name]); | ||
510 | } | ||
511 | |||
512 | public function getOption(string $name): mixed | ||
513 | { | ||
514 | return $this->_options[$name] ?? null; | ||
515 | } | ||
516 | |||
517 | /** @return array<string, mixed> */ | ||
518 | public function getOptions(): array | ||
519 | { | ||
520 | return $this->_options; | ||
521 | } | ||
522 | |||
523 | /** | ||
524 | * Clone of a Table triggers a deep clone of all affected assets. | ||
525 | */ | ||
526 | public function __clone() | ||
527 | { | ||
528 | foreach ($this->_columns as $k => $column) { | ||
529 | $this->_columns[$k] = clone $column; | ||
530 | } | ||
531 | |||
532 | foreach ($this->_indexes as $k => $index) { | ||
533 | $this->_indexes[$k] = clone $index; | ||
534 | } | ||
535 | |||
536 | foreach ($this->_fkConstraints as $k => $fk) { | ||
537 | $this->_fkConstraints[$k] = clone $fk; | ||
538 | } | ||
539 | } | ||
540 | |||
541 | protected function _getMaxIdentifierLength(): int | ||
542 | { | ||
543 | return $this->_schemaConfig instanceof SchemaConfig | ||
544 | ? $this->_schemaConfig->getMaxIdentifierLength() | ||
545 | : 63; | ||
546 | } | ||
547 | |||
548 | protected function _addColumn(Column $column): void | ||
549 | { | ||
550 | $columnName = $column->getName(); | ||
551 | $columnName = $this->normalizeIdentifier($columnName); | ||
552 | |||
553 | if (isset($this->_columns[$columnName])) { | ||
554 | throw ColumnAlreadyExists::new($this->getName(), $columnName); | ||
555 | } | ||
556 | |||
557 | $this->_columns[$columnName] = $column; | ||
558 | } | ||
559 | |||
560 | /** | ||
561 | * Adds an index to the table. | ||
562 | */ | ||
563 | protected function _addIndex(Index $indexCandidate): self | ||
564 | { | ||
565 | $indexName = $indexCandidate->getName(); | ||
566 | $indexName = $this->normalizeIdentifier($indexName); | ||
567 | $replacedImplicitIndexes = []; | ||
568 | |||
569 | foreach ($this->implicitIndexes as $name => $implicitIndex) { | ||
570 | if (! $implicitIndex->isFulfilledBy($indexCandidate) || ! isset($this->_indexes[$name])) { | ||
571 | continue; | ||
572 | } | ||
573 | |||
574 | $replacedImplicitIndexes[] = $name; | ||
575 | } | ||
576 | |||
577 | if ( | ||
578 | (isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) || | ||
579 | ($this->_primaryKeyName !== null && $indexCandidate->isPrimary()) | ||
580 | ) { | ||
581 | throw IndexAlreadyExists::new($indexName, $this->_name); | ||
582 | } | ||
583 | |||
584 | foreach ($replacedImplicitIndexes as $name) { | ||
585 | unset($this->_indexes[$name], $this->implicitIndexes[$name]); | ||
586 | } | ||
587 | |||
588 | if ($indexCandidate->isPrimary()) { | ||
589 | $this->_primaryKeyName = $indexName; | ||
590 | } | ||
591 | |||
592 | $this->_indexes[$indexName] = $indexCandidate; | ||
593 | |||
594 | return $this; | ||
595 | } | ||
596 | |||
597 | protected function _addUniqueConstraint(UniqueConstraint $constraint): self | ||
598 | { | ||
599 | $name = $constraint->getName() !== '' | ||
600 | ? $constraint->getName() | ||
601 | : $this->_generateIdentifierName( | ||
602 | array_merge((array) $this->getName(), $constraint->getColumns()), | ||
603 | 'fk', | ||
604 | $this->_getMaxIdentifierLength(), | ||
605 | ); | ||
606 | |||
607 | $name = $this->normalizeIdentifier($name); | ||
608 | |||
609 | $this->uniqueConstraints[$name] = $constraint; | ||
610 | |||
611 | // If there is already an index that fulfills this requirements drop the request. In the case of __construct | ||
612 | // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates. | ||
613 | // This creates computation overhead in this case, however no duplicate indexes are ever added (column based). | ||
614 | $indexName = $this->_generateIdentifierName( | ||
615 | array_merge([$this->getName()], $constraint->getColumns()), | ||
616 | 'idx', | ||
617 | $this->_getMaxIdentifierLength(), | ||
618 | ); | ||
619 | |||
620 | $indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, true, false); | ||
621 | |||
622 | foreach ($this->_indexes as $existingIndex) { | ||
623 | if ($indexCandidate->isFulfilledBy($existingIndex)) { | ||
624 | return $this; | ||
625 | } | ||
626 | } | ||
627 | |||
628 | $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; | ||
629 | |||
630 | return $this; | ||
631 | } | ||
632 | |||
633 | protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint): self | ||
634 | { | ||
635 | $name = $constraint->getName() !== '' | ||
636 | ? $constraint->getName() | ||
637 | : $this->_generateIdentifierName( | ||
638 | array_merge((array) $this->getName(), $constraint->getLocalColumns()), | ||
639 | 'fk', | ||
640 | $this->_getMaxIdentifierLength(), | ||
641 | ); | ||
642 | |||
643 | $name = $this->normalizeIdentifier($name); | ||
644 | |||
645 | $this->_fkConstraints[$name] = $constraint; | ||
646 | |||
647 | // add an explicit index on the foreign key columns. | ||
648 | // If there is already an index that fulfills this requirements drop the request. In the case of __construct | ||
649 | // calling this method during hydration from schema-details all the explicitly added indexes lead to duplicates. | ||
650 | // This creates computation overhead in this case, however no duplicate indexes are ever added (column based). | ||
651 | $indexName = $this->_generateIdentifierName( | ||
652 | array_merge([$this->getName()], $constraint->getLocalColumns()), | ||
653 | 'idx', | ||
654 | $this->_getMaxIdentifierLength(), | ||
655 | ); | ||
656 | |||
657 | $indexCandidate = $this->_createIndex($constraint->getLocalColumns(), $indexName, false, false); | ||
658 | |||
659 | foreach ($this->_indexes as $existingIndex) { | ||
660 | if ($indexCandidate->isFulfilledBy($existingIndex)) { | ||
661 | return $this; | ||
662 | } | ||
663 | } | ||
664 | |||
665 | $this->_addIndex($indexCandidate); | ||
666 | $this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate; | ||
667 | |||
668 | return $this; | ||
669 | } | ||
670 | |||
671 | /** | ||
672 | * Normalizes a given identifier. | ||
673 | * | ||
674 | * Trims quotes and lowercases the given identifier. | ||
675 | */ | ||
676 | private function normalizeIdentifier(?string $identifier): string | ||
677 | { | ||
678 | if ($identifier === null) { | ||
679 | return ''; | ||
680 | } | ||
681 | |||
682 | return $this->trimQuotes(strtolower($identifier)); | ||
683 | } | ||
684 | |||
685 | public function setComment(string $comment): self | ||
686 | { | ||
687 | // For keeping backward compatibility with MySQL in previous releases, table comments are stored as options. | ||
688 | $this->addOption('comment', $comment); | ||
689 | |||
690 | return $this; | ||
691 | } | ||
692 | |||
693 | public function getComment(): ?string | ||
694 | { | ||
695 | return $this->_options['comment'] ?? null; | ||
696 | } | ||
697 | |||
698 | /** | ||
699 | * @param array<string|int, string> $columns | ||
700 | * @param array<int, string> $flags | ||
701 | * @param array<string, mixed> $options | ||
702 | */ | ||
703 | private function _createUniqueConstraint( | ||
704 | array $columns, | ||
705 | string $indexName, | ||
706 | array $flags = [], | ||
707 | array $options = [], | ||
708 | ): UniqueConstraint { | ||
709 | if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) { | ||
710 | throw IndexNameInvalid::new($indexName); | ||
711 | } | ||
712 | |||
713 | foreach ($columns as $index => $value) { | ||
714 | if (is_string($index)) { | ||
715 | $columnName = $index; | ||
716 | } else { | ||
717 | $columnName = $value; | ||
718 | } | ||
719 | |||
720 | if (! $this->hasColumn($columnName)) { | ||
721 | throw ColumnDoesNotExist::new($columnName, $this->_name); | ||
722 | } | ||
723 | } | ||
724 | |||
725 | return new UniqueConstraint($indexName, $columns, $flags, $options); | ||
726 | } | ||
727 | |||
728 | /** | ||
729 | * @param array<int, string> $columns | ||
730 | * @param array<int, string> $flags | ||
731 | * @param array<string, mixed> $options | ||
732 | */ | ||
733 | private function _createIndex( | ||
734 | array $columns, | ||
735 | string $indexName, | ||
736 | bool $isUnique, | ||
737 | bool $isPrimary, | ||
738 | array $flags = [], | ||
739 | array $options = [], | ||
740 | ): Index { | ||
741 | if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName)) === 1) { | ||
742 | throw IndexNameInvalid::new($indexName); | ||
743 | } | ||
744 | |||
745 | foreach ($columns as $columnName) { | ||
746 | if (! $this->hasColumn($columnName)) { | ||
747 | throw ColumnDoesNotExist::new($columnName, $this->_name); | ||
748 | } | ||
749 | } | ||
750 | |||
751 | return new Index($indexName, $columns, $isUnique, $isPrimary, $flags, $options); | ||
752 | } | ||
753 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/TableDiff.php b/vendor/doctrine/dbal/src/Schema/TableDiff.php new file mode 100644 index 0000000..1cd59e8 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/TableDiff.php | |||
@@ -0,0 +1,164 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use function array_filter; | ||
8 | use function count; | ||
9 | |||
10 | /** | ||
11 | * Table Diff. | ||
12 | */ | ||
13 | class TableDiff | ||
14 | { | ||
15 | /** | ||
16 | * Constructs a TableDiff object. | ||
17 | * | ||
18 | * @internal The diff can be only instantiated by a {@see Comparator}. | ||
19 | * | ||
20 | * @param array<ForeignKeyConstraint> $droppedForeignKeys | ||
21 | * @param array<Column> $addedColumns | ||
22 | * @param array<ColumnDiff> $modifiedColumns | ||
23 | * @param array<Column> $droppedColumns | ||
24 | * @param array<string, Column> $renamedColumns | ||
25 | * @param array<Index> $addedIndexes | ||
26 | * @param array<Index> $modifiedIndexes | ||
27 | * @param array<Index> $droppedIndexes | ||
28 | * @param array<string, Index> $renamedIndexes | ||
29 | * @param array<ForeignKeyConstraint> $addedForeignKeys | ||
30 | * @param array<ForeignKeyConstraint> $modifiedForeignKeys | ||
31 | */ | ||
32 | public function __construct( | ||
33 | private readonly Table $oldTable, | ||
34 | private readonly array $addedColumns, | ||
35 | private readonly array $modifiedColumns, | ||
36 | private readonly array $droppedColumns, | ||
37 | private readonly array $renamedColumns, | ||
38 | private array $addedIndexes, | ||
39 | private readonly array $modifiedIndexes, | ||
40 | private array $droppedIndexes, | ||
41 | private readonly array $renamedIndexes, | ||
42 | private readonly array $addedForeignKeys, | ||
43 | private readonly array $modifiedForeignKeys, | ||
44 | private readonly array $droppedForeignKeys, | ||
45 | ) { | ||
46 | } | ||
47 | |||
48 | public function getOldTable(): Table | ||
49 | { | ||
50 | return $this->oldTable; | ||
51 | } | ||
52 | |||
53 | /** @return array<Column> */ | ||
54 | public function getAddedColumns(): array | ||
55 | { | ||
56 | return $this->addedColumns; | ||
57 | } | ||
58 | |||
59 | /** @return array<ColumnDiff> */ | ||
60 | public function getModifiedColumns(): array | ||
61 | { | ||
62 | return $this->modifiedColumns; | ||
63 | } | ||
64 | |||
65 | /** @return array<Column> */ | ||
66 | public function getDroppedColumns(): array | ||
67 | { | ||
68 | return $this->droppedColumns; | ||
69 | } | ||
70 | |||
71 | /** @return array<string,Column> */ | ||
72 | public function getRenamedColumns(): array | ||
73 | { | ||
74 | return $this->renamedColumns; | ||
75 | } | ||
76 | |||
77 | /** @return array<Index> */ | ||
78 | public function getAddedIndexes(): array | ||
79 | { | ||
80 | return $this->addedIndexes; | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * @internal This method exists only for compatibility with the current implementation of schema managers | ||
85 | * that modify the diff while processing it. | ||
86 | */ | ||
87 | public function unsetAddedIndex(Index $index): void | ||
88 | { | ||
89 | $this->addedIndexes = array_filter( | ||
90 | $this->addedIndexes, | ||
91 | static function (Index $addedIndex) use ($index): bool { | ||
92 | return $addedIndex !== $index; | ||
93 | }, | ||
94 | ); | ||
95 | } | ||
96 | |||
97 | /** @return array<Index> */ | ||
98 | public function getModifiedIndexes(): array | ||
99 | { | ||
100 | return $this->modifiedIndexes; | ||
101 | } | ||
102 | |||
103 | /** @return array<Index> */ | ||
104 | public function getDroppedIndexes(): array | ||
105 | { | ||
106 | return $this->droppedIndexes; | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * @internal This method exists only for compatibility with the current implementation of schema managers | ||
111 | * that modify the diff while processing it. | ||
112 | */ | ||
113 | public function unsetDroppedIndex(Index $index): void | ||
114 | { | ||
115 | $this->droppedIndexes = array_filter( | ||
116 | $this->droppedIndexes, | ||
117 | static function (Index $droppedIndex) use ($index): bool { | ||
118 | return $droppedIndex !== $index; | ||
119 | }, | ||
120 | ); | ||
121 | } | ||
122 | |||
123 | /** @return array<string,Index> */ | ||
124 | public function getRenamedIndexes(): array | ||
125 | { | ||
126 | return $this->renamedIndexes; | ||
127 | } | ||
128 | |||
129 | /** @return array<ForeignKeyConstraint> */ | ||
130 | public function getAddedForeignKeys(): array | ||
131 | { | ||
132 | return $this->addedForeignKeys; | ||
133 | } | ||
134 | |||
135 | /** @return array<ForeignKeyConstraint> */ | ||
136 | public function getModifiedForeignKeys(): array | ||
137 | { | ||
138 | return $this->modifiedForeignKeys; | ||
139 | } | ||
140 | |||
141 | /** @return array<ForeignKeyConstraint> */ | ||
142 | public function getDroppedForeignKeys(): array | ||
143 | { | ||
144 | return $this->droppedForeignKeys; | ||
145 | } | ||
146 | |||
147 | /** | ||
148 | * Returns whether the diff is empty (contains no changes). | ||
149 | */ | ||
150 | public function isEmpty(): bool | ||
151 | { | ||
152 | return count($this->addedColumns) === 0 | ||
153 | && count($this->modifiedColumns) === 0 | ||
154 | && count($this->droppedColumns) === 0 | ||
155 | && count($this->renamedColumns) === 0 | ||
156 | && count($this->addedIndexes) === 0 | ||
157 | && count($this->modifiedIndexes) === 0 | ||
158 | && count($this->droppedIndexes) === 0 | ||
159 | && count($this->renamedIndexes) === 0 | ||
160 | && count($this->addedForeignKeys) === 0 | ||
161 | && count($this->modifiedForeignKeys) === 0 | ||
162 | && count($this->droppedForeignKeys) === 0; | ||
163 | } | ||
164 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/UniqueConstraint.php b/vendor/doctrine/dbal/src/Schema/UniqueConstraint.php new file mode 100644 index 0000000..a33d446 --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/UniqueConstraint.php | |||
@@ -0,0 +1,152 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\AbstractPlatform; | ||
8 | |||
9 | use function array_keys; | ||
10 | use function array_map; | ||
11 | use function strtolower; | ||
12 | |||
13 | /** | ||
14 | * Class for a unique constraint. | ||
15 | */ | ||
16 | class UniqueConstraint extends AbstractAsset | ||
17 | { | ||
18 | /** | ||
19 | * Asset identifier instances of the column names the unique constraint is associated with. | ||
20 | * | ||
21 | * @var array<string, Identifier> | ||
22 | */ | ||
23 | protected array $columns = []; | ||
24 | |||
25 | /** | ||
26 | * Platform specific flags | ||
27 | * | ||
28 | * @var array<string, true> | ||
29 | */ | ||
30 | protected array $flags = []; | ||
31 | |||
32 | /** | ||
33 | * @param array<string> $columns | ||
34 | * @param array<string> $flags | ||
35 | * @param array<string, mixed> $options | ||
36 | */ | ||
37 | public function __construct( | ||
38 | string $name, | ||
39 | array $columns, | ||
40 | array $flags = [], | ||
41 | private readonly array $options = [], | ||
42 | ) { | ||
43 | $this->_setName($name); | ||
44 | |||
45 | foreach ($columns as $column) { | ||
46 | $this->addColumn($column); | ||
47 | } | ||
48 | |||
49 | foreach ($flags as $flag) { | ||
50 | $this->addFlag($flag); | ||
51 | } | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * Returns the names of the referencing table columns the constraint is associated with. | ||
56 | * | ||
57 | * @return list<string> | ||
58 | */ | ||
59 | public function getColumns(): array | ||
60 | { | ||
61 | return array_keys($this->columns); | ||
62 | } | ||
63 | |||
64 | /** | ||
65 | * Returns the quoted representation of the column names the constraint is associated with. | ||
66 | * | ||
67 | * But only if they were defined with one or a column name | ||
68 | * is a keyword reserved by the platform. | ||
69 | * Otherwise, the plain unquoted value as inserted is returned. | ||
70 | * | ||
71 | * @param AbstractPlatform $platform The platform to use for quotation. | ||
72 | * | ||
73 | * @return list<string> | ||
74 | */ | ||
75 | public function getQuotedColumns(AbstractPlatform $platform): array | ||
76 | { | ||
77 | $columns = []; | ||
78 | |||
79 | foreach ($this->columns as $column) { | ||
80 | $columns[] = $column->getQuotedName($platform); | ||
81 | } | ||
82 | |||
83 | return $columns; | ||
84 | } | ||
85 | |||
86 | /** @return array<int, string> */ | ||
87 | public function getUnquotedColumns(): array | ||
88 | { | ||
89 | return array_map($this->trimQuotes(...), $this->getColumns()); | ||
90 | } | ||
91 | |||
92 | /** | ||
93 | * Returns platform specific flags for unique constraint. | ||
94 | * | ||
95 | * @return array<int, string> | ||
96 | */ | ||
97 | public function getFlags(): array | ||
98 | { | ||
99 | return array_keys($this->flags); | ||
100 | } | ||
101 | |||
102 | /** | ||
103 | * Adds flag for a unique constraint that translates to platform specific handling. | ||
104 | * | ||
105 | * @return $this | ||
106 | * | ||
107 | * @example $uniqueConstraint->addFlag('CLUSTERED') | ||
108 | */ | ||
109 | public function addFlag(string $flag): self | ||
110 | { | ||
111 | $this->flags[strtolower($flag)] = true; | ||
112 | |||
113 | return $this; | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * Does this unique constraint have a specific flag? | ||
118 | */ | ||
119 | public function hasFlag(string $flag): bool | ||
120 | { | ||
121 | return isset($this->flags[strtolower($flag)]); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * Removes a flag. | ||
126 | */ | ||
127 | public function removeFlag(string $flag): void | ||
128 | { | ||
129 | unset($this->flags[strtolower($flag)]); | ||
130 | } | ||
131 | |||
132 | public function hasOption(string $name): bool | ||
133 | { | ||
134 | return isset($this->options[strtolower($name)]); | ||
135 | } | ||
136 | |||
137 | public function getOption(string $name): mixed | ||
138 | { | ||
139 | return $this->options[strtolower($name)]; | ||
140 | } | ||
141 | |||
142 | /** @return array<string, mixed> */ | ||
143 | public function getOptions(): array | ||
144 | { | ||
145 | return $this->options; | ||
146 | } | ||
147 | |||
148 | protected function addColumn(string $column): void | ||
149 | { | ||
150 | $this->columns[$column] = new Identifier($column); | ||
151 | } | ||
152 | } | ||
diff --git a/vendor/doctrine/dbal/src/Schema/View.php b/vendor/doctrine/dbal/src/Schema/View.php new file mode 100644 index 0000000..81f5f8a --- /dev/null +++ b/vendor/doctrine/dbal/src/Schema/View.php | |||
@@ -0,0 +1,21 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Schema; | ||
6 | |||
7 | /** | ||
8 | * Representation of a Database View. | ||
9 | */ | ||
10 | class View extends AbstractAsset | ||
11 | { | ||
12 | public function __construct(string $name, private readonly string $sql) | ||
13 | { | ||
14 | $this->_setName($name); | ||
15 | } | ||
16 | |||
17 | public function getSql(): string | ||
18 | { | ||
19 | return $this->sql; | ||
20 | } | ||
21 | } | ||