diff options
Diffstat (limited to 'vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php')
-rw-r--r-- | vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php | 842 |
1 files changed, 842 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php b/vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php new file mode 100644 index 0000000..770d863 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php | |||
@@ -0,0 +1,842 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | use Doctrine\DBAL\Connection; | ||
8 | use Doctrine\DBAL\Exception; | ||
9 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
10 | use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords; | ||
11 | use Doctrine\DBAL\Schema\AbstractAsset; | ||
12 | use Doctrine\DBAL\Schema\ForeignKeyConstraint; | ||
13 | use Doctrine\DBAL\Schema\Identifier; | ||
14 | use Doctrine\DBAL\Schema\Index; | ||
15 | use Doctrine\DBAL\Schema\MySQLSchemaManager; | ||
16 | use Doctrine\DBAL\Schema\TableDiff; | ||
17 | use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; | ||
18 | use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; | ||
19 | use Doctrine\DBAL\TransactionIsolationLevel; | ||
20 | use Doctrine\DBAL\Types\Types; | ||
21 | |||
22 | use function array_merge; | ||
23 | use function array_unique; | ||
24 | use function array_values; | ||
25 | use function count; | ||
26 | use function implode; | ||
27 | use function in_array; | ||
28 | use function is_numeric; | ||
29 | use function sprintf; | ||
30 | use function str_replace; | ||
31 | use function strtolower; | ||
32 | |||
33 | /** | ||
34 | * Provides the base implementation for the lowest versions of supported MySQL-like database platforms. | ||
35 | */ | ||
36 | abstract class AbstractMySQLPlatform extends AbstractPlatform | ||
37 | { | ||
38 | final public const LENGTH_LIMIT_TINYTEXT = 255; | ||
39 | final public const LENGTH_LIMIT_TEXT = 65535; | ||
40 | final public const LENGTH_LIMIT_MEDIUMTEXT = 16777215; | ||
41 | |||
42 | final public const LENGTH_LIMIT_TINYBLOB = 255; | ||
43 | final public const LENGTH_LIMIT_BLOB = 65535; | ||
44 | final public const LENGTH_LIMIT_MEDIUMBLOB = 16777215; | ||
45 | |||
46 | protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string | ||
47 | { | ||
48 | if ($limit !== null) { | ||
49 | $query .= sprintf(' LIMIT %d', $limit); | ||
50 | |||
51 | if ($offset > 0) { | ||
52 | $query .= sprintf(' OFFSET %d', $offset); | ||
53 | } | ||
54 | } elseif ($offset > 0) { | ||
55 | // 2^64-1 is the maximum of unsigned BIGINT, the biggest limit possible | ||
56 | $query .= sprintf(' LIMIT 18446744073709551615 OFFSET %d', $offset); | ||
57 | } | ||
58 | |||
59 | return $query; | ||
60 | } | ||
61 | |||
62 | public function quoteSingleIdentifier(string $str): string | ||
63 | { | ||
64 | return '`' . str_replace('`', '``', $str) . '`'; | ||
65 | } | ||
66 | |||
67 | public function getRegexpExpression(): string | ||
68 | { | ||
69 | return 'RLIKE'; | ||
70 | } | ||
71 | |||
72 | public function getLocateExpression(string $string, string $substring, ?string $start = null): string | ||
73 | { | ||
74 | if ($start === null) { | ||
75 | return sprintf('LOCATE(%s, %s)', $substring, $string); | ||
76 | } | ||
77 | |||
78 | return sprintf('LOCATE(%s, %s, %s)', $substring, $string, $start); | ||
79 | } | ||
80 | |||
81 | public function getConcatExpression(string ...$string): string | ||
82 | { | ||
83 | return sprintf('CONCAT(%s)', implode(', ', $string)); | ||
84 | } | ||
85 | |||
86 | protected function getDateArithmeticIntervalExpression( | ||
87 | string $date, | ||
88 | string $operator, | ||
89 | string $interval, | ||
90 | DateIntervalUnit $unit, | ||
91 | ): string { | ||
92 | $function = $operator === '+' ? 'DATE_ADD' : 'DATE_SUB'; | ||
93 | |||
94 | return $function . '(' . $date . ', INTERVAL ' . $interval . ' ' . $unit->value . ')'; | ||
95 | } | ||
96 | |||
97 | public function getDateDiffExpression(string $date1, string $date2): string | ||
98 | { | ||
99 | return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; | ||
100 | } | ||
101 | |||
102 | public function getCurrentDatabaseExpression(): string | ||
103 | { | ||
104 | return 'DATABASE()'; | ||
105 | } | ||
106 | |||
107 | public function getLengthExpression(string $string): string | ||
108 | { | ||
109 | return 'CHAR_LENGTH(' . $string . ')'; | ||
110 | } | ||
111 | |||
112 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
113 | public function getListDatabasesSQL(): string | ||
114 | { | ||
115 | return 'SHOW DATABASES'; | ||
116 | } | ||
117 | |||
118 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
119 | public function getListViewsSQL(string $database): string | ||
120 | { | ||
121 | return 'SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ' . $this->quoteStringLiteral($database); | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * {@inheritDoc} | ||
126 | */ | ||
127 | public function getJsonTypeDeclarationSQL(array $column): string | ||
128 | { | ||
129 | return 'JSON'; | ||
130 | } | ||
131 | |||
132 | /** | ||
133 | * Gets the SQL snippet used to declare a CLOB column type. | ||
134 | * TINYTEXT : 2 ^ 8 - 1 = 255 | ||
135 | * TEXT : 2 ^ 16 - 1 = 65535 | ||
136 | * MEDIUMTEXT : 2 ^ 24 - 1 = 16777215 | ||
137 | * LONGTEXT : 2 ^ 32 - 1 = 4294967295 | ||
138 | * | ||
139 | * {@inheritDoc} | ||
140 | */ | ||
141 | public function getClobTypeDeclarationSQL(array $column): string | ||
142 | { | ||
143 | if (! empty($column['length']) && is_numeric($column['length'])) { | ||
144 | $length = $column['length']; | ||
145 | |||
146 | if ($length <= static::LENGTH_LIMIT_TINYTEXT) { | ||
147 | return 'TINYTEXT'; | ||
148 | } | ||
149 | |||
150 | if ($length <= static::LENGTH_LIMIT_TEXT) { | ||
151 | return 'TEXT'; | ||
152 | } | ||
153 | |||
154 | if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) { | ||
155 | return 'MEDIUMTEXT'; | ||
156 | } | ||
157 | } | ||
158 | |||
159 | return 'LONGTEXT'; | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * {@inheritDoc} | ||
164 | */ | ||
165 | public function getDateTimeTypeDeclarationSQL(array $column): string | ||
166 | { | ||
167 | if (isset($column['version']) && $column['version'] === true) { | ||
168 | return 'TIMESTAMP'; | ||
169 | } | ||
170 | |||
171 | return 'DATETIME'; | ||
172 | } | ||
173 | |||
174 | /** | ||
175 | * {@inheritDoc} | ||
176 | */ | ||
177 | public function getDateTypeDeclarationSQL(array $column): string | ||
178 | { | ||
179 | return 'DATE'; | ||
180 | } | ||
181 | |||
182 | /** | ||
183 | * {@inheritDoc} | ||
184 | */ | ||
185 | public function getTimeTypeDeclarationSQL(array $column): string | ||
186 | { | ||
187 | return 'TIME'; | ||
188 | } | ||
189 | |||
190 | /** | ||
191 | * {@inheritDoc} | ||
192 | */ | ||
193 | public function getBooleanTypeDeclarationSQL(array $column): string | ||
194 | { | ||
195 | return 'TINYINT(1)'; | ||
196 | } | ||
197 | |||
198 | /** | ||
199 | * {@inheritDoc} | ||
200 | * | ||
201 | * MySQL supports this through AUTO_INCREMENT columns. | ||
202 | */ | ||
203 | public function supportsIdentityColumns(): bool | ||
204 | { | ||
205 | return true; | ||
206 | } | ||
207 | |||
208 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
209 | public function supportsInlineColumnComments(): bool | ||
210 | { | ||
211 | return true; | ||
212 | } | ||
213 | |||
214 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
215 | public function supportsColumnCollation(): bool | ||
216 | { | ||
217 | return true; | ||
218 | } | ||
219 | |||
220 | /** | ||
221 | * The SQL snippet required to elucidate a column type | ||
222 | * | ||
223 | * Returns a column type SELECT snippet string | ||
224 | */ | ||
225 | public function getColumnTypeSQLSnippet(string $tableAlias, string $databaseName): string | ||
226 | { | ||
227 | return $tableAlias . '.COLUMN_TYPE'; | ||
228 | } | ||
229 | |||
230 | /** | ||
231 | * {@inheritDoc} | ||
232 | */ | ||
233 | protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array | ||
234 | { | ||
235 | $queryFields = $this->getColumnDeclarationListSQL($columns); | ||
236 | |||
237 | if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { | ||
238 | foreach ($options['uniqueConstraints'] as $definition) { | ||
239 | $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition); | ||
240 | } | ||
241 | } | ||
242 | |||
243 | // add all indexes | ||
244 | if (isset($options['indexes']) && ! empty($options['indexes'])) { | ||
245 | foreach ($options['indexes'] as $definition) { | ||
246 | $queryFields .= ', ' . $this->getIndexDeclarationSQL($definition); | ||
247 | } | ||
248 | } | ||
249 | |||
250 | // attach all primary keys | ||
251 | if (isset($options['primary']) && ! empty($options['primary'])) { | ||
252 | $keyColumns = array_unique(array_values($options['primary'])); | ||
253 | $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; | ||
254 | } | ||
255 | |||
256 | $sql = ['CREATE']; | ||
257 | |||
258 | if (! empty($options['temporary'])) { | ||
259 | $sql[] = 'TEMPORARY'; | ||
260 | } | ||
261 | |||
262 | $sql[] = 'TABLE ' . $name . ' (' . $queryFields . ')'; | ||
263 | |||
264 | $tableOptions = $this->buildTableOptions($options); | ||
265 | |||
266 | if ($tableOptions !== '') { | ||
267 | $sql[] = $tableOptions; | ||
268 | } | ||
269 | |||
270 | if (isset($options['partition_options'])) { | ||
271 | $sql[] = $options['partition_options']; | ||
272 | } | ||
273 | |||
274 | $sql = [implode(' ', $sql)]; | ||
275 | |||
276 | if (isset($options['foreignKeys'])) { | ||
277 | foreach ($options['foreignKeys'] as $definition) { | ||
278 | $sql[] = $this->getCreateForeignKeySQL($definition, $name); | ||
279 | } | ||
280 | } | ||
281 | |||
282 | return $sql; | ||
283 | } | ||
284 | |||
285 | public function createSelectSQLBuilder(): SelectSQLBuilder | ||
286 | { | ||
287 | return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', null); | ||
288 | } | ||
289 | |||
290 | /** | ||
291 | * Build SQL for table options | ||
292 | * | ||
293 | * @param mixed[] $options | ||
294 | */ | ||
295 | private function buildTableOptions(array $options): string | ||
296 | { | ||
297 | if (isset($options['table_options'])) { | ||
298 | return $options['table_options']; | ||
299 | } | ||
300 | |||
301 | $tableOptions = []; | ||
302 | |||
303 | if (isset($options['charset'])) { | ||
304 | $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']); | ||
305 | } | ||
306 | |||
307 | if (isset($options['collation'])) { | ||
308 | $tableOptions[] = $this->getColumnCollationDeclarationSQL($options['collation']); | ||
309 | } | ||
310 | |||
311 | if (isset($options['engine'])) { | ||
312 | $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); | ||
313 | } | ||
314 | |||
315 | // Auto increment | ||
316 | if (isset($options['auto_increment'])) { | ||
317 | $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); | ||
318 | } | ||
319 | |||
320 | // Comment | ||
321 | if (isset($options['comment'])) { | ||
322 | $tableOptions[] = sprintf('COMMENT = %s ', $this->quoteStringLiteral($options['comment'])); | ||
323 | } | ||
324 | |||
325 | // Row format | ||
326 | if (isset($options['row_format'])) { | ||
327 | $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); | ||
328 | } | ||
329 | |||
330 | return implode(' ', $tableOptions); | ||
331 | } | ||
332 | |||
333 | /** | ||
334 | * {@inheritDoc} | ||
335 | */ | ||
336 | public function getAlterTableSQL(TableDiff $diff): array | ||
337 | { | ||
338 | $columnSql = []; | ||
339 | $queryParts = []; | ||
340 | |||
341 | foreach ($diff->getAddedColumns() as $column) { | ||
342 | $columnProperties = array_merge($column->toArray(), [ | ||
343 | 'comment' => $column->getComment(), | ||
344 | ]); | ||
345 | |||
346 | $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL( | ||
347 | $column->getQuotedName($this), | ||
348 | $columnProperties, | ||
349 | ); | ||
350 | } | ||
351 | |||
352 | foreach ($diff->getDroppedColumns() as $column) { | ||
353 | $queryParts[] = 'DROP ' . $column->getQuotedName($this); | ||
354 | } | ||
355 | |||
356 | foreach ($diff->getModifiedColumns() as $columnDiff) { | ||
357 | $newColumn = $columnDiff->getNewColumn(); | ||
358 | |||
359 | $newColumnProperties = array_merge($newColumn->toArray(), [ | ||
360 | 'comment' => $newColumn->getComment(), | ||
361 | ]); | ||
362 | |||
363 | $oldColumn = $columnDiff->getOldColumn(); | ||
364 | |||
365 | $queryParts[] = 'CHANGE ' . $oldColumn->getQuotedName($this) . ' ' | ||
366 | . $this->getColumnDeclarationSQL($newColumn->getQuotedName($this), $newColumnProperties); | ||
367 | } | ||
368 | |||
369 | foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { | ||
370 | $oldColumnName = new Identifier($oldColumnName); | ||
371 | |||
372 | $columnProperties = array_merge($column->toArray(), [ | ||
373 | 'comment' => $column->getComment(), | ||
374 | ]); | ||
375 | |||
376 | $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' ' | ||
377 | . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties); | ||
378 | } | ||
379 | |||
380 | $addedIndexes = $this->indexAssetsByLowerCaseName($diff->getAddedIndexes()); | ||
381 | $modifiedIndexes = $this->indexAssetsByLowerCaseName($diff->getModifiedIndexes()); | ||
382 | $diffModified = false; | ||
383 | |||
384 | if (isset($addedIndexes['primary'])) { | ||
385 | $keyColumns = array_values(array_unique($addedIndexes['primary']->getColumns())); | ||
386 | $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; | ||
387 | unset($addedIndexes['primary']); | ||
388 | $diffModified = true; | ||
389 | } elseif (isset($modifiedIndexes['primary'])) { | ||
390 | $addedColumns = $this->indexAssetsByLowerCaseName($diff->getAddedColumns()); | ||
391 | |||
392 | // Necessary in case the new primary key includes a new auto_increment column | ||
393 | foreach ($modifiedIndexes['primary']->getColumns() as $columnName) { | ||
394 | if (isset($addedColumns[$columnName]) && $addedColumns[$columnName]->getAutoincrement()) { | ||
395 | $keyColumns = array_values(array_unique($modifiedIndexes['primary']->getColumns())); | ||
396 | $queryParts[] = 'DROP PRIMARY KEY'; | ||
397 | $queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')'; | ||
398 | unset($modifiedIndexes['primary']); | ||
399 | $diffModified = true; | ||
400 | break; | ||
401 | } | ||
402 | } | ||
403 | } | ||
404 | |||
405 | if ($diffModified) { | ||
406 | $diff = new TableDiff( | ||
407 | $diff->getOldTable(), | ||
408 | $diff->getAddedColumns(), | ||
409 | $diff->getModifiedColumns(), | ||
410 | $diff->getDroppedColumns(), | ||
411 | $diff->getRenamedColumns(), | ||
412 | array_values($addedIndexes), | ||
413 | array_values($modifiedIndexes), | ||
414 | $diff->getDroppedIndexes(), | ||
415 | $diff->getRenamedIndexes(), | ||
416 | $diff->getAddedForeignKeys(), | ||
417 | $diff->getModifiedForeignKeys(), | ||
418 | $diff->getDroppedForeignKeys(), | ||
419 | ); | ||
420 | } | ||
421 | |||
422 | $sql = []; | ||
423 | $tableSql = []; | ||
424 | |||
425 | if (count($queryParts) > 0) { | ||
426 | $sql[] = 'ALTER TABLE ' . $diff->getOldTable()->getQuotedName($this) . ' ' | ||
427 | . implode(', ', $queryParts); | ||
428 | } | ||
429 | |||
430 | $sql = array_merge( | ||
431 | $this->getPreAlterTableIndexForeignKeySQL($diff), | ||
432 | $sql, | ||
433 | $this->getPostAlterTableIndexForeignKeySQL($diff), | ||
434 | ); | ||
435 | |||
436 | return array_merge($sql, $tableSql, $columnSql); | ||
437 | } | ||
438 | |||
439 | /** | ||
440 | * {@inheritDoc} | ||
441 | */ | ||
442 | protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array | ||
443 | { | ||
444 | $sql = []; | ||
445 | |||
446 | $tableNameSQL = $diff->getOldTable()->getQuotedName($this); | ||
447 | |||
448 | foreach ($diff->getModifiedIndexes() as $changedIndex) { | ||
449 | $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $changedIndex)); | ||
450 | } | ||
451 | |||
452 | foreach ($diff->getDroppedIndexes() as $droppedIndex) { | ||
453 | $sql = array_merge($sql, $this->getPreAlterTableAlterPrimaryKeySQL($diff, $droppedIndex)); | ||
454 | |||
455 | foreach ($diff->getAddedIndexes() as $addedIndex) { | ||
456 | if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) { | ||
457 | continue; | ||
458 | } | ||
459 | |||
460 | $indexClause = 'INDEX ' . $addedIndex->getName(); | ||
461 | |||
462 | if ($addedIndex->isPrimary()) { | ||
463 | $indexClause = 'PRIMARY KEY'; | ||
464 | } elseif ($addedIndex->isUnique()) { | ||
465 | $indexClause = 'UNIQUE INDEX ' . $addedIndex->getName(); | ||
466 | } | ||
467 | |||
468 | $query = 'ALTER TABLE ' . $tableNameSQL . ' DROP INDEX ' . $droppedIndex->getName() . ', '; | ||
469 | $query .= 'ADD ' . $indexClause; | ||
470 | $query .= ' (' . implode(', ', $addedIndex->getQuotedColumns($this)) . ')'; | ||
471 | |||
472 | $sql[] = $query; | ||
473 | |||
474 | $diff->unsetAddedIndex($addedIndex); | ||
475 | $diff->unsetDroppedIndex($droppedIndex); | ||
476 | |||
477 | break; | ||
478 | } | ||
479 | } | ||
480 | |||
481 | return array_merge( | ||
482 | $sql, | ||
483 | $this->getPreAlterTableAlterIndexForeignKeySQL($diff), | ||
484 | parent::getPreAlterTableIndexForeignKeySQL($diff), | ||
485 | $this->getPreAlterTableRenameIndexForeignKeySQL($diff), | ||
486 | ); | ||
487 | } | ||
488 | |||
489 | /** | ||
490 | * @return list<string> | ||
491 | * | ||
492 | * @throws Exception | ||
493 | */ | ||
494 | private function getPreAlterTableAlterPrimaryKeySQL(TableDiff $diff, Index $index): array | ||
495 | { | ||
496 | if (! $index->isPrimary()) { | ||
497 | return []; | ||
498 | } | ||
499 | |||
500 | $table = $diff->getOldTable(); | ||
501 | |||
502 | $sql = []; | ||
503 | |||
504 | $tableNameSQL = $table->getQuotedName($this); | ||
505 | |||
506 | // Dropping primary keys requires to unset autoincrement attribute on the particular column first. | ||
507 | foreach ($index->getColumns() as $columnName) { | ||
508 | if (! $table->hasColumn($columnName)) { | ||
509 | continue; | ||
510 | } | ||
511 | |||
512 | $column = $table->getColumn($columnName); | ||
513 | |||
514 | if (! $column->getAutoincrement()) { | ||
515 | continue; | ||
516 | } | ||
517 | |||
518 | $column->setAutoincrement(false); | ||
519 | |||
520 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' . | ||
521 | $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); | ||
522 | |||
523 | // original autoincrement information might be needed later on by other parts of the table alteration | ||
524 | $column->setAutoincrement(true); | ||
525 | } | ||
526 | |||
527 | return $sql; | ||
528 | } | ||
529 | |||
530 | /** | ||
531 | * @param TableDiff $diff The table diff to gather the SQL for. | ||
532 | * | ||
533 | * @return list<string> | ||
534 | * | ||
535 | * @throws Exception | ||
536 | */ | ||
537 | private function getPreAlterTableAlterIndexForeignKeySQL(TableDiff $diff): array | ||
538 | { | ||
539 | $table = $diff->getOldTable(); | ||
540 | |||
541 | $primaryKey = $table->getPrimaryKey(); | ||
542 | |||
543 | if ($primaryKey === null) { | ||
544 | return []; | ||
545 | } | ||
546 | |||
547 | $primaryKeyColumns = []; | ||
548 | |||
549 | foreach ($primaryKey->getColumns() as $columnName) { | ||
550 | if (! $table->hasColumn($columnName)) { | ||
551 | continue; | ||
552 | } | ||
553 | |||
554 | $primaryKeyColumns[] = $table->getColumn($columnName); | ||
555 | } | ||
556 | |||
557 | if (count($primaryKeyColumns) === 0) { | ||
558 | return []; | ||
559 | } | ||
560 | |||
561 | $sql = []; | ||
562 | |||
563 | $tableNameSQL = $table->getQuotedName($this); | ||
564 | |||
565 | foreach ($diff->getModifiedIndexes() as $changedIndex) { | ||
566 | // Changed primary key | ||
567 | if (! $changedIndex->isPrimary()) { | ||
568 | continue; | ||
569 | } | ||
570 | |||
571 | foreach ($primaryKeyColumns as $column) { | ||
572 | // Check if an autoincrement column was dropped from the primary key. | ||
573 | if (! $column->getAutoincrement() || in_array($column->getName(), $changedIndex->getColumns(), true)) { | ||
574 | continue; | ||
575 | } | ||
576 | |||
577 | // The autoincrement attribute needs to be removed from the dropped column | ||
578 | // before we can drop and recreate the primary key. | ||
579 | $column->setAutoincrement(false); | ||
580 | |||
581 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY ' . | ||
582 | $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); | ||
583 | |||
584 | // Restore the autoincrement attribute as it might be needed later on | ||
585 | // by other parts of the table alteration. | ||
586 | $column->setAutoincrement(true); | ||
587 | } | ||
588 | } | ||
589 | |||
590 | return $sql; | ||
591 | } | ||
592 | |||
593 | /** | ||
594 | * @param TableDiff $diff The table diff to gather the SQL for. | ||
595 | * | ||
596 | * @return list<string> | ||
597 | */ | ||
598 | protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff): array | ||
599 | { | ||
600 | return []; | ||
601 | } | ||
602 | |||
603 | protected function getCreateIndexSQLFlags(Index $index): string | ||
604 | { | ||
605 | $type = ''; | ||
606 | if ($index->isUnique()) { | ||
607 | $type .= 'UNIQUE '; | ||
608 | } elseif ($index->hasFlag('fulltext')) { | ||
609 | $type .= 'FULLTEXT '; | ||
610 | } elseif ($index->hasFlag('spatial')) { | ||
611 | $type .= 'SPATIAL '; | ||
612 | } | ||
613 | |||
614 | return $type; | ||
615 | } | ||
616 | |||
617 | /** | ||
618 | * {@inheritDoc} | ||
619 | */ | ||
620 | public function getIntegerTypeDeclarationSQL(array $column): string | ||
621 | { | ||
622 | return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
623 | } | ||
624 | |||
625 | /** | ||
626 | * {@inheritDoc} | ||
627 | */ | ||
628 | public function getBigIntTypeDeclarationSQL(array $column): string | ||
629 | { | ||
630 | return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
631 | } | ||
632 | |||
633 | /** | ||
634 | * {@inheritDoc} | ||
635 | */ | ||
636 | public function getSmallIntTypeDeclarationSQL(array $column): string | ||
637 | { | ||
638 | return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
639 | } | ||
640 | |||
641 | /** | ||
642 | * {@inheritDoc} | ||
643 | */ | ||
644 | public function getFloatDeclarationSQL(array $column): string | ||
645 | { | ||
646 | return 'DOUBLE PRECISION' . $this->getUnsignedDeclaration($column); | ||
647 | } | ||
648 | |||
649 | /** | ||
650 | * {@inheritDoc} | ||
651 | */ | ||
652 | public function getDecimalTypeDeclarationSQL(array $column): string | ||
653 | { | ||
654 | return parent::getDecimalTypeDeclarationSQL($column) . $this->getUnsignedDeclaration($column); | ||
655 | } | ||
656 | |||
657 | /** | ||
658 | * Get unsigned declaration for a column. | ||
659 | * | ||
660 | * @param mixed[] $columnDef | ||
661 | */ | ||
662 | private function getUnsignedDeclaration(array $columnDef): string | ||
663 | { | ||
664 | return ! empty($columnDef['unsigned']) ? ' UNSIGNED' : ''; | ||
665 | } | ||
666 | |||
667 | /** | ||
668 | * {@inheritDoc} | ||
669 | */ | ||
670 | protected function _getCommonIntegerTypeDeclarationSQL(array $column): string | ||
671 | { | ||
672 | $autoinc = ''; | ||
673 | if (! empty($column['autoincrement'])) { | ||
674 | $autoinc = ' AUTO_INCREMENT'; | ||
675 | } | ||
676 | |||
677 | return $this->getUnsignedDeclaration($column) . $autoinc; | ||
678 | } | ||
679 | |||
680 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
681 | public function getColumnCharsetDeclarationSQL(string $charset): string | ||
682 | { | ||
683 | return 'CHARACTER SET ' . $charset; | ||
684 | } | ||
685 | |||
686 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
687 | public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string | ||
688 | { | ||
689 | $query = ''; | ||
690 | if ($foreignKey->hasOption('match')) { | ||
691 | $query .= ' MATCH ' . $foreignKey->getOption('match'); | ||
692 | } | ||
693 | |||
694 | $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); | ||
695 | |||
696 | return $query; | ||
697 | } | ||
698 | |||
699 | public function getDropIndexSQL(string $name, string $table): string | ||
700 | { | ||
701 | return 'DROP INDEX ' . $name . ' ON ' . $table; | ||
702 | } | ||
703 | |||
704 | /** | ||
705 | * The `ALTER TABLE ... DROP CONSTRAINT` syntax is only available as of MySQL 8.0.19. | ||
706 | * | ||
707 | * @link https://dev.mysql.com/doc/refman/8.0/en/alter-table.html | ||
708 | */ | ||
709 | public function getDropUniqueConstraintSQL(string $name, string $tableName): string | ||
710 | { | ||
711 | return $this->getDropIndexSQL($name, $tableName); | ||
712 | } | ||
713 | |||
714 | public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string | ||
715 | { | ||
716 | return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); | ||
717 | } | ||
718 | |||
719 | protected function initializeDoctrineTypeMappings(): void | ||
720 | { | ||
721 | $this->doctrineTypeMapping = [ | ||
722 | 'bigint' => Types::BIGINT, | ||
723 | 'binary' => Types::BINARY, | ||
724 | 'blob' => Types::BLOB, | ||
725 | 'char' => Types::STRING, | ||
726 | 'date' => Types::DATE_MUTABLE, | ||
727 | 'datetime' => Types::DATETIME_MUTABLE, | ||
728 | 'decimal' => Types::DECIMAL, | ||
729 | 'double' => Types::FLOAT, | ||
730 | 'float' => Types::FLOAT, | ||
731 | 'int' => Types::INTEGER, | ||
732 | 'integer' => Types::INTEGER, | ||
733 | 'json' => Types::JSON, | ||
734 | 'longblob' => Types::BLOB, | ||
735 | 'longtext' => Types::TEXT, | ||
736 | 'mediumblob' => Types::BLOB, | ||
737 | 'mediumint' => Types::INTEGER, | ||
738 | 'mediumtext' => Types::TEXT, | ||
739 | 'numeric' => Types::DECIMAL, | ||
740 | 'real' => Types::FLOAT, | ||
741 | 'set' => Types::SIMPLE_ARRAY, | ||
742 | 'smallint' => Types::SMALLINT, | ||
743 | 'string' => Types::STRING, | ||
744 | 'text' => Types::TEXT, | ||
745 | 'time' => Types::TIME_MUTABLE, | ||
746 | 'timestamp' => Types::DATETIME_MUTABLE, | ||
747 | 'tinyblob' => Types::BLOB, | ||
748 | 'tinyint' => Types::BOOLEAN, | ||
749 | 'tinytext' => Types::TEXT, | ||
750 | 'varbinary' => Types::BINARY, | ||
751 | 'varchar' => Types::STRING, | ||
752 | 'year' => Types::DATE_MUTABLE, | ||
753 | ]; | ||
754 | } | ||
755 | |||
756 | protected function createReservedKeywordsList(): KeywordList | ||
757 | { | ||
758 | return new MySQLKeywords(); | ||
759 | } | ||
760 | |||
761 | /** | ||
762 | * {@inheritDoc} | ||
763 | * | ||
764 | * MySQL commits a transaction implicitly when DROP TABLE is executed, however not | ||
765 | * if DROP TEMPORARY TABLE is executed. | ||
766 | */ | ||
767 | public function getDropTemporaryTableSQL(string $table): string | ||
768 | { | ||
769 | return 'DROP TEMPORARY TABLE ' . $table; | ||
770 | } | ||
771 | |||
772 | /** | ||
773 | * Gets the SQL Snippet used to declare a BLOB column type. | ||
774 | * TINYBLOB : 2 ^ 8 - 1 = 255 | ||
775 | * BLOB : 2 ^ 16 - 1 = 65535 | ||
776 | * MEDIUMBLOB : 2 ^ 24 - 1 = 16777215 | ||
777 | * LONGBLOB : 2 ^ 32 - 1 = 4294967295 | ||
778 | * | ||
779 | * {@inheritDoc} | ||
780 | */ | ||
781 | public function getBlobTypeDeclarationSQL(array $column): string | ||
782 | { | ||
783 | if (! empty($column['length']) && is_numeric($column['length'])) { | ||
784 | $length = $column['length']; | ||
785 | |||
786 | if ($length <= static::LENGTH_LIMIT_TINYBLOB) { | ||
787 | return 'TINYBLOB'; | ||
788 | } | ||
789 | |||
790 | if ($length <= static::LENGTH_LIMIT_BLOB) { | ||
791 | return 'BLOB'; | ||
792 | } | ||
793 | |||
794 | if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) { | ||
795 | return 'MEDIUMBLOB'; | ||
796 | } | ||
797 | } | ||
798 | |||
799 | return 'LONGBLOB'; | ||
800 | } | ||
801 | |||
802 | public function quoteStringLiteral(string $str): string | ||
803 | { | ||
804 | // MySQL requires backslashes to be escaped as well. | ||
805 | $str = str_replace('\\', '\\\\', $str); | ||
806 | |||
807 | return parent::quoteStringLiteral($str); | ||
808 | } | ||
809 | |||
810 | public function getDefaultTransactionIsolationLevel(): TransactionIsolationLevel | ||
811 | { | ||
812 | return TransactionIsolationLevel::REPEATABLE_READ; | ||
813 | } | ||
814 | |||
815 | public function supportsColumnLengthIndexes(): bool | ||
816 | { | ||
817 | return true; | ||
818 | } | ||
819 | |||
820 | public function createSchemaManager(Connection $connection): MySQLSchemaManager | ||
821 | { | ||
822 | return new MySQLSchemaManager($connection, $this); | ||
823 | } | ||
824 | |||
825 | /** | ||
826 | * @param array<T> $assets | ||
827 | * | ||
828 | * @return array<string,T> | ||
829 | * | ||
830 | * @template T of AbstractAsset | ||
831 | */ | ||
832 | private function indexAssetsByLowerCaseName(array $assets): array | ||
833 | { | ||
834 | $result = []; | ||
835 | |||
836 | foreach ($assets as $asset) { | ||
837 | $result[strtolower($asset->getName())] = $asset; | ||
838 | } | ||
839 | |||
840 | return $result; | ||
841 | } | ||
842 | } | ||