diff options
Diffstat (limited to 'vendor/doctrine/dbal/src/Platforms')
38 files changed, 9931 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 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php b/vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php new file mode 100644 index 0000000..0beb37a --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php | |||
@@ -0,0 +1,2219 @@ | |||
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\Exception\InvalidArgumentException; | ||
10 | use Doctrine\DBAL\Exception\InvalidColumnDeclaration; | ||
11 | use Doctrine\DBAL\Exception\InvalidColumnType; | ||
12 | use Doctrine\DBAL\Exception\InvalidColumnType\ColumnLengthRequired; | ||
13 | use Doctrine\DBAL\Exception\InvalidColumnType\ColumnPrecisionRequired; | ||
14 | use Doctrine\DBAL\Exception\InvalidColumnType\ColumnScaleRequired; | ||
15 | use Doctrine\DBAL\LockMode; | ||
16 | use Doctrine\DBAL\Platforms\Exception\NoColumnsSpecifiedForTable; | ||
17 | use Doctrine\DBAL\Platforms\Exception\NotSupported; | ||
18 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
19 | use Doctrine\DBAL\Schema\AbstractSchemaManager; | ||
20 | use Doctrine\DBAL\Schema\Column; | ||
21 | use Doctrine\DBAL\Schema\ForeignKeyConstraint; | ||
22 | use Doctrine\DBAL\Schema\Identifier; | ||
23 | use Doctrine\DBAL\Schema\Index; | ||
24 | use Doctrine\DBAL\Schema\SchemaDiff; | ||
25 | use Doctrine\DBAL\Schema\Sequence; | ||
26 | use Doctrine\DBAL\Schema\Table; | ||
27 | use Doctrine\DBAL\Schema\TableDiff; | ||
28 | use Doctrine\DBAL\Schema\UniqueConstraint; | ||
29 | use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; | ||
30 | use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; | ||
31 | use Doctrine\DBAL\SQL\Parser; | ||
32 | use Doctrine\DBAL\TransactionIsolationLevel; | ||
33 | use Doctrine\DBAL\Types; | ||
34 | use Doctrine\DBAL\Types\Exception\TypeNotFound; | ||
35 | use Doctrine\DBAL\Types\Type; | ||
36 | |||
37 | use function addcslashes; | ||
38 | use function array_map; | ||
39 | use function array_merge; | ||
40 | use function array_unique; | ||
41 | use function array_values; | ||
42 | use function assert; | ||
43 | use function count; | ||
44 | use function explode; | ||
45 | use function implode; | ||
46 | use function in_array; | ||
47 | use function is_array; | ||
48 | use function is_bool; | ||
49 | use function is_float; | ||
50 | use function is_int; | ||
51 | use function is_string; | ||
52 | use function preg_quote; | ||
53 | use function preg_replace; | ||
54 | use function sprintf; | ||
55 | use function str_contains; | ||
56 | use function str_replace; | ||
57 | use function strlen; | ||
58 | use function strtolower; | ||
59 | use function strtoupper; | ||
60 | |||
61 | /** | ||
62 | * Base class for all DatabasePlatforms. The DatabasePlatforms are the central | ||
63 | * point of abstraction of platform-specific behaviors, features and SQL dialects. | ||
64 | * They are a passive source of information. | ||
65 | * | ||
66 | * @todo Remove any unnecessary methods. | ||
67 | */ | ||
68 | abstract class AbstractPlatform | ||
69 | { | ||
70 | /** @deprecated */ | ||
71 | public const CREATE_INDEXES = 1; | ||
72 | |||
73 | /** @deprecated */ | ||
74 | public const CREATE_FOREIGNKEYS = 2; | ||
75 | |||
76 | /** @var string[]|null */ | ||
77 | protected ?array $doctrineTypeMapping = null; | ||
78 | |||
79 | /** | ||
80 | * Holds the KeywordList instance for the current platform. | ||
81 | */ | ||
82 | protected ?KeywordList $_keywords = null; | ||
83 | |||
84 | /** | ||
85 | * Returns the SQL snippet that declares a boolean column. | ||
86 | * | ||
87 | * @param mixed[] $column | ||
88 | */ | ||
89 | abstract public function getBooleanTypeDeclarationSQL(array $column): string; | ||
90 | |||
91 | /** | ||
92 | * Returns the SQL snippet that declares a 4 byte integer column. | ||
93 | * | ||
94 | * @param mixed[] $column | ||
95 | */ | ||
96 | abstract public function getIntegerTypeDeclarationSQL(array $column): string; | ||
97 | |||
98 | /** | ||
99 | * Returns the SQL snippet that declares an 8 byte integer column. | ||
100 | * | ||
101 | * @param mixed[] $column | ||
102 | */ | ||
103 | abstract public function getBigIntTypeDeclarationSQL(array $column): string; | ||
104 | |||
105 | /** | ||
106 | * Returns the SQL snippet that declares a 2 byte integer column. | ||
107 | * | ||
108 | * @param mixed[] $column | ||
109 | */ | ||
110 | abstract public function getSmallIntTypeDeclarationSQL(array $column): string; | ||
111 | |||
112 | /** | ||
113 | * Returns the SQL snippet that declares common properties of an integer column. | ||
114 | * | ||
115 | * @param mixed[] $column | ||
116 | */ | ||
117 | abstract protected function _getCommonIntegerTypeDeclarationSQL(array $column): string; | ||
118 | |||
119 | /** | ||
120 | * Lazy load Doctrine Type Mappings. | ||
121 | */ | ||
122 | abstract protected function initializeDoctrineTypeMappings(): void; | ||
123 | |||
124 | /** | ||
125 | * Initializes Doctrine Type Mappings with the platform defaults | ||
126 | * and with all additional type mappings. | ||
127 | */ | ||
128 | private function initializeAllDoctrineTypeMappings(): void | ||
129 | { | ||
130 | $this->initializeDoctrineTypeMappings(); | ||
131 | |||
132 | foreach (Type::getTypesMap() as $typeName => $className) { | ||
133 | foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) { | ||
134 | $dbType = strtolower($dbType); | ||
135 | $this->doctrineTypeMapping[$dbType] = $typeName; | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | |||
140 | /** | ||
141 | * Returns the SQL snippet used to declare a column that can | ||
142 | * store characters in the ASCII character set | ||
143 | * | ||
144 | * @param array<string, mixed> $column The column definition. | ||
145 | */ | ||
146 | public function getAsciiStringTypeDeclarationSQL(array $column): string | ||
147 | { | ||
148 | return $this->getStringTypeDeclarationSQL($column); | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * Returns the SQL snippet used to declare a string column type. | ||
153 | * | ||
154 | * @param array<string, mixed> $column The column definition. | ||
155 | */ | ||
156 | public function getStringTypeDeclarationSQL(array $column): string | ||
157 | { | ||
158 | $length = $column['length'] ?? null; | ||
159 | |||
160 | if (empty($column['fixed'])) { | ||
161 | try { | ||
162 | return $this->getVarcharTypeDeclarationSQLSnippet($length); | ||
163 | } catch (InvalidColumnType $e) { | ||
164 | throw InvalidColumnDeclaration::fromInvalidColumnType($column['name'], $e); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | return $this->getCharTypeDeclarationSQLSnippet($length); | ||
169 | } | ||
170 | |||
171 | /** | ||
172 | * Returns the SQL snippet used to declare a binary string column type. | ||
173 | * | ||
174 | * @param array<string, mixed> $column The column definition. | ||
175 | */ | ||
176 | public function getBinaryTypeDeclarationSQL(array $column): string | ||
177 | { | ||
178 | $length = $column['length'] ?? null; | ||
179 | |||
180 | try { | ||
181 | if (empty($column['fixed'])) { | ||
182 | return $this->getVarbinaryTypeDeclarationSQLSnippet($length); | ||
183 | } | ||
184 | |||
185 | return $this->getBinaryTypeDeclarationSQLSnippet($length); | ||
186 | } catch (InvalidColumnType $e) { | ||
187 | throw InvalidColumnDeclaration::fromInvalidColumnType($column['name'], $e); | ||
188 | } | ||
189 | } | ||
190 | |||
191 | /** | ||
192 | * Returns the SQL snippet to declare a GUID/UUID column. | ||
193 | * | ||
194 | * By default this maps directly to a CHAR(36) and only maps to more | ||
195 | * special datatypes when the underlying databases support this datatype. | ||
196 | * | ||
197 | * @param array<string, mixed> $column The column definition. | ||
198 | */ | ||
199 | public function getGuidTypeDeclarationSQL(array $column): string | ||
200 | { | ||
201 | $column['length'] = 36; | ||
202 | $column['fixed'] = true; | ||
203 | |||
204 | return $this->getStringTypeDeclarationSQL($column); | ||
205 | } | ||
206 | |||
207 | /** | ||
208 | * Returns the SQL snippet to declare a JSON column. | ||
209 | * | ||
210 | * By default this maps directly to a CLOB and only maps to more | ||
211 | * special datatypes when the underlying databases support this datatype. | ||
212 | * | ||
213 | * @param mixed[] $column | ||
214 | */ | ||
215 | public function getJsonTypeDeclarationSQL(array $column): string | ||
216 | { | ||
217 | return $this->getClobTypeDeclarationSQL($column); | ||
218 | } | ||
219 | |||
220 | /** | ||
221 | * @param int|null $length The length of the column in characters | ||
222 | * or NULL if the length should be omitted. | ||
223 | */ | ||
224 | protected function getCharTypeDeclarationSQLSnippet(?int $length): string | ||
225 | { | ||
226 | $sql = 'CHAR'; | ||
227 | |||
228 | if ($length !== null) { | ||
229 | $sql .= sprintf('(%d)', $length); | ||
230 | } | ||
231 | |||
232 | return $sql; | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * @param int|null $length The length of the column in characters | ||
237 | * or NULL if the length should be omitted. | ||
238 | */ | ||
239 | protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string | ||
240 | { | ||
241 | if ($length === null) { | ||
242 | throw ColumnLengthRequired::new($this, 'VARCHAR'); | ||
243 | } | ||
244 | |||
245 | return sprintf('VARCHAR(%d)', $length); | ||
246 | } | ||
247 | |||
248 | /** | ||
249 | * Returns the SQL snippet used to declare a fixed length binary column type. | ||
250 | * | ||
251 | * @param int|null $length The length of the column in bytes | ||
252 | * or NULL if the length should be omitted. | ||
253 | */ | ||
254 | protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string | ||
255 | { | ||
256 | $sql = 'BINARY'; | ||
257 | |||
258 | if ($length !== null) { | ||
259 | $sql .= sprintf('(%d)', $length); | ||
260 | } | ||
261 | |||
262 | return $sql; | ||
263 | } | ||
264 | |||
265 | /** | ||
266 | * Returns the SQL snippet used to declare a variable length binary column type. | ||
267 | * | ||
268 | * @param int|null $length The length of the column in bytes | ||
269 | * or NULL if the length should be omitted. | ||
270 | */ | ||
271 | protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string | ||
272 | { | ||
273 | if ($length === null) { | ||
274 | throw ColumnLengthRequired::new($this, 'VARBINARY'); | ||
275 | } | ||
276 | |||
277 | return sprintf('VARBINARY(%d)', $length); | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * Returns the SQL snippet used to declare a CLOB column type. | ||
282 | * | ||
283 | * @param mixed[] $column | ||
284 | */ | ||
285 | abstract public function getClobTypeDeclarationSQL(array $column): string; | ||
286 | |||
287 | /** | ||
288 | * Returns the SQL Snippet used to declare a BLOB column type. | ||
289 | * | ||
290 | * @param mixed[] $column | ||
291 | */ | ||
292 | abstract public function getBlobTypeDeclarationSQL(array $column): string; | ||
293 | |||
294 | /** | ||
295 | * Registers a doctrine type to be used in conjunction with a column type of this platform. | ||
296 | * | ||
297 | * @throws Exception If the type is not found. | ||
298 | */ | ||
299 | public function registerDoctrineTypeMapping(string $dbType, string $doctrineType): void | ||
300 | { | ||
301 | if ($this->doctrineTypeMapping === null) { | ||
302 | $this->initializeAllDoctrineTypeMappings(); | ||
303 | } | ||
304 | |||
305 | if (! Types\Type::hasType($doctrineType)) { | ||
306 | throw TypeNotFound::new($doctrineType); | ||
307 | } | ||
308 | |||
309 | $dbType = strtolower($dbType); | ||
310 | $this->doctrineTypeMapping[$dbType] = $doctrineType; | ||
311 | } | ||
312 | |||
313 | /** | ||
314 | * Gets the Doctrine type that is mapped for the given database column type. | ||
315 | */ | ||
316 | public function getDoctrineTypeMapping(string $dbType): string | ||
317 | { | ||
318 | if ($this->doctrineTypeMapping === null) { | ||
319 | $this->initializeAllDoctrineTypeMappings(); | ||
320 | } | ||
321 | |||
322 | $dbType = strtolower($dbType); | ||
323 | |||
324 | if (! isset($this->doctrineTypeMapping[$dbType])) { | ||
325 | throw new InvalidArgumentException(sprintf( | ||
326 | 'Unknown database type "%s" requested, %s may not support it.', | ||
327 | $dbType, | ||
328 | static::class, | ||
329 | )); | ||
330 | } | ||
331 | |||
332 | return $this->doctrineTypeMapping[$dbType]; | ||
333 | } | ||
334 | |||
335 | /** | ||
336 | * Checks if a database type is currently supported by this platform. | ||
337 | */ | ||
338 | public function hasDoctrineTypeMappingFor(string $dbType): bool | ||
339 | { | ||
340 | if ($this->doctrineTypeMapping === null) { | ||
341 | $this->initializeAllDoctrineTypeMappings(); | ||
342 | } | ||
343 | |||
344 | $dbType = strtolower($dbType); | ||
345 | |||
346 | return isset($this->doctrineTypeMapping[$dbType]); | ||
347 | } | ||
348 | |||
349 | /** | ||
350 | * Returns the regular expression operator. | ||
351 | */ | ||
352 | public function getRegexpExpression(): string | ||
353 | { | ||
354 | throw NotSupported::new(__METHOD__); | ||
355 | } | ||
356 | |||
357 | /** | ||
358 | * Returns the SQL snippet to get the length of a text column in characters. | ||
359 | * | ||
360 | * @param string $string SQL expression producing the string. | ||
361 | */ | ||
362 | public function getLengthExpression(string $string): string | ||
363 | { | ||
364 | return 'LENGTH(' . $string . ')'; | ||
365 | } | ||
366 | |||
367 | /** | ||
368 | * Returns the SQL snippet to get the remainder of the operation of division of dividend by divisor. | ||
369 | * | ||
370 | * @param string $dividend SQL expression producing the dividend. | ||
371 | * @param string $divisor SQL expression producing the divisor. | ||
372 | */ | ||
373 | public function getModExpression(string $dividend, string $divisor): string | ||
374 | { | ||
375 | return 'MOD(' . $dividend . ', ' . $divisor . ')'; | ||
376 | } | ||
377 | |||
378 | /** | ||
379 | * Returns the SQL snippet to trim a string. | ||
380 | * | ||
381 | * @param string $str The expression to apply the trim to. | ||
382 | * @param TrimMode $mode The position of the trim. | ||
383 | * @param string|null $char The char to trim, has to be quoted already. Defaults to space. | ||
384 | */ | ||
385 | public function getTrimExpression( | ||
386 | string $str, | ||
387 | TrimMode $mode = TrimMode::UNSPECIFIED, | ||
388 | ?string $char = null, | ||
389 | ): string { | ||
390 | $tokens = []; | ||
391 | |||
392 | switch ($mode) { | ||
393 | case TrimMode::UNSPECIFIED: | ||
394 | break; | ||
395 | |||
396 | case TrimMode::LEADING: | ||
397 | $tokens[] = 'LEADING'; | ||
398 | break; | ||
399 | |||
400 | case TrimMode::TRAILING: | ||
401 | $tokens[] = 'TRAILING'; | ||
402 | break; | ||
403 | |||
404 | case TrimMode::BOTH: | ||
405 | $tokens[] = 'BOTH'; | ||
406 | break; | ||
407 | } | ||
408 | |||
409 | if ($char !== null) { | ||
410 | $tokens[] = $char; | ||
411 | } | ||
412 | |||
413 | if (count($tokens) > 0) { | ||
414 | $tokens[] = 'FROM'; | ||
415 | } | ||
416 | |||
417 | $tokens[] = $str; | ||
418 | |||
419 | return sprintf('TRIM(%s)', implode(' ', $tokens)); | ||
420 | } | ||
421 | |||
422 | /** | ||
423 | * Returns the SQL snippet to get the position of the first occurrence of the substring in the string. | ||
424 | * | ||
425 | * @param string $string SQL expression producing the string to locate the substring in. | ||
426 | * @param string $substring SQL expression producing the substring to locate. | ||
427 | * @param string|null $start SQL expression producing the position to start at. | ||
428 | * Defaults to the beginning of the string. | ||
429 | */ | ||
430 | abstract public function getLocateExpression(string $string, string $substring, ?string $start = null): string; | ||
431 | |||
432 | /** | ||
433 | * Returns an SQL snippet to get a substring inside the string. | ||
434 | * | ||
435 | * Note: Not SQL92, but common functionality. | ||
436 | * | ||
437 | * @param string $string SQL expression producing the string from which a substring should be extracted. | ||
438 | * @param string $start SQL expression producing the position to start at, | ||
439 | * @param string|null $length SQL expression producing the length of the substring portion to be returned. | ||
440 | * By default, the entire substring is returned. | ||
441 | */ | ||
442 | public function getSubstringExpression(string $string, string $start, ?string $length = null): string | ||
443 | { | ||
444 | if ($length === null) { | ||
445 | return sprintf('SUBSTRING(%s FROM %s)', $string, $start); | ||
446 | } | ||
447 | |||
448 | return sprintf('SUBSTRING(%s FROM %s FOR %s)', $string, $start, $length); | ||
449 | } | ||
450 | |||
451 | /** | ||
452 | * Returns a SQL snippet to concatenate the given strings. | ||
453 | */ | ||
454 | public function getConcatExpression(string ...$string): string | ||
455 | { | ||
456 | return implode(' || ', $string); | ||
457 | } | ||
458 | |||
459 | /** | ||
460 | * Returns the SQL to calculate the difference in days between the two passed dates. | ||
461 | * | ||
462 | * Computes diff = date1 - date2. | ||
463 | */ | ||
464 | abstract public function getDateDiffExpression(string $date1, string $date2): string; | ||
465 | |||
466 | /** | ||
467 | * Returns the SQL to add the number of given seconds to a date. | ||
468 | * | ||
469 | * @param string $date SQL expression producing the date. | ||
470 | * @param string $seconds SQL expression producing the number of seconds. | ||
471 | */ | ||
472 | public function getDateAddSecondsExpression(string $date, string $seconds): string | ||
473 | { | ||
474 | return $this->getDateArithmeticIntervalExpression($date, '+', $seconds, DateIntervalUnit::SECOND); | ||
475 | } | ||
476 | |||
477 | /** | ||
478 | * Returns the SQL to subtract the number of given seconds from a date. | ||
479 | * | ||
480 | * @param string $date SQL expression producing the date. | ||
481 | * @param string $seconds SQL expression producing the number of seconds. | ||
482 | */ | ||
483 | public function getDateSubSecondsExpression(string $date, string $seconds): string | ||
484 | { | ||
485 | return $this->getDateArithmeticIntervalExpression($date, '-', $seconds, DateIntervalUnit::SECOND); | ||
486 | } | ||
487 | |||
488 | /** | ||
489 | * Returns the SQL to add the number of given minutes to a date. | ||
490 | * | ||
491 | * @param string $date SQL expression producing the date. | ||
492 | * @param string $minutes SQL expression producing the number of minutes. | ||
493 | */ | ||
494 | public function getDateAddMinutesExpression(string $date, string $minutes): string | ||
495 | { | ||
496 | return $this->getDateArithmeticIntervalExpression($date, '+', $minutes, DateIntervalUnit::MINUTE); | ||
497 | } | ||
498 | |||
499 | /** | ||
500 | * Returns the SQL to subtract the number of given minutes from a date. | ||
501 | * | ||
502 | * @param string $date SQL expression producing the date. | ||
503 | * @param string $minutes SQL expression producing the number of minutes. | ||
504 | */ | ||
505 | public function getDateSubMinutesExpression(string $date, string $minutes): string | ||
506 | { | ||
507 | return $this->getDateArithmeticIntervalExpression($date, '-', $minutes, DateIntervalUnit::MINUTE); | ||
508 | } | ||
509 | |||
510 | /** | ||
511 | * Returns the SQL to add the number of given hours to a date. | ||
512 | * | ||
513 | * @param string $date SQL expression producing the date. | ||
514 | * @param string $hours SQL expression producing the number of hours. | ||
515 | */ | ||
516 | public function getDateAddHourExpression(string $date, string $hours): string | ||
517 | { | ||
518 | return $this->getDateArithmeticIntervalExpression($date, '+', $hours, DateIntervalUnit::HOUR); | ||
519 | } | ||
520 | |||
521 | /** | ||
522 | * Returns the SQL to subtract the number of given hours to a date. | ||
523 | * | ||
524 | * @param string $date SQL expression producing the date. | ||
525 | * @param string $hours SQL expression producing the number of hours. | ||
526 | */ | ||
527 | public function getDateSubHourExpression(string $date, string $hours): string | ||
528 | { | ||
529 | return $this->getDateArithmeticIntervalExpression($date, '-', $hours, DateIntervalUnit::HOUR); | ||
530 | } | ||
531 | |||
532 | /** | ||
533 | * Returns the SQL to add the number of given days to a date. | ||
534 | * | ||
535 | * @param string $date SQL expression producing the date. | ||
536 | * @param string $days SQL expression producing the number of days. | ||
537 | */ | ||
538 | public function getDateAddDaysExpression(string $date, string $days): string | ||
539 | { | ||
540 | return $this->getDateArithmeticIntervalExpression($date, '+', $days, DateIntervalUnit::DAY); | ||
541 | } | ||
542 | |||
543 | /** | ||
544 | * Returns the SQL to subtract the number of given days to a date. | ||
545 | * | ||
546 | * @param string $date SQL expression producing the date. | ||
547 | * @param string $days SQL expression producing the number of days. | ||
548 | */ | ||
549 | public function getDateSubDaysExpression(string $date, string $days): string | ||
550 | { | ||
551 | return $this->getDateArithmeticIntervalExpression($date, '-', $days, DateIntervalUnit::DAY); | ||
552 | } | ||
553 | |||
554 | /** | ||
555 | * Returns the SQL to add the number of given weeks to a date. | ||
556 | * | ||
557 | * @param string $date SQL expression producing the date. | ||
558 | * @param string $weeks SQL expression producing the number of weeks. | ||
559 | */ | ||
560 | public function getDateAddWeeksExpression(string $date, string $weeks): string | ||
561 | { | ||
562 | return $this->getDateArithmeticIntervalExpression($date, '+', $weeks, DateIntervalUnit::WEEK); | ||
563 | } | ||
564 | |||
565 | /** | ||
566 | * Returns the SQL to subtract the number of given weeks from a date. | ||
567 | * | ||
568 | * @param string $date SQL expression producing the date. | ||
569 | * @param string $weeks SQL expression producing the number of weeks. | ||
570 | */ | ||
571 | public function getDateSubWeeksExpression(string $date, string $weeks): string | ||
572 | { | ||
573 | return $this->getDateArithmeticIntervalExpression($date, '-', $weeks, DateIntervalUnit::WEEK); | ||
574 | } | ||
575 | |||
576 | /** | ||
577 | * Returns the SQL to add the number of given months to a date. | ||
578 | * | ||
579 | * @param string $date SQL expression producing the date. | ||
580 | * @param string $months SQL expression producing the number of months. | ||
581 | */ | ||
582 | public function getDateAddMonthExpression(string $date, string $months): string | ||
583 | { | ||
584 | return $this->getDateArithmeticIntervalExpression($date, '+', $months, DateIntervalUnit::MONTH); | ||
585 | } | ||
586 | |||
587 | /** | ||
588 | * Returns the SQL to subtract the number of given months to a date. | ||
589 | * | ||
590 | * @param string $date SQL expression producing the date. | ||
591 | * @param string $months SQL expression producing the number of months. | ||
592 | */ | ||
593 | public function getDateSubMonthExpression(string $date, string $months): string | ||
594 | { | ||
595 | return $this->getDateArithmeticIntervalExpression($date, '-', $months, DateIntervalUnit::MONTH); | ||
596 | } | ||
597 | |||
598 | /** | ||
599 | * Returns the SQL to add the number of given quarters to a date. | ||
600 | * | ||
601 | * @param string $date SQL expression producing the date. | ||
602 | * @param string $quarters SQL expression producing the number of quarters. | ||
603 | */ | ||
604 | public function getDateAddQuartersExpression(string $date, string $quarters): string | ||
605 | { | ||
606 | return $this->getDateArithmeticIntervalExpression($date, '+', $quarters, DateIntervalUnit::QUARTER); | ||
607 | } | ||
608 | |||
609 | /** | ||
610 | * Returns the SQL to subtract the number of given quarters from a date. | ||
611 | * | ||
612 | * @param string $date SQL expression producing the date. | ||
613 | * @param string $quarters SQL expression producing the number of quarters. | ||
614 | */ | ||
615 | public function getDateSubQuartersExpression(string $date, string $quarters): string | ||
616 | { | ||
617 | return $this->getDateArithmeticIntervalExpression($date, '-', $quarters, DateIntervalUnit::QUARTER); | ||
618 | } | ||
619 | |||
620 | /** | ||
621 | * Returns the SQL to add the number of given years to a date. | ||
622 | * | ||
623 | * @param string $date SQL expression producing the date. | ||
624 | * @param string $years SQL expression producing the number of years. | ||
625 | */ | ||
626 | public function getDateAddYearsExpression(string $date, string $years): string | ||
627 | { | ||
628 | return $this->getDateArithmeticIntervalExpression($date, '+', $years, DateIntervalUnit::YEAR); | ||
629 | } | ||
630 | |||
631 | /** | ||
632 | * Returns the SQL to subtract the number of given years from a date. | ||
633 | * | ||
634 | * @param string $date SQL expression producing the date. | ||
635 | * @param string $years SQL expression producing the number of years. | ||
636 | */ | ||
637 | public function getDateSubYearsExpression(string $date, string $years): string | ||
638 | { | ||
639 | return $this->getDateArithmeticIntervalExpression($date, '-', $years, DateIntervalUnit::YEAR); | ||
640 | } | ||
641 | |||
642 | /** | ||
643 | * Returns the SQL for a date arithmetic expression. | ||
644 | * | ||
645 | * @param string $date SQL expression representing a date to perform the arithmetic operation on. | ||
646 | * @param string $operator The arithmetic operator (+ or -). | ||
647 | * @param string $interval SQL expression representing the value of the interval that shall be calculated | ||
648 | * into the date. | ||
649 | * @param DateIntervalUnit $unit The unit of the interval that shall be calculated into the date. | ||
650 | */ | ||
651 | abstract protected function getDateArithmeticIntervalExpression( | ||
652 | string $date, | ||
653 | string $operator, | ||
654 | string $interval, | ||
655 | DateIntervalUnit $unit, | ||
656 | ): string; | ||
657 | |||
658 | /** | ||
659 | * Generates the SQL expression which represents the given date interval multiplied by a number | ||
660 | * | ||
661 | * @param string $interval SQL expression describing the interval value | ||
662 | * @param int $multiplier Interval multiplier | ||
663 | */ | ||
664 | protected function multiplyInterval(string $interval, int $multiplier): string | ||
665 | { | ||
666 | return sprintf('(%s * %d)', $interval, $multiplier); | ||
667 | } | ||
668 | |||
669 | /** | ||
670 | * Returns the SQL bit AND comparison expression. | ||
671 | * | ||
672 | * @param string $value1 SQL expression producing the first value. | ||
673 | * @param string $value2 SQL expression producing the second value. | ||
674 | */ | ||
675 | public function getBitAndComparisonExpression(string $value1, string $value2): string | ||
676 | { | ||
677 | return '(' . $value1 . ' & ' . $value2 . ')'; | ||
678 | } | ||
679 | |||
680 | /** | ||
681 | * Returns the SQL bit OR comparison expression. | ||
682 | * | ||
683 | * @param string $value1 SQL expression producing the first value. | ||
684 | * @param string $value2 SQL expression producing the second value. | ||
685 | */ | ||
686 | public function getBitOrComparisonExpression(string $value1, string $value2): string | ||
687 | { | ||
688 | return '(' . $value1 . ' | ' . $value2 . ')'; | ||
689 | } | ||
690 | |||
691 | /** | ||
692 | * Returns the SQL expression which represents the currently selected database. | ||
693 | */ | ||
694 | abstract public function getCurrentDatabaseExpression(): string; | ||
695 | |||
696 | /** | ||
697 | * Honors that some SQL vendors such as MsSql use table hints for locking instead of the | ||
698 | * ANSI SQL FOR UPDATE specification. | ||
699 | * | ||
700 | * @param string $fromClause The FROM clause to append the hint for the given lock mode to | ||
701 | */ | ||
702 | public function appendLockHint(string $fromClause, LockMode $lockMode): string | ||
703 | { | ||
704 | return $fromClause; | ||
705 | } | ||
706 | |||
707 | /** | ||
708 | * Returns the SQL snippet to drop an existing table. | ||
709 | */ | ||
710 | public function getDropTableSQL(string $table): string | ||
711 | { | ||
712 | return 'DROP TABLE ' . $table; | ||
713 | } | ||
714 | |||
715 | /** | ||
716 | * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction. | ||
717 | */ | ||
718 | public function getDropTemporaryTableSQL(string $table): string | ||
719 | { | ||
720 | return $this->getDropTableSQL($table); | ||
721 | } | ||
722 | |||
723 | /** | ||
724 | * Returns the SQL to drop an index from a table. | ||
725 | */ | ||
726 | public function getDropIndexSQL(string $name, string $table): string | ||
727 | { | ||
728 | return 'DROP INDEX ' . $name; | ||
729 | } | ||
730 | |||
731 | /** | ||
732 | * Returns the SQL to drop a constraint. | ||
733 | * | ||
734 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
735 | */ | ||
736 | protected function getDropConstraintSQL(string $name, string $table): string | ||
737 | { | ||
738 | return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name; | ||
739 | } | ||
740 | |||
741 | /** | ||
742 | * Returns the SQL to drop a foreign key. | ||
743 | */ | ||
744 | public function getDropForeignKeySQL(string $foreignKey, string $table): string | ||
745 | { | ||
746 | return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey; | ||
747 | } | ||
748 | |||
749 | /** | ||
750 | * Returns the SQL to drop a unique constraint. | ||
751 | */ | ||
752 | public function getDropUniqueConstraintSQL(string $name, string $tableName): string | ||
753 | { | ||
754 | return $this->getDropConstraintSQL($name, $tableName); | ||
755 | } | ||
756 | |||
757 | /** | ||
758 | * Returns the SQL statement(s) to create a table with the specified name, columns and constraints | ||
759 | * on this platform. | ||
760 | * | ||
761 | * @return list<string> The list of SQL statements. | ||
762 | */ | ||
763 | public function getCreateTableSQL(Table $table): array | ||
764 | { | ||
765 | return $this->buildCreateTableSQL($table, true); | ||
766 | } | ||
767 | |||
768 | public function createSelectSQLBuilder(): SelectSQLBuilder | ||
769 | { | ||
770 | return new DefaultSelectSQLBuilder($this, 'FOR UPDATE', 'SKIP LOCKED'); | ||
771 | } | ||
772 | |||
773 | /** | ||
774 | * @internal | ||
775 | * | ||
776 | * @return list<string> | ||
777 | */ | ||
778 | final protected function getCreateTableWithoutForeignKeysSQL(Table $table): array | ||
779 | { | ||
780 | return $this->buildCreateTableSQL($table, false); | ||
781 | } | ||
782 | |||
783 | /** @return list<string> */ | ||
784 | private function buildCreateTableSQL(Table $table, bool $createForeignKeys): array | ||
785 | { | ||
786 | if (count($table->getColumns()) === 0) { | ||
787 | throw NoColumnsSpecifiedForTable::new($table->getName()); | ||
788 | } | ||
789 | |||
790 | $tableName = $table->getQuotedName($this); | ||
791 | $options = $table->getOptions(); | ||
792 | $options['uniqueConstraints'] = []; | ||
793 | $options['indexes'] = []; | ||
794 | $options['primary'] = []; | ||
795 | |||
796 | foreach ($table->getIndexes() as $index) { | ||
797 | if (! $index->isPrimary()) { | ||
798 | $options['indexes'][$index->getQuotedName($this)] = $index; | ||
799 | |||
800 | continue; | ||
801 | } | ||
802 | |||
803 | $options['primary'] = $index->getQuotedColumns($this); | ||
804 | $options['primary_index'] = $index; | ||
805 | } | ||
806 | |||
807 | foreach ($table->getUniqueConstraints() as $uniqueConstraint) { | ||
808 | $options['uniqueConstraints'][$uniqueConstraint->getQuotedName($this)] = $uniqueConstraint; | ||
809 | } | ||
810 | |||
811 | if ($createForeignKeys) { | ||
812 | $options['foreignKeys'] = []; | ||
813 | |||
814 | foreach ($table->getForeignKeys() as $fkConstraint) { | ||
815 | $options['foreignKeys'][] = $fkConstraint; | ||
816 | } | ||
817 | } | ||
818 | |||
819 | $columnSql = []; | ||
820 | $columns = []; | ||
821 | |||
822 | foreach ($table->getColumns() as $column) { | ||
823 | $columnData = $this->columnToArray($column); | ||
824 | |||
825 | if (in_array($column->getName(), $options['primary'], true)) { | ||
826 | $columnData['primary'] = true; | ||
827 | } | ||
828 | |||
829 | $columns[] = $columnData; | ||
830 | } | ||
831 | |||
832 | $sql = $this->_getCreateTableSQL($tableName, $columns, $options); | ||
833 | |||
834 | if ($this->supportsCommentOnStatement()) { | ||
835 | if ($table->hasOption('comment')) { | ||
836 | $sql[] = $this->getCommentOnTableSQL($tableName, $table->getOption('comment')); | ||
837 | } | ||
838 | |||
839 | foreach ($table->getColumns() as $column) { | ||
840 | $comment = $column->getComment(); | ||
841 | |||
842 | if ($comment === '') { | ||
843 | continue; | ||
844 | } | ||
845 | |||
846 | $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getQuotedName($this), $comment); | ||
847 | } | ||
848 | } | ||
849 | |||
850 | return array_merge($sql, $columnSql); | ||
851 | } | ||
852 | |||
853 | /** | ||
854 | * @param array<Table> $tables | ||
855 | * | ||
856 | * @return list<string> | ||
857 | */ | ||
858 | public function getCreateTablesSQL(array $tables): array | ||
859 | { | ||
860 | $sql = []; | ||
861 | |||
862 | foreach ($tables as $table) { | ||
863 | $sql = array_merge($sql, $this->getCreateTableWithoutForeignKeysSQL($table)); | ||
864 | } | ||
865 | |||
866 | foreach ($tables as $table) { | ||
867 | foreach ($table->getForeignKeys() as $foreignKey) { | ||
868 | $sql[] = $this->getCreateForeignKeySQL( | ||
869 | $foreignKey, | ||
870 | $table->getQuotedName($this), | ||
871 | ); | ||
872 | } | ||
873 | } | ||
874 | |||
875 | return $sql; | ||
876 | } | ||
877 | |||
878 | /** | ||
879 | * @param array<Table> $tables | ||
880 | * | ||
881 | * @return list<string> | ||
882 | */ | ||
883 | public function getDropTablesSQL(array $tables): array | ||
884 | { | ||
885 | $sql = []; | ||
886 | |||
887 | foreach ($tables as $table) { | ||
888 | foreach ($table->getForeignKeys() as $foreignKey) { | ||
889 | $sql[] = $this->getDropForeignKeySQL( | ||
890 | $foreignKey->getQuotedName($this), | ||
891 | $table->getQuotedName($this), | ||
892 | ); | ||
893 | } | ||
894 | } | ||
895 | |||
896 | foreach ($tables as $table) { | ||
897 | $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); | ||
898 | } | ||
899 | |||
900 | return $sql; | ||
901 | } | ||
902 | |||
903 | protected function getCommentOnTableSQL(string $tableName, string $comment): string | ||
904 | { | ||
905 | $tableName = new Identifier($tableName); | ||
906 | |||
907 | return sprintf( | ||
908 | 'COMMENT ON TABLE %s IS %s', | ||
909 | $tableName->getQuotedName($this), | ||
910 | $this->quoteStringLiteral($comment), | ||
911 | ); | ||
912 | } | ||
913 | |||
914 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
915 | public function getCommentOnColumnSQL(string $tableName, string $columnName, string $comment): string | ||
916 | { | ||
917 | $tableName = new Identifier($tableName); | ||
918 | $columnName = new Identifier($columnName); | ||
919 | |||
920 | return sprintf( | ||
921 | 'COMMENT ON COLUMN %s.%s IS %s', | ||
922 | $tableName->getQuotedName($this), | ||
923 | $columnName->getQuotedName($this), | ||
924 | $this->quoteStringLiteral($comment), | ||
925 | ); | ||
926 | } | ||
927 | |||
928 | /** | ||
929 | * Returns the SQL to create inline comment on a column. | ||
930 | * | ||
931 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
932 | */ | ||
933 | public function getInlineColumnCommentSQL(string $comment): string | ||
934 | { | ||
935 | if (! $this->supportsInlineColumnComments()) { | ||
936 | throw NotSupported::new(__METHOD__); | ||
937 | } | ||
938 | |||
939 | return 'COMMENT ' . $this->quoteStringLiteral($comment); | ||
940 | } | ||
941 | |||
942 | /** | ||
943 | * Returns the SQL used to create a table. | ||
944 | * | ||
945 | * @param mixed[][] $columns | ||
946 | * @param mixed[] $options | ||
947 | * | ||
948 | * @return array<int, string> | ||
949 | */ | ||
950 | protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array | ||
951 | { | ||
952 | $columnListSql = $this->getColumnDeclarationListSQL($columns); | ||
953 | |||
954 | if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { | ||
955 | foreach ($options['uniqueConstraints'] as $definition) { | ||
956 | $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition); | ||
957 | } | ||
958 | } | ||
959 | |||
960 | if (isset($options['primary']) && ! empty($options['primary'])) { | ||
961 | $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; | ||
962 | } | ||
963 | |||
964 | if (isset($options['indexes']) && ! empty($options['indexes'])) { | ||
965 | foreach ($options['indexes'] as $index => $definition) { | ||
966 | $columnListSql .= ', ' . $this->getIndexDeclarationSQL($definition); | ||
967 | } | ||
968 | } | ||
969 | |||
970 | $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; | ||
971 | $check = $this->getCheckDeclarationSQL($columns); | ||
972 | |||
973 | if (! empty($check)) { | ||
974 | $query .= ', ' . $check; | ||
975 | } | ||
976 | |||
977 | $query .= ')'; | ||
978 | |||
979 | $sql = [$query]; | ||
980 | |||
981 | if (isset($options['foreignKeys'])) { | ||
982 | foreach ($options['foreignKeys'] as $definition) { | ||
983 | $sql[] = $this->getCreateForeignKeySQL($definition, $name); | ||
984 | } | ||
985 | } | ||
986 | |||
987 | return $sql; | ||
988 | } | ||
989 | |||
990 | public function getCreateTemporaryTableSnippetSQL(): string | ||
991 | { | ||
992 | return 'CREATE TEMPORARY TABLE'; | ||
993 | } | ||
994 | |||
995 | /** | ||
996 | * Generates SQL statements that can be used to apply the diff. | ||
997 | * | ||
998 | * @return list<string> | ||
999 | */ | ||
1000 | public function getAlterSchemaSQL(SchemaDiff $diff): array | ||
1001 | { | ||
1002 | $sql = []; | ||
1003 | |||
1004 | if ($this->supportsSchemas()) { | ||
1005 | foreach ($diff->getCreatedSchemas() as $schema) { | ||
1006 | $sql[] = $this->getCreateSchemaSQL($schema); | ||
1007 | } | ||
1008 | } | ||
1009 | |||
1010 | if ($this->supportsSequences()) { | ||
1011 | foreach ($diff->getAlteredSequences() as $sequence) { | ||
1012 | $sql[] = $this->getAlterSequenceSQL($sequence); | ||
1013 | } | ||
1014 | |||
1015 | foreach ($diff->getDroppedSequences() as $sequence) { | ||
1016 | $sql[] = $this->getDropSequenceSQL($sequence->getQuotedName($this)); | ||
1017 | } | ||
1018 | |||
1019 | foreach ($diff->getCreatedSequences() as $sequence) { | ||
1020 | $sql[] = $this->getCreateSequenceSQL($sequence); | ||
1021 | } | ||
1022 | } | ||
1023 | |||
1024 | $sql = array_merge( | ||
1025 | $sql, | ||
1026 | $this->getCreateTablesSQL( | ||
1027 | $diff->getCreatedTables(), | ||
1028 | ), | ||
1029 | $this->getDropTablesSQL( | ||
1030 | $diff->getDroppedTables(), | ||
1031 | ), | ||
1032 | ); | ||
1033 | |||
1034 | foreach ($diff->getAlteredTables() as $tableDiff) { | ||
1035 | $sql = array_merge($sql, $this->getAlterTableSQL($tableDiff)); | ||
1036 | } | ||
1037 | |||
1038 | return $sql; | ||
1039 | } | ||
1040 | |||
1041 | /** | ||
1042 | * Returns the SQL to create a sequence on this platform. | ||
1043 | */ | ||
1044 | public function getCreateSequenceSQL(Sequence $sequence): string | ||
1045 | { | ||
1046 | throw NotSupported::new(__METHOD__); | ||
1047 | } | ||
1048 | |||
1049 | /** | ||
1050 | * Returns the SQL to change a sequence on this platform. | ||
1051 | */ | ||
1052 | public function getAlterSequenceSQL(Sequence $sequence): string | ||
1053 | { | ||
1054 | throw NotSupported::new(__METHOD__); | ||
1055 | } | ||
1056 | |||
1057 | /** | ||
1058 | * Returns the SQL snippet to drop an existing sequence. | ||
1059 | */ | ||
1060 | public function getDropSequenceSQL(string $name): string | ||
1061 | { | ||
1062 | if (! $this->supportsSequences()) { | ||
1063 | throw NotSupported::new(__METHOD__); | ||
1064 | } | ||
1065 | |||
1066 | return 'DROP SEQUENCE ' . $name; | ||
1067 | } | ||
1068 | |||
1069 | /** | ||
1070 | * Returns the SQL to create an index on a table on this platform. | ||
1071 | */ | ||
1072 | public function getCreateIndexSQL(Index $index, string $table): string | ||
1073 | { | ||
1074 | $name = $index->getQuotedName($this); | ||
1075 | $columns = $index->getColumns(); | ||
1076 | |||
1077 | if (count($columns) === 0) { | ||
1078 | throw new InvalidArgumentException(sprintf( | ||
1079 | 'Incomplete or invalid index definition %s on table %s', | ||
1080 | $name, | ||
1081 | $table, | ||
1082 | )); | ||
1083 | } | ||
1084 | |||
1085 | if ($index->isPrimary()) { | ||
1086 | return $this->getCreatePrimaryKeySQL($index, $table); | ||
1087 | } | ||
1088 | |||
1089 | $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; | ||
1090 | $query .= ' (' . implode(', ', $index->getQuotedColumns($this)) . ')' . $this->getPartialIndexSQL($index); | ||
1091 | |||
1092 | return $query; | ||
1093 | } | ||
1094 | |||
1095 | /** | ||
1096 | * Adds condition for partial index. | ||
1097 | */ | ||
1098 | protected function getPartialIndexSQL(Index $index): string | ||
1099 | { | ||
1100 | if ($this->supportsPartialIndexes() && $index->hasOption('where')) { | ||
1101 | return ' WHERE ' . $index->getOption('where'); | ||
1102 | } | ||
1103 | |||
1104 | return ''; | ||
1105 | } | ||
1106 | |||
1107 | /** | ||
1108 | * Adds additional flags for index generation. | ||
1109 | */ | ||
1110 | protected function getCreateIndexSQLFlags(Index $index): string | ||
1111 | { | ||
1112 | return $index->isUnique() ? 'UNIQUE ' : ''; | ||
1113 | } | ||
1114 | |||
1115 | /** | ||
1116 | * Returns the SQL to create an unnamed primary key constraint. | ||
1117 | */ | ||
1118 | public function getCreatePrimaryKeySQL(Index $index, string $table): string | ||
1119 | { | ||
1120 | return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . implode(', ', $index->getQuotedColumns($this)) . ')'; | ||
1121 | } | ||
1122 | |||
1123 | /** | ||
1124 | * Returns the SQL to create a named schema. | ||
1125 | */ | ||
1126 | public function getCreateSchemaSQL(string $schemaName): string | ||
1127 | { | ||
1128 | if (! $this->supportsSchemas()) { | ||
1129 | throw NotSupported::new(__METHOD__); | ||
1130 | } | ||
1131 | |||
1132 | return 'CREATE SCHEMA ' . $schemaName; | ||
1133 | } | ||
1134 | |||
1135 | /** | ||
1136 | * Returns the SQL to create a unique constraint on a table on this platform. | ||
1137 | */ | ||
1138 | public function getCreateUniqueConstraintSQL(UniqueConstraint $constraint, string $tableName): string | ||
1139 | { | ||
1140 | return 'ALTER TABLE ' . $tableName . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this) . ' UNIQUE' | ||
1141 | . ' (' . implode(', ', $constraint->getQuotedColumns($this)) . ')'; | ||
1142 | } | ||
1143 | |||
1144 | /** | ||
1145 | * Returns the SQL snippet to drop a schema. | ||
1146 | */ | ||
1147 | public function getDropSchemaSQL(string $schemaName): string | ||
1148 | { | ||
1149 | if (! $this->supportsSchemas()) { | ||
1150 | throw NotSupported::new(__METHOD__); | ||
1151 | } | ||
1152 | |||
1153 | return 'DROP SCHEMA ' . $schemaName; | ||
1154 | } | ||
1155 | |||
1156 | /** | ||
1157 | * Quotes a string so that it can be safely used as a table or column name, | ||
1158 | * even if it is a reserved word of the platform. This also detects identifier | ||
1159 | * chains separated by dot and quotes them independently. | ||
1160 | * | ||
1161 | * NOTE: Just because you CAN use quoted identifiers doesn't mean | ||
1162 | * you SHOULD use them. In general, they end up causing way more | ||
1163 | * problems than they solve. | ||
1164 | * | ||
1165 | * @param string $identifier The identifier name to be quoted. | ||
1166 | * | ||
1167 | * @return string The quoted identifier string. | ||
1168 | */ | ||
1169 | public function quoteIdentifier(string $identifier): string | ||
1170 | { | ||
1171 | if (str_contains($identifier, '.')) { | ||
1172 | $parts = array_map($this->quoteSingleIdentifier(...), explode('.', $identifier)); | ||
1173 | |||
1174 | return implode('.', $parts); | ||
1175 | } | ||
1176 | |||
1177 | return $this->quoteSingleIdentifier($identifier); | ||
1178 | } | ||
1179 | |||
1180 | /** | ||
1181 | * Quotes a single identifier (no dot chain separation). | ||
1182 | * | ||
1183 | * @param string $str The identifier name to be quoted. | ||
1184 | * | ||
1185 | * @return string The quoted identifier string. | ||
1186 | */ | ||
1187 | public function quoteSingleIdentifier(string $str): string | ||
1188 | { | ||
1189 | return '"' . str_replace('"', '""', $str) . '"'; | ||
1190 | } | ||
1191 | |||
1192 | /** | ||
1193 | * Returns the SQL to create a new foreign key. | ||
1194 | * | ||
1195 | * @param ForeignKeyConstraint $foreignKey The foreign key constraint. | ||
1196 | * @param string $table The name of the table on which the foreign key is to be created. | ||
1197 | */ | ||
1198 | public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, string $table): string | ||
1199 | { | ||
1200 | return 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); | ||
1201 | } | ||
1202 | |||
1203 | /** | ||
1204 | * Gets the SQL statements for altering an existing table. | ||
1205 | * | ||
1206 | * This method returns an array of SQL statements, since some platforms need several statements. | ||
1207 | * | ||
1208 | * @return list<string> | ||
1209 | */ | ||
1210 | abstract public function getAlterTableSQL(TableDiff $diff): array; | ||
1211 | |||
1212 | public function getRenameTableSQL(string $oldName, string $newName): string | ||
1213 | { | ||
1214 | return sprintf('ALTER TABLE %s RENAME TO %s', $oldName, $newName); | ||
1215 | } | ||
1216 | |||
1217 | /** @return list<string> */ | ||
1218 | protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array | ||
1219 | { | ||
1220 | $tableNameSQL = $diff->getOldTable()->getQuotedName($this); | ||
1221 | |||
1222 | $sql = []; | ||
1223 | |||
1224 | foreach ($diff->getDroppedForeignKeys() as $foreignKey) { | ||
1225 | $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL); | ||
1226 | } | ||
1227 | |||
1228 | foreach ($diff->getModifiedForeignKeys() as $foreignKey) { | ||
1229 | $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableNameSQL); | ||
1230 | } | ||
1231 | |||
1232 | foreach ($diff->getDroppedIndexes() as $index) { | ||
1233 | $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL); | ||
1234 | } | ||
1235 | |||
1236 | foreach ($diff->getModifiedIndexes() as $index) { | ||
1237 | $sql[] = $this->getDropIndexSQL($index->getQuotedName($this), $tableNameSQL); | ||
1238 | } | ||
1239 | |||
1240 | return $sql; | ||
1241 | } | ||
1242 | |||
1243 | /** @return list<string> */ | ||
1244 | protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array | ||
1245 | { | ||
1246 | $sql = []; | ||
1247 | |||
1248 | $tableNameSQL = $diff->getOldTable()->getQuotedName($this); | ||
1249 | |||
1250 | foreach ($diff->getAddedForeignKeys() as $foreignKey) { | ||
1251 | $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); | ||
1252 | } | ||
1253 | |||
1254 | foreach ($diff->getModifiedForeignKeys() as $foreignKey) { | ||
1255 | $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableNameSQL); | ||
1256 | } | ||
1257 | |||
1258 | foreach ($diff->getAddedIndexes() as $index) { | ||
1259 | $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL); | ||
1260 | } | ||
1261 | |||
1262 | foreach ($diff->getModifiedIndexes() as $index) { | ||
1263 | $sql[] = $this->getCreateIndexSQL($index, $tableNameSQL); | ||
1264 | } | ||
1265 | |||
1266 | foreach ($diff->getRenamedIndexes() as $oldIndexName => $index) { | ||
1267 | $oldIndexName = new Identifier($oldIndexName); | ||
1268 | $sql = array_merge( | ||
1269 | $sql, | ||
1270 | $this->getRenameIndexSQL($oldIndexName->getQuotedName($this), $index, $tableNameSQL), | ||
1271 | ); | ||
1272 | } | ||
1273 | |||
1274 | return $sql; | ||
1275 | } | ||
1276 | |||
1277 | /** | ||
1278 | * Returns the SQL for renaming an index on a table. | ||
1279 | * | ||
1280 | * @param string $oldIndexName The name of the index to rename from. | ||
1281 | * @param Index $index The definition of the index to rename to. | ||
1282 | * @param string $tableName The table to rename the given index on. | ||
1283 | * | ||
1284 | * @return list<string> The sequence of SQL statements for renaming the given index. | ||
1285 | */ | ||
1286 | protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array | ||
1287 | { | ||
1288 | return [ | ||
1289 | $this->getDropIndexSQL($oldIndexName, $tableName), | ||
1290 | $this->getCreateIndexSQL($index, $tableName), | ||
1291 | ]; | ||
1292 | } | ||
1293 | |||
1294 | /** | ||
1295 | * Gets declaration of a number of columns in bulk. | ||
1296 | * | ||
1297 | * @param mixed[][] $columns A multidimensional array. | ||
1298 | * The first dimension determines the ordinal position of the column, | ||
1299 | * while the second dimension is keyed with the name of the properties | ||
1300 | * of the column being declared as array indexes. Currently, the types | ||
1301 | * of supported column properties are as follows: | ||
1302 | * | ||
1303 | * length | ||
1304 | * Integer value that determines the maximum length of the text | ||
1305 | * column. If this argument is missing the column should be | ||
1306 | * declared to have the longest length allowed by the DBMS. | ||
1307 | * default | ||
1308 | * Text value to be used as default for this column. | ||
1309 | * notnull | ||
1310 | * Boolean flag that indicates whether this column is constrained | ||
1311 | * to not be set to null. | ||
1312 | * charset | ||
1313 | * Text value with the default CHARACTER SET for this column. | ||
1314 | * collation | ||
1315 | * Text value with the default COLLATION for this column. | ||
1316 | */ | ||
1317 | public function getColumnDeclarationListSQL(array $columns): string | ||
1318 | { | ||
1319 | $declarations = []; | ||
1320 | |||
1321 | foreach ($columns as $column) { | ||
1322 | $declarations[] = $this->getColumnDeclarationSQL($column['name'], $column); | ||
1323 | } | ||
1324 | |||
1325 | return implode(', ', $declarations); | ||
1326 | } | ||
1327 | |||
1328 | /** | ||
1329 | * Obtains DBMS specific SQL code portion needed to declare a generic type | ||
1330 | * column to be used in statements like CREATE TABLE. | ||
1331 | * | ||
1332 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1333 | * | ||
1334 | * @param string $name The name the column to be declared. | ||
1335 | * @param mixed[] $column An associative array with the name of the properties | ||
1336 | * of the column being declared as array indexes. Currently, the types | ||
1337 | * of supported column properties are as follows: | ||
1338 | * | ||
1339 | * length | ||
1340 | * Integer value that determines the maximum length of the text | ||
1341 | * column. If this argument is missing the column should be | ||
1342 | * declared to have the longest length allowed by the DBMS. | ||
1343 | * default | ||
1344 | * Text value to be used as default for this column. | ||
1345 | * notnull | ||
1346 | * Boolean flag that indicates whether this column is constrained | ||
1347 | * to not be set to null. | ||
1348 | * charset | ||
1349 | * Text value with the default CHARACTER SET for this column. | ||
1350 | * collation | ||
1351 | * Text value with the default COLLATION for this column. | ||
1352 | * columnDefinition | ||
1353 | * a string that defines the complete column | ||
1354 | * | ||
1355 | * @return string DBMS specific SQL code portion that should be used to declare the column. | ||
1356 | */ | ||
1357 | public function getColumnDeclarationSQL(string $name, array $column): string | ||
1358 | { | ||
1359 | if (isset($column['columnDefinition'])) { | ||
1360 | $declaration = $column['columnDefinition']; | ||
1361 | } else { | ||
1362 | $default = $this->getDefaultValueDeclarationSQL($column); | ||
1363 | |||
1364 | $charset = ! empty($column['charset']) ? | ||
1365 | ' ' . $this->getColumnCharsetDeclarationSQL($column['charset']) : ''; | ||
1366 | |||
1367 | $collation = ! empty($column['collation']) ? | ||
1368 | ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; | ||
1369 | |||
1370 | $notnull = ! empty($column['notnull']) ? ' NOT NULL' : ''; | ||
1371 | |||
1372 | $typeDecl = $column['type']->getSQLDeclaration($column, $this); | ||
1373 | $declaration = $typeDecl . $charset . $default . $notnull . $collation; | ||
1374 | |||
1375 | if ($this->supportsInlineColumnComments() && isset($column['comment']) && $column['comment'] !== '') { | ||
1376 | $declaration .= ' ' . $this->getInlineColumnCommentSQL($column['comment']); | ||
1377 | } | ||
1378 | } | ||
1379 | |||
1380 | return $name . ' ' . $declaration; | ||
1381 | } | ||
1382 | |||
1383 | /** | ||
1384 | * Returns the SQL snippet that declares a floating point column of arbitrary precision. | ||
1385 | * | ||
1386 | * @param mixed[] $column | ||
1387 | */ | ||
1388 | public function getDecimalTypeDeclarationSQL(array $column): string | ||
1389 | { | ||
1390 | if (! isset($column['precision'])) { | ||
1391 | $e = ColumnPrecisionRequired::new(); | ||
1392 | } elseif (! isset($column['scale'])) { | ||
1393 | $e = ColumnScaleRequired::new(); | ||
1394 | } else { | ||
1395 | $e = null; | ||
1396 | } | ||
1397 | |||
1398 | if ($e !== null) { | ||
1399 | throw InvalidColumnDeclaration::fromInvalidColumnType($column['name'], $e); | ||
1400 | } | ||
1401 | |||
1402 | return 'NUMERIC(' . $column['precision'] . ', ' . $column['scale'] . ')'; | ||
1403 | } | ||
1404 | |||
1405 | /** | ||
1406 | * Obtains DBMS specific SQL code portion needed to set a default value | ||
1407 | * declaration to be used in statements like CREATE TABLE. | ||
1408 | * | ||
1409 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1410 | * | ||
1411 | * @param mixed[] $column The column definition array. | ||
1412 | * | ||
1413 | * @return string DBMS specific SQL code portion needed to set a default value. | ||
1414 | */ | ||
1415 | public function getDefaultValueDeclarationSQL(array $column): string | ||
1416 | { | ||
1417 | if (! isset($column['default'])) { | ||
1418 | return empty($column['notnull']) ? ' DEFAULT NULL' : ''; | ||
1419 | } | ||
1420 | |||
1421 | $default = $column['default']; | ||
1422 | |||
1423 | if (! isset($column['type'])) { | ||
1424 | return " DEFAULT '" . $default . "'"; | ||
1425 | } | ||
1426 | |||
1427 | $type = $column['type']; | ||
1428 | |||
1429 | if ($type instanceof Types\PhpIntegerMappingType) { | ||
1430 | return ' DEFAULT ' . $default; | ||
1431 | } | ||
1432 | |||
1433 | if ($type instanceof Types\PhpDateTimeMappingType && $default === $this->getCurrentTimestampSQL()) { | ||
1434 | return ' DEFAULT ' . $this->getCurrentTimestampSQL(); | ||
1435 | } | ||
1436 | |||
1437 | if ($type instanceof Types\PhpTimeMappingType && $default === $this->getCurrentTimeSQL()) { | ||
1438 | return ' DEFAULT ' . $this->getCurrentTimeSQL(); | ||
1439 | } | ||
1440 | |||
1441 | if ($type instanceof Types\PhpDateMappingType && $default === $this->getCurrentDateSQL()) { | ||
1442 | return ' DEFAULT ' . $this->getCurrentDateSQL(); | ||
1443 | } | ||
1444 | |||
1445 | if ($type instanceof Types\BooleanType) { | ||
1446 | return ' DEFAULT ' . $this->convertBooleans($default); | ||
1447 | } | ||
1448 | |||
1449 | if (is_int($default) || is_float($default)) { | ||
1450 | return ' DEFAULT ' . $default; | ||
1451 | } | ||
1452 | |||
1453 | return ' DEFAULT ' . $this->quoteStringLiteral($default); | ||
1454 | } | ||
1455 | |||
1456 | /** | ||
1457 | * Obtains DBMS specific SQL code portion needed to set a CHECK constraint | ||
1458 | * declaration to be used in statements like CREATE TABLE. | ||
1459 | * | ||
1460 | * @param string[]|mixed[][] $definition The check definition. | ||
1461 | * | ||
1462 | * @return string DBMS specific SQL code portion needed to set a CHECK constraint. | ||
1463 | */ | ||
1464 | public function getCheckDeclarationSQL(array $definition): string | ||
1465 | { | ||
1466 | $constraints = []; | ||
1467 | foreach ($definition as $def) { | ||
1468 | if (is_string($def)) { | ||
1469 | $constraints[] = 'CHECK (' . $def . ')'; | ||
1470 | } else { | ||
1471 | if (isset($def['min'])) { | ||
1472 | $constraints[] = 'CHECK (' . $def['name'] . ' >= ' . $def['min'] . ')'; | ||
1473 | } | ||
1474 | |||
1475 | if (! isset($def['max'])) { | ||
1476 | continue; | ||
1477 | } | ||
1478 | |||
1479 | $constraints[] = 'CHECK (' . $def['name'] . ' <= ' . $def['max'] . ')'; | ||
1480 | } | ||
1481 | } | ||
1482 | |||
1483 | return implode(', ', $constraints); | ||
1484 | } | ||
1485 | |||
1486 | /** | ||
1487 | * Obtains DBMS specific SQL code portion needed to set a unique | ||
1488 | * constraint declaration to be used in statements like CREATE TABLE. | ||
1489 | * | ||
1490 | * @param UniqueConstraint $constraint The unique constraint definition. | ||
1491 | * | ||
1492 | * @return string DBMS specific SQL code portion needed to set a constraint. | ||
1493 | */ | ||
1494 | public function getUniqueConstraintDeclarationSQL(UniqueConstraint $constraint): string | ||
1495 | { | ||
1496 | $columns = $constraint->getColumns(); | ||
1497 | |||
1498 | if (count($columns) === 0) { | ||
1499 | throw new InvalidArgumentException('Incomplete definition. "columns" required.'); | ||
1500 | } | ||
1501 | |||
1502 | $chunks = ['CONSTRAINT']; | ||
1503 | |||
1504 | if ($constraint->getName() !== '') { | ||
1505 | $chunks[] = $constraint->getQuotedName($this); | ||
1506 | } | ||
1507 | |||
1508 | $chunks[] = 'UNIQUE'; | ||
1509 | |||
1510 | if ($constraint->hasFlag('clustered')) { | ||
1511 | $chunks[] = 'CLUSTERED'; | ||
1512 | } | ||
1513 | |||
1514 | $chunks[] = sprintf('(%s)', implode(', ', $columns)); | ||
1515 | |||
1516 | return implode(' ', $chunks); | ||
1517 | } | ||
1518 | |||
1519 | /** | ||
1520 | * Obtains DBMS specific SQL code portion needed to set an index | ||
1521 | * declaration to be used in statements like CREATE TABLE. | ||
1522 | * | ||
1523 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1524 | * | ||
1525 | * @param Index $index The index definition. | ||
1526 | * | ||
1527 | * @return string DBMS specific SQL code portion needed to set an index. | ||
1528 | */ | ||
1529 | public function getIndexDeclarationSQL(Index $index): string | ||
1530 | { | ||
1531 | $columns = $index->getColumns(); | ||
1532 | |||
1533 | if (count($columns) === 0) { | ||
1534 | throw new InvalidArgumentException('Incomplete definition. "columns" required.'); | ||
1535 | } | ||
1536 | |||
1537 | return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $index->getQuotedName($this) | ||
1538 | . ' (' . implode(', ', $index->getQuotedColumns($this)) . ')' . $this->getPartialIndexSQL($index); | ||
1539 | } | ||
1540 | |||
1541 | /** | ||
1542 | * Some vendors require temporary table names to be qualified specially. | ||
1543 | */ | ||
1544 | public function getTemporaryTableName(string $tableName): string | ||
1545 | { | ||
1546 | return $tableName; | ||
1547 | } | ||
1548 | |||
1549 | /** | ||
1550 | * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint | ||
1551 | * of a column declaration to be used in statements like CREATE TABLE. | ||
1552 | * | ||
1553 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1554 | * | ||
1555 | * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint | ||
1556 | * of a column declaration. | ||
1557 | */ | ||
1558 | public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey): string | ||
1559 | { | ||
1560 | $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey); | ||
1561 | $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey); | ||
1562 | |||
1563 | return $sql; | ||
1564 | } | ||
1565 | |||
1566 | /** | ||
1567 | * Returns the FOREIGN KEY query section dealing with non-standard options | ||
1568 | * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... | ||
1569 | * | ||
1570 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1571 | * | ||
1572 | * @param ForeignKeyConstraint $foreignKey The foreign key definition. | ||
1573 | */ | ||
1574 | public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string | ||
1575 | { | ||
1576 | $query = ''; | ||
1577 | if ($foreignKey->hasOption('onUpdate')) { | ||
1578 | $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate')); | ||
1579 | } | ||
1580 | |||
1581 | if ($foreignKey->hasOption('onDelete')) { | ||
1582 | $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); | ||
1583 | } | ||
1584 | |||
1585 | return $query; | ||
1586 | } | ||
1587 | |||
1588 | /** | ||
1589 | * Returns the given referential action in uppercase if valid, otherwise throws an exception. | ||
1590 | * | ||
1591 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1592 | * | ||
1593 | * @param string $action The foreign key referential action. | ||
1594 | */ | ||
1595 | public function getForeignKeyReferentialActionSQL(string $action): string | ||
1596 | { | ||
1597 | $upper = strtoupper($action); | ||
1598 | |||
1599 | return match ($upper) { | ||
1600 | 'CASCADE', | ||
1601 | 'SET NULL', | ||
1602 | 'NO ACTION', | ||
1603 | 'RESTRICT', | ||
1604 | 'SET DEFAULT' => $upper, | ||
1605 | default => throw new InvalidArgumentException(sprintf('Invalid foreign key action "%s".', $upper)), | ||
1606 | }; | ||
1607 | } | ||
1608 | |||
1609 | /** | ||
1610 | * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint | ||
1611 | * of a column declaration to be used in statements like CREATE TABLE. | ||
1612 | */ | ||
1613 | public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey): string | ||
1614 | { | ||
1615 | $sql = ''; | ||
1616 | if ($foreignKey->getName() !== '') { | ||
1617 | $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; | ||
1618 | } | ||
1619 | |||
1620 | $sql .= 'FOREIGN KEY ('; | ||
1621 | |||
1622 | if (count($foreignKey->getLocalColumns()) === 0) { | ||
1623 | throw new InvalidArgumentException('Incomplete definition. "local" required.'); | ||
1624 | } | ||
1625 | |||
1626 | if (count($foreignKey->getForeignColumns()) === 0) { | ||
1627 | throw new InvalidArgumentException('Incomplete definition. "foreign" required.'); | ||
1628 | } | ||
1629 | |||
1630 | if (strlen($foreignKey->getForeignTableName()) === 0) { | ||
1631 | throw new InvalidArgumentException('Incomplete definition. "foreignTable" required.'); | ||
1632 | } | ||
1633 | |||
1634 | return $sql . implode(', ', $foreignKey->getQuotedLocalColumns($this)) | ||
1635 | . ') REFERENCES ' | ||
1636 | . $foreignKey->getQuotedForeignTableName($this) . ' (' | ||
1637 | . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')'; | ||
1638 | } | ||
1639 | |||
1640 | /** | ||
1641 | * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET | ||
1642 | * of a column declaration to be used in statements like CREATE TABLE. | ||
1643 | * | ||
1644 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1645 | * | ||
1646 | * @param string $charset The name of the charset. | ||
1647 | * | ||
1648 | * @return string DBMS specific SQL code portion needed to set the CHARACTER SET | ||
1649 | * of a column declaration. | ||
1650 | */ | ||
1651 | public function getColumnCharsetDeclarationSQL(string $charset): string | ||
1652 | { | ||
1653 | return ''; | ||
1654 | } | ||
1655 | |||
1656 | /** | ||
1657 | * Obtains DBMS specific SQL code portion needed to set the COLLATION | ||
1658 | * of a column declaration to be used in statements like CREATE TABLE. | ||
1659 | * | ||
1660 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1661 | * | ||
1662 | * @param string $collation The name of the collation. | ||
1663 | * | ||
1664 | * @return string DBMS specific SQL code portion needed to set the COLLATION | ||
1665 | * of a column declaration. | ||
1666 | */ | ||
1667 | public function getColumnCollationDeclarationSQL(string $collation): string | ||
1668 | { | ||
1669 | return $this->supportsColumnCollation() ? 'COLLATE ' . $this->quoteSingleIdentifier($collation) : ''; | ||
1670 | } | ||
1671 | |||
1672 | /** | ||
1673 | * Some platforms need the boolean values to be converted. | ||
1674 | * | ||
1675 | * The default conversion in this implementation converts to integers (false => 0, true => 1). | ||
1676 | * | ||
1677 | * Note: if the input is not a boolean the original input might be returned. | ||
1678 | * | ||
1679 | * There are two contexts when converting booleans: Literals and Prepared Statements. | ||
1680 | * This method should handle the literal case | ||
1681 | * | ||
1682 | * @param mixed $item A boolean or an array of them. | ||
1683 | * | ||
1684 | * @return mixed A boolean database value or an array of them. | ||
1685 | */ | ||
1686 | public function convertBooleans(mixed $item): mixed | ||
1687 | { | ||
1688 | if (is_array($item)) { | ||
1689 | foreach ($item as $k => $value) { | ||
1690 | if (! is_bool($value)) { | ||
1691 | continue; | ||
1692 | } | ||
1693 | |||
1694 | $item[$k] = (int) $value; | ||
1695 | } | ||
1696 | } elseif (is_bool($item)) { | ||
1697 | $item = (int) $item; | ||
1698 | } | ||
1699 | |||
1700 | return $item; | ||
1701 | } | ||
1702 | |||
1703 | /** | ||
1704 | * Some platforms have boolean literals that needs to be correctly converted | ||
1705 | * | ||
1706 | * The default conversion tries to convert value into bool "(bool)$item" | ||
1707 | * | ||
1708 | * @param T $item | ||
1709 | * | ||
1710 | * @return (T is null ? null : bool) | ||
1711 | * | ||
1712 | * @template T | ||
1713 | */ | ||
1714 | public function convertFromBoolean(mixed $item): ?bool | ||
1715 | { | ||
1716 | if ($item === null) { | ||
1717 | return null; | ||
1718 | } | ||
1719 | |||
1720 | return (bool) $item; | ||
1721 | } | ||
1722 | |||
1723 | /** | ||
1724 | * This method should handle the prepared statements case. When there is no | ||
1725 | * distinction, it's OK to use the same method. | ||
1726 | * | ||
1727 | * Note: if the input is not a boolean the original input might be returned. | ||
1728 | * | ||
1729 | * @param mixed $item A boolean or an array of them. | ||
1730 | * | ||
1731 | * @return mixed A boolean database value or an array of them. | ||
1732 | */ | ||
1733 | public function convertBooleansToDatabaseValue(mixed $item): mixed | ||
1734 | { | ||
1735 | return $this->convertBooleans($item); | ||
1736 | } | ||
1737 | |||
1738 | /** | ||
1739 | * Returns the SQL specific for the platform to get the current date. | ||
1740 | */ | ||
1741 | public function getCurrentDateSQL(): string | ||
1742 | { | ||
1743 | return 'CURRENT_DATE'; | ||
1744 | } | ||
1745 | |||
1746 | /** | ||
1747 | * Returns the SQL specific for the platform to get the current time. | ||
1748 | */ | ||
1749 | public function getCurrentTimeSQL(): string | ||
1750 | { | ||
1751 | return 'CURRENT_TIME'; | ||
1752 | } | ||
1753 | |||
1754 | /** | ||
1755 | * Returns the SQL specific for the platform to get the current timestamp | ||
1756 | */ | ||
1757 | public function getCurrentTimestampSQL(): string | ||
1758 | { | ||
1759 | return 'CURRENT_TIMESTAMP'; | ||
1760 | } | ||
1761 | |||
1762 | /** | ||
1763 | * Returns the SQL for a given transaction isolation level Connection constant. | ||
1764 | */ | ||
1765 | protected function _getTransactionIsolationLevelSQL(TransactionIsolationLevel $level): string | ||
1766 | { | ||
1767 | return match ($level) { | ||
1768 | TransactionIsolationLevel::READ_UNCOMMITTED => 'READ UNCOMMITTED', | ||
1769 | TransactionIsolationLevel::READ_COMMITTED => 'READ COMMITTED', | ||
1770 | TransactionIsolationLevel::REPEATABLE_READ => 'REPEATABLE READ', | ||
1771 | TransactionIsolationLevel::SERIALIZABLE => 'SERIALIZABLE', | ||
1772 | }; | ||
1773 | } | ||
1774 | |||
1775 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
1776 | public function getListDatabasesSQL(): string | ||
1777 | { | ||
1778 | throw NotSupported::new(__METHOD__); | ||
1779 | } | ||
1780 | |||
1781 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
1782 | public function getListSequencesSQL(string $database): string | ||
1783 | { | ||
1784 | throw NotSupported::new(__METHOD__); | ||
1785 | } | ||
1786 | |||
1787 | /** | ||
1788 | * Returns the SQL to list all views of a database or user. | ||
1789 | * | ||
1790 | * @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. | ||
1791 | */ | ||
1792 | abstract public function getListViewsSQL(string $database): string; | ||
1793 | |||
1794 | public function getCreateViewSQL(string $name, string $sql): string | ||
1795 | { | ||
1796 | return 'CREATE VIEW ' . $name . ' AS ' . $sql; | ||
1797 | } | ||
1798 | |||
1799 | public function getDropViewSQL(string $name): string | ||
1800 | { | ||
1801 | return 'DROP VIEW ' . $name; | ||
1802 | } | ||
1803 | |||
1804 | public function getSequenceNextValSQL(string $sequence): string | ||
1805 | { | ||
1806 | throw NotSupported::new(__METHOD__); | ||
1807 | } | ||
1808 | |||
1809 | /** | ||
1810 | * Returns the SQL to create a new database. | ||
1811 | * | ||
1812 | * @param string $name The name of the database that should be created. | ||
1813 | */ | ||
1814 | public function getCreateDatabaseSQL(string $name): string | ||
1815 | { | ||
1816 | return 'CREATE DATABASE ' . $name; | ||
1817 | } | ||
1818 | |||
1819 | /** | ||
1820 | * Returns the SQL snippet to drop an existing database. | ||
1821 | * | ||
1822 | * @param string $name The name of the database that should be dropped. | ||
1823 | */ | ||
1824 | public function getDropDatabaseSQL(string $name): string | ||
1825 | { | ||
1826 | return 'DROP DATABASE ' . $name; | ||
1827 | } | ||
1828 | |||
1829 | /** | ||
1830 | * Returns the SQL to set the transaction isolation level. | ||
1831 | */ | ||
1832 | abstract public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string; | ||
1833 | |||
1834 | /** | ||
1835 | * Obtains DBMS specific SQL to be used to create datetime columns in | ||
1836 | * statements like CREATE TABLE. | ||
1837 | * | ||
1838 | * @param mixed[] $column | ||
1839 | */ | ||
1840 | abstract public function getDateTimeTypeDeclarationSQL(array $column): string; | ||
1841 | |||
1842 | /** | ||
1843 | * Obtains DBMS specific SQL to be used to create datetime with timezone offset columns. | ||
1844 | * | ||
1845 | * @param mixed[] $column | ||
1846 | */ | ||
1847 | public function getDateTimeTzTypeDeclarationSQL(array $column): string | ||
1848 | { | ||
1849 | return $this->getDateTimeTypeDeclarationSQL($column); | ||
1850 | } | ||
1851 | |||
1852 | /** | ||
1853 | * Obtains DBMS specific SQL to be used to create date columns in statements | ||
1854 | * like CREATE TABLE. | ||
1855 | * | ||
1856 | * @param mixed[] $column | ||
1857 | */ | ||
1858 | abstract public function getDateTypeDeclarationSQL(array $column): string; | ||
1859 | |||
1860 | /** | ||
1861 | * Obtains DBMS specific SQL to be used to create time columns in statements | ||
1862 | * like CREATE TABLE. | ||
1863 | * | ||
1864 | * @param mixed[] $column | ||
1865 | */ | ||
1866 | abstract public function getTimeTypeDeclarationSQL(array $column): string; | ||
1867 | |||
1868 | /** @param mixed[] $column */ | ||
1869 | public function getFloatDeclarationSQL(array $column): string | ||
1870 | { | ||
1871 | return 'DOUBLE PRECISION'; | ||
1872 | } | ||
1873 | |||
1874 | /** | ||
1875 | * Gets the default transaction isolation level of the platform. | ||
1876 | * | ||
1877 | * @return TransactionIsolationLevel The default isolation level. | ||
1878 | */ | ||
1879 | public function getDefaultTransactionIsolationLevel(): TransactionIsolationLevel | ||
1880 | { | ||
1881 | return TransactionIsolationLevel::READ_COMMITTED; | ||
1882 | } | ||
1883 | |||
1884 | /* supports*() methods */ | ||
1885 | |||
1886 | /** | ||
1887 | * Whether the platform supports sequences. | ||
1888 | */ | ||
1889 | public function supportsSequences(): bool | ||
1890 | { | ||
1891 | return false; | ||
1892 | } | ||
1893 | |||
1894 | /** | ||
1895 | * Whether the platform supports identity columns. | ||
1896 | * | ||
1897 | * Identity columns are columns that receive an auto-generated value from the | ||
1898 | * database on insert of a row. | ||
1899 | */ | ||
1900 | public function supportsIdentityColumns(): bool | ||
1901 | { | ||
1902 | return false; | ||
1903 | } | ||
1904 | |||
1905 | /** | ||
1906 | * Whether the platform supports partial indexes. | ||
1907 | * | ||
1908 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1909 | */ | ||
1910 | public function supportsPartialIndexes(): bool | ||
1911 | { | ||
1912 | return false; | ||
1913 | } | ||
1914 | |||
1915 | /** | ||
1916 | * Whether the platform supports indexes with column length definitions. | ||
1917 | */ | ||
1918 | public function supportsColumnLengthIndexes(): bool | ||
1919 | { | ||
1920 | return false; | ||
1921 | } | ||
1922 | |||
1923 | /** | ||
1924 | * Whether the platform supports savepoints. | ||
1925 | */ | ||
1926 | public function supportsSavepoints(): bool | ||
1927 | { | ||
1928 | return true; | ||
1929 | } | ||
1930 | |||
1931 | /** | ||
1932 | * Whether the platform supports releasing savepoints. | ||
1933 | */ | ||
1934 | public function supportsReleaseSavepoints(): bool | ||
1935 | { | ||
1936 | return $this->supportsSavepoints(); | ||
1937 | } | ||
1938 | |||
1939 | /** | ||
1940 | * Whether the platform supports database schemas. | ||
1941 | */ | ||
1942 | public function supportsSchemas(): bool | ||
1943 | { | ||
1944 | return false; | ||
1945 | } | ||
1946 | |||
1947 | /** | ||
1948 | * Whether this platform support to add inline column comments as postfix. | ||
1949 | * | ||
1950 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1951 | */ | ||
1952 | public function supportsInlineColumnComments(): bool | ||
1953 | { | ||
1954 | return false; | ||
1955 | } | ||
1956 | |||
1957 | /** | ||
1958 | * Whether this platform support the proprietary syntax "COMMENT ON asset". | ||
1959 | * | ||
1960 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1961 | */ | ||
1962 | public function supportsCommentOnStatement(): bool | ||
1963 | { | ||
1964 | return false; | ||
1965 | } | ||
1966 | |||
1967 | /** | ||
1968 | * Does this platform support column collation? | ||
1969 | * | ||
1970 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1971 | */ | ||
1972 | public function supportsColumnCollation(): bool | ||
1973 | { | ||
1974 | return false; | ||
1975 | } | ||
1976 | |||
1977 | /** | ||
1978 | * Gets the format string, as accepted by the date() function, that describes | ||
1979 | * the format of a stored datetime value of this platform. | ||
1980 | * | ||
1981 | * @return string The format string. | ||
1982 | */ | ||
1983 | public function getDateTimeFormatString(): string | ||
1984 | { | ||
1985 | return 'Y-m-d H:i:s'; | ||
1986 | } | ||
1987 | |||
1988 | /** | ||
1989 | * Gets the format string, as accepted by the date() function, that describes | ||
1990 | * the format of a stored datetime with timezone value of this platform. | ||
1991 | * | ||
1992 | * @return string The format string. | ||
1993 | */ | ||
1994 | public function getDateTimeTzFormatString(): string | ||
1995 | { | ||
1996 | return 'Y-m-d H:i:s'; | ||
1997 | } | ||
1998 | |||
1999 | /** | ||
2000 | * Gets the format string, as accepted by the date() function, that describes | ||
2001 | * the format of a stored date value of this platform. | ||
2002 | * | ||
2003 | * @return string The format string. | ||
2004 | */ | ||
2005 | public function getDateFormatString(): string | ||
2006 | { | ||
2007 | return 'Y-m-d'; | ||
2008 | } | ||
2009 | |||
2010 | /** | ||
2011 | * Gets the format string, as accepted by the date() function, that describes | ||
2012 | * the format of a stored time value of this platform. | ||
2013 | * | ||
2014 | * @return string The format string. | ||
2015 | */ | ||
2016 | public function getTimeFormatString(): string | ||
2017 | { | ||
2018 | return 'H:i:s'; | ||
2019 | } | ||
2020 | |||
2021 | /** | ||
2022 | * Adds an driver-specific LIMIT clause to the query. | ||
2023 | */ | ||
2024 | final public function modifyLimitQuery(string $query, ?int $limit, int $offset = 0): string | ||
2025 | { | ||
2026 | if ($offset < 0) { | ||
2027 | throw new InvalidArgumentException(sprintf( | ||
2028 | 'Offset must be a positive integer or zero, %d given.', | ||
2029 | $offset, | ||
2030 | )); | ||
2031 | } | ||
2032 | |||
2033 | return $this->doModifyLimitQuery($query, $limit, $offset); | ||
2034 | } | ||
2035 | |||
2036 | /** | ||
2037 | * Adds an platform-specific LIMIT clause to the query. | ||
2038 | */ | ||
2039 | protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string | ||
2040 | { | ||
2041 | if ($limit !== null) { | ||
2042 | $query .= sprintf(' LIMIT %d', $limit); | ||
2043 | } | ||
2044 | |||
2045 | if ($offset > 0) { | ||
2046 | $query .= sprintf(' OFFSET %d', $offset); | ||
2047 | } | ||
2048 | |||
2049 | return $query; | ||
2050 | } | ||
2051 | |||
2052 | /** | ||
2053 | * Maximum length of any given database identifier, like tables or column names. | ||
2054 | */ | ||
2055 | public function getMaxIdentifierLength(): int | ||
2056 | { | ||
2057 | return 63; | ||
2058 | } | ||
2059 | |||
2060 | /** | ||
2061 | * Returns the insert SQL for an empty insert statement. | ||
2062 | */ | ||
2063 | public function getEmptyIdentityInsertSQL(string $quotedTableName, string $quotedIdentifierColumnName): string | ||
2064 | { | ||
2065 | return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (null)'; | ||
2066 | } | ||
2067 | |||
2068 | /** | ||
2069 | * Generates a Truncate Table SQL statement for a given table. | ||
2070 | * | ||
2071 | * Cascade is not supported on many platforms but would optionally cascade the truncate by | ||
2072 | * following the foreign keys. | ||
2073 | */ | ||
2074 | public function getTruncateTableSQL(string $tableName, bool $cascade = false): string | ||
2075 | { | ||
2076 | $tableIdentifier = new Identifier($tableName); | ||
2077 | |||
2078 | return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); | ||
2079 | } | ||
2080 | |||
2081 | /** | ||
2082 | * This is for test reasons, many vendors have special requirements for dummy statements. | ||
2083 | */ | ||
2084 | public function getDummySelectSQL(string $expression = '1'): string | ||
2085 | { | ||
2086 | return sprintf('SELECT %s', $expression); | ||
2087 | } | ||
2088 | |||
2089 | /** | ||
2090 | * Returns the SQL to create a new savepoint. | ||
2091 | */ | ||
2092 | public function createSavePoint(string $savepoint): string | ||
2093 | { | ||
2094 | return 'SAVEPOINT ' . $savepoint; | ||
2095 | } | ||
2096 | |||
2097 | /** | ||
2098 | * Returns the SQL to release a savepoint. | ||
2099 | */ | ||
2100 | public function releaseSavePoint(string $savepoint): string | ||
2101 | { | ||
2102 | return 'RELEASE SAVEPOINT ' . $savepoint; | ||
2103 | } | ||
2104 | |||
2105 | /** | ||
2106 | * Returns the SQL to rollback a savepoint. | ||
2107 | */ | ||
2108 | public function rollbackSavePoint(string $savepoint): string | ||
2109 | { | ||
2110 | return 'ROLLBACK TO SAVEPOINT ' . $savepoint; | ||
2111 | } | ||
2112 | |||
2113 | /** | ||
2114 | * Returns the keyword list instance of this platform. | ||
2115 | */ | ||
2116 | final public function getReservedKeywordsList(): KeywordList | ||
2117 | { | ||
2118 | // Store the instance so it doesn't need to be generated on every request. | ||
2119 | return $this->_keywords ??= $this->createReservedKeywordsList(); | ||
2120 | } | ||
2121 | |||
2122 | /** | ||
2123 | * Creates an instance of the reserved keyword list of this platform. | ||
2124 | */ | ||
2125 | abstract protected function createReservedKeywordsList(): KeywordList; | ||
2126 | |||
2127 | /** | ||
2128 | * Quotes a literal string. | ||
2129 | * This method is NOT meant to fix SQL injections! | ||
2130 | * It is only meant to escape this platform's string literal | ||
2131 | * quote character inside the given literal string. | ||
2132 | * | ||
2133 | * @param string $str The literal string to be quoted. | ||
2134 | * | ||
2135 | * @return string The quoted literal string. | ||
2136 | */ | ||
2137 | public function quoteStringLiteral(string $str): string | ||
2138 | { | ||
2139 | return "'" . str_replace("'", "''", $str) . "'"; | ||
2140 | } | ||
2141 | |||
2142 | /** | ||
2143 | * Escapes metacharacters in a string intended to be used with a LIKE | ||
2144 | * operator. | ||
2145 | * | ||
2146 | * @param string $inputString a literal, unquoted string | ||
2147 | * @param string $escapeChar should be reused by the caller in the LIKE | ||
2148 | * expression. | ||
2149 | */ | ||
2150 | final public function escapeStringForLike(string $inputString, string $escapeChar): string | ||
2151 | { | ||
2152 | $sql = preg_replace( | ||
2153 | '~([' . preg_quote($this->getLikeWildcardCharacters() . $escapeChar, '~') . '])~u', | ||
2154 | addcslashes($escapeChar, '\\') . '$1', | ||
2155 | $inputString, | ||
2156 | ); | ||
2157 | |||
2158 | assert(is_string($sql)); | ||
2159 | |||
2160 | return $sql; | ||
2161 | } | ||
2162 | |||
2163 | /** | ||
2164 | * @return array<string,mixed> An associative array with the name of the properties | ||
2165 | * of the column being declared as array indexes. | ||
2166 | */ | ||
2167 | private function columnToArray(Column $column): array | ||
2168 | { | ||
2169 | return array_merge($column->toArray(), [ | ||
2170 | 'name' => $column->getQuotedName($this), | ||
2171 | 'version' => $column->hasPlatformOption('version') ? $column->getPlatformOption('version') : false, | ||
2172 | 'comment' => $column->getComment(), | ||
2173 | ]); | ||
2174 | } | ||
2175 | |||
2176 | /** @internal */ | ||
2177 | public function createSQLParser(): Parser | ||
2178 | { | ||
2179 | return new Parser(false); | ||
2180 | } | ||
2181 | |||
2182 | protected function getLikeWildcardCharacters(): string | ||
2183 | { | ||
2184 | return '%_'; | ||
2185 | } | ||
2186 | |||
2187 | /** | ||
2188 | * Compares the definitions of the given columns in the context of this platform. | ||
2189 | */ | ||
2190 | public function columnsEqual(Column $column1, Column $column2): bool | ||
2191 | { | ||
2192 | $column1Array = $this->columnToArray($column1); | ||
2193 | $column2Array = $this->columnToArray($column2); | ||
2194 | |||
2195 | // ignore explicit columnDefinition since it's not set on the Column generated by the SchemaManager | ||
2196 | unset($column1Array['columnDefinition']); | ||
2197 | unset($column2Array['columnDefinition']); | ||
2198 | |||
2199 | if ( | ||
2200 | $this->getColumnDeclarationSQL('', $column1Array) | ||
2201 | !== $this->getColumnDeclarationSQL('', $column2Array) | ||
2202 | ) { | ||
2203 | return false; | ||
2204 | } | ||
2205 | |||
2206 | // If the platform supports inline comments, all comparison is already done above | ||
2207 | if ($this->supportsInlineColumnComments()) { | ||
2208 | return true; | ||
2209 | } | ||
2210 | |||
2211 | return $column1->getComment() === $column2->getComment(); | ||
2212 | } | ||
2213 | |||
2214 | /** | ||
2215 | * Creates the schema manager that can be used to inspect and change the underlying | ||
2216 | * database schema according to the dialect of the platform. | ||
2217 | */ | ||
2218 | abstract public function createSchemaManager(Connection $connection): AbstractSchemaManager; | ||
2219 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/DB2Platform.php b/vendor/doctrine/dbal/src/Platforms/DB2Platform.php new file mode 100644 index 0000000..a00b24b --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/DB2Platform.php | |||
@@ -0,0 +1,593 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | use Doctrine\DBAL\Connection; | ||
8 | use Doctrine\DBAL\Platforms\Exception\NotSupported; | ||
9 | use Doctrine\DBAL\Platforms\Keywords\DB2Keywords; | ||
10 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
11 | use Doctrine\DBAL\Schema\ColumnDiff; | ||
12 | use Doctrine\DBAL\Schema\DB2SchemaManager; | ||
13 | use Doctrine\DBAL\Schema\Identifier; | ||
14 | use Doctrine\DBAL\Schema\Index; | ||
15 | use Doctrine\DBAL\Schema\TableDiff; | ||
16 | use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; | ||
17 | use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; | ||
18 | use Doctrine\DBAL\TransactionIsolationLevel; | ||
19 | use Doctrine\DBAL\Types\Types; | ||
20 | |||
21 | use function array_merge; | ||
22 | use function count; | ||
23 | use function current; | ||
24 | use function explode; | ||
25 | use function implode; | ||
26 | use function sprintf; | ||
27 | use function str_contains; | ||
28 | |||
29 | /** | ||
30 | * Provides the behavior, features and SQL dialect of the IBM DB2 database platform of the oldest supported version. | ||
31 | */ | ||
32 | class DB2Platform extends AbstractPlatform | ||
33 | { | ||
34 | /** | ||
35 | * {@inheritDoc} | ||
36 | */ | ||
37 | public function getBlobTypeDeclarationSQL(array $column): string | ||
38 | { | ||
39 | // todo blob(n) with $column['length']; | ||
40 | return 'BLOB(1M)'; | ||
41 | } | ||
42 | |||
43 | protected function initializeDoctrineTypeMappings(): void | ||
44 | { | ||
45 | $this->doctrineTypeMapping = [ | ||
46 | 'bigint' => Types::BIGINT, | ||
47 | 'binary' => Types::BINARY, | ||
48 | 'blob' => Types::BLOB, | ||
49 | 'character' => Types::STRING, | ||
50 | 'clob' => Types::TEXT, | ||
51 | 'date' => Types::DATE_MUTABLE, | ||
52 | 'decimal' => Types::DECIMAL, | ||
53 | 'double' => Types::FLOAT, | ||
54 | 'integer' => Types::INTEGER, | ||
55 | 'real' => Types::FLOAT, | ||
56 | 'smallint' => Types::SMALLINT, | ||
57 | 'time' => Types::TIME_MUTABLE, | ||
58 | 'timestamp' => Types::DATETIME_MUTABLE, | ||
59 | 'varbinary' => Types::BINARY, | ||
60 | 'varchar' => Types::STRING, | ||
61 | ]; | ||
62 | } | ||
63 | |||
64 | protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string | ||
65 | { | ||
66 | return $this->getCharTypeDeclarationSQLSnippet($length) . ' FOR BIT DATA'; | ||
67 | } | ||
68 | |||
69 | protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string | ||
70 | { | ||
71 | return $this->getVarcharTypeDeclarationSQLSnippet($length) . ' FOR BIT DATA'; | ||
72 | } | ||
73 | |||
74 | /** | ||
75 | * {@inheritDoc} | ||
76 | */ | ||
77 | public function getClobTypeDeclarationSQL(array $column): string | ||
78 | { | ||
79 | // todo clob(n) with $column['length']; | ||
80 | return 'CLOB(1M)'; | ||
81 | } | ||
82 | |||
83 | /** | ||
84 | * {@inheritDoc} | ||
85 | */ | ||
86 | public function getBooleanTypeDeclarationSQL(array $column): string | ||
87 | { | ||
88 | return 'SMALLINT'; | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * {@inheritDoc} | ||
93 | */ | ||
94 | public function getIntegerTypeDeclarationSQL(array $column): string | ||
95 | { | ||
96 | return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
97 | } | ||
98 | |||
99 | /** | ||
100 | * {@inheritDoc} | ||
101 | */ | ||
102 | public function getBigIntTypeDeclarationSQL(array $column): string | ||
103 | { | ||
104 | return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
105 | } | ||
106 | |||
107 | /** | ||
108 | * {@inheritDoc} | ||
109 | */ | ||
110 | public function getSmallIntTypeDeclarationSQL(array $column): string | ||
111 | { | ||
112 | return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
113 | } | ||
114 | |||
115 | /** | ||
116 | * {@inheritDoc} | ||
117 | */ | ||
118 | protected function _getCommonIntegerTypeDeclarationSQL(array $column): string | ||
119 | { | ||
120 | $autoinc = ''; | ||
121 | if (! empty($column['autoincrement'])) { | ||
122 | $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; | ||
123 | } | ||
124 | |||
125 | return $autoinc; | ||
126 | } | ||
127 | |||
128 | public function getBitAndComparisonExpression(string $value1, string $value2): string | ||
129 | { | ||
130 | return 'BITAND(' . $value1 . ', ' . $value2 . ')'; | ||
131 | } | ||
132 | |||
133 | public function getBitOrComparisonExpression(string $value1, string $value2): string | ||
134 | { | ||
135 | return 'BITOR(' . $value1 . ', ' . $value2 . ')'; | ||
136 | } | ||
137 | |||
138 | protected function getDateArithmeticIntervalExpression( | ||
139 | string $date, | ||
140 | string $operator, | ||
141 | string $interval, | ||
142 | DateIntervalUnit $unit, | ||
143 | ): string { | ||
144 | switch ($unit) { | ||
145 | case DateIntervalUnit::WEEK: | ||
146 | $interval = $this->multiplyInterval($interval, 7); | ||
147 | $unit = DateIntervalUnit::DAY; | ||
148 | break; | ||
149 | |||
150 | case DateIntervalUnit::QUARTER: | ||
151 | $interval = $this->multiplyInterval($interval, 3); | ||
152 | $unit = DateIntervalUnit::MONTH; | ||
153 | break; | ||
154 | } | ||
155 | |||
156 | return $date . ' ' . $operator . ' ' . $interval . ' ' . $unit->value; | ||
157 | } | ||
158 | |||
159 | public function getDateDiffExpression(string $date1, string $date2): string | ||
160 | { | ||
161 | return 'DAYS(' . $date1 . ') - DAYS(' . $date2 . ')'; | ||
162 | } | ||
163 | |||
164 | /** | ||
165 | * {@inheritDoc} | ||
166 | */ | ||
167 | public function getDateTimeTypeDeclarationSQL(array $column): string | ||
168 | { | ||
169 | if (isset($column['version']) && $column['version'] === true) { | ||
170 | return 'TIMESTAMP(0) WITH DEFAULT'; | ||
171 | } | ||
172 | |||
173 | return 'TIMESTAMP(0)'; | ||
174 | } | ||
175 | |||
176 | /** | ||
177 | * {@inheritDoc} | ||
178 | */ | ||
179 | public function getDateTypeDeclarationSQL(array $column): string | ||
180 | { | ||
181 | return 'DATE'; | ||
182 | } | ||
183 | |||
184 | /** | ||
185 | * {@inheritDoc} | ||
186 | */ | ||
187 | public function getTimeTypeDeclarationSQL(array $column): string | ||
188 | { | ||
189 | return 'TIME'; | ||
190 | } | ||
191 | |||
192 | public function getTruncateTableSQL(string $tableName, bool $cascade = false): string | ||
193 | { | ||
194 | $tableIdentifier = new Identifier($tableName); | ||
195 | |||
196 | return 'TRUNCATE ' . $tableIdentifier->getQuotedName($this) . ' IMMEDIATE'; | ||
197 | } | ||
198 | |||
199 | public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string | ||
200 | { | ||
201 | throw NotSupported::new(__METHOD__); | ||
202 | } | ||
203 | |||
204 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
205 | public function getListViewsSQL(string $database): string | ||
206 | { | ||
207 | return 'SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS'; | ||
208 | } | ||
209 | |||
210 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
211 | public function supportsCommentOnStatement(): bool | ||
212 | { | ||
213 | return true; | ||
214 | } | ||
215 | |||
216 | public function getCurrentDateSQL(): string | ||
217 | { | ||
218 | return 'CURRENT DATE'; | ||
219 | } | ||
220 | |||
221 | public function getCurrentTimeSQL(): string | ||
222 | { | ||
223 | return 'CURRENT TIME'; | ||
224 | } | ||
225 | |||
226 | public function getCurrentTimestampSQL(): string | ||
227 | { | ||
228 | return 'CURRENT TIMESTAMP'; | ||
229 | } | ||
230 | |||
231 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
232 | public function getIndexDeclarationSQL(Index $index): string | ||
233 | { | ||
234 | // Index declaration in statements like CREATE TABLE is not supported. | ||
235 | throw NotSupported::new(__METHOD__); | ||
236 | } | ||
237 | |||
238 | /** | ||
239 | * {@inheritDoc} | ||
240 | */ | ||
241 | protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array | ||
242 | { | ||
243 | $indexes = []; | ||
244 | if (isset($options['indexes'])) { | ||
245 | $indexes = $options['indexes']; | ||
246 | } | ||
247 | |||
248 | $options['indexes'] = []; | ||
249 | |||
250 | $sqls = parent::_getCreateTableSQL($name, $columns, $options); | ||
251 | |||
252 | foreach ($indexes as $definition) { | ||
253 | $sqls[] = $this->getCreateIndexSQL($definition, $name); | ||
254 | } | ||
255 | |||
256 | return $sqls; | ||
257 | } | ||
258 | |||
259 | /** | ||
260 | * {@inheritDoc} | ||
261 | */ | ||
262 | public function getAlterTableSQL(TableDiff $diff): array | ||
263 | { | ||
264 | $sql = []; | ||
265 | $columnSql = []; | ||
266 | $commentsSQL = []; | ||
267 | |||
268 | $tableNameSQL = $diff->getOldTable()->getQuotedName($this); | ||
269 | |||
270 | $queryParts = []; | ||
271 | foreach ($diff->getAddedColumns() as $column) { | ||
272 | $columnDef = $column->toArray(); | ||
273 | $queryPart = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); | ||
274 | |||
275 | // Adding non-nullable columns to a table requires a default value to be specified. | ||
276 | if ( | ||
277 | ! empty($columnDef['notnull']) && | ||
278 | ! isset($columnDef['default']) && | ||
279 | empty($columnDef['autoincrement']) | ||
280 | ) { | ||
281 | $queryPart .= ' WITH DEFAULT'; | ||
282 | } | ||
283 | |||
284 | $queryParts[] = $queryPart; | ||
285 | |||
286 | $comment = $column->getComment(); | ||
287 | |||
288 | if ($comment === '') { | ||
289 | continue; | ||
290 | } | ||
291 | |||
292 | $commentsSQL[] = $this->getCommentOnColumnSQL( | ||
293 | $tableNameSQL, | ||
294 | $column->getQuotedName($this), | ||
295 | $comment, | ||
296 | ); | ||
297 | } | ||
298 | |||
299 | foreach ($diff->getDroppedColumns() as $column) { | ||
300 | $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); | ||
301 | } | ||
302 | |||
303 | foreach ($diff->getModifiedColumns() as $columnDiff) { | ||
304 | if ($columnDiff->hasCommentChanged()) { | ||
305 | $newColumn = $columnDiff->getNewColumn(); | ||
306 | $commentsSQL[] = $this->getCommentOnColumnSQL( | ||
307 | $tableNameSQL, | ||
308 | $newColumn->getQuotedName($this), | ||
309 | $newColumn->getComment(), | ||
310 | ); | ||
311 | } | ||
312 | |||
313 | $this->gatherAlterColumnSQL( | ||
314 | $tableNameSQL, | ||
315 | $columnDiff, | ||
316 | $sql, | ||
317 | $queryParts, | ||
318 | ); | ||
319 | } | ||
320 | |||
321 | foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { | ||
322 | $oldColumnName = new Identifier($oldColumnName); | ||
323 | |||
324 | $queryParts[] = 'RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . | ||
325 | ' TO ' . $column->getQuotedName($this); | ||
326 | } | ||
327 | |||
328 | if (count($queryParts) > 0) { | ||
329 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . implode(' ', $queryParts); | ||
330 | } | ||
331 | |||
332 | // Some table alteration operations require a table reorganization. | ||
333 | if (count($diff->getDroppedColumns()) > 0 || count($diff->getModifiedColumns()) > 0) { | ||
334 | $sql[] = "CALL SYSPROC.ADMIN_CMD ('REORG TABLE " . $tableNameSQL . "')"; | ||
335 | } | ||
336 | |||
337 | $sql = array_merge( | ||
338 | $this->getPreAlterTableIndexForeignKeySQL($diff), | ||
339 | $sql, | ||
340 | $commentsSQL, | ||
341 | $this->getPostAlterTableIndexForeignKeySQL($diff), | ||
342 | ); | ||
343 | |||
344 | return array_merge($sql, $columnSql); | ||
345 | } | ||
346 | |||
347 | public function getRenameTableSQL(string $oldName, string $newName): string | ||
348 | { | ||
349 | return sprintf('RENAME TABLE %s TO %s', $oldName, $newName); | ||
350 | } | ||
351 | |||
352 | /** | ||
353 | * Gathers the table alteration SQL for a given column diff. | ||
354 | * | ||
355 | * @param string $table The table to gather the SQL for. | ||
356 | * @param ColumnDiff $columnDiff The column diff to evaluate. | ||
357 | * @param list<string> $sql The sequence of table alteration statements to fill. | ||
358 | * @param list<string> $queryParts The sequence of column alteration clauses to fill. | ||
359 | */ | ||
360 | private function gatherAlterColumnSQL( | ||
361 | string $table, | ||
362 | ColumnDiff $columnDiff, | ||
363 | array &$sql, | ||
364 | array &$queryParts, | ||
365 | ): void { | ||
366 | $alterColumnClauses = $this->getAlterColumnClausesSQL($columnDiff); | ||
367 | |||
368 | if (empty($alterColumnClauses)) { | ||
369 | return; | ||
370 | } | ||
371 | |||
372 | // If we have a single column alteration, we can append the clause to the main query. | ||
373 | if (count($alterColumnClauses) === 1) { | ||
374 | $queryParts[] = current($alterColumnClauses); | ||
375 | |||
376 | return; | ||
377 | } | ||
378 | |||
379 | // We have multiple alterations for the same column, | ||
380 | // so we need to trigger a complete ALTER TABLE statement | ||
381 | // for each ALTER COLUMN clause. | ||
382 | foreach ($alterColumnClauses as $alterColumnClause) { | ||
383 | $sql[] = 'ALTER TABLE ' . $table . ' ' . $alterColumnClause; | ||
384 | } | ||
385 | } | ||
386 | |||
387 | /** | ||
388 | * Returns the ALTER COLUMN SQL clauses for altering a column described by the given column diff. | ||
389 | * | ||
390 | * @return string[] | ||
391 | */ | ||
392 | private function getAlterColumnClausesSQL(ColumnDiff $columnDiff): array | ||
393 | { | ||
394 | $newColumn = $columnDiff->getNewColumn()->toArray(); | ||
395 | |||
396 | $alterClause = 'ALTER COLUMN ' . $columnDiff->getNewColumn()->getQuotedName($this); | ||
397 | |||
398 | if ($newColumn['columnDefinition'] !== null) { | ||
399 | return [$alterClause . ' ' . $newColumn['columnDefinition']]; | ||
400 | } | ||
401 | |||
402 | $clauses = []; | ||
403 | |||
404 | if ( | ||
405 | $columnDiff->hasTypeChanged() || | ||
406 | $columnDiff->hasLengthChanged() || | ||
407 | $columnDiff->hasPrecisionChanged() || | ||
408 | $columnDiff->hasScaleChanged() || | ||
409 | $columnDiff->hasFixedChanged() | ||
410 | ) { | ||
411 | $clauses[] = $alterClause . ' SET DATA TYPE ' . $newColumn['type']->getSQLDeclaration($newColumn, $this); | ||
412 | } | ||
413 | |||
414 | if ($columnDiff->hasNotNullChanged()) { | ||
415 | $clauses[] = $newColumn['notnull'] ? $alterClause . ' SET NOT NULL' : $alterClause . ' DROP NOT NULL'; | ||
416 | } | ||
417 | |||
418 | if ($columnDiff->hasDefaultChanged()) { | ||
419 | if (isset($newColumn['default'])) { | ||
420 | $defaultClause = $this->getDefaultValueDeclarationSQL($newColumn); | ||
421 | |||
422 | if ($defaultClause !== '') { | ||
423 | $clauses[] = $alterClause . ' SET' . $defaultClause; | ||
424 | } | ||
425 | } else { | ||
426 | $clauses[] = $alterClause . ' DROP DEFAULT'; | ||
427 | } | ||
428 | } | ||
429 | |||
430 | return $clauses; | ||
431 | } | ||
432 | |||
433 | /** | ||
434 | * {@inheritDoc} | ||
435 | */ | ||
436 | protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array | ||
437 | { | ||
438 | $sql = []; | ||
439 | |||
440 | $tableNameSQL = $diff->getOldTable()->getQuotedName($this); | ||
441 | |||
442 | foreach ($diff->getDroppedIndexes() as $droppedIndex) { | ||
443 | foreach ($diff->getAddedIndexes() as $addedIndex) { | ||
444 | if ($droppedIndex->getColumns() !== $addedIndex->getColumns()) { | ||
445 | continue; | ||
446 | } | ||
447 | |||
448 | if ($droppedIndex->isPrimary()) { | ||
449 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP PRIMARY KEY'; | ||
450 | } elseif ($droppedIndex->isUnique()) { | ||
451 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP UNIQUE ' . $droppedIndex->getQuotedName($this); | ||
452 | } else { | ||
453 | $sql[] = $this->getDropIndexSQL($droppedIndex->getQuotedName($this), $tableNameSQL); | ||
454 | } | ||
455 | |||
456 | $sql[] = $this->getCreateIndexSQL($addedIndex, $tableNameSQL); | ||
457 | |||
458 | $diff->unsetAddedIndex($addedIndex); | ||
459 | $diff->unsetDroppedIndex($droppedIndex); | ||
460 | |||
461 | break; | ||
462 | } | ||
463 | } | ||
464 | |||
465 | return array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff)); | ||
466 | } | ||
467 | |||
468 | /** | ||
469 | * {@inheritDoc} | ||
470 | */ | ||
471 | protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array | ||
472 | { | ||
473 | if (str_contains($tableName, '.')) { | ||
474 | [$schema] = explode('.', $tableName); | ||
475 | $oldIndexName = $schema . '.' . $oldIndexName; | ||
476 | } | ||
477 | |||
478 | return ['RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; | ||
479 | } | ||
480 | |||
481 | /** | ||
482 | * {@inheritDoc} | ||
483 | * | ||
484 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
485 | */ | ||
486 | public function getDefaultValueDeclarationSQL(array $column): string | ||
487 | { | ||
488 | if (! empty($column['autoincrement'])) { | ||
489 | return ''; | ||
490 | } | ||
491 | |||
492 | if (! empty($column['version'])) { | ||
493 | if ((string) $column['type'] !== 'DateTime') { | ||
494 | $column['default'] = '1'; | ||
495 | } | ||
496 | } | ||
497 | |||
498 | return parent::getDefaultValueDeclarationSQL($column); | ||
499 | } | ||
500 | |||
501 | public function getEmptyIdentityInsertSQL(string $quotedTableName, string $quotedIdentifierColumnName): string | ||
502 | { | ||
503 | return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; | ||
504 | } | ||
505 | |||
506 | public function getCreateTemporaryTableSnippetSQL(): string | ||
507 | { | ||
508 | return 'DECLARE GLOBAL TEMPORARY TABLE'; | ||
509 | } | ||
510 | |||
511 | public function getTemporaryTableName(string $tableName): string | ||
512 | { | ||
513 | return 'SESSION.' . $tableName; | ||
514 | } | ||
515 | |||
516 | protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string | ||
517 | { | ||
518 | if ($offset > 0) { | ||
519 | $query .= sprintf(' OFFSET %d ROWS', $offset); | ||
520 | } | ||
521 | |||
522 | if ($limit !== null) { | ||
523 | $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit); | ||
524 | } | ||
525 | |||
526 | return $query; | ||
527 | } | ||
528 | |||
529 | public function getLocateExpression(string $string, string $substring, ?string $start = null): string | ||
530 | { | ||
531 | if ($start === null) { | ||
532 | return sprintf('LOCATE(%s, %s)', $substring, $string); | ||
533 | } | ||
534 | |||
535 | return sprintf('LOCATE(%s, %s, %s)', $substring, $string, $start); | ||
536 | } | ||
537 | |||
538 | public function getSubstringExpression(string $string, string $start, ?string $length = null): string | ||
539 | { | ||
540 | if ($length === null) { | ||
541 | return sprintf('SUBSTR(%s, %s)', $string, $start); | ||
542 | } | ||
543 | |||
544 | return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length); | ||
545 | } | ||
546 | |||
547 | public function getLengthExpression(string $string): string | ||
548 | { | ||
549 | return 'LENGTH(' . $string . ', CODEUNITS32)'; | ||
550 | } | ||
551 | |||
552 | public function getCurrentDatabaseExpression(): string | ||
553 | { | ||
554 | return 'CURRENT_USER'; | ||
555 | } | ||
556 | |||
557 | public function supportsIdentityColumns(): bool | ||
558 | { | ||
559 | return true; | ||
560 | } | ||
561 | |||
562 | public function createSelectSQLBuilder(): SelectSQLBuilder | ||
563 | { | ||
564 | return new DefaultSelectSQLBuilder($this, 'WITH RR USE AND KEEP UPDATE LOCKS', null); | ||
565 | } | ||
566 | |||
567 | public function getDummySelectSQL(string $expression = '1'): string | ||
568 | { | ||
569 | return sprintf('SELECT %s FROM sysibm.sysdummy1', $expression); | ||
570 | } | ||
571 | |||
572 | /** | ||
573 | * {@inheritDoc} | ||
574 | * | ||
575 | * DB2 supports savepoints, but they work semantically different than on other vendor platforms. | ||
576 | * | ||
577 | * TODO: We have to investigate how to get DB2 up and running with savepoints. | ||
578 | */ | ||
579 | public function supportsSavepoints(): bool | ||
580 | { | ||
581 | return false; | ||
582 | } | ||
583 | |||
584 | protected function createReservedKeywordsList(): KeywordList | ||
585 | { | ||
586 | return new DB2Keywords(); | ||
587 | } | ||
588 | |||
589 | public function createSchemaManager(Connection $connection): DB2SchemaManager | ||
590 | { | ||
591 | return new DB2SchemaManager($connection, $this); | ||
592 | } | ||
593 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php b/vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php new file mode 100644 index 0000000..ba783f3 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php | |||
@@ -0,0 +1,17 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | enum DateIntervalUnit: string | ||
8 | { | ||
9 | case SECOND = 'SECOND'; | ||
10 | case MINUTE = 'MINUTE'; | ||
11 | case HOUR = 'HOUR'; | ||
12 | case DAY = 'DAY'; | ||
13 | case WEEK = 'WEEK'; | ||
14 | case MONTH = 'MONTH'; | ||
15 | case QUARTER = 'QUARTER'; | ||
16 | case YEAR = 'YEAR'; | ||
17 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Exception/InvalidPlatformVersion.php b/vendor/doctrine/dbal/src/Platforms/Exception/InvalidPlatformVersion.php new file mode 100644 index 0000000..dd70190 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Exception/InvalidPlatformVersion.php | |||
@@ -0,0 +1,28 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Exception; | ||
6 | |||
7 | use Exception; | ||
8 | |||
9 | use function sprintf; | ||
10 | |||
11 | /** @psalm-immutable */ | ||
12 | final class InvalidPlatformVersion extends Exception implements PlatformException | ||
13 | { | ||
14 | /** | ||
15 | * Returns a new instance for an invalid specified platform version. | ||
16 | * | ||
17 | * @param string $version The invalid platform version given. | ||
18 | * @param string $expectedFormat The expected platform version format. | ||
19 | */ | ||
20 | public static function new(string $version, string $expectedFormat): self | ||
21 | { | ||
22 | return new self(sprintf( | ||
23 | 'Invalid platform version "%s" specified. The platform version has to be specified in the format: "%s".', | ||
24 | $version, | ||
25 | $expectedFormat, | ||
26 | )); | ||
27 | } | ||
28 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Exception/NoColumnsSpecifiedForTable.php b/vendor/doctrine/dbal/src/Platforms/Exception/NoColumnsSpecifiedForTable.php new file mode 100644 index 0000000..0690eaa --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Exception/NoColumnsSpecifiedForTable.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Exception; | ||
6 | |||
7 | use LogicException; | ||
8 | |||
9 | use function sprintf; | ||
10 | |||
11 | /** @psalm-immutable */ | ||
12 | final class NoColumnsSpecifiedForTable extends LogicException implements PlatformException | ||
13 | { | ||
14 | public static function new(string $tableName): self | ||
15 | { | ||
16 | return new self(sprintf('No columns specified for table "%s".', $tableName)); | ||
17 | } | ||
18 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Exception/NotSupported.php b/vendor/doctrine/dbal/src/Platforms/Exception/NotSupported.php new file mode 100644 index 0000000..4ab6fc9 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Exception/NotSupported.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Exception; | ||
6 | |||
7 | use LogicException; | ||
8 | |||
9 | use function sprintf; | ||
10 | |||
11 | /** @psalm-immutable */ | ||
12 | final class NotSupported extends LogicException implements PlatformException | ||
13 | { | ||
14 | public static function new(string $method): self | ||
15 | { | ||
16 | return new self(sprintf('Operation "%s" is not supported by platform.', $method)); | ||
17 | } | ||
18 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Exception/PlatformException.php b/vendor/doctrine/dbal/src/Platforms/Exception/PlatformException.php new file mode 100644 index 0000000..be546f5 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Exception/PlatformException.php | |||
@@ -0,0 +1,11 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Exception; | ||
6 | |||
7 | use Doctrine\DBAL\Exception; | ||
8 | |||
9 | interface PlatformException extends Exception | ||
10 | { | ||
11 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/DB2Keywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/DB2Keywords.php new file mode 100644 index 0000000..5ab6a6c --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/DB2Keywords.php | |||
@@ -0,0 +1,414 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Keywords; | ||
6 | |||
7 | /** | ||
8 | * DB2 Keywords. | ||
9 | */ | ||
10 | class DB2Keywords extends KeywordList | ||
11 | { | ||
12 | /** | ||
13 | * {@inheritDoc} | ||
14 | */ | ||
15 | protected function getKeywords(): array | ||
16 | { | ||
17 | return [ | ||
18 | 'ACTIVATE', | ||
19 | 'ADD', | ||
20 | 'AFTER', | ||
21 | 'ALIAS', | ||
22 | 'ALL', | ||
23 | 'ALLOCATE', | ||
24 | 'ALLOW', | ||
25 | 'ALTER', | ||
26 | 'AND', | ||
27 | 'ANY', | ||
28 | 'AS', | ||
29 | 'ASENSITIVE', | ||
30 | 'ASSOCIATE', | ||
31 | 'ASUTIME', | ||
32 | 'AT', | ||
33 | 'ATTRIBUTES', | ||
34 | 'AUDIT', | ||
35 | 'AUTHORIZATION', | ||
36 | 'AUX', | ||
37 | 'AUXILIARY', | ||
38 | 'BEFORE', | ||
39 | 'BEGIN', | ||
40 | 'BETWEEN', | ||
41 | 'BINARY', | ||
42 | 'BUFFERPOOL', | ||
43 | 'BY', | ||
44 | 'CACHE', | ||
45 | 'CALL', | ||
46 | 'CALLED', | ||
47 | 'CAPTURE', | ||
48 | 'CARDINALITY', | ||
49 | 'CASCADED', | ||
50 | 'CASE', | ||
51 | 'CAST', | ||
52 | 'CCSID', | ||
53 | 'CHAR', | ||
54 | 'CHARACTER', | ||
55 | 'CHECK', | ||
56 | 'CLONE', | ||
57 | 'CLOSE', | ||
58 | 'CLUSTER', | ||
59 | 'COLLECTION', | ||
60 | 'COLLID', | ||
61 | 'COLUMN', | ||
62 | 'COMMENT', | ||
63 | 'COMMIT', | ||
64 | 'CONCAT', | ||
65 | 'CONDITION', | ||
66 | 'CONNECT', | ||
67 | 'CONNECTION', | ||
68 | 'CONSTRAINT', | ||
69 | 'CONTAINS', | ||
70 | 'CONTINUE', | ||
71 | 'COUNT', | ||
72 | 'COUNT_BIG', | ||
73 | 'CREATE', | ||
74 | 'CROSS', | ||
75 | 'CURRENT', | ||
76 | 'CURRENT_DATE', | ||
77 | 'CURRENT_LC_CTYPE', | ||
78 | 'CURRENT_PATH', | ||
79 | 'CURRENT_SCHEMA', | ||
80 | 'CURRENT_SERVER', | ||
81 | 'CURRENT_TIME', | ||
82 | 'CURRENT_TIMESTAMP', | ||
83 | 'CURRENT_TIMEZONE', | ||
84 | 'CURRENT_USER', | ||
85 | 'CURSOR', | ||
86 | 'CYCLE', | ||
87 | 'DATA', | ||
88 | 'DATABASE', | ||
89 | 'DATAPARTITIONNAME', | ||
90 | 'DATAPARTITIONNUM', | ||
91 | 'DATE', | ||
92 | 'DAY', | ||
93 | 'DAYS', | ||
94 | 'DB2GENERAL', | ||
95 | 'DB2GENRL', | ||
96 | 'DB2SQL', | ||
97 | 'DBINFO', | ||
98 | 'DBPARTITIONNAME', | ||
99 | 'DBPARTITIONNUM', | ||
100 | 'DEALLOCATE', | ||
101 | 'DECLARE', | ||
102 | 'DEFAULT', | ||
103 | 'DEFAULTS', | ||
104 | 'DEFINITION', | ||
105 | 'DELETE', | ||
106 | 'DENSE_RANK', | ||
107 | 'DENSERANK', | ||
108 | 'DESCRIBE', | ||
109 | 'DESCRIPTOR', | ||
110 | 'DETERMINISTIC', | ||
111 | 'DIAGNOSTICS', | ||
112 | 'DISABLE', | ||
113 | 'DISALLOW', | ||
114 | 'DISCONNECT', | ||
115 | 'DISTINCT', | ||
116 | 'DO', | ||
117 | 'DOCUMENT', | ||
118 | 'DOUBLE', | ||
119 | 'DROP', | ||
120 | 'DSSIZE', | ||
121 | 'DYNAMIC', | ||
122 | 'EACH', | ||
123 | 'EDITPROC', | ||
124 | 'ELSE', | ||
125 | 'ELSEIF', | ||
126 | 'ENABLE', | ||
127 | 'ENCODING', | ||
128 | 'ENCRYPTION', | ||
129 | 'END', | ||
130 | 'END-EXEC', | ||
131 | 'ENDING', | ||
132 | 'ERASE', | ||
133 | 'ESCAPE', | ||
134 | 'EVERY', | ||
135 | 'EXCEPT', | ||
136 | 'EXCEPTION', | ||
137 | 'EXCLUDING', | ||
138 | 'EXCLUSIVE', | ||
139 | 'EXECUTE', | ||
140 | 'EXISTS', | ||
141 | 'EXIT', | ||
142 | 'EXPLAIN', | ||
143 | 'EXTERNAL', | ||
144 | 'EXTRACT', | ||
145 | 'FENCED', | ||
146 | 'FETCH', | ||
147 | 'FIELDPROC', | ||
148 | 'FILE', | ||
149 | 'FINAL', | ||
150 | 'FOR', | ||
151 | 'FOREIGN', | ||
152 | 'FREE', | ||
153 | 'FROM', | ||
154 | 'FULL', | ||
155 | 'FUNCTION', | ||
156 | 'GENERAL', | ||
157 | 'GENERATED', | ||
158 | 'GET', | ||
159 | 'GLOBAL', | ||
160 | 'GO', | ||
161 | 'GOTO', | ||
162 | 'GRANT', | ||
163 | 'GRAPHIC', | ||
164 | 'GROUP', | ||
165 | 'HANDLER', | ||
166 | 'HASH', | ||
167 | 'HASHED_VALUE', | ||
168 | 'HAVING', | ||
169 | 'HINT', | ||
170 | 'HOLD', | ||
171 | 'HOUR', | ||
172 | 'HOURS', | ||
173 | 'IDENTITY', | ||
174 | 'IF', | ||
175 | 'IMMEDIATE', | ||
176 | 'IN', | ||
177 | 'INCLUDING', | ||
178 | 'INCLUSIVE', | ||
179 | 'INCREMENT', | ||
180 | 'INDEX', | ||
181 | 'INDICATOR', | ||
182 | 'INF', | ||
183 | 'INFINITY', | ||
184 | 'INHERIT', | ||
185 | 'INNER', | ||
186 | 'INOUT', | ||
187 | 'INSENSITIVE', | ||
188 | 'INSERT', | ||
189 | 'INTEGRITY', | ||
190 | 'INTERSECT', | ||
191 | 'INTO', | ||
192 | 'IS', | ||
193 | 'ISOBID', | ||
194 | 'ISOLATION', | ||
195 | 'ITERATE', | ||
196 | 'JAR', | ||
197 | 'JAVA', | ||
198 | 'JOIN', | ||
199 | 'KEEP', | ||
200 | 'KEY', | ||
201 | 'LABEL', | ||
202 | 'LANGUAGE', | ||
203 | 'LATERAL', | ||
204 | 'LC_CTYPE', | ||
205 | 'LEAVE', | ||
206 | 'LEFT', | ||
207 | 'LIKE', | ||
208 | 'LINKTYPE', | ||
209 | 'LOCAL', | ||
210 | 'LOCALDATE', | ||
211 | 'LOCALE', | ||
212 | 'LOCALTIME', | ||
213 | 'LOCALTIMESTAMP RIGHT', | ||
214 | 'LOCATOR', | ||
215 | 'LOCATORS', | ||
216 | 'LOCK', | ||
217 | 'LOCKMAX', | ||
218 | 'LOCKSIZE', | ||
219 | 'LONG', | ||
220 | 'LOOP', | ||
221 | 'MAINTAINED', | ||
222 | 'MATERIALIZED', | ||
223 | 'MAXVALUE', | ||
224 | 'MICROSECOND', | ||
225 | 'MICROSECONDS', | ||
226 | 'MINUTE', | ||
227 | 'MINUTES', | ||
228 | 'MINVALUE', | ||
229 | 'MODE', | ||
230 | 'MODIFIES', | ||
231 | 'MONTH', | ||
232 | 'MONTHS', | ||
233 | 'NAN', | ||
234 | 'NEW', | ||
235 | 'NEW_TABLE', | ||
236 | 'NEXTVAL', | ||
237 | 'NO', | ||
238 | 'NOCACHE', | ||
239 | 'NOCYCLE', | ||
240 | 'NODENAME', | ||
241 | 'NODENUMBER', | ||
242 | 'NOMAXVALUE', | ||
243 | 'NOMINVALUE', | ||
244 | 'NONE', | ||
245 | 'NOORDER', | ||
246 | 'NORMALIZED', | ||
247 | 'NOT', | ||
248 | 'NULL', | ||
249 | 'NULLS', | ||
250 | 'NUMPARTS', | ||
251 | 'OBID', | ||
252 | 'OF', | ||
253 | 'OLD', | ||
254 | 'OLD_TABLE', | ||
255 | 'ON', | ||
256 | 'OPEN', | ||
257 | 'OPTIMIZATION', | ||
258 | 'OPTIMIZE', | ||
259 | 'OPTION', | ||
260 | 'OR', | ||
261 | 'ORDER', | ||
262 | 'OUT', | ||
263 | 'OUTER', | ||
264 | 'OVER', | ||
265 | 'OVERRIDING', | ||
266 | 'PACKAGE', | ||
267 | 'PADDED', | ||
268 | 'PAGESIZE', | ||
269 | 'PARAMETER', | ||
270 | 'PART', | ||
271 | 'PARTITION', | ||
272 | 'PARTITIONED', | ||
273 | 'PARTITIONING', | ||
274 | 'PARTITIONS', | ||
275 | 'PASSWORD', | ||
276 | 'PATH', | ||
277 | 'PIECESIZE', | ||
278 | 'PLAN', | ||
279 | 'POSITION', | ||
280 | 'PRECISION', | ||
281 | 'PREPARE', | ||
282 | 'PREVVAL', | ||
283 | 'PRIMARY', | ||
284 | 'PRIQTY', | ||
285 | 'PRIVILEGES', | ||
286 | 'PROCEDURE', | ||
287 | 'PROGRAM', | ||
288 | 'PSID', | ||
289 | 'PUBLIC', | ||
290 | 'QUERY', | ||
291 | 'QUERYNO', | ||
292 | 'RANGE', | ||
293 | 'RANK', | ||
294 | 'READ', | ||
295 | 'READS', | ||
296 | 'RECOVERY', | ||
297 | 'REFERENCES', | ||
298 | 'REFERENCING', | ||
299 | 'REFRESH', | ||
300 | 'RELEASE', | ||
301 | 'RENAME', | ||
302 | 'REPEAT', | ||
303 | 'RESET', | ||
304 | 'RESIGNAL', | ||
305 | 'RESTART', | ||
306 | 'RESTRICT', | ||
307 | 'RESULT', | ||
308 | 'RESULT_SET_LOCATOR WLM', | ||
309 | 'RETURN', | ||
310 | 'RETURNS', | ||
311 | 'REVOKE', | ||
312 | 'ROLE', | ||
313 | 'ROLLBACK', | ||
314 | 'ROUND_CEILING', | ||
315 | 'ROUND_DOWN', | ||
316 | 'ROUND_FLOOR', | ||
317 | 'ROUND_HALF_DOWN', | ||
318 | 'ROUND_HALF_EVEN', | ||
319 | 'ROUND_HALF_UP', | ||
320 | 'ROUND_UP', | ||
321 | 'ROUTINE', | ||
322 | 'ROW', | ||
323 | 'ROW_NUMBER', | ||
324 | 'ROWNUMBER', | ||
325 | 'ROWS', | ||
326 | 'ROWSET', | ||
327 | 'RRN', | ||
328 | 'RUN', | ||
329 | 'SAVEPOINT', | ||
330 | 'SCHEMA', | ||
331 | 'SCRATCHPAD', | ||
332 | 'SCROLL', | ||
333 | 'SEARCH', | ||
334 | 'SECOND', | ||
335 | 'SECONDS', | ||
336 | 'SECQTY', | ||
337 | 'SECURITY', | ||
338 | 'SELECT', | ||
339 | 'SENSITIVE', | ||
340 | 'SEQUENCE', | ||
341 | 'SESSION', | ||
342 | 'SESSION_USER', | ||
343 | 'SET', | ||
344 | 'SIGNAL', | ||
345 | 'SIMPLE', | ||
346 | 'SNAN', | ||
347 | 'SOME', | ||
348 | 'SOURCE', | ||
349 | 'SPECIFIC', | ||
350 | 'SQL', | ||
351 | 'SQLID', | ||
352 | 'STACKED', | ||
353 | 'STANDARD', | ||
354 | 'START', | ||
355 | 'STARTING', | ||
356 | 'STATEMENT', | ||
357 | 'STATIC', | ||
358 | 'STATMENT', | ||
359 | 'STAY', | ||
360 | 'STOGROUP', | ||
361 | 'STORES', | ||
362 | 'STYLE', | ||
363 | 'SUBSTRING', | ||
364 | 'SUMMARY', | ||
365 | 'SYNONYM', | ||
366 | 'SYSFUN', | ||
367 | 'SYSIBM', | ||
368 | 'SYSPROC', | ||
369 | 'SYSTEM', | ||
370 | 'SYSTEM_USER', | ||
371 | 'TABLE', | ||
372 | 'TABLESPACE', | ||
373 | 'THEN', | ||
374 | 'TIME', | ||
375 | 'TIMESTAMP', | ||
376 | 'TO', | ||
377 | 'TRANSACTION', | ||
378 | 'TRIGGER', | ||
379 | 'TRIM', | ||
380 | 'TRUNCATE', | ||
381 | 'TYPE', | ||
382 | 'UNDO', | ||
383 | 'UNION', | ||
384 | 'UNIQUE', | ||
385 | 'UNTIL', | ||
386 | 'UPDATE', | ||
387 | 'USAGE', | ||
388 | 'USER', | ||
389 | 'USING', | ||
390 | 'VALIDPROC', | ||
391 | 'VALUE', | ||
392 | 'VALUES', | ||
393 | 'VARIABLE', | ||
394 | 'VARIANT', | ||
395 | 'VCAT', | ||
396 | 'VERSION', | ||
397 | 'VIEW', | ||
398 | 'VOLATILE', | ||
399 | 'VOLUMES', | ||
400 | 'WHEN', | ||
401 | 'WHENEVER', | ||
402 | 'WHERE', | ||
403 | 'WHILE', | ||
404 | 'WITH', | ||
405 | 'WITHOUT', | ||
406 | 'WRITE', | ||
407 | 'XMLELEMENT', | ||
408 | 'XMLEXISTS', | ||
409 | 'XMLNAMESPACES', | ||
410 | 'YEAR', | ||
411 | 'YEARS', | ||
412 | ]; | ||
413 | } | ||
414 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/KeywordList.php b/vendor/doctrine/dbal/src/Platforms/Keywords/KeywordList.php new file mode 100644 index 0000000..150ca3b --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/KeywordList.php | |||
@@ -0,0 +1,42 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Keywords; | ||
6 | |||
7 | use function array_flip; | ||
8 | use function array_map; | ||
9 | use function strtoupper; | ||
10 | |||
11 | /** | ||
12 | * Abstract interface for a SQL reserved keyword dictionary. | ||
13 | */ | ||
14 | abstract class KeywordList | ||
15 | { | ||
16 | /** @var string[]|null */ | ||
17 | private ?array $keywords = null; | ||
18 | |||
19 | /** | ||
20 | * Checks if the given word is a keyword of this dialect/vendor platform. | ||
21 | */ | ||
22 | public function isKeyword(string $word): bool | ||
23 | { | ||
24 | if ($this->keywords === null) { | ||
25 | $this->initializeKeywords(); | ||
26 | } | ||
27 | |||
28 | return isset($this->keywords[strtoupper($word)]); | ||
29 | } | ||
30 | |||
31 | protected function initializeKeywords(): void | ||
32 | { | ||
33 | $this->keywords = array_flip(array_map('strtoupper', $this->getKeywords())); | ||
34 | } | ||
35 | |||
36 | /** | ||
37 | * Returns the list of keywords. | ||
38 | * | ||
39 | * @return string[] | ||
40 | */ | ||
41 | abstract protected function getKeywords(): array; | ||
42 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php new file mode 100644 index 0000000..6857cd3 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php | |||
@@ -0,0 +1,264 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Keywords; | ||
6 | |||
7 | class MariaDBKeywords extends MySQLKeywords | ||
8 | { | ||
9 | /** | ||
10 | * {@inheritDoc} | ||
11 | */ | ||
12 | protected function getKeywords(): array | ||
13 | { | ||
14 | return [ | ||
15 | 'ACCESSIBLE', | ||
16 | 'ADD', | ||
17 | 'ALL', | ||
18 | 'ALTER', | ||
19 | 'ANALYZE', | ||
20 | 'AND', | ||
21 | 'AS', | ||
22 | 'ASC', | ||
23 | 'ASENSITIVE', | ||
24 | 'BEFORE', | ||
25 | 'BETWEEN', | ||
26 | 'BIGINT', | ||
27 | 'BINARY', | ||
28 | 'BLOB', | ||
29 | 'BOTH', | ||
30 | 'BY', | ||
31 | 'CALL', | ||
32 | 'CASCADE', | ||
33 | 'CASE', | ||
34 | 'CHANGE', | ||
35 | 'CHAR', | ||
36 | 'CHARACTER', | ||
37 | 'CHECK', | ||
38 | 'COLLATE', | ||
39 | 'COLUMN', | ||
40 | 'CONDITION', | ||
41 | 'CONSTRAINT', | ||
42 | 'CONTINUE', | ||
43 | 'CONVERT', | ||
44 | 'CREATE', | ||
45 | 'CROSS', | ||
46 | 'CURRENT_DATE', | ||
47 | 'CURRENT_TIME', | ||
48 | 'CURRENT_TIMESTAMP', | ||
49 | 'CURRENT_USER', | ||
50 | 'CURSOR', | ||
51 | 'DATABASE', | ||
52 | 'DATABASES', | ||
53 | 'DAY_HOUR', | ||
54 | 'DAY_MICROSECOND', | ||
55 | 'DAY_MINUTE', | ||
56 | 'DAY_SECOND', | ||
57 | 'DEC', | ||
58 | 'DECIMAL', | ||
59 | 'DECLARE', | ||
60 | 'DEFAULT', | ||
61 | 'DELAYED', | ||
62 | 'DELETE', | ||
63 | 'DESC', | ||
64 | 'DESCRIBE', | ||
65 | 'DETERMINISTIC', | ||
66 | 'DISTINCT', | ||
67 | 'DISTINCTROW', | ||
68 | 'DIV', | ||
69 | 'DOUBLE', | ||
70 | 'DROP', | ||
71 | 'DUAL', | ||
72 | 'EACH', | ||
73 | 'ELSE', | ||
74 | 'ELSEIF', | ||
75 | 'ENCLOSED', | ||
76 | 'ESCAPED', | ||
77 | 'EXCEPT', | ||
78 | 'EXISTS', | ||
79 | 'EXIT', | ||
80 | 'EXPLAIN', | ||
81 | 'FALSE', | ||
82 | 'FETCH', | ||
83 | 'FLOAT', | ||
84 | 'FLOAT4', | ||
85 | 'FLOAT8', | ||
86 | 'FOR', | ||
87 | 'FORCE', | ||
88 | 'FOREIGN', | ||
89 | 'FROM', | ||
90 | 'FULLTEXT', | ||
91 | 'GENERATED', | ||
92 | 'GET', | ||
93 | 'GENERAL', | ||
94 | 'GRANT', | ||
95 | 'GROUP', | ||
96 | 'HAVING', | ||
97 | 'HIGH_PRIORITY', | ||
98 | 'HOUR_MICROSECOND', | ||
99 | 'HOUR_MINUTE', | ||
100 | 'HOUR_SECOND', | ||
101 | 'IF', | ||
102 | 'IGNORE', | ||
103 | 'IGNORE_SERVER_IDS', | ||
104 | 'IN', | ||
105 | 'INDEX', | ||
106 | 'INFILE', | ||
107 | 'INNER', | ||
108 | 'INOUT', | ||
109 | 'INSENSITIVE', | ||
110 | 'INSERT', | ||
111 | 'INT', | ||
112 | 'INT1', | ||
113 | 'INT2', | ||
114 | 'INT3', | ||
115 | 'INT4', | ||
116 | 'INT8', | ||
117 | 'INTEGER', | ||
118 | 'INTERSECT', | ||
119 | 'INTERVAL', | ||
120 | 'INTO', | ||
121 | 'IO_AFTER_GTIDS', | ||
122 | 'IO_BEFORE_GTIDS', | ||
123 | 'IS', | ||
124 | 'ITERATE', | ||
125 | 'JOIN', | ||
126 | 'KEY', | ||
127 | 'KEYS', | ||
128 | 'KILL', | ||
129 | 'LEADING', | ||
130 | 'LEAVE', | ||
131 | 'LEFT', | ||
132 | 'LIKE', | ||
133 | 'LIMIT', | ||
134 | 'LINEAR', | ||
135 | 'LINES', | ||
136 | 'LOAD', | ||
137 | 'LOCALTIME', | ||
138 | 'LOCALTIMESTAMP', | ||
139 | 'LOCK', | ||
140 | 'LONG', | ||
141 | 'LONGBLOB', | ||
142 | 'LONGTEXT', | ||
143 | 'LOOP', | ||
144 | 'LOW_PRIORITY', | ||
145 | 'MASTER_BIND', | ||
146 | 'MASTER_HEARTBEAT_PERIOD', | ||
147 | 'MASTER_SSL_VERIFY_SERVER_CERT', | ||
148 | 'MATCH', | ||
149 | 'MAXVALUE', | ||
150 | 'MEDIUMBLOB', | ||
151 | 'MEDIUMINT', | ||
152 | 'MEDIUMTEXT', | ||
153 | 'MIDDLEINT', | ||
154 | 'MINUTE_MICROSECOND', | ||
155 | 'MINUTE_SECOND', | ||
156 | 'MOD', | ||
157 | 'MODIFIES', | ||
158 | 'NATURAL', | ||
159 | 'NO_WRITE_TO_BINLOG', | ||
160 | 'NOT', | ||
161 | 'NULL', | ||
162 | 'NUMERIC', | ||
163 | 'OFFSET', | ||
164 | 'ON', | ||
165 | 'OPTIMIZE', | ||
166 | 'OPTIMIZER_COSTS', | ||
167 | 'OPTION', | ||
168 | 'OPTIONALLY', | ||
169 | 'OR', | ||
170 | 'ORDER', | ||
171 | 'OUT', | ||
172 | 'OUTER', | ||
173 | 'OUTFILE', | ||
174 | 'OVER', | ||
175 | 'PARTITION', | ||
176 | 'PRECISION', | ||
177 | 'PRIMARY', | ||
178 | 'PROCEDURE', | ||
179 | 'PURGE', | ||
180 | 'RANGE', | ||
181 | 'READ', | ||
182 | 'READ_WRITE', | ||
183 | 'READS', | ||
184 | 'REAL', | ||
185 | 'RECURSIVE', | ||
186 | 'REFERENCES', | ||
187 | 'REGEXP', | ||
188 | 'RELEASE', | ||
189 | 'RENAME', | ||
190 | 'REPEAT', | ||
191 | 'REPLACE', | ||
192 | 'REQUIRE', | ||
193 | 'RESIGNAL', | ||
194 | 'RESTRICT', | ||
195 | 'RETURN', | ||
196 | 'RETURNING', | ||
197 | 'REVOKE', | ||
198 | 'RIGHT', | ||
199 | 'RLIKE', | ||
200 | 'ROWS', | ||
201 | 'SCHEMA', | ||
202 | 'SCHEMAS', | ||
203 | 'SECOND_MICROSECOND', | ||
204 | 'SELECT', | ||
205 | 'SENSITIVE', | ||
206 | 'SEPARATOR', | ||
207 | 'SET', | ||
208 | 'SHOW', | ||
209 | 'SIGNAL', | ||
210 | 'SLOW', | ||
211 | 'SMALLINT', | ||
212 | 'SPATIAL', | ||
213 | 'SPECIFIC', | ||
214 | 'SQL', | ||
215 | 'SQL_BIG_RESULT', | ||
216 | 'SQL_CALC_FOUND_ROWS', | ||
217 | 'SQL_SMALL_RESULT', | ||
218 | 'SQLEXCEPTION', | ||
219 | 'SQLSTATE', | ||
220 | 'SQLWARNING', | ||
221 | 'SSL', | ||
222 | 'STARTING', | ||
223 | 'STORED', | ||
224 | 'STRAIGHT_JOIN', | ||
225 | 'TABLE', | ||
226 | 'TERMINATED', | ||
227 | 'THEN', | ||
228 | 'TINYBLOB', | ||
229 | 'TINYINT', | ||
230 | 'TINYTEXT', | ||
231 | 'TO', | ||
232 | 'TRAILING', | ||
233 | 'TRIGGER', | ||
234 | 'TRUE', | ||
235 | 'UNDO', | ||
236 | 'UNION', | ||
237 | 'UNIQUE', | ||
238 | 'UNLOCK', | ||
239 | 'UNSIGNED', | ||
240 | 'UPDATE', | ||
241 | 'USAGE', | ||
242 | 'USE', | ||
243 | 'USING', | ||
244 | 'UTC_DATE', | ||
245 | 'UTC_TIME', | ||
246 | 'UTC_TIMESTAMP', | ||
247 | 'VALUES', | ||
248 | 'VARBINARY', | ||
249 | 'VARCHAR', | ||
250 | 'VARCHARACTER', | ||
251 | 'VARYING', | ||
252 | 'VIRTUAL', | ||
253 | 'WHEN', | ||
254 | 'WHERE', | ||
255 | 'WHILE', | ||
256 | 'WINDOW', | ||
257 | 'WITH', | ||
258 | 'WRITE', | ||
259 | 'XOR', | ||
260 | 'YEAR_MONTH', | ||
261 | 'ZEROFILL', | ||
262 | ]; | ||
263 | } | ||
264 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/MySQL80Keywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/MySQL80Keywords.php new file mode 100644 index 0000000..331192a --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/MySQL80Keywords.php | |||
@@ -0,0 +1,59 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Keywords; | ||
6 | |||
7 | use function array_merge; | ||
8 | |||
9 | /** | ||
10 | * MySQL 8.0 reserved keywords list. | ||
11 | */ | ||
12 | class MySQL80Keywords extends MySQLKeywords | ||
13 | { | ||
14 | /** | ||
15 | * {@inheritDoc} | ||
16 | * | ||
17 | * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html | ||
18 | */ | ||
19 | protected function getKeywords(): array | ||
20 | { | ||
21 | $keywords = parent::getKeywords(); | ||
22 | |||
23 | $keywords = array_merge($keywords, [ | ||
24 | 'ADMIN', | ||
25 | 'ARRAY', | ||
26 | 'CUBE', | ||
27 | 'CUME_DIST', | ||
28 | 'DENSE_RANK', | ||
29 | 'EMPTY', | ||
30 | 'EXCEPT', | ||
31 | 'FIRST_VALUE', | ||
32 | 'FUNCTION', | ||
33 | 'GROUPING', | ||
34 | 'GROUPS', | ||
35 | 'JSON_TABLE', | ||
36 | 'LAG', | ||
37 | 'LAST_VALUE', | ||
38 | 'LATERAL', | ||
39 | 'LEAD', | ||
40 | 'MEMBER', | ||
41 | 'NTH_VALUE', | ||
42 | 'NTILE', | ||
43 | 'OF', | ||
44 | 'OVER', | ||
45 | 'PERCENT_RANK', | ||
46 | 'PERSIST', | ||
47 | 'PERSIST_ONLY', | ||
48 | 'RANK', | ||
49 | 'RECURSIVE', | ||
50 | 'ROW', | ||
51 | 'ROWS', | ||
52 | 'ROW_NUMBER', | ||
53 | 'SYSTEM', | ||
54 | 'WINDOW', | ||
55 | ]); | ||
56 | |||
57 | return $keywords; | ||
58 | } | ||
59 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/MySQLKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/MySQLKeywords.php new file mode 100644 index 0000000..2fbc461 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/MySQLKeywords.php | |||
@@ -0,0 +1,257 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Keywords; | ||
6 | |||
7 | /** | ||
8 | * MySQL Keywordlist. | ||
9 | */ | ||
10 | class MySQLKeywords extends KeywordList | ||
11 | { | ||
12 | /** | ||
13 | * {@inheritDoc} | ||
14 | * | ||
15 | * @link https://dev.mysql.com/doc/mysqld-version-reference/en/keywords-5-7.html | ||
16 | */ | ||
17 | protected function getKeywords(): array | ||
18 | { | ||
19 | return [ | ||
20 | 'ACCESSIBLE', | ||
21 | 'ADD', | ||
22 | 'ALL', | ||
23 | 'ALTER', | ||
24 | 'ANALYZE', | ||
25 | 'AND', | ||
26 | 'AS', | ||
27 | 'ASC', | ||
28 | 'ASENSITIVE', | ||
29 | 'BEFORE', | ||
30 | 'BETWEEN', | ||
31 | 'BIGINT', | ||
32 | 'BINARY', | ||
33 | 'BLOB', | ||
34 | 'BOTH', | ||
35 | 'BY', | ||
36 | 'CALL', | ||
37 | 'CASCADE', | ||
38 | 'CASE', | ||
39 | 'CHANGE', | ||
40 | 'CHAR', | ||
41 | 'CHARACTER', | ||
42 | 'CHECK', | ||
43 | 'COLLATE', | ||
44 | 'COLUMN', | ||
45 | 'CONDITION', | ||
46 | 'CONSTRAINT', | ||
47 | 'CONTINUE', | ||
48 | 'CONVERT', | ||
49 | 'CREATE', | ||
50 | 'CROSS', | ||
51 | 'CURRENT_DATE', | ||
52 | 'CURRENT_TIME', | ||
53 | 'CURRENT_TIMESTAMP', | ||
54 | 'CURRENT_USER', | ||
55 | 'CURSOR', | ||
56 | 'DATABASE', | ||
57 | 'DATABASES', | ||
58 | 'DAY_HOUR', | ||
59 | 'DAY_MICROSECOND', | ||
60 | 'DAY_MINUTE', | ||
61 | 'DAY_SECOND', | ||
62 | 'DEC', | ||
63 | 'DECIMAL', | ||
64 | 'DECLARE', | ||
65 | 'DEFAULT', | ||
66 | 'DELAYED', | ||
67 | 'DELETE', | ||
68 | 'DESC', | ||
69 | 'DESCRIBE', | ||
70 | 'DETERMINISTIC', | ||
71 | 'DISTINCT', | ||
72 | 'DISTINCTROW', | ||
73 | 'DIV', | ||
74 | 'DOUBLE', | ||
75 | 'DROP', | ||
76 | 'DUAL', | ||
77 | 'EACH', | ||
78 | 'ELSE', | ||
79 | 'ELSEIF', | ||
80 | 'ENCLOSED', | ||
81 | 'ESCAPED', | ||
82 | 'EXISTS', | ||
83 | 'EXIT', | ||
84 | 'EXPLAIN', | ||
85 | 'FALSE', | ||
86 | 'FETCH', | ||
87 | 'FLOAT', | ||
88 | 'FLOAT4', | ||
89 | 'FLOAT8', | ||
90 | 'FOR', | ||
91 | 'FORCE', | ||
92 | 'FOREIGN', | ||
93 | 'FROM', | ||
94 | 'FULLTEXT', | ||
95 | 'GENERATED', | ||
96 | 'GET', | ||
97 | 'GRANT', | ||
98 | 'GROUP', | ||
99 | 'HAVING', | ||
100 | 'HIGH_PRIORITY', | ||
101 | 'HOUR_MICROSECOND', | ||
102 | 'HOUR_MINUTE', | ||
103 | 'HOUR_SECOND', | ||
104 | 'IF', | ||
105 | 'IGNORE', | ||
106 | 'IN', | ||
107 | 'INDEX', | ||
108 | 'INFILE', | ||
109 | 'INNER', | ||
110 | 'INOUT', | ||
111 | 'INSENSITIVE', | ||
112 | 'INSERT', | ||
113 | 'INT', | ||
114 | 'INT1', | ||
115 | 'INT2', | ||
116 | 'INT3', | ||
117 | 'INT4', | ||
118 | 'INT8', | ||
119 | 'INTEGER', | ||
120 | 'INTERVAL', | ||
121 | 'INTO', | ||
122 | 'IO_AFTER_GTIDS', | ||
123 | 'IO_BEFORE_GTIDS', | ||
124 | 'IS', | ||
125 | 'ITERATE', | ||
126 | 'JOIN', | ||
127 | 'KEY', | ||
128 | 'KEYS', | ||
129 | 'KILL', | ||
130 | 'LEADING', | ||
131 | 'LEAVE', | ||
132 | 'LEFT', | ||
133 | 'LIKE', | ||
134 | 'LIMIT', | ||
135 | 'LINEAR', | ||
136 | 'LINES', | ||
137 | 'LOAD', | ||
138 | 'LOCALTIME', | ||
139 | 'LOCALTIMESTAMP', | ||
140 | 'LOCK', | ||
141 | 'LONG', | ||
142 | 'LONGBLOB', | ||
143 | 'LONGTEXT', | ||
144 | 'LOOP', | ||
145 | 'LOW_PRIORITY', | ||
146 | 'MASTER_BIND', | ||
147 | 'MASTER_SSL_VERIFY_SERVER_CERT', | ||
148 | 'MATCH', | ||
149 | 'MAXVALUE', | ||
150 | 'MEDIUMBLOB', | ||
151 | 'MEDIUMINT', | ||
152 | 'MEDIUMTEXT', | ||
153 | 'MIDDLEINT', | ||
154 | 'MINUTE_MICROSECOND', | ||
155 | 'MINUTE_SECOND', | ||
156 | 'MOD', | ||
157 | 'MODIFIES', | ||
158 | 'NATURAL', | ||
159 | 'NO_WRITE_TO_BINLOG', | ||
160 | 'NOT', | ||
161 | 'NULL', | ||
162 | 'NUMERIC', | ||
163 | 'ON', | ||
164 | 'OPTIMIZE', | ||
165 | 'OPTIMIZER_COSTS', | ||
166 | 'OPTION', | ||
167 | 'OPTIONALLY', | ||
168 | 'OR', | ||
169 | 'ORDER', | ||
170 | 'OUT', | ||
171 | 'OUTER', | ||
172 | 'OUTFILE', | ||
173 | 'PARTITION', | ||
174 | 'PRECISION', | ||
175 | 'PRIMARY', | ||
176 | 'PROCEDURE', | ||
177 | 'PURGE', | ||
178 | 'RANGE', | ||
179 | 'READ', | ||
180 | 'READ_WRITE', | ||
181 | 'READS', | ||
182 | 'REAL', | ||
183 | 'REFERENCES', | ||
184 | 'REGEXP', | ||
185 | 'RELEASE', | ||
186 | 'RENAME', | ||
187 | 'REPEAT', | ||
188 | 'REPLACE', | ||
189 | 'REQUIRE', | ||
190 | 'RESIGNAL', | ||
191 | 'RESTRICT', | ||
192 | 'RETURN', | ||
193 | 'REVOKE', | ||
194 | 'RIGHT', | ||
195 | 'RLIKE', | ||
196 | 'SCHEMA', | ||
197 | 'SCHEMAS', | ||
198 | 'SECOND_MICROSECOND', | ||
199 | 'SELECT', | ||
200 | 'SENSITIVE', | ||
201 | 'SEPARATOR', | ||
202 | 'SET', | ||
203 | 'SHOW', | ||
204 | 'SIGNAL', | ||
205 | 'SMALLINT', | ||
206 | 'SPATIAL', | ||
207 | 'SPECIFIC', | ||
208 | 'SQL', | ||
209 | 'SQL_BIG_RESULT', | ||
210 | 'SQL_CALC_FOUND_ROWS', | ||
211 | 'SQL_SMALL_RESULT', | ||
212 | 'SQLEXCEPTION', | ||
213 | 'SQLSTATE', | ||
214 | 'SQLWARNING', | ||
215 | 'SSL', | ||
216 | 'STARTING', | ||
217 | 'STORED', | ||
218 | 'STRAIGHT_JOIN', | ||
219 | 'TABLE', | ||
220 | 'TERMINATED', | ||
221 | 'THEN', | ||
222 | 'TINYBLOB', | ||
223 | 'TINYINT', | ||
224 | 'TINYTEXT', | ||
225 | 'TO', | ||
226 | 'TRAILING', | ||
227 | 'TRIGGER', | ||
228 | 'TRUE', | ||
229 | 'UNDO', | ||
230 | 'UNION', | ||
231 | 'UNIQUE', | ||
232 | 'UNLOCK', | ||
233 | 'UNSIGNED', | ||
234 | 'UPDATE', | ||
235 | 'USAGE', | ||
236 | 'USE', | ||
237 | 'USING', | ||
238 | 'UTC_DATE', | ||
239 | 'UTC_TIME', | ||
240 | 'UTC_TIMESTAMP', | ||
241 | 'VALUES', | ||
242 | 'VARBINARY', | ||
243 | 'VARCHAR', | ||
244 | 'VARCHARACTER', | ||
245 | 'VARYING', | ||
246 | 'VIRTUAL', | ||
247 | 'WHEN', | ||
248 | 'WHERE', | ||
249 | 'WHILE', | ||
250 | 'WITH', | ||
251 | 'WRITE', | ||
252 | 'XOR', | ||
253 | 'YEAR_MONTH', | ||
254 | 'ZEROFILL', | ||
255 | ]; | ||
256 | } | ||
257 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/OracleKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/OracleKeywords.php new file mode 100644 index 0000000..589d092 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/OracleKeywords.php | |||
@@ -0,0 +1,133 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Keywords; | ||
6 | |||
7 | /** | ||
8 | * Oracle Keywordlist. | ||
9 | */ | ||
10 | class OracleKeywords extends KeywordList | ||
11 | { | ||
12 | /** | ||
13 | * {@inheritDoc} | ||
14 | */ | ||
15 | protected function getKeywords(): array | ||
16 | { | ||
17 | return [ | ||
18 | 'ACCESS', | ||
19 | 'ADD', | ||
20 | 'ALL', | ||
21 | 'ALTER', | ||
22 | 'AND', | ||
23 | 'ANY', | ||
24 | 'ARRAYLEN', | ||
25 | 'AS', | ||
26 | 'ASC', | ||
27 | 'AUDIT', | ||
28 | 'BETWEEN', | ||
29 | 'BY', | ||
30 | 'CHAR', | ||
31 | 'CHECK', | ||
32 | 'CLUSTER', | ||
33 | 'COLUMN', | ||
34 | 'COMMENT', | ||
35 | 'COMPRESS', | ||
36 | 'CONNECT', | ||
37 | 'CREATE', | ||
38 | 'CURRENT', | ||
39 | 'DATE', | ||
40 | 'DECIMAL', | ||
41 | 'DEFAULT', | ||
42 | 'DELETE', | ||
43 | 'DESC', | ||
44 | 'DISTINCT', | ||
45 | 'DROP', | ||
46 | 'ELSE', | ||
47 | 'EXCLUSIVE', | ||
48 | 'EXISTS', | ||
49 | 'FILE', | ||
50 | 'FLOAT', | ||
51 | 'FOR', | ||
52 | 'FROM', | ||
53 | 'GRANT', | ||
54 | 'GROUP', | ||
55 | 'HAVING', | ||
56 | 'IDENTIFIED', | ||
57 | 'IMMEDIATE', | ||
58 | 'IN', | ||
59 | 'INCREMENT', | ||
60 | 'INDEX', | ||
61 | 'INITIAL', | ||
62 | 'INSERT', | ||
63 | 'INTEGER', | ||
64 | 'INTERSECT', | ||
65 | 'INTO', | ||
66 | 'IS', | ||
67 | 'LEVEL', | ||
68 | 'LIKE', | ||
69 | 'LOCK', | ||
70 | 'LONG', | ||
71 | 'MAXEXTENTS', | ||
72 | 'MINUS', | ||
73 | 'MODE', | ||
74 | 'MODIFY', | ||
75 | 'NOAUDIT', | ||
76 | 'NOCOMPRESS', | ||
77 | 'NOT', | ||
78 | 'NOTFOUND', | ||
79 | 'NOWAIT', | ||
80 | 'NULL', | ||
81 | 'NUMBER', | ||
82 | 'OF', | ||
83 | 'OFFLINE', | ||
84 | 'ON', | ||
85 | 'ONLINE', | ||
86 | 'OPTION', | ||
87 | 'OR', | ||
88 | 'ORDER', | ||
89 | 'PCTFREE', | ||
90 | 'PRIOR', | ||
91 | 'PRIVILEGES', | ||
92 | 'PUBLIC', | ||
93 | 'RANGE', | ||
94 | 'RAW', | ||
95 | 'RENAME', | ||
96 | 'RESOURCE', | ||
97 | 'REVOKE', | ||
98 | 'ROW', | ||
99 | 'ROWID', | ||
100 | 'ROWLABEL', | ||
101 | 'ROWNUM', | ||
102 | 'ROWS', | ||
103 | 'SELECT', | ||
104 | 'SESSION', | ||
105 | 'SET', | ||
106 | 'SHARE', | ||
107 | 'SIZE', | ||
108 | 'SMALLINT', | ||
109 | 'SQLBUF', | ||
110 | 'START', | ||
111 | 'SUCCESSFUL', | ||
112 | 'SYNONYM', | ||
113 | 'SYSDATE', | ||
114 | 'TABLE', | ||
115 | 'THEN', | ||
116 | 'TO', | ||
117 | 'TRIGGER', | ||
118 | 'UID', | ||
119 | 'UNION', | ||
120 | 'UNIQUE', | ||
121 | 'UPDATE', | ||
122 | 'USER', | ||
123 | 'VALIDATE', | ||
124 | 'VALUES', | ||
125 | 'VARCHAR', | ||
126 | 'VARCHAR2', | ||
127 | 'VIEW', | ||
128 | 'WHENEVER', | ||
129 | 'WHERE', | ||
130 | 'WITH', | ||
131 | ]; | ||
132 | } | ||
133 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/PostgreSQLKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/PostgreSQLKeywords.php new file mode 100644 index 0000000..62ef98a --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/PostgreSQLKeywords.php | |||
@@ -0,0 +1,119 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Keywords; | ||
6 | |||
7 | /** | ||
8 | * Reserved keywords list corresponding to the PostgreSQL database platform of the oldest supported version. | ||
9 | */ | ||
10 | class PostgreSQLKeywords extends KeywordList | ||
11 | { | ||
12 | /** | ||
13 | * {@inheritDoc} | ||
14 | */ | ||
15 | protected function getKeywords(): array | ||
16 | { | ||
17 | return [ | ||
18 | 'ALL', | ||
19 | 'ANALYSE', | ||
20 | 'ANALYZE', | ||
21 | 'AND', | ||
22 | 'ANY', | ||
23 | 'ARRAY', | ||
24 | 'AS', | ||
25 | 'ASC', | ||
26 | 'ASYMMETRIC', | ||
27 | 'AUTHORIZATION', | ||
28 | 'BINARY', | ||
29 | 'BOTH', | ||
30 | 'CASE', | ||
31 | 'CAST', | ||
32 | 'CHECK', | ||
33 | 'COLLATE', | ||
34 | 'COLLATION', | ||
35 | 'COLUMN', | ||
36 | 'CONCURRENTLY', | ||
37 | 'CONSTRAINT', | ||
38 | 'CREATE', | ||
39 | 'CROSS', | ||
40 | 'CURRENT_CATALOG', | ||
41 | 'CURRENT_DATE', | ||
42 | 'CURRENT_ROLE', | ||
43 | 'CURRENT_SCHEMA', | ||
44 | 'CURRENT_TIME', | ||
45 | 'CURRENT_TIMESTAMP', | ||
46 | 'CURRENT_USER', | ||
47 | 'DEFAULT', | ||
48 | 'DEFERRABLE', | ||
49 | 'DESC', | ||
50 | 'DISTINCT', | ||
51 | 'DO', | ||
52 | 'ELSE', | ||
53 | 'END', | ||
54 | 'EXCEPT', | ||
55 | 'FALSE', | ||
56 | 'FETCH', | ||
57 | 'FOR', | ||
58 | 'FOREIGN', | ||
59 | 'FREEZE', | ||
60 | 'FROM', | ||
61 | 'FULL', | ||
62 | 'GRANT', | ||
63 | 'GROUP', | ||
64 | 'HAVING', | ||
65 | 'ILIKE', | ||
66 | 'IN', | ||
67 | 'INITIALLY', | ||
68 | 'INNER', | ||
69 | 'INTERSECT', | ||
70 | 'INTO', | ||
71 | 'IS', | ||
72 | 'ISNULL', | ||
73 | 'JOIN', | ||
74 | 'LATERAL', | ||
75 | 'LEADING', | ||
76 | 'LEFT', | ||
77 | 'LIKE', | ||
78 | 'LIMIT', | ||
79 | 'LOCALTIME', | ||
80 | 'LOCALTIMESTAMP', | ||
81 | 'NATURAL', | ||
82 | 'NOT', | ||
83 | 'NOTNULL', | ||
84 | 'NULL', | ||
85 | 'OFFSET', | ||
86 | 'ON', | ||
87 | 'ONLY', | ||
88 | 'OR', | ||
89 | 'ORDER', | ||
90 | 'OUTER', | ||
91 | 'OVERLAPS', | ||
92 | 'PLACING', | ||
93 | 'PRIMARY', | ||
94 | 'REFERENCES', | ||
95 | 'RETURNING', | ||
96 | 'RIGHT', | ||
97 | 'SELECT', | ||
98 | 'SESSION_USER', | ||
99 | 'SIMILAR', | ||
100 | 'SOME', | ||
101 | 'SYMMETRIC', | ||
102 | 'TABLE', | ||
103 | 'THEN', | ||
104 | 'TO', | ||
105 | 'TRAILING', | ||
106 | 'TRUE', | ||
107 | 'UNION', | ||
108 | 'UNIQUE', | ||
109 | 'USER', | ||
110 | 'USING', | ||
111 | 'VARIADIC', | ||
112 | 'VERBOSE', | ||
113 | 'WHEN', | ||
114 | 'WHERE', | ||
115 | 'WINDOW', | ||
116 | 'WITH', | ||
117 | ]; | ||
118 | } | ||
119 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/SQLServerKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLServerKeywords.php new file mode 100644 index 0000000..02d52cf --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLServerKeywords.php | |||
@@ -0,0 +1,207 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Keywords; | ||
6 | |||
7 | /** | ||
8 | * Reserved keywords list corresponding to the Microsoft SQL Server database platform of the oldest supported version. | ||
9 | */ | ||
10 | class SQLServerKeywords extends KeywordList | ||
11 | { | ||
12 | /** | ||
13 | * {@inheritDoc} | ||
14 | * | ||
15 | * @link http://msdn.microsoft.com/en-us/library/aa238507%28v=sql.80%29.aspx | ||
16 | */ | ||
17 | protected function getKeywords(): array | ||
18 | { | ||
19 | return [ | ||
20 | 'ADD', | ||
21 | 'ALL', | ||
22 | 'ALTER', | ||
23 | 'AND', | ||
24 | 'ANY', | ||
25 | 'AS', | ||
26 | 'ASC', | ||
27 | 'AUTHORIZATION', | ||
28 | 'BACKUP', | ||
29 | 'BEGIN', | ||
30 | 'BETWEEN', | ||
31 | 'BREAK', | ||
32 | 'BROWSE', | ||
33 | 'BULK', | ||
34 | 'BY', | ||
35 | 'CASCADE', | ||
36 | 'CASE', | ||
37 | 'CHECK', | ||
38 | 'CHECKPOINT', | ||
39 | 'CLOSE', | ||
40 | 'CLUSTERED', | ||
41 | 'COALESCE', | ||
42 | 'COLLATE', | ||
43 | 'COLUMN', | ||
44 | 'COMMIT', | ||
45 | 'COMPUTE', | ||
46 | 'CONSTRAINT', | ||
47 | 'CONTAINS', | ||
48 | 'CONTAINSTABLE', | ||
49 | 'CONTINUE', | ||
50 | 'CONVERT', | ||
51 | 'CREATE', | ||
52 | 'CROSS', | ||
53 | 'CURRENT', | ||
54 | 'CURRENT_DATE', | ||
55 | 'CURRENT_TIME', | ||
56 | 'CURRENT_TIMESTAMP', | ||
57 | 'CURRENT_USER', | ||
58 | 'CURSOR', | ||
59 | 'DATABASE', | ||
60 | 'DBCC', | ||
61 | 'DEALLOCATE', | ||
62 | 'DECLARE', | ||
63 | 'DEFAULT', | ||
64 | 'DELETE', | ||
65 | 'DENY', | ||
66 | 'DESC', | ||
67 | 'DISK', | ||
68 | 'DISTINCT', | ||
69 | 'DISTRIBUTED', | ||
70 | 'DOUBLE', | ||
71 | 'DROP', | ||
72 | 'DUMP', | ||
73 | 'ELSE', | ||
74 | 'END', | ||
75 | 'ERRLVL', | ||
76 | 'ESCAPE', | ||
77 | 'EXCEPT', | ||
78 | 'EXEC', | ||
79 | 'EXECUTE', | ||
80 | 'EXISTS', | ||
81 | 'EXIT', | ||
82 | 'EXTERNAL', | ||
83 | 'FETCH', | ||
84 | 'FILE', | ||
85 | 'FILLFACTOR', | ||
86 | 'FOR', | ||
87 | 'FOREIGN', | ||
88 | 'FREETEXT', | ||
89 | 'FREETEXTTABLE', | ||
90 | 'FROM', | ||
91 | 'FULL', | ||
92 | 'FUNCTION', | ||
93 | 'GOTO', | ||
94 | 'GRANT', | ||
95 | 'GROUP', | ||
96 | 'HAVING', | ||
97 | 'HOLDLOCK', | ||
98 | 'IDENTITY', | ||
99 | 'IDENTITY_INSERT', | ||
100 | 'IDENTITYCOL', | ||
101 | 'IF', | ||
102 | 'IN', | ||
103 | 'INDEX', | ||
104 | 'INNER', | ||
105 | 'INSERT', | ||
106 | 'INTERSECT', | ||
107 | 'INTO', | ||
108 | 'IS', | ||
109 | 'JOIN', | ||
110 | 'KEY', | ||
111 | 'KILL', | ||
112 | 'LEFT', | ||
113 | 'LIKE', | ||
114 | 'LINENO', | ||
115 | 'LOAD', | ||
116 | 'MERGE', | ||
117 | 'NATIONAL', | ||
118 | 'NOCHECK ', | ||
119 | 'NONCLUSTERED', | ||
120 | 'NOT', | ||
121 | 'NULL', | ||
122 | 'NULLIF', | ||
123 | 'OF', | ||
124 | 'OFF', | ||
125 | 'OFFSETS', | ||
126 | 'ON', | ||
127 | 'OPEN', | ||
128 | 'OPENDATASOURCE', | ||
129 | 'OPENQUERY', | ||
130 | 'OPENROWSET', | ||
131 | 'OPENXML', | ||
132 | 'OPTION', | ||
133 | 'OR', | ||
134 | 'ORDER', | ||
135 | 'OUTER', | ||
136 | 'OVER', | ||
137 | 'PERCENT', | ||
138 | 'PIVOT', | ||
139 | 'PLAN', | ||
140 | 'PRECISION', | ||
141 | 'PRIMARY', | ||
142 | 'PRINT', | ||
143 | 'PROC', | ||
144 | 'PROCEDURE', | ||
145 | 'PUBLIC', | ||
146 | 'RAISERROR', | ||
147 | 'READ', | ||
148 | 'READTEXT', | ||
149 | 'RECONFIGURE', | ||
150 | 'REFERENCES', | ||
151 | 'REPLICATION', | ||
152 | 'RESTORE', | ||
153 | 'RESTRICT', | ||
154 | 'RETURN', | ||
155 | 'REVERT', | ||
156 | 'REVOKE', | ||
157 | 'RIGHT', | ||
158 | 'ROLLBACK', | ||
159 | 'ROWCOUNT', | ||
160 | 'ROWGUIDCOL', | ||
161 | 'RULE', | ||
162 | 'SAVE', | ||
163 | 'SCHEMA', | ||
164 | 'SECURITYAUDIT', | ||
165 | 'SELECT', | ||
166 | 'SEMANTICKEYPHRASETABLE', | ||
167 | 'SEMANTICSIMILARITYDETAILSTABLE', | ||
168 | 'SEMANTICSIMILARITYTABLE', | ||
169 | 'SESSION_USER', | ||
170 | 'SET', | ||
171 | 'SETUSER', | ||
172 | 'SHUTDOWN', | ||
173 | 'SOME', | ||
174 | 'STATISTICS', | ||
175 | 'SYSTEM_USER', | ||
176 | 'TABLE', | ||
177 | 'TABLESAMPLE', | ||
178 | 'TEXTSIZE', | ||
179 | 'THEN', | ||
180 | 'TO', | ||
181 | 'TOP', | ||
182 | 'TRAN', | ||
183 | 'TRANSACTION', | ||
184 | 'TRIGGER', | ||
185 | 'TRUNCATE', | ||
186 | 'TRY_CONVERT', | ||
187 | 'TSEQUAL', | ||
188 | 'UNION', | ||
189 | 'UNIQUE', | ||
190 | 'UNPIVOT', | ||
191 | 'UPDATE', | ||
192 | 'UPDATETEXT', | ||
193 | 'USE', | ||
194 | 'USER', | ||
195 | 'VALUES', | ||
196 | 'VARYING', | ||
197 | 'VIEW', | ||
198 | 'WAITFOR', | ||
199 | 'WHEN', | ||
200 | 'WHERE', | ||
201 | 'WHILE', | ||
202 | 'WITH', | ||
203 | 'WITHIN GROUP', | ||
204 | 'WRITETEXT', | ||
205 | ]; | ||
206 | } | ||
207 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/Keywords/SQLiteKeywords.php b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLiteKeywords.php new file mode 100644 index 0000000..e7de212 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/Keywords/SQLiteKeywords.php | |||
@@ -0,0 +1,141 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\Keywords; | ||
6 | |||
7 | /** | ||
8 | * SQLite Keywordlist. | ||
9 | */ | ||
10 | class SQLiteKeywords extends KeywordList | ||
11 | { | ||
12 | /** | ||
13 | * {@inheritDoc} | ||
14 | */ | ||
15 | protected function getKeywords(): array | ||
16 | { | ||
17 | return [ | ||
18 | 'ABORT', | ||
19 | 'ACTION', | ||
20 | 'ADD', | ||
21 | 'AFTER', | ||
22 | 'ALL', | ||
23 | 'ALTER', | ||
24 | 'ANALYZE', | ||
25 | 'AND', | ||
26 | 'AS', | ||
27 | 'ASC', | ||
28 | 'ATTACH', | ||
29 | 'AUTOINCREMENT', | ||
30 | 'BEFORE', | ||
31 | 'BEGIN', | ||
32 | 'BETWEEN', | ||
33 | 'BY', | ||
34 | 'CASCADE', | ||
35 | 'CASE', | ||
36 | 'CAST', | ||
37 | 'CHECK', | ||
38 | 'COLLATE', | ||
39 | 'COLUMN', | ||
40 | 'COMMIT', | ||
41 | 'CONFLICT', | ||
42 | 'CONSTRAINT', | ||
43 | 'CREATE', | ||
44 | 'CROSS', | ||
45 | 'CURRENT_DATE', | ||
46 | 'CURRENT_TIME', | ||
47 | 'CURRENT_TIMESTAMP', | ||
48 | 'DATABASE', | ||
49 | 'DEFAULT', | ||
50 | 'DEFERRABLE', | ||
51 | 'DEFERRED', | ||
52 | 'DELETE', | ||
53 | 'DESC', | ||
54 | 'DETACH', | ||
55 | 'DISTINCT', | ||
56 | 'DROP', | ||
57 | 'EACH', | ||
58 | 'ELSE', | ||
59 | 'END', | ||
60 | 'ESCAPE', | ||
61 | 'EXCEPT', | ||
62 | 'EXCLUSIVE', | ||
63 | 'EXISTS', | ||
64 | 'EXPLAIN', | ||
65 | 'FAIL', | ||
66 | 'FOR', | ||
67 | 'FOREIGN', | ||
68 | 'FROM', | ||
69 | 'FULL', | ||
70 | 'GLOB', | ||
71 | 'GROUP', | ||
72 | 'HAVING', | ||
73 | 'IF', | ||
74 | 'IGNORE', | ||
75 | 'IMMEDIATE', | ||
76 | 'IN', | ||
77 | 'INDEX', | ||
78 | 'INDEXED', | ||
79 | 'INITIALLY', | ||
80 | 'INNER', | ||
81 | 'INSERT', | ||
82 | 'INSTEAD', | ||
83 | 'INTERSECT', | ||
84 | 'INTO', | ||
85 | 'IS', | ||
86 | 'ISNULL', | ||
87 | 'JOIN', | ||
88 | 'KEY', | ||
89 | 'LEFT', | ||
90 | 'LIKE', | ||
91 | 'LIMIT', | ||
92 | 'MATCH', | ||
93 | 'NATURAL', | ||
94 | 'NO', | ||
95 | 'NOT', | ||
96 | 'NOTNULL', | ||
97 | 'NULL', | ||
98 | 'OF', | ||
99 | 'OFFSET', | ||
100 | 'ON', | ||
101 | 'OR', | ||
102 | 'ORDER', | ||
103 | 'OUTER', | ||
104 | 'PLAN', | ||
105 | 'PRAGMA', | ||
106 | 'PRIMARY', | ||
107 | 'QUERY', | ||
108 | 'RAISE', | ||
109 | 'REFERENCES', | ||
110 | 'REGEXP', | ||
111 | 'REINDEX', | ||
112 | 'RELEASE', | ||
113 | 'RENAME', | ||
114 | 'REPLACE', | ||
115 | 'RESTRICT', | ||
116 | 'RIGHT', | ||
117 | 'ROLLBACK', | ||
118 | 'ROW', | ||
119 | 'SAVEPOINT', | ||
120 | 'SELECT', | ||
121 | 'SET', | ||
122 | 'TABLE', | ||
123 | 'TEMP', | ||
124 | 'TEMPORARY', | ||
125 | 'THEN', | ||
126 | 'TO', | ||
127 | 'TRANSACTION', | ||
128 | 'TRIGGER', | ||
129 | 'UNION', | ||
130 | 'UNIQUE', | ||
131 | 'UPDATE', | ||
132 | 'USING', | ||
133 | 'VACUUM', | ||
134 | 'VALUES', | ||
135 | 'VIEW', | ||
136 | 'VIRTUAL', | ||
137 | 'WHEN', | ||
138 | 'WHERE', | ||
139 | ]; | ||
140 | } | ||
141 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MariaDB1052Platform.php b/vendor/doctrine/dbal/src/Platforms/MariaDB1052Platform.php new file mode 100644 index 0000000..ccccbb0 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MariaDB1052Platform.php | |||
@@ -0,0 +1,38 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | use Doctrine\DBAL\Schema\Index; | ||
8 | use Doctrine\DBAL\Schema\TableDiff; | ||
9 | |||
10 | /** | ||
11 | * Provides the behavior, features and SQL dialect of the MariaDB 10.5 database platform. | ||
12 | */ | ||
13 | class MariaDB1052Platform extends MariaDBPlatform | ||
14 | { | ||
15 | /** | ||
16 | * {@inheritDoc} | ||
17 | */ | ||
18 | protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff): array | ||
19 | { | ||
20 | return AbstractMySQLPlatform::getPreAlterTableRenameIndexForeignKeySQL($diff); | ||
21 | } | ||
22 | |||
23 | /** | ||
24 | * {@inheritDoc} | ||
25 | */ | ||
26 | protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array | ||
27 | { | ||
28 | return AbstractMySQLPlatform::getPostAlterTableIndexForeignKeySQL($diff); | ||
29 | } | ||
30 | |||
31 | /** | ||
32 | * {@inheritDoc} | ||
33 | */ | ||
34 | protected function getRenameIndexSQL(string $oldIndexName, Index $index, $tableName): array | ||
35 | { | ||
36 | return ['ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; | ||
37 | } | ||
38 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MariaDB1060Platform.php b/vendor/doctrine/dbal/src/Platforms/MariaDB1060Platform.php new file mode 100644 index 0000000..028473d --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MariaDB1060Platform.php | |||
@@ -0,0 +1,18 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; | ||
8 | |||
9 | /** | ||
10 | * Provides the behavior, features and SQL dialect of the MariaDB 10.6 database platform. | ||
11 | */ | ||
12 | class MariaDB1060Platform extends MariaDB1052Platform | ||
13 | { | ||
14 | public function createSelectSQLBuilder(): SelectSQLBuilder | ||
15 | { | ||
16 | return AbstractPlatform::createSelectSQLBuilder(); | ||
17 | } | ||
18 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MariaDBPlatform.php b/vendor/doctrine/dbal/src/Platforms/MariaDBPlatform.php new file mode 100644 index 0000000..d4082ae --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MariaDBPlatform.php | |||
@@ -0,0 +1,165 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
8 | use Doctrine\DBAL\Platforms\Keywords\MariaDBKeywords; | ||
9 | use Doctrine\DBAL\Schema\ForeignKeyConstraint; | ||
10 | use Doctrine\DBAL\Schema\TableDiff; | ||
11 | use Doctrine\DBAL\Types\JsonType; | ||
12 | |||
13 | use function array_diff_key; | ||
14 | use function array_merge; | ||
15 | use function count; | ||
16 | use function in_array; | ||
17 | |||
18 | /** | ||
19 | * Provides the behavior, features and SQL dialect of the MariaDB database platform of the oldest supported version. | ||
20 | */ | ||
21 | class MariaDBPlatform extends AbstractMySQLPlatform | ||
22 | { | ||
23 | /** | ||
24 | * Generate SQL snippets to reverse the aliasing of JSON to LONGTEXT. | ||
25 | * | ||
26 | * MariaDb aliases columns specified as JSON to LONGTEXT and sets a CHECK constraint to ensure the column | ||
27 | * is valid json. This function generates the SQL snippets which reverse this aliasing i.e. report a column | ||
28 | * as JSON where it was originally specified as such instead of LONGTEXT. | ||
29 | * | ||
30 | * The CHECK constraints are stored in information_schema.CHECK_CONSTRAINTS so query that table. | ||
31 | */ | ||
32 | public function getColumnTypeSQLSnippet(string $tableAlias, string $databaseName): string | ||
33 | { | ||
34 | $subQueryAlias = 'i_' . $tableAlias; | ||
35 | |||
36 | $databaseName = $this->quoteStringLiteral($databaseName); | ||
37 | |||
38 | // The check for `CONSTRAINT_SCHEMA = $databaseName` is mandatory here to prevent performance issues | ||
39 | return <<<SQL | ||
40 | IF( | ||
41 | $tableAlias.COLUMN_TYPE = 'longtext' | ||
42 | AND EXISTS( | ||
43 | SELECT * FROM information_schema.CHECK_CONSTRAINTS $subQueryAlias | ||
44 | WHERE $subQueryAlias.CONSTRAINT_SCHEMA = $databaseName | ||
45 | AND $subQueryAlias.TABLE_NAME = $tableAlias.TABLE_NAME | ||
46 | AND $subQueryAlias.CHECK_CLAUSE = CONCAT( | ||
47 | 'json_valid(`', | ||
48 | $tableAlias.COLUMN_NAME, | ||
49 | '`)' | ||
50 | ) | ||
51 | ), | ||
52 | 'json', | ||
53 | $tableAlias.COLUMN_TYPE | ||
54 | ) | ||
55 | SQL; | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * {@inheritDoc} | ||
60 | */ | ||
61 | protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff): array | ||
62 | { | ||
63 | $sql = []; | ||
64 | $tableName = $diff->getOldTable()->getQuotedName($this); | ||
65 | |||
66 | $modifiedForeignKeys = $diff->getModifiedForeignKeys(); | ||
67 | |||
68 | foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { | ||
69 | if (in_array($foreignKey, $modifiedForeignKeys, true)) { | ||
70 | continue; | ||
71 | } | ||
72 | |||
73 | $sql[] = $this->getDropForeignKeySQL($foreignKey->getQuotedName($this), $tableName); | ||
74 | } | ||
75 | |||
76 | return $sql; | ||
77 | } | ||
78 | |||
79 | /** | ||
80 | * {@inheritDoc} | ||
81 | */ | ||
82 | protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array | ||
83 | { | ||
84 | return array_merge( | ||
85 | parent::getPostAlterTableIndexForeignKeySQL($diff), | ||
86 | $this->getPostAlterTableRenameIndexForeignKeySQL($diff), | ||
87 | ); | ||
88 | } | ||
89 | |||
90 | /** @return list<string> */ | ||
91 | private function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff): array | ||
92 | { | ||
93 | $sql = []; | ||
94 | |||
95 | $tableName = $diff->getOldTable()->getQuotedName($this); | ||
96 | |||
97 | $modifiedForeignKeys = $diff->getModifiedForeignKeys(); | ||
98 | |||
99 | foreach ($this->getRemainingForeignKeyConstraintsRequiringRenamedIndexes($diff) as $foreignKey) { | ||
100 | if (in_array($foreignKey, $modifiedForeignKeys, true)) { | ||
101 | continue; | ||
102 | } | ||
103 | |||
104 | $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); | ||
105 | } | ||
106 | |||
107 | return $sql; | ||
108 | } | ||
109 | |||
110 | /** | ||
111 | * Returns the remaining foreign key constraints that require one of the renamed indexes. | ||
112 | * | ||
113 | * "Remaining" here refers to the diff between the foreign keys currently defined in the associated | ||
114 | * table and the foreign keys to be removed. | ||
115 | * | ||
116 | * @param TableDiff $diff The table diff to evaluate. | ||
117 | * | ||
118 | * @return ForeignKeyConstraint[] | ||
119 | */ | ||
120 | private function getRemainingForeignKeyConstraintsRequiringRenamedIndexes(TableDiff $diff): array | ||
121 | { | ||
122 | $renamedIndexes = $diff->getRenamedIndexes(); | ||
123 | |||
124 | if (count($renamedIndexes) === 0) { | ||
125 | return []; | ||
126 | } | ||
127 | |||
128 | $foreignKeys = []; | ||
129 | |||
130 | $remainingForeignKeys = array_diff_key( | ||
131 | $diff->getOldTable()->getForeignKeys(), | ||
132 | $diff->getDroppedForeignKeys(), | ||
133 | ); | ||
134 | |||
135 | foreach ($remainingForeignKeys as $foreignKey) { | ||
136 | foreach ($renamedIndexes as $index) { | ||
137 | if ($foreignKey->intersectsIndexColumns($index)) { | ||
138 | $foreignKeys[] = $foreignKey; | ||
139 | |||
140 | break; | ||
141 | } | ||
142 | } | ||
143 | } | ||
144 | |||
145 | return $foreignKeys; | ||
146 | } | ||
147 | |||
148 | /** {@inheritDoc} */ | ||
149 | public function getColumnDeclarationSQL(string $name, array $column): string | ||
150 | { | ||
151 | // MariaDb forces column collation to utf8mb4_bin where the column was declared as JSON so ignore | ||
152 | // collation and character set for json columns as attempting to set them can cause an error. | ||
153 | if ($this->getJsonTypeDeclarationSQL([]) === 'JSON' && ($column['type'] ?? null) instanceof JsonType) { | ||
154 | unset($column['collation']); | ||
155 | unset($column['charset']); | ||
156 | } | ||
157 | |||
158 | return parent::getColumnDeclarationSQL($name, $column); | ||
159 | } | ||
160 | |||
161 | protected function createReservedKeywordsList(): KeywordList | ||
162 | { | ||
163 | return new MariaDBKeywords(); | ||
164 | } | ||
165 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider.php new file mode 100644 index 0000000..665543e --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider.php | |||
@@ -0,0 +1,11 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\MySQL; | ||
6 | |||
7 | /** @internal */ | ||
8 | interface CharsetMetadataProvider | ||
9 | { | ||
10 | public function getDefaultCharsetCollation(string $charset): ?string; | ||
11 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/CachingCharsetMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/CachingCharsetMetadataProvider.php new file mode 100644 index 0000000..dadc841 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/CachingCharsetMetadataProvider.php | |||
@@ -0,0 +1,29 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider; | ||
8 | |||
9 | use function array_key_exists; | ||
10 | |||
11 | /** @internal */ | ||
12 | final class CachingCharsetMetadataProvider implements CharsetMetadataProvider | ||
13 | { | ||
14 | /** @var array<string,?string> */ | ||
15 | private array $cache = []; | ||
16 | |||
17 | public function __construct(private readonly CharsetMetadataProvider $charsetMetadataProvider) | ||
18 | { | ||
19 | } | ||
20 | |||
21 | public function getDefaultCharsetCollation(string $charset): ?string | ||
22 | { | ||
23 | if (array_key_exists($charset, $this->cache)) { | ||
24 | return $this->cache[$charset]; | ||
25 | } | ||
26 | |||
27 | return $this->cache[$charset] = $this->charsetMetadataProvider->getDefaultCharsetCollation($charset); | ||
28 | } | ||
29 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/ConnectionCharsetMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/ConnectionCharsetMetadataProvider.php new file mode 100644 index 0000000..65b63df --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/ConnectionCharsetMetadataProvider.php | |||
@@ -0,0 +1,37 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider; | ||
6 | |||
7 | use Doctrine\DBAL\Connection; | ||
8 | use Doctrine\DBAL\Exception; | ||
9 | use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider; | ||
10 | |||
11 | /** @internal */ | ||
12 | final class ConnectionCharsetMetadataProvider implements CharsetMetadataProvider | ||
13 | { | ||
14 | public function __construct(private readonly Connection $connection) | ||
15 | { | ||
16 | } | ||
17 | |||
18 | /** @throws Exception */ | ||
19 | public function getDefaultCharsetCollation(string $charset): ?string | ||
20 | { | ||
21 | $collation = $this->connection->fetchOne( | ||
22 | <<<'SQL' | ||
23 | SELECT DEFAULT_COLLATE_NAME | ||
24 | FROM information_schema.CHARACTER_SETS | ||
25 | WHERE CHARACTER_SET_NAME = ?; | ||
26 | SQL | ||
27 | , | ||
28 | [$charset], | ||
29 | ); | ||
30 | |||
31 | if ($collation !== false) { | ||
32 | return $collation; | ||
33 | } | ||
34 | |||
35 | return null; | ||
36 | } | ||
37 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider.php new file mode 100644 index 0000000..d52ca74 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider.php | |||
@@ -0,0 +1,11 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\MySQL; | ||
6 | |||
7 | /** @internal */ | ||
8 | interface CollationMetadataProvider | ||
9 | { | ||
10 | public function getCollationCharset(string $collation): ?string; | ||
11 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php new file mode 100644 index 0000000..0c99aa3 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php | |||
@@ -0,0 +1,29 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; | ||
8 | |||
9 | use function array_key_exists; | ||
10 | |||
11 | /** @internal */ | ||
12 | final class CachingCollationMetadataProvider implements CollationMetadataProvider | ||
13 | { | ||
14 | /** @var array<string,?string> */ | ||
15 | private array $cache = []; | ||
16 | |||
17 | public function __construct(private readonly CollationMetadataProvider $collationMetadataProvider) | ||
18 | { | ||
19 | } | ||
20 | |||
21 | public function getCollationCharset(string $collation): ?string | ||
22 | { | ||
23 | if (array_key_exists($collation, $this->cache)) { | ||
24 | return $this->cache[$collation]; | ||
25 | } | ||
26 | |||
27 | return $this->cache[$collation] = $this->collationMetadataProvider->getCollationCharset($collation); | ||
28 | } | ||
29 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php new file mode 100644 index 0000000..fcd9995 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php | |||
@@ -0,0 +1,37 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; | ||
6 | |||
7 | use Doctrine\DBAL\Connection; | ||
8 | use Doctrine\DBAL\Exception; | ||
9 | use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider; | ||
10 | |||
11 | /** @internal */ | ||
12 | final class ConnectionCollationMetadataProvider implements CollationMetadataProvider | ||
13 | { | ||
14 | public function __construct(private readonly Connection $connection) | ||
15 | { | ||
16 | } | ||
17 | |||
18 | /** @throws Exception */ | ||
19 | public function getCollationCharset(string $collation): ?string | ||
20 | { | ||
21 | $charset = $this->connection->fetchOne( | ||
22 | <<<'SQL' | ||
23 | SELECT CHARACTER_SET_NAME | ||
24 | FROM information_schema.COLLATIONS | ||
25 | WHERE COLLATION_NAME = ?; | ||
26 | SQL | ||
27 | , | ||
28 | [$collation], | ||
29 | ); | ||
30 | |||
31 | if ($charset !== false) { | ||
32 | return $charset; | ||
33 | } | ||
34 | |||
35 | return null; | ||
36 | } | ||
37 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php b/vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php new file mode 100644 index 0000000..ebe025d --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php | |||
@@ -0,0 +1,93 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\MySQL; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; | ||
8 | use Doctrine\DBAL\Schema\Comparator as BaseComparator; | ||
9 | use Doctrine\DBAL\Schema\Table; | ||
10 | use Doctrine\DBAL\Schema\TableDiff; | ||
11 | |||
12 | use function array_diff_assoc; | ||
13 | |||
14 | /** | ||
15 | * Compares schemas in the context of MySQL platform. | ||
16 | * | ||
17 | * In MySQL, unless specified explicitly, the column's character set and collation are inherited from its containing | ||
18 | * table. So during comparison, an omitted value and the value that matches the default value of table in the | ||
19 | * desired schema must be considered equal. | ||
20 | */ | ||
21 | class Comparator extends BaseComparator | ||
22 | { | ||
23 | /** @internal The comparator can be only instantiated by a schema manager. */ | ||
24 | public function __construct( | ||
25 | AbstractMySQLPlatform $platform, | ||
26 | private readonly CharsetMetadataProvider $charsetMetadataProvider, | ||
27 | private readonly CollationMetadataProvider $collationMetadataProvider, | ||
28 | private readonly DefaultTableOptions $defaultTableOptions, | ||
29 | ) { | ||
30 | parent::__construct($platform); | ||
31 | } | ||
32 | |||
33 | public function compareTables(Table $oldTable, Table $newTable): TableDiff | ||
34 | { | ||
35 | return parent::compareTables( | ||
36 | $this->normalizeTable($oldTable), | ||
37 | $this->normalizeTable($newTable), | ||
38 | ); | ||
39 | } | ||
40 | |||
41 | private function normalizeTable(Table $table): Table | ||
42 | { | ||
43 | $charset = $table->getOption('charset'); | ||
44 | $collation = $table->getOption('collation'); | ||
45 | |||
46 | if ($charset === null && $collation !== null) { | ||
47 | $charset = $this->collationMetadataProvider->getCollationCharset($collation); | ||
48 | } elseif ($charset !== null && $collation === null) { | ||
49 | $collation = $this->charsetMetadataProvider->getDefaultCharsetCollation($charset); | ||
50 | } elseif ($charset === null && $collation === null) { | ||
51 | $charset = $this->defaultTableOptions->getCharset(); | ||
52 | $collation = $this->defaultTableOptions->getCollation(); | ||
53 | } | ||
54 | |||
55 | $tableOptions = [ | ||
56 | 'charset' => $charset, | ||
57 | 'collation' => $collation, | ||
58 | ]; | ||
59 | |||
60 | $table = clone $table; | ||
61 | |||
62 | foreach ($table->getColumns() as $column) { | ||
63 | $originalOptions = $column->getPlatformOptions(); | ||
64 | $normalizedOptions = $this->normalizeOptions($originalOptions); | ||
65 | |||
66 | $overrideOptions = array_diff_assoc($normalizedOptions, $tableOptions); | ||
67 | |||
68 | if ($overrideOptions === $originalOptions) { | ||
69 | continue; | ||
70 | } | ||
71 | |||
72 | $column->setPlatformOptions($overrideOptions); | ||
73 | } | ||
74 | |||
75 | return $table; | ||
76 | } | ||
77 | |||
78 | /** | ||
79 | * @param array<string,string> $options | ||
80 | * | ||
81 | * @return array<string,string|null> | ||
82 | */ | ||
83 | private function normalizeOptions(array $options): array | ||
84 | { | ||
85 | if (isset($options['charset']) && ! isset($options['collation'])) { | ||
86 | $options['collation'] = $this->charsetMetadataProvider->getDefaultCharsetCollation($options['charset']); | ||
87 | } elseif (isset($options['collation']) && ! isset($options['charset'])) { | ||
88 | $options['charset'] = $this->collationMetadataProvider->getCollationCharset($options['collation']); | ||
89 | } | ||
90 | |||
91 | return $options; | ||
92 | } | ||
93 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL/DefaultTableOptions.php b/vendor/doctrine/dbal/src/Platforms/MySQL/DefaultTableOptions.php new file mode 100644 index 0000000..ede3ba2 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL/DefaultTableOptions.php | |||
@@ -0,0 +1,23 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\MySQL; | ||
6 | |||
7 | /** @internal */ | ||
8 | final class DefaultTableOptions | ||
9 | { | ||
10 | public function __construct(private readonly string $charset, private readonly string $collation) | ||
11 | { | ||
12 | } | ||
13 | |||
14 | public function getCharset(): string | ||
15 | { | ||
16 | return $this->charset; | ||
17 | } | ||
18 | |||
19 | public function getCollation(): string | ||
20 | { | ||
21 | return $this->collation; | ||
22 | } | ||
23 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php b/vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php new file mode 100644 index 0000000..14a81a6 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php | |||
@@ -0,0 +1,25 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
8 | use Doctrine\DBAL\Platforms\Keywords\MySQL80Keywords; | ||
9 | use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; | ||
10 | |||
11 | /** | ||
12 | * Provides the behavior, features and SQL dialect of the MySQL 8.0 database platform. | ||
13 | */ | ||
14 | class MySQL80Platform extends MySQLPlatform | ||
15 | { | ||
16 | protected function createReservedKeywordsList(): KeywordList | ||
17 | { | ||
18 | return new MySQL80Keywords(); | ||
19 | } | ||
20 | |||
21 | public function createSelectSQLBuilder(): SelectSQLBuilder | ||
22 | { | ||
23 | return AbstractPlatform::createSelectSQLBuilder(); | ||
24 | } | ||
25 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/MySQLPlatform.php b/vendor/doctrine/dbal/src/Platforms/MySQLPlatform.php new file mode 100644 index 0000000..840ff57 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/MySQLPlatform.php | |||
@@ -0,0 +1,49 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
8 | use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords; | ||
9 | use Doctrine\DBAL\Schema\Index; | ||
10 | use Doctrine\DBAL\Types\BlobType; | ||
11 | use Doctrine\DBAL\Types\TextType; | ||
12 | |||
13 | /** | ||
14 | * Provides the behavior, features and SQL dialect of the Oracle MySQL database platform | ||
15 | * of the oldest supported version. | ||
16 | */ | ||
17 | class MySQLPlatform extends AbstractMySQLPlatform | ||
18 | { | ||
19 | /** | ||
20 | * {@inheritDoc} | ||
21 | * | ||
22 | * Oracle MySQL does not support default values on TEXT/BLOB columns until 8.0.13. | ||
23 | * | ||
24 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
25 | * | ||
26 | * @link https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-13.html#mysqld-8-0-13-data-types | ||
27 | */ | ||
28 | public function getDefaultValueDeclarationSQL(array $column): string | ||
29 | { | ||
30 | if ($column['type'] instanceof TextType || $column['type'] instanceof BlobType) { | ||
31 | unset($column['default']); | ||
32 | } | ||
33 | |||
34 | return parent::getDefaultValueDeclarationSQL($column); | ||
35 | } | ||
36 | |||
37 | /** | ||
38 | * {@inheritDoc} | ||
39 | */ | ||
40 | protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array | ||
41 | { | ||
42 | return ['ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)]; | ||
43 | } | ||
44 | |||
45 | protected function createReservedKeywordsList(): KeywordList | ||
46 | { | ||
47 | return new MySQLKeywords(); | ||
48 | } | ||
49 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/OraclePlatform.php b/vendor/doctrine/dbal/src/Platforms/OraclePlatform.php new file mode 100644 index 0000000..314f6ee --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/OraclePlatform.php | |||
@@ -0,0 +1,784 @@ | |||
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\InvalidColumnType\ColumnLengthRequired; | ||
9 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
10 | use Doctrine\DBAL\Platforms\Keywords\OracleKeywords; | ||
11 | use Doctrine\DBAL\Schema\ForeignKeyConstraint; | ||
12 | use Doctrine\DBAL\Schema\Identifier; | ||
13 | use Doctrine\DBAL\Schema\Index; | ||
14 | use Doctrine\DBAL\Schema\OracleSchemaManager; | ||
15 | use Doctrine\DBAL\Schema\Sequence; | ||
16 | use Doctrine\DBAL\Schema\TableDiff; | ||
17 | use Doctrine\DBAL\TransactionIsolationLevel; | ||
18 | use Doctrine\DBAL\Types\Types; | ||
19 | use InvalidArgumentException; | ||
20 | |||
21 | use function array_merge; | ||
22 | use function count; | ||
23 | use function explode; | ||
24 | use function implode; | ||
25 | use function sprintf; | ||
26 | use function str_contains; | ||
27 | use function strlen; | ||
28 | use function strtoupper; | ||
29 | use function substr; | ||
30 | |||
31 | /** | ||
32 | * OraclePlatform. | ||
33 | */ | ||
34 | class OraclePlatform extends AbstractPlatform | ||
35 | { | ||
36 | public function getSubstringExpression(string $string, string $start, ?string $length = null): string | ||
37 | { | ||
38 | if ($length === null) { | ||
39 | return sprintf('SUBSTR(%s, %s)', $string, $start); | ||
40 | } | ||
41 | |||
42 | return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length); | ||
43 | } | ||
44 | |||
45 | public function getLocateExpression(string $string, string $substring, ?string $start = null): string | ||
46 | { | ||
47 | if ($start === null) { | ||
48 | return sprintf('INSTR(%s, %s)', $string, $substring); | ||
49 | } | ||
50 | |||
51 | return sprintf('INSTR(%s, %s, %s)', $string, $substring, $start); | ||
52 | } | ||
53 | |||
54 | protected function getDateArithmeticIntervalExpression( | ||
55 | string $date, | ||
56 | string $operator, | ||
57 | string $interval, | ||
58 | DateIntervalUnit $unit, | ||
59 | ): string { | ||
60 | switch ($unit) { | ||
61 | case DateIntervalUnit::MONTH: | ||
62 | case DateIntervalUnit::QUARTER: | ||
63 | case DateIntervalUnit::YEAR: | ||
64 | switch ($unit) { | ||
65 | case DateIntervalUnit::QUARTER: | ||
66 | $interval = $this->multiplyInterval($interval, 3); | ||
67 | break; | ||
68 | |||
69 | case DateIntervalUnit::YEAR: | ||
70 | $interval = $this->multiplyInterval($interval, 12); | ||
71 | break; | ||
72 | } | ||
73 | |||
74 | return 'ADD_MONTHS(' . $date . ', ' . $operator . $interval . ')'; | ||
75 | |||
76 | default: | ||
77 | $calculationClause = ''; | ||
78 | |||
79 | switch ($unit) { | ||
80 | case DateIntervalUnit::SECOND: | ||
81 | $calculationClause = '/24/60/60'; | ||
82 | break; | ||
83 | |||
84 | case DateIntervalUnit::MINUTE: | ||
85 | $calculationClause = '/24/60'; | ||
86 | break; | ||
87 | |||
88 | case DateIntervalUnit::HOUR: | ||
89 | $calculationClause = '/24'; | ||
90 | break; | ||
91 | |||
92 | case DateIntervalUnit::WEEK: | ||
93 | $calculationClause = '*7'; | ||
94 | break; | ||
95 | } | ||
96 | |||
97 | return '(' . $date . $operator . $interval . $calculationClause . ')'; | ||
98 | } | ||
99 | } | ||
100 | |||
101 | public function getDateDiffExpression(string $date1, string $date2): string | ||
102 | { | ||
103 | return sprintf('TRUNC(%s) - TRUNC(%s)', $date1, $date2); | ||
104 | } | ||
105 | |||
106 | public function getBitAndComparisonExpression(string $value1, string $value2): string | ||
107 | { | ||
108 | return 'BITAND(' . $value1 . ', ' . $value2 . ')'; | ||
109 | } | ||
110 | |||
111 | public function getCurrentDatabaseExpression(): string | ||
112 | { | ||
113 | return "SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')"; | ||
114 | } | ||
115 | |||
116 | public function getBitOrComparisonExpression(string $value1, string $value2): string | ||
117 | { | ||
118 | return '(' . $value1 . '-' . | ||
119 | $this->getBitAndComparisonExpression($value1, $value2) | ||
120 | . '+' . $value2 . ')'; | ||
121 | } | ||
122 | |||
123 | public function getCreatePrimaryKeySQL(Index $index, string $table): string | ||
124 | { | ||
125 | return 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $index->getQuotedName($this) | ||
126 | . ' PRIMARY KEY (' . implode(', ', $index->getQuotedColumns($this)) . ')'; | ||
127 | } | ||
128 | |||
129 | /** | ||
130 | * {@inheritDoc} | ||
131 | * | ||
132 | * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH. | ||
133 | * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection | ||
134 | * in {@see listSequences()} | ||
135 | */ | ||
136 | public function getCreateSequenceSQL(Sequence $sequence): string | ||
137 | { | ||
138 | return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . | ||
139 | ' START WITH ' . $sequence->getInitialValue() . | ||
140 | ' MINVALUE ' . $sequence->getInitialValue() . | ||
141 | ' INCREMENT BY ' . $sequence->getAllocationSize() . | ||
142 | $this->getSequenceCacheSQL($sequence); | ||
143 | } | ||
144 | |||
145 | public function getAlterSequenceSQL(Sequence $sequence): string | ||
146 | { | ||
147 | return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . | ||
148 | ' INCREMENT BY ' . $sequence->getAllocationSize() | ||
149 | . $this->getSequenceCacheSQL($sequence); | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * Cache definition for sequences | ||
154 | */ | ||
155 | private function getSequenceCacheSQL(Sequence $sequence): string | ||
156 | { | ||
157 | if ($sequence->getCache() === 0) { | ||
158 | return ' NOCACHE'; | ||
159 | } | ||
160 | |||
161 | if ($sequence->getCache() === 1) { | ||
162 | return ' NOCACHE'; | ||
163 | } | ||
164 | |||
165 | if ($sequence->getCache() > 1) { | ||
166 | return ' CACHE ' . $sequence->getCache(); | ||
167 | } | ||
168 | |||
169 | return ''; | ||
170 | } | ||
171 | |||
172 | public function getSequenceNextValSQL(string $sequence): string | ||
173 | { | ||
174 | return 'SELECT ' . $sequence . '.nextval FROM DUAL'; | ||
175 | } | ||
176 | |||
177 | public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string | ||
178 | { | ||
179 | return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); | ||
180 | } | ||
181 | |||
182 | protected function _getTransactionIsolationLevelSQL(TransactionIsolationLevel $level): string | ||
183 | { | ||
184 | return match ($level) { | ||
185 | TransactionIsolationLevel::READ_UNCOMMITTED => 'READ UNCOMMITTED', | ||
186 | TransactionIsolationLevel::READ_COMMITTED => 'READ COMMITTED', | ||
187 | TransactionIsolationLevel::REPEATABLE_READ, | ||
188 | TransactionIsolationLevel::SERIALIZABLE => 'SERIALIZABLE', | ||
189 | }; | ||
190 | } | ||
191 | |||
192 | /** | ||
193 | * {@inheritDoc} | ||
194 | */ | ||
195 | public function getBooleanTypeDeclarationSQL(array $column): string | ||
196 | { | ||
197 | return 'NUMBER(1)'; | ||
198 | } | ||
199 | |||
200 | /** | ||
201 | * {@inheritDoc} | ||
202 | */ | ||
203 | public function getIntegerTypeDeclarationSQL(array $column): string | ||
204 | { | ||
205 | return 'NUMBER(10)'; | ||
206 | } | ||
207 | |||
208 | /** | ||
209 | * {@inheritDoc} | ||
210 | */ | ||
211 | public function getBigIntTypeDeclarationSQL(array $column): string | ||
212 | { | ||
213 | return 'NUMBER(20)'; | ||
214 | } | ||
215 | |||
216 | /** | ||
217 | * {@inheritDoc} | ||
218 | */ | ||
219 | public function getSmallIntTypeDeclarationSQL(array $column): string | ||
220 | { | ||
221 | return 'NUMBER(5)'; | ||
222 | } | ||
223 | |||
224 | /** | ||
225 | * {@inheritDoc} | ||
226 | */ | ||
227 | public function getDateTimeTypeDeclarationSQL(array $column): string | ||
228 | { | ||
229 | return 'TIMESTAMP(0)'; | ||
230 | } | ||
231 | |||
232 | /** | ||
233 | * {@inheritDoc} | ||
234 | */ | ||
235 | public function getDateTimeTzTypeDeclarationSQL(array $column): string | ||
236 | { | ||
237 | return 'TIMESTAMP(0) WITH TIME ZONE'; | ||
238 | } | ||
239 | |||
240 | /** | ||
241 | * {@inheritDoc} | ||
242 | */ | ||
243 | public function getDateTypeDeclarationSQL(array $column): string | ||
244 | { | ||
245 | return 'DATE'; | ||
246 | } | ||
247 | |||
248 | /** | ||
249 | * {@inheritDoc} | ||
250 | */ | ||
251 | public function getTimeTypeDeclarationSQL(array $column): string | ||
252 | { | ||
253 | return 'DATE'; | ||
254 | } | ||
255 | |||
256 | /** | ||
257 | * {@inheritDoc} | ||
258 | */ | ||
259 | protected function _getCommonIntegerTypeDeclarationSQL(array $column): string | ||
260 | { | ||
261 | return ''; | ||
262 | } | ||
263 | |||
264 | protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string | ||
265 | { | ||
266 | if ($length === null) { | ||
267 | throw ColumnLengthRequired::new($this, 'VARCHAR2'); | ||
268 | } | ||
269 | |||
270 | return sprintf('VARCHAR2(%d)', $length); | ||
271 | } | ||
272 | |||
273 | protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string | ||
274 | { | ||
275 | if ($length === null) { | ||
276 | throw ColumnLengthRequired::new($this, 'RAW'); | ||
277 | } | ||
278 | |||
279 | return sprintf('RAW(%d)', $length); | ||
280 | } | ||
281 | |||
282 | protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string | ||
283 | { | ||
284 | return $this->getBinaryTypeDeclarationSQLSnippet($length); | ||
285 | } | ||
286 | |||
287 | /** | ||
288 | * {@inheritDoc} | ||
289 | */ | ||
290 | public function getClobTypeDeclarationSQL(array $column): string | ||
291 | { | ||
292 | return 'CLOB'; | ||
293 | } | ||
294 | |||
295 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
296 | public function getListDatabasesSQL(): string | ||
297 | { | ||
298 | return 'SELECT username FROM all_users'; | ||
299 | } | ||
300 | |||
301 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
302 | public function getListSequencesSQL(string $database): string | ||
303 | { | ||
304 | return 'SELECT SEQUENCE_NAME, MIN_VALUE, INCREMENT_BY FROM SYS.ALL_SEQUENCES WHERE SEQUENCE_OWNER = ' | ||
305 | . $this->quoteStringLiteral( | ||
306 | $this->normalizeIdentifier($database)->getName(), | ||
307 | ); | ||
308 | } | ||
309 | |||
310 | /** | ||
311 | * {@inheritDoc} | ||
312 | */ | ||
313 | protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array | ||
314 | { | ||
315 | $indexes = $options['indexes'] ?? []; | ||
316 | $options['indexes'] = []; | ||
317 | $sql = parent::_getCreateTableSQL($name, $columns, $options); | ||
318 | |||
319 | foreach ($columns as $column) { | ||
320 | if (isset($column['sequence'])) { | ||
321 | $sql[] = $this->getCreateSequenceSQL($column['sequence']); | ||
322 | } | ||
323 | |||
324 | if ( | ||
325 | empty($column['autoincrement']) | ||
326 | ) { | ||
327 | continue; | ||
328 | } | ||
329 | |||
330 | $sql = array_merge($sql, $this->getCreateAutoincrementSql($column['name'], $name)); | ||
331 | } | ||
332 | |||
333 | foreach ($indexes as $index) { | ||
334 | $sql[] = $this->getCreateIndexSQL($index, $name); | ||
335 | } | ||
336 | |||
337 | return $sql; | ||
338 | } | ||
339 | |||
340 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
341 | public function getListViewsSQL(string $database): string | ||
342 | { | ||
343 | return 'SELECT view_name, text FROM sys.user_views'; | ||
344 | } | ||
345 | |||
346 | /** @return array<int, string> */ | ||
347 | protected function getCreateAutoincrementSql(string $name, string $table, int $start = 1): array | ||
348 | { | ||
349 | $tableIdentifier = $this->normalizeIdentifier($table); | ||
350 | $quotedTableName = $tableIdentifier->getQuotedName($this); | ||
351 | $unquotedTableName = $tableIdentifier->getName(); | ||
352 | |||
353 | $nameIdentifier = $this->normalizeIdentifier($name); | ||
354 | $quotedName = $nameIdentifier->getQuotedName($this); | ||
355 | $unquotedName = $nameIdentifier->getName(); | ||
356 | |||
357 | $sql = []; | ||
358 | |||
359 | $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier); | ||
360 | |||
361 | $idx = new Index($autoincrementIdentifierName, [$quotedName], true, true); | ||
362 | |||
363 | $sql[] = "DECLARE | ||
364 | constraints_Count NUMBER; | ||
365 | BEGIN | ||
366 | SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count | ||
367 | FROM USER_CONSTRAINTS | ||
368 | WHERE TABLE_NAME = '" . $unquotedTableName . "' | ||
369 | AND CONSTRAINT_TYPE = 'P'; | ||
370 | IF constraints_Count = 0 OR constraints_Count = '' THEN | ||
371 | EXECUTE IMMEDIATE '" . $this->getCreateIndexSQL($idx, $quotedTableName) . "'; | ||
372 | END IF; | ||
373 | END;"; | ||
374 | |||
375 | $sequenceName = $this->getIdentitySequenceName( | ||
376 | $tableIdentifier->isQuoted() ? $quotedTableName : $unquotedTableName, | ||
377 | ); | ||
378 | $sequence = new Sequence($sequenceName, $start); | ||
379 | $sql[] = $this->getCreateSequenceSQL($sequence); | ||
380 | |||
381 | $sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . ' | ||
382 | BEFORE INSERT | ||
383 | ON ' . $quotedTableName . ' | ||
384 | FOR EACH ROW | ||
385 | DECLARE | ||
386 | last_Sequence NUMBER; | ||
387 | last_InsertID NUMBER; | ||
388 | BEGIN | ||
389 | IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.' . $quotedName . ' = 0) THEN | ||
390 | SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL; | ||
391 | ELSE | ||
392 | SELECT NVL(Last_Number, 0) INTO last_Sequence | ||
393 | FROM User_Sequences | ||
394 | WHERE Sequence_Name = \'' . $sequence->getName() . '\'; | ||
395 | SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL; | ||
396 | WHILE (last_InsertID > last_Sequence) LOOP | ||
397 | SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; | ||
398 | END LOOP; | ||
399 | SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; | ||
400 | END IF; | ||
401 | END;'; | ||
402 | |||
403 | return $sql; | ||
404 | } | ||
405 | |||
406 | /** | ||
407 | * @internal The method should be only used from within the OracleSchemaManager class hierarchy. | ||
408 | * | ||
409 | * Returns the SQL statements to drop the autoincrement for the given table name. | ||
410 | * | ||
411 | * @param string $table The table name to drop the autoincrement for. | ||
412 | * | ||
413 | * @return string[] | ||
414 | */ | ||
415 | public function getDropAutoincrementSql(string $table): array | ||
416 | { | ||
417 | $table = $this->normalizeIdentifier($table); | ||
418 | $autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table); | ||
419 | $identitySequenceName = $this->getIdentitySequenceName( | ||
420 | $table->isQuoted() ? $table->getQuotedName($this) : $table->getName(), | ||
421 | ); | ||
422 | |||
423 | return [ | ||
424 | 'DROP TRIGGER ' . $autoincrementIdentifierName, | ||
425 | $this->getDropSequenceSQL($identitySequenceName), | ||
426 | $this->getDropConstraintSQL($autoincrementIdentifierName, $table->getQuotedName($this)), | ||
427 | ]; | ||
428 | } | ||
429 | |||
430 | /** | ||
431 | * Normalizes the given identifier. | ||
432 | * | ||
433 | * Uppercases the given identifier if it is not quoted by intention | ||
434 | * to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers. | ||
435 | * | ||
436 | * @param string $name The identifier to normalize. | ||
437 | */ | ||
438 | private function normalizeIdentifier(string $name): Identifier | ||
439 | { | ||
440 | $identifier = new Identifier($name); | ||
441 | |||
442 | return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name)); | ||
443 | } | ||
444 | |||
445 | /** | ||
446 | * Adds suffix to identifier, | ||
447 | * | ||
448 | * if the new string exceeds max identifier length, | ||
449 | * keeps $suffix, cuts from $identifier as much as the part exceeding. | ||
450 | */ | ||
451 | private function addSuffix(string $identifier, string $suffix): string | ||
452 | { | ||
453 | $maxPossibleLengthWithoutSuffix = $this->getMaxIdentifierLength() - strlen($suffix); | ||
454 | if (strlen($identifier) > $maxPossibleLengthWithoutSuffix) { | ||
455 | $identifier = substr($identifier, 0, $maxPossibleLengthWithoutSuffix); | ||
456 | } | ||
457 | |||
458 | return $identifier . $suffix; | ||
459 | } | ||
460 | |||
461 | /** | ||
462 | * Returns the autoincrement primary key identifier name for the given table identifier. | ||
463 | * | ||
464 | * Quotes the autoincrement primary key identifier name | ||
465 | * if the given table name is quoted by intention. | ||
466 | */ | ||
467 | private function getAutoincrementIdentifierName(Identifier $table): string | ||
468 | { | ||
469 | $identifierName = $this->addSuffix($table->getName(), '_AI_PK'); | ||
470 | |||
471 | return $table->isQuoted() | ||
472 | ? $this->quoteSingleIdentifier($identifierName) | ||
473 | : $identifierName; | ||
474 | } | ||
475 | |||
476 | public function getDropForeignKeySQL(string $foreignKey, string $table): string | ||
477 | { | ||
478 | return $this->getDropConstraintSQL($foreignKey, $table); | ||
479 | } | ||
480 | |||
481 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
482 | public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string | ||
483 | { | ||
484 | $referentialAction = ''; | ||
485 | |||
486 | if ($foreignKey->hasOption('onDelete')) { | ||
487 | $referentialAction = $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); | ||
488 | } | ||
489 | |||
490 | if ($referentialAction !== '') { | ||
491 | return ' ON DELETE ' . $referentialAction; | ||
492 | } | ||
493 | |||
494 | return ''; | ||
495 | } | ||
496 | |||
497 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
498 | public function getForeignKeyReferentialActionSQL(string $action): string | ||
499 | { | ||
500 | $action = strtoupper($action); | ||
501 | |||
502 | return match ($action) { | ||
503 | 'RESTRICT', | ||
504 | 'NO ACTION' => '', | ||
505 | 'CASCADE', | ||
506 | 'SET NULL' => $action, | ||
507 | default => throw new InvalidArgumentException(sprintf('Invalid foreign key action "%s".', $action)), | ||
508 | }; | ||
509 | } | ||
510 | |||
511 | public function getCreateDatabaseSQL(string $name): string | ||
512 | { | ||
513 | return 'CREATE USER ' . $name; | ||
514 | } | ||
515 | |||
516 | public function getDropDatabaseSQL(string $name): string | ||
517 | { | ||
518 | return 'DROP USER ' . $name . ' CASCADE'; | ||
519 | } | ||
520 | |||
521 | /** | ||
522 | * {@inheritDoc} | ||
523 | */ | ||
524 | public function getAlterTableSQL(TableDiff $diff): array | ||
525 | { | ||
526 | $sql = []; | ||
527 | $commentsSQL = []; | ||
528 | $columnSql = []; | ||
529 | |||
530 | $addColumnSQL = []; | ||
531 | |||
532 | $tableNameSQL = $diff->getOldTable()->getQuotedName($this); | ||
533 | |||
534 | foreach ($diff->getAddedColumns() as $column) { | ||
535 | $addColumnSQL[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); | ||
536 | $comment = $column->getComment(); | ||
537 | |||
538 | if ($comment === '') { | ||
539 | continue; | ||
540 | } | ||
541 | |||
542 | $commentsSQL[] = $this->getCommentOnColumnSQL( | ||
543 | $tableNameSQL, | ||
544 | $column->getQuotedName($this), | ||
545 | $comment, | ||
546 | ); | ||
547 | } | ||
548 | |||
549 | if (count($addColumnSQL) > 0) { | ||
550 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ADD (' . implode(', ', $addColumnSQL) . ')'; | ||
551 | } | ||
552 | |||
553 | $modifyColumnSQL = []; | ||
554 | foreach ($diff->getModifiedColumns() as $columnDiff) { | ||
555 | $newColumn = $columnDiff->getNewColumn(); | ||
556 | $oldColumn = $columnDiff->getOldColumn(); | ||
557 | |||
558 | $newColumnProperties = $newColumn->toArray(); | ||
559 | $oldColumnProperties = $oldColumn->toArray(); | ||
560 | |||
561 | $oldSQL = $this->getColumnDeclarationSQL('', $oldColumnProperties); | ||
562 | $newSQL = $this->getColumnDeclarationSQL('', $newColumnProperties); | ||
563 | |||
564 | if ($newSQL !== $oldSQL) { | ||
565 | if (! $columnDiff->hasNotNullChanged()) { | ||
566 | unset($newColumnProperties['notnull']); | ||
567 | $newSQL = $this->getColumnDeclarationSQL('', $newColumnProperties); | ||
568 | } | ||
569 | |||
570 | $modifyColumnSQL[] = $newColumn->getQuotedName($this) . $newSQL; | ||
571 | } | ||
572 | |||
573 | if (! $columnDiff->hasCommentChanged()) { | ||
574 | continue; | ||
575 | } | ||
576 | |||
577 | $commentsSQL[] = $this->getCommentOnColumnSQL( | ||
578 | $tableNameSQL, | ||
579 | $newColumn->getQuotedName($this), | ||
580 | $newColumn->getComment(), | ||
581 | ); | ||
582 | } | ||
583 | |||
584 | if (count($modifyColumnSQL) > 0) { | ||
585 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' MODIFY (' . implode(', ', $modifyColumnSQL) . ')'; | ||
586 | } | ||
587 | |||
588 | foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { | ||
589 | $oldColumnName = new Identifier($oldColumnName); | ||
590 | |||
591 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) | ||
592 | . ' TO ' . $column->getQuotedName($this); | ||
593 | } | ||
594 | |||
595 | $dropColumnSQL = []; | ||
596 | foreach ($diff->getDroppedColumns() as $column) { | ||
597 | $dropColumnSQL[] = $column->getQuotedName($this); | ||
598 | } | ||
599 | |||
600 | if (count($dropColumnSQL) > 0) { | ||
601 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' DROP (' . implode(', ', $dropColumnSQL) . ')'; | ||
602 | } | ||
603 | |||
604 | return array_merge( | ||
605 | $this->getPreAlterTableIndexForeignKeySQL($diff), | ||
606 | $sql, | ||
607 | $commentsSQL, | ||
608 | $this->getPostAlterTableIndexForeignKeySQL($diff), | ||
609 | $columnSql, | ||
610 | ); | ||
611 | } | ||
612 | |||
613 | /** | ||
614 | * {@inheritDoc} | ||
615 | * | ||
616 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
617 | */ | ||
618 | public function getColumnDeclarationSQL(string $name, array $column): string | ||
619 | { | ||
620 | if (isset($column['columnDefinition'])) { | ||
621 | $declaration = $column['columnDefinition']; | ||
622 | } else { | ||
623 | $default = $this->getDefaultValueDeclarationSQL($column); | ||
624 | |||
625 | $notnull = ''; | ||
626 | |||
627 | if (isset($column['notnull'])) { | ||
628 | $notnull = $column['notnull'] ? ' NOT NULL' : ' NULL'; | ||
629 | } | ||
630 | |||
631 | $typeDecl = $column['type']->getSQLDeclaration($column, $this); | ||
632 | $declaration = $typeDecl . $default . $notnull; | ||
633 | } | ||
634 | |||
635 | return $name . ' ' . $declaration; | ||
636 | } | ||
637 | |||
638 | /** | ||
639 | * {@inheritDoc} | ||
640 | */ | ||
641 | protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array | ||
642 | { | ||
643 | if (str_contains($tableName, '.')) { | ||
644 | [$schema] = explode('.', $tableName); | ||
645 | $oldIndexName = $schema . '.' . $oldIndexName; | ||
646 | } | ||
647 | |||
648 | return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; | ||
649 | } | ||
650 | |||
651 | protected function getIdentitySequenceName(string $tableName): string | ||
652 | { | ||
653 | $table = new Identifier($tableName); | ||
654 | |||
655 | // No usage of column name to preserve BC compatibility with <2.5 | ||
656 | $identitySequenceName = $this->addSuffix($table->getName(), '_SEQ'); | ||
657 | |||
658 | if ($table->isQuoted()) { | ||
659 | $identitySequenceName = '"' . $identitySequenceName . '"'; | ||
660 | } | ||
661 | |||
662 | $identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName); | ||
663 | |||
664 | return $identitySequenceIdentifier->getQuotedName($this); | ||
665 | } | ||
666 | |||
667 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
668 | public function supportsCommentOnStatement(): bool | ||
669 | { | ||
670 | return true; | ||
671 | } | ||
672 | |||
673 | protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string | ||
674 | { | ||
675 | if ($offset > 0) { | ||
676 | $query .= sprintf(' OFFSET %d ROWS', $offset); | ||
677 | } | ||
678 | |||
679 | if ($limit !== null) { | ||
680 | $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit); | ||
681 | } | ||
682 | |||
683 | return $query; | ||
684 | } | ||
685 | |||
686 | public function getCreateTemporaryTableSnippetSQL(): string | ||
687 | { | ||
688 | return 'CREATE GLOBAL TEMPORARY TABLE'; | ||
689 | } | ||
690 | |||
691 | public function getDateTimeTzFormatString(): string | ||
692 | { | ||
693 | return 'Y-m-d H:i:sP'; | ||
694 | } | ||
695 | |||
696 | public function getDateFormatString(): string | ||
697 | { | ||
698 | return 'Y-m-d 00:00:00'; | ||
699 | } | ||
700 | |||
701 | public function getTimeFormatString(): string | ||
702 | { | ||
703 | return '1900-01-01 H:i:s'; | ||
704 | } | ||
705 | |||
706 | public function getMaxIdentifierLength(): int | ||
707 | { | ||
708 | return 128; | ||
709 | } | ||
710 | |||
711 | public function supportsSequences(): bool | ||
712 | { | ||
713 | return true; | ||
714 | } | ||
715 | |||
716 | public function supportsReleaseSavepoints(): bool | ||
717 | { | ||
718 | return false; | ||
719 | } | ||
720 | |||
721 | public function getTruncateTableSQL(string $tableName, bool $cascade = false): string | ||
722 | { | ||
723 | $tableIdentifier = new Identifier($tableName); | ||
724 | |||
725 | return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); | ||
726 | } | ||
727 | |||
728 | public function getDummySelectSQL(string $expression = '1'): string | ||
729 | { | ||
730 | return sprintf('SELECT %s FROM DUAL', $expression); | ||
731 | } | ||
732 | |||
733 | protected function initializeDoctrineTypeMappings(): void | ||
734 | { | ||
735 | $this->doctrineTypeMapping = [ | ||
736 | 'binary_double' => Types::FLOAT, | ||
737 | 'binary_float' => Types::FLOAT, | ||
738 | 'binary_integer' => Types::BOOLEAN, | ||
739 | 'blob' => Types::BLOB, | ||
740 | 'char' => Types::STRING, | ||
741 | 'clob' => Types::TEXT, | ||
742 | 'date' => Types::DATE_MUTABLE, | ||
743 | 'float' => Types::FLOAT, | ||
744 | 'integer' => Types::INTEGER, | ||
745 | 'long' => Types::STRING, | ||
746 | 'long raw' => Types::BLOB, | ||
747 | 'nchar' => Types::STRING, | ||
748 | 'nclob' => Types::TEXT, | ||
749 | 'number' => Types::INTEGER, | ||
750 | 'nvarchar2' => Types::STRING, | ||
751 | 'pls_integer' => Types::BOOLEAN, | ||
752 | 'raw' => Types::BINARY, | ||
753 | 'rowid' => Types::STRING, | ||
754 | 'timestamp' => Types::DATETIME_MUTABLE, | ||
755 | 'timestamptz' => Types::DATETIMETZ_MUTABLE, | ||
756 | 'urowid' => Types::STRING, | ||
757 | 'varchar' => Types::STRING, | ||
758 | 'varchar2' => Types::STRING, | ||
759 | ]; | ||
760 | } | ||
761 | |||
762 | public function releaseSavePoint(string $savepoint): string | ||
763 | { | ||
764 | return ''; | ||
765 | } | ||
766 | |||
767 | protected function createReservedKeywordsList(): KeywordList | ||
768 | { | ||
769 | return new OracleKeywords(); | ||
770 | } | ||
771 | |||
772 | /** | ||
773 | * {@inheritDoc} | ||
774 | */ | ||
775 | public function getBlobTypeDeclarationSQL(array $column): string | ||
776 | { | ||
777 | return 'BLOB'; | ||
778 | } | ||
779 | |||
780 | public function createSchemaManager(Connection $connection): OracleSchemaManager | ||
781 | { | ||
782 | return new OracleSchemaManager($connection, $this); | ||
783 | } | ||
784 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/PostgreSQLPlatform.php b/vendor/doctrine/dbal/src/Platforms/PostgreSQLPlatform.php new file mode 100644 index 0000000..166ee75 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/PostgreSQLPlatform.php | |||
@@ -0,0 +1,784 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | use Doctrine\DBAL\Connection; | ||
8 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
9 | use Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords; | ||
10 | use Doctrine\DBAL\Schema\ForeignKeyConstraint; | ||
11 | use Doctrine\DBAL\Schema\Identifier; | ||
12 | use Doctrine\DBAL\Schema\Index; | ||
13 | use Doctrine\DBAL\Schema\PostgreSQLSchemaManager; | ||
14 | use Doctrine\DBAL\Schema\Sequence; | ||
15 | use Doctrine\DBAL\Schema\TableDiff; | ||
16 | use Doctrine\DBAL\TransactionIsolationLevel; | ||
17 | use Doctrine\DBAL\Types\Types; | ||
18 | use UnexpectedValueException; | ||
19 | |||
20 | use function array_merge; | ||
21 | use function array_unique; | ||
22 | use function array_values; | ||
23 | use function explode; | ||
24 | use function implode; | ||
25 | use function in_array; | ||
26 | use function is_array; | ||
27 | use function is_bool; | ||
28 | use function is_numeric; | ||
29 | use function is_string; | ||
30 | use function sprintf; | ||
31 | use function str_contains; | ||
32 | use function strtolower; | ||
33 | use function trim; | ||
34 | |||
35 | /** | ||
36 | * Provides the behavior, features and SQL dialect of the PostgreSQL 9.4+ database platform. | ||
37 | */ | ||
38 | class PostgreSQLPlatform extends AbstractPlatform | ||
39 | { | ||
40 | private bool $useBooleanTrueFalseStrings = true; | ||
41 | |||
42 | /** @var string[][] PostgreSQL booleans literals */ | ||
43 | private array $booleanLiterals = [ | ||
44 | 'true' => [ | ||
45 | 't', | ||
46 | 'true', | ||
47 | 'y', | ||
48 | 'yes', | ||
49 | 'on', | ||
50 | '1', | ||
51 | ], | ||
52 | 'false' => [ | ||
53 | 'f', | ||
54 | 'false', | ||
55 | 'n', | ||
56 | 'no', | ||
57 | 'off', | ||
58 | '0', | ||
59 | ], | ||
60 | ]; | ||
61 | |||
62 | /** | ||
63 | * PostgreSQL has different behavior with some drivers | ||
64 | * with regard to how booleans have to be handled. | ||
65 | * | ||
66 | * Enables use of 'true'/'false' or otherwise 1 and 0 instead. | ||
67 | */ | ||
68 | public function setUseBooleanTrueFalseStrings(bool $flag): void | ||
69 | { | ||
70 | $this->useBooleanTrueFalseStrings = $flag; | ||
71 | } | ||
72 | |||
73 | public function getRegexpExpression(): string | ||
74 | { | ||
75 | return 'SIMILAR TO'; | ||
76 | } | ||
77 | |||
78 | public function getLocateExpression(string $string, string $substring, ?string $start = null): string | ||
79 | { | ||
80 | if ($start !== null) { | ||
81 | $string = $this->getSubstringExpression($string, $start); | ||
82 | |||
83 | return 'CASE WHEN (POSITION(' . $substring . ' IN ' . $string . ') = 0) THEN 0' | ||
84 | . ' ELSE (POSITION(' . $substring . ' IN ' . $string . ') + ' . $start . ' - 1) END'; | ||
85 | } | ||
86 | |||
87 | return sprintf('POSITION(%s IN %s)', $substring, $string); | ||
88 | } | ||
89 | |||
90 | protected function getDateArithmeticIntervalExpression( | ||
91 | string $date, | ||
92 | string $operator, | ||
93 | string $interval, | ||
94 | DateIntervalUnit $unit, | ||
95 | ): string { | ||
96 | if ($unit === DateIntervalUnit::QUARTER) { | ||
97 | $interval = $this->multiplyInterval($interval, 3); | ||
98 | $unit = DateIntervalUnit::MONTH; | ||
99 | } | ||
100 | |||
101 | return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit->value . "')::interval)"; | ||
102 | } | ||
103 | |||
104 | public function getDateDiffExpression(string $date1, string $date2): string | ||
105 | { | ||
106 | return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))'; | ||
107 | } | ||
108 | |||
109 | public function getCurrentDatabaseExpression(): string | ||
110 | { | ||
111 | return 'CURRENT_DATABASE()'; | ||
112 | } | ||
113 | |||
114 | public function supportsSequences(): bool | ||
115 | { | ||
116 | return true; | ||
117 | } | ||
118 | |||
119 | public function supportsSchemas(): bool | ||
120 | { | ||
121 | return true; | ||
122 | } | ||
123 | |||
124 | public function supportsIdentityColumns(): bool | ||
125 | { | ||
126 | return true; | ||
127 | } | ||
128 | |||
129 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
130 | public function supportsPartialIndexes(): bool | ||
131 | { | ||
132 | return true; | ||
133 | } | ||
134 | |||
135 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
136 | public function supportsCommentOnStatement(): bool | ||
137 | { | ||
138 | return true; | ||
139 | } | ||
140 | |||
141 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
142 | public function getListDatabasesSQL(): string | ||
143 | { | ||
144 | return 'SELECT datname FROM pg_database'; | ||
145 | } | ||
146 | |||
147 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
148 | public function getListSequencesSQL(string $database): string | ||
149 | { | ||
150 | return 'SELECT sequence_name AS relname, | ||
151 | sequence_schema AS schemaname, | ||
152 | minimum_value AS min_value, | ||
153 | increment AS increment_by | ||
154 | FROM information_schema.sequences | ||
155 | WHERE sequence_catalog = ' . $this->quoteStringLiteral($database) . " | ||
156 | AND sequence_schema NOT LIKE 'pg\_%' | ||
157 | AND sequence_schema != 'information_schema'"; | ||
158 | } | ||
159 | |||
160 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
161 | public function getListViewsSQL(string $database): string | ||
162 | { | ||
163 | return 'SELECT quote_ident(table_name) AS viewname, | ||
164 | table_schema AS schemaname, | ||
165 | view_definition AS definition | ||
166 | FROM information_schema.views | ||
167 | WHERE view_definition IS NOT NULL'; | ||
168 | } | ||
169 | |||
170 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
171 | public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string | ||
172 | { | ||
173 | $query = ''; | ||
174 | |||
175 | if ($foreignKey->hasOption('match')) { | ||
176 | $query .= ' MATCH ' . $foreignKey->getOption('match'); | ||
177 | } | ||
178 | |||
179 | $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); | ||
180 | |||
181 | if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) { | ||
182 | $query .= ' DEFERRABLE'; | ||
183 | } else { | ||
184 | $query .= ' NOT DEFERRABLE'; | ||
185 | } | ||
186 | |||
187 | if ( | ||
188 | $foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false | ||
189 | ) { | ||
190 | $query .= ' INITIALLY DEFERRED'; | ||
191 | } else { | ||
192 | $query .= ' INITIALLY IMMEDIATE'; | ||
193 | } | ||
194 | |||
195 | return $query; | ||
196 | } | ||
197 | |||
198 | /** | ||
199 | * {@inheritDoc} | ||
200 | */ | ||
201 | public function getAlterTableSQL(TableDiff $diff): array | ||
202 | { | ||
203 | $sql = []; | ||
204 | $commentsSQL = []; | ||
205 | $columnSql = []; | ||
206 | |||
207 | $table = $diff->getOldTable(); | ||
208 | |||
209 | $tableNameSQL = $table->getQuotedName($this); | ||
210 | |||
211 | foreach ($diff->getAddedColumns() as $addedColumn) { | ||
212 | $query = 'ADD ' . $this->getColumnDeclarationSQL( | ||
213 | $addedColumn->getQuotedName($this), | ||
214 | $addedColumn->toArray(), | ||
215 | ); | ||
216 | |||
217 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; | ||
218 | |||
219 | $comment = $addedColumn->getComment(); | ||
220 | |||
221 | if ($comment === '') { | ||
222 | continue; | ||
223 | } | ||
224 | |||
225 | $commentsSQL[] = $this->getCommentOnColumnSQL( | ||
226 | $tableNameSQL, | ||
227 | $addedColumn->getQuotedName($this), | ||
228 | $comment, | ||
229 | ); | ||
230 | } | ||
231 | |||
232 | foreach ($diff->getDroppedColumns() as $droppedColumn) { | ||
233 | $query = 'DROP ' . $droppedColumn->getQuotedName($this); | ||
234 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; | ||
235 | } | ||
236 | |||
237 | foreach ($diff->getModifiedColumns() as $columnDiff) { | ||
238 | $oldColumn = $columnDiff->getOldColumn(); | ||
239 | $newColumn = $columnDiff->getNewColumn(); | ||
240 | |||
241 | $oldColumnName = $oldColumn->getQuotedName($this); | ||
242 | |||
243 | if ( | ||
244 | $columnDiff->hasTypeChanged() | ||
245 | || $columnDiff->hasPrecisionChanged() | ||
246 | || $columnDiff->hasScaleChanged() | ||
247 | || $columnDiff->hasFixedChanged() | ||
248 | ) { | ||
249 | $type = $newColumn->getType(); | ||
250 | |||
251 | // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type | ||
252 | $columnDefinition = $newColumn->toArray(); | ||
253 | $columnDefinition['autoincrement'] = false; | ||
254 | |||
255 | // here was a server version check before, but DBAL API does not support this anymore. | ||
256 | $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this); | ||
257 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; | ||
258 | } | ||
259 | |||
260 | if ($columnDiff->hasDefaultChanged()) { | ||
261 | $defaultClause = $newColumn->getDefault() === null | ||
262 | ? ' DROP DEFAULT' | ||
263 | : ' SET' . $this->getDefaultValueDeclarationSQL($newColumn->toArray()); | ||
264 | |||
265 | $query = 'ALTER ' . $oldColumnName . $defaultClause; | ||
266 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; | ||
267 | } | ||
268 | |||
269 | if ($columnDiff->hasNotNullChanged()) { | ||
270 | $query = 'ALTER ' . $oldColumnName . ' ' . ($newColumn->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL'; | ||
271 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; | ||
272 | } | ||
273 | |||
274 | if ($columnDiff->hasAutoIncrementChanged()) { | ||
275 | if ($newColumn->getAutoincrement()) { | ||
276 | $query = 'ADD GENERATED BY DEFAULT AS IDENTITY'; | ||
277 | } else { | ||
278 | $query = 'DROP IDENTITY'; | ||
279 | } | ||
280 | |||
281 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ALTER ' . $oldColumnName . ' ' . $query; | ||
282 | } | ||
283 | |||
284 | $newComment = $newColumn->getComment(); | ||
285 | $oldComment = $columnDiff->getOldColumn()->getComment(); | ||
286 | |||
287 | if ($columnDiff->hasCommentChanged() || $oldComment !== $newComment) { | ||
288 | $commentsSQL[] = $this->getCommentOnColumnSQL( | ||
289 | $tableNameSQL, | ||
290 | $newColumn->getQuotedName($this), | ||
291 | $newComment, | ||
292 | ); | ||
293 | } | ||
294 | |||
295 | if (! $columnDiff->hasLengthChanged()) { | ||
296 | continue; | ||
297 | } | ||
298 | |||
299 | $query = 'ALTER ' . $oldColumnName . ' TYPE ' | ||
300 | . $newColumn->getType()->getSQLDeclaration($newColumn->toArray(), $this); | ||
301 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; | ||
302 | } | ||
303 | |||
304 | foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { | ||
305 | $oldColumnName = new Identifier($oldColumnName); | ||
306 | |||
307 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) | ||
308 | . ' TO ' . $column->getQuotedName($this); | ||
309 | } | ||
310 | |||
311 | return array_merge( | ||
312 | $this->getPreAlterTableIndexForeignKeySQL($diff), | ||
313 | $sql, | ||
314 | $commentsSQL, | ||
315 | $this->getPostAlterTableIndexForeignKeySQL($diff), | ||
316 | $columnSql, | ||
317 | ); | ||
318 | } | ||
319 | |||
320 | /** | ||
321 | * {@inheritDoc} | ||
322 | */ | ||
323 | protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array | ||
324 | { | ||
325 | if (str_contains($tableName, '.')) { | ||
326 | [$schema] = explode('.', $tableName); | ||
327 | $oldIndexName = $schema . '.' . $oldIndexName; | ||
328 | } | ||
329 | |||
330 | return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)]; | ||
331 | } | ||
332 | |||
333 | public function getCreateSequenceSQL(Sequence $sequence): string | ||
334 | { | ||
335 | return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . | ||
336 | ' INCREMENT BY ' . $sequence->getAllocationSize() . | ||
337 | ' MINVALUE ' . $sequence->getInitialValue() . | ||
338 | ' START ' . $sequence->getInitialValue() . | ||
339 | $this->getSequenceCacheSQL($sequence); | ||
340 | } | ||
341 | |||
342 | public function getAlterSequenceSQL(Sequence $sequence): string | ||
343 | { | ||
344 | return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . | ||
345 | ' INCREMENT BY ' . $sequence->getAllocationSize() . | ||
346 | $this->getSequenceCacheSQL($sequence); | ||
347 | } | ||
348 | |||
349 | /** | ||
350 | * Cache definition for sequences | ||
351 | */ | ||
352 | private function getSequenceCacheSQL(Sequence $sequence): string | ||
353 | { | ||
354 | if ($sequence->getCache() > 1) { | ||
355 | return ' CACHE ' . $sequence->getCache(); | ||
356 | } | ||
357 | |||
358 | return ''; | ||
359 | } | ||
360 | |||
361 | public function getDropSequenceSQL(string $name): string | ||
362 | { | ||
363 | return parent::getDropSequenceSQL($name) . ' CASCADE'; | ||
364 | } | ||
365 | |||
366 | public function getDropForeignKeySQL(string $foreignKey, string $table): string | ||
367 | { | ||
368 | return $this->getDropConstraintSQL($foreignKey, $table); | ||
369 | } | ||
370 | |||
371 | public function getDropIndexSQL(string $name, string $table): string | ||
372 | { | ||
373 | if ($name === '"primary"') { | ||
374 | $constraintName = $table . '_pkey'; | ||
375 | |||
376 | return $this->getDropConstraintSQL($constraintName, $table); | ||
377 | } | ||
378 | |||
379 | return parent::getDropIndexSQL($name, $table); | ||
380 | } | ||
381 | |||
382 | /** | ||
383 | * {@inheritDoc} | ||
384 | */ | ||
385 | protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array | ||
386 | { | ||
387 | $queryFields = $this->getColumnDeclarationListSQL($columns); | ||
388 | |||
389 | if (isset($options['primary']) && ! empty($options['primary'])) { | ||
390 | $keyColumns = array_unique(array_values($options['primary'])); | ||
391 | $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; | ||
392 | } | ||
393 | |||
394 | $unlogged = isset($options['unlogged']) && $options['unlogged'] === true ? ' UNLOGGED' : ''; | ||
395 | |||
396 | $query = 'CREATE' . $unlogged . ' TABLE ' . $name . ' (' . $queryFields . ')'; | ||
397 | |||
398 | $sql = [$query]; | ||
399 | |||
400 | if (isset($options['indexes']) && ! empty($options['indexes'])) { | ||
401 | foreach ($options['indexes'] as $index) { | ||
402 | $sql[] = $this->getCreateIndexSQL($index, $name); | ||
403 | } | ||
404 | } | ||
405 | |||
406 | if (isset($options['uniqueConstraints'])) { | ||
407 | foreach ($options['uniqueConstraints'] as $uniqueConstraint) { | ||
408 | $sql[] = $this->getCreateUniqueConstraintSQL($uniqueConstraint, $name); | ||
409 | } | ||
410 | } | ||
411 | |||
412 | if (isset($options['foreignKeys'])) { | ||
413 | foreach ($options['foreignKeys'] as $definition) { | ||
414 | $sql[] = $this->getCreateForeignKeySQL($definition, $name); | ||
415 | } | ||
416 | } | ||
417 | |||
418 | return $sql; | ||
419 | } | ||
420 | |||
421 | /** | ||
422 | * Converts a single boolean value. | ||
423 | * | ||
424 | * First converts the value to its native PHP boolean type | ||
425 | * and passes it to the given callback function to be reconverted | ||
426 | * into any custom representation. | ||
427 | * | ||
428 | * @param mixed $value The value to convert. | ||
429 | * @param callable $callback The callback function to use for converting the real boolean value. | ||
430 | * | ||
431 | * @throws UnexpectedValueException | ||
432 | */ | ||
433 | private function convertSingleBooleanValue(mixed $value, callable $callback): mixed | ||
434 | { | ||
435 | if ($value === null) { | ||
436 | return $callback(null); | ||
437 | } | ||
438 | |||
439 | if (is_bool($value) || is_numeric($value)) { | ||
440 | return $callback((bool) $value); | ||
441 | } | ||
442 | |||
443 | if (! is_string($value)) { | ||
444 | return $callback(true); | ||
445 | } | ||
446 | |||
447 | /** | ||
448 | * Better safe than sorry: http://php.net/in_array#106319 | ||
449 | */ | ||
450 | if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) { | ||
451 | return $callback(false); | ||
452 | } | ||
453 | |||
454 | if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) { | ||
455 | return $callback(true); | ||
456 | } | ||
457 | |||
458 | throw new UnexpectedValueException(sprintf( | ||
459 | 'Unrecognized boolean literal, %s given.', | ||
460 | $value, | ||
461 | )); | ||
462 | } | ||
463 | |||
464 | /** | ||
465 | * Converts one or multiple boolean values. | ||
466 | * | ||
467 | * First converts the value(s) to their native PHP boolean type | ||
468 | * and passes them to the given callback function to be reconverted | ||
469 | * into any custom representation. | ||
470 | * | ||
471 | * @param mixed $item The value(s) to convert. | ||
472 | * @param callable $callback The callback function to use for converting the real boolean value(s). | ||
473 | */ | ||
474 | private function doConvertBooleans(mixed $item, callable $callback): mixed | ||
475 | { | ||
476 | if (is_array($item)) { | ||
477 | foreach ($item as $key => $value) { | ||
478 | $item[$key] = $this->convertSingleBooleanValue($value, $callback); | ||
479 | } | ||
480 | |||
481 | return $item; | ||
482 | } | ||
483 | |||
484 | return $this->convertSingleBooleanValue($item, $callback); | ||
485 | } | ||
486 | |||
487 | /** | ||
488 | * {@inheritDoc} | ||
489 | * | ||
490 | * Postgres wants boolean values converted to the strings 'true'/'false'. | ||
491 | */ | ||
492 | public function convertBooleans(mixed $item): mixed | ||
493 | { | ||
494 | if (! $this->useBooleanTrueFalseStrings) { | ||
495 | return parent::convertBooleans($item); | ||
496 | } | ||
497 | |||
498 | return $this->doConvertBooleans( | ||
499 | $item, | ||
500 | /** @param mixed $value */ | ||
501 | static function ($value): string { | ||
502 | if ($value === null) { | ||
503 | return 'NULL'; | ||
504 | } | ||
505 | |||
506 | return $value === true ? 'true' : 'false'; | ||
507 | }, | ||
508 | ); | ||
509 | } | ||
510 | |||
511 | public function convertBooleansToDatabaseValue(mixed $item): mixed | ||
512 | { | ||
513 | if (! $this->useBooleanTrueFalseStrings) { | ||
514 | return parent::convertBooleansToDatabaseValue($item); | ||
515 | } | ||
516 | |||
517 | return $this->doConvertBooleans( | ||
518 | $item, | ||
519 | /** @param mixed $value */ | ||
520 | static function ($value): ?int { | ||
521 | return $value === null ? null : (int) $value; | ||
522 | }, | ||
523 | ); | ||
524 | } | ||
525 | |||
526 | /** | ||
527 | * @param T $item | ||
528 | * | ||
529 | * @return (T is null ? null : bool) | ||
530 | * | ||
531 | * @template T | ||
532 | */ | ||
533 | public function convertFromBoolean(mixed $item): ?bool | ||
534 | { | ||
535 | if (in_array($item, $this->booleanLiterals['false'], true)) { | ||
536 | return false; | ||
537 | } | ||
538 | |||
539 | return parent::convertFromBoolean($item); | ||
540 | } | ||
541 | |||
542 | public function getSequenceNextValSQL(string $sequence): string | ||
543 | { | ||
544 | return "SELECT NEXTVAL('" . $sequence . "')"; | ||
545 | } | ||
546 | |||
547 | public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string | ||
548 | { | ||
549 | return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ' | ||
550 | . $this->_getTransactionIsolationLevelSQL($level); | ||
551 | } | ||
552 | |||
553 | /** | ||
554 | * {@inheritDoc} | ||
555 | */ | ||
556 | public function getBooleanTypeDeclarationSQL(array $column): string | ||
557 | { | ||
558 | return 'BOOLEAN'; | ||
559 | } | ||
560 | |||
561 | /** | ||
562 | * {@inheritDoc} | ||
563 | */ | ||
564 | public function getIntegerTypeDeclarationSQL(array $column): string | ||
565 | { | ||
566 | return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
567 | } | ||
568 | |||
569 | /** | ||
570 | * {@inheritDoc} | ||
571 | */ | ||
572 | public function getBigIntTypeDeclarationSQL(array $column): string | ||
573 | { | ||
574 | return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
575 | } | ||
576 | |||
577 | /** | ||
578 | * {@inheritDoc} | ||
579 | */ | ||
580 | public function getSmallIntTypeDeclarationSQL(array $column): string | ||
581 | { | ||
582 | return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
583 | } | ||
584 | |||
585 | /** | ||
586 | * {@inheritDoc} | ||
587 | */ | ||
588 | public function getGuidTypeDeclarationSQL(array $column): string | ||
589 | { | ||
590 | return 'UUID'; | ||
591 | } | ||
592 | |||
593 | /** | ||
594 | * {@inheritDoc} | ||
595 | */ | ||
596 | public function getDateTimeTypeDeclarationSQL(array $column): string | ||
597 | { | ||
598 | return 'TIMESTAMP(0) WITHOUT TIME ZONE'; | ||
599 | } | ||
600 | |||
601 | /** | ||
602 | * {@inheritDoc} | ||
603 | */ | ||
604 | public function getDateTimeTzTypeDeclarationSQL(array $column): string | ||
605 | { | ||
606 | return 'TIMESTAMP(0) WITH TIME ZONE'; | ||
607 | } | ||
608 | |||
609 | /** | ||
610 | * {@inheritDoc} | ||
611 | */ | ||
612 | public function getDateTypeDeclarationSQL(array $column): string | ||
613 | { | ||
614 | return 'DATE'; | ||
615 | } | ||
616 | |||
617 | /** | ||
618 | * {@inheritDoc} | ||
619 | */ | ||
620 | public function getTimeTypeDeclarationSQL(array $column): string | ||
621 | { | ||
622 | return 'TIME(0) WITHOUT TIME ZONE'; | ||
623 | } | ||
624 | |||
625 | /** | ||
626 | * {@inheritDoc} | ||
627 | */ | ||
628 | protected function _getCommonIntegerTypeDeclarationSQL(array $column): string | ||
629 | { | ||
630 | if (! empty($column['autoincrement'])) { | ||
631 | return ' GENERATED BY DEFAULT AS IDENTITY'; | ||
632 | } | ||
633 | |||
634 | return ''; | ||
635 | } | ||
636 | |||
637 | protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string | ||
638 | { | ||
639 | $sql = 'VARCHAR'; | ||
640 | |||
641 | if ($length !== null) { | ||
642 | $sql .= sprintf('(%d)', $length); | ||
643 | } | ||
644 | |||
645 | return $sql; | ||
646 | } | ||
647 | |||
648 | protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string | ||
649 | { | ||
650 | return 'BYTEA'; | ||
651 | } | ||
652 | |||
653 | protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string | ||
654 | { | ||
655 | return 'BYTEA'; | ||
656 | } | ||
657 | |||
658 | /** | ||
659 | * {@inheritDoc} | ||
660 | */ | ||
661 | public function getClobTypeDeclarationSQL(array $column): string | ||
662 | { | ||
663 | return 'TEXT'; | ||
664 | } | ||
665 | |||
666 | public function getDateTimeTzFormatString(): string | ||
667 | { | ||
668 | return 'Y-m-d H:i:sO'; | ||
669 | } | ||
670 | |||
671 | public function getEmptyIdentityInsertSQL(string $quotedTableName, string $quotedIdentifierColumnName): string | ||
672 | { | ||
673 | return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; | ||
674 | } | ||
675 | |||
676 | public function getTruncateTableSQL(string $tableName, bool $cascade = false): string | ||
677 | { | ||
678 | $tableIdentifier = new Identifier($tableName); | ||
679 | $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this); | ||
680 | |||
681 | if ($cascade) { | ||
682 | $sql .= ' CASCADE'; | ||
683 | } | ||
684 | |||
685 | return $sql; | ||
686 | } | ||
687 | |||
688 | protected function initializeDoctrineTypeMappings(): void | ||
689 | { | ||
690 | $this->doctrineTypeMapping = [ | ||
691 | 'bigint' => Types::BIGINT, | ||
692 | 'bigserial' => Types::BIGINT, | ||
693 | 'bool' => Types::BOOLEAN, | ||
694 | 'boolean' => Types::BOOLEAN, | ||
695 | 'bpchar' => Types::STRING, | ||
696 | 'bytea' => Types::BLOB, | ||
697 | 'char' => Types::STRING, | ||
698 | 'date' => Types::DATE_MUTABLE, | ||
699 | 'datetime' => Types::DATETIME_MUTABLE, | ||
700 | 'decimal' => Types::DECIMAL, | ||
701 | 'double' => Types::FLOAT, | ||
702 | 'double precision' => Types::FLOAT, | ||
703 | 'float' => Types::FLOAT, | ||
704 | 'float4' => Types::FLOAT, | ||
705 | 'float8' => Types::FLOAT, | ||
706 | 'inet' => Types::STRING, | ||
707 | 'int' => Types::INTEGER, | ||
708 | 'int2' => Types::SMALLINT, | ||
709 | 'int4' => Types::INTEGER, | ||
710 | 'int8' => Types::BIGINT, | ||
711 | 'integer' => Types::INTEGER, | ||
712 | 'interval' => Types::STRING, | ||
713 | 'json' => Types::JSON, | ||
714 | 'jsonb' => Types::JSON, | ||
715 | 'money' => Types::DECIMAL, | ||
716 | 'numeric' => Types::DECIMAL, | ||
717 | 'serial' => Types::INTEGER, | ||
718 | 'serial4' => Types::INTEGER, | ||
719 | 'serial8' => Types::BIGINT, | ||
720 | 'real' => Types::FLOAT, | ||
721 | 'smallint' => Types::SMALLINT, | ||
722 | 'text' => Types::TEXT, | ||
723 | 'time' => Types::TIME_MUTABLE, | ||
724 | 'timestamp' => Types::DATETIME_MUTABLE, | ||
725 | 'timestamptz' => Types::DATETIMETZ_MUTABLE, | ||
726 | 'timetz' => Types::TIME_MUTABLE, | ||
727 | 'tsvector' => Types::TEXT, | ||
728 | 'uuid' => Types::GUID, | ||
729 | 'varchar' => Types::STRING, | ||
730 | 'year' => Types::DATE_MUTABLE, | ||
731 | '_varchar' => Types::STRING, | ||
732 | ]; | ||
733 | } | ||
734 | |||
735 | protected function createReservedKeywordsList(): KeywordList | ||
736 | { | ||
737 | return new PostgreSQLKeywords(); | ||
738 | } | ||
739 | |||
740 | /** | ||
741 | * {@inheritDoc} | ||
742 | */ | ||
743 | public function getBlobTypeDeclarationSQL(array $column): string | ||
744 | { | ||
745 | return 'BYTEA'; | ||
746 | } | ||
747 | |||
748 | /** | ||
749 | * {@inheritDoc} | ||
750 | * | ||
751 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
752 | */ | ||
753 | public function getDefaultValueDeclarationSQL(array $column): string | ||
754 | { | ||
755 | if (isset($column['autoincrement']) && $column['autoincrement'] === true) { | ||
756 | return ''; | ||
757 | } | ||
758 | |||
759 | return parent::getDefaultValueDeclarationSQL($column); | ||
760 | } | ||
761 | |||
762 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
763 | public function supportsColumnCollation(): bool | ||
764 | { | ||
765 | return true; | ||
766 | } | ||
767 | |||
768 | /** | ||
769 | * {@inheritDoc} | ||
770 | */ | ||
771 | public function getJsonTypeDeclarationSQL(array $column): string | ||
772 | { | ||
773 | if (! empty($column['jsonb'])) { | ||
774 | return 'JSONB'; | ||
775 | } | ||
776 | |||
777 | return 'JSON'; | ||
778 | } | ||
779 | |||
780 | public function createSchemaManager(Connection $connection): PostgreSQLSchemaManager | ||
781 | { | ||
782 | return new PostgreSQLSchemaManager($connection, $this); | ||
783 | } | ||
784 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php b/vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php new file mode 100644 index 0000000..aa8d9fb --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php | |||
@@ -0,0 +1,50 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\SQLServer; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\SQLServerPlatform; | ||
8 | use Doctrine\DBAL\Schema\Comparator as BaseComparator; | ||
9 | use Doctrine\DBAL\Schema\Table; | ||
10 | use Doctrine\DBAL\Schema\TableDiff; | ||
11 | |||
12 | /** | ||
13 | * Compares schemas in the context of SQL Server platform. | ||
14 | * | ||
15 | * @link https://docs.microsoft.com/en-us/sql/t-sql/statements/collations?view=sql-server-ver15 | ||
16 | */ | ||
17 | class Comparator extends BaseComparator | ||
18 | { | ||
19 | /** @internal The comparator can be only instantiated by a schema manager. */ | ||
20 | public function __construct(SQLServerPlatform $platform, private readonly string $databaseCollation) | ||
21 | { | ||
22 | parent::__construct($platform); | ||
23 | } | ||
24 | |||
25 | public function compareTables(Table $oldTable, Table $newTable): TableDiff | ||
26 | { | ||
27 | return parent::compareTables( | ||
28 | $this->normalizeColumns($oldTable), | ||
29 | $this->normalizeColumns($newTable), | ||
30 | ); | ||
31 | } | ||
32 | |||
33 | private function normalizeColumns(Table $table): Table | ||
34 | { | ||
35 | $table = clone $table; | ||
36 | |||
37 | foreach ($table->getColumns() as $column) { | ||
38 | $options = $column->getPlatformOptions(); | ||
39 | |||
40 | if (! isset($options['collation']) || $options['collation'] !== $this->databaseCollation) { | ||
41 | continue; | ||
42 | } | ||
43 | |||
44 | unset($options['collation']); | ||
45 | $column->setPlatformOptions($options); | ||
46 | } | ||
47 | |||
48 | return $table; | ||
49 | } | ||
50 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php b/vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php new file mode 100644 index 0000000..ea6bb60 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php | |||
@@ -0,0 +1,84 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\SQLServer\SQL\Builder; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\SQLServerPlatform; | ||
8 | use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode; | ||
9 | use Doctrine\DBAL\Query\SelectQuery; | ||
10 | use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; | ||
11 | |||
12 | use function count; | ||
13 | use function implode; | ||
14 | |||
15 | final class SQLServerSelectSQLBuilder implements SelectSQLBuilder | ||
16 | { | ||
17 | /** @internal The SQL builder should be instantiated only by database platforms. */ | ||
18 | public function __construct( | ||
19 | private readonly SQLServerPlatform $platform, | ||
20 | ) { | ||
21 | } | ||
22 | |||
23 | public function buildSQL(SelectQuery $query): string | ||
24 | { | ||
25 | $parts = ['SELECT']; | ||
26 | |||
27 | if ($query->isDistinct()) { | ||
28 | $parts[] = 'DISTINCT'; | ||
29 | } | ||
30 | |||
31 | $parts[] = implode(', ', $query->getColumns()); | ||
32 | |||
33 | $from = $query->getFrom(); | ||
34 | |||
35 | if (count($from) > 0) { | ||
36 | $parts[] = 'FROM ' . implode(', ', $from); | ||
37 | } | ||
38 | |||
39 | $forUpdate = $query->getForUpdate(); | ||
40 | |||
41 | if ($forUpdate !== null) { | ||
42 | $with = ['UPDLOCK', 'ROWLOCK']; | ||
43 | |||
44 | if ($forUpdate->getConflictResolutionMode() === ConflictResolutionMode::SKIP_LOCKED) { | ||
45 | $with[] = 'READPAST'; | ||
46 | } | ||
47 | |||
48 | $parts[] = 'WITH (' . implode(', ', $with) . ')'; | ||
49 | } | ||
50 | |||
51 | $where = $query->getWhere(); | ||
52 | |||
53 | if ($where !== null) { | ||
54 | $parts[] = 'WHERE ' . $where; | ||
55 | } | ||
56 | |||
57 | $groupBy = $query->getGroupBy(); | ||
58 | |||
59 | if (count($groupBy) > 0) { | ||
60 | $parts[] = 'GROUP BY ' . implode(', ', $groupBy); | ||
61 | } | ||
62 | |||
63 | $having = $query->getHaving(); | ||
64 | |||
65 | if ($having !== null) { | ||
66 | $parts[] = 'HAVING ' . $having; | ||
67 | } | ||
68 | |||
69 | $orderBy = $query->getOrderBy(); | ||
70 | |||
71 | if (count($orderBy) > 0) { | ||
72 | $parts[] = 'ORDER BY ' . implode(', ', $orderBy); | ||
73 | } | ||
74 | |||
75 | $sql = implode(' ', $parts); | ||
76 | $limit = $query->getLimit(); | ||
77 | |||
78 | if ($limit->isDefined()) { | ||
79 | $sql = $this->platform->modifyLimitQuery($sql, $limit->getMaxResults(), $limit->getFirstResult()); | ||
80 | } | ||
81 | |||
82 | return $sql; | ||
83 | } | ||
84 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLServerPlatform.php b/vendor/doctrine/dbal/src/Platforms/SQLServerPlatform.php new file mode 100644 index 0000000..7a10a32 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SQLServerPlatform.php | |||
@@ -0,0 +1,1223 @@ | |||
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\InvalidColumnType\ColumnLengthRequired; | ||
9 | use Doctrine\DBAL\LockMode; | ||
10 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
11 | use Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords; | ||
12 | use Doctrine\DBAL\Platforms\SQLServer\SQL\Builder\SQLServerSelectSQLBuilder; | ||
13 | use Doctrine\DBAL\Schema\Column; | ||
14 | use Doctrine\DBAL\Schema\ColumnDiff; | ||
15 | use Doctrine\DBAL\Schema\Identifier; | ||
16 | use Doctrine\DBAL\Schema\Index; | ||
17 | use Doctrine\DBAL\Schema\Sequence; | ||
18 | use Doctrine\DBAL\Schema\SQLServerSchemaManager; | ||
19 | use Doctrine\DBAL\Schema\TableDiff; | ||
20 | use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; | ||
21 | use Doctrine\DBAL\TransactionIsolationLevel; | ||
22 | use Doctrine\DBAL\Types\Types; | ||
23 | use InvalidArgumentException; | ||
24 | |||
25 | use function array_merge; | ||
26 | use function array_unique; | ||
27 | use function array_values; | ||
28 | use function explode; | ||
29 | use function implode; | ||
30 | use function is_array; | ||
31 | use function is_bool; | ||
32 | use function is_numeric; | ||
33 | use function preg_match; | ||
34 | use function preg_match_all; | ||
35 | use function sprintf; | ||
36 | use function str_contains; | ||
37 | use function str_ends_with; | ||
38 | use function str_replace; | ||
39 | use function str_starts_with; | ||
40 | use function strtoupper; | ||
41 | use function substr; | ||
42 | use function substr_count; | ||
43 | |||
44 | use const PREG_OFFSET_CAPTURE; | ||
45 | |||
46 | /** | ||
47 | * Provides the behavior, features and SQL dialect of the Microsoft SQL Server database platform | ||
48 | * of the oldest supported version. | ||
49 | */ | ||
50 | class SQLServerPlatform extends AbstractPlatform | ||
51 | { | ||
52 | /** @internal Should be used only from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
53 | public const OPTION_DEFAULT_CONSTRAINT_NAME = 'default_constraint_name'; | ||
54 | |||
55 | public function createSelectSQLBuilder(): SelectSQLBuilder | ||
56 | { | ||
57 | return new SQLServerSelectSQLBuilder($this); | ||
58 | } | ||
59 | |||
60 | public function getCurrentDateSQL(): string | ||
61 | { | ||
62 | return $this->getConvertExpression('date', 'GETDATE()'); | ||
63 | } | ||
64 | |||
65 | public function getCurrentTimeSQL(): string | ||
66 | { | ||
67 | return $this->getConvertExpression('time', 'GETDATE()'); | ||
68 | } | ||
69 | |||
70 | /** | ||
71 | * Returns an expression that converts an expression of one data type to another. | ||
72 | * | ||
73 | * @param string $dataType The target native data type. Alias data types cannot be used. | ||
74 | * @param string $expression The SQL expression to convert. | ||
75 | */ | ||
76 | private function getConvertExpression(string $dataType, string $expression): string | ||
77 | { | ||
78 | return sprintf('CONVERT(%s, %s)', $dataType, $expression); | ||
79 | } | ||
80 | |||
81 | protected function getDateArithmeticIntervalExpression( | ||
82 | string $date, | ||
83 | string $operator, | ||
84 | string $interval, | ||
85 | DateIntervalUnit $unit, | ||
86 | ): string { | ||
87 | $factorClause = ''; | ||
88 | |||
89 | if ($operator === '-') { | ||
90 | $factorClause = '-1 * '; | ||
91 | } | ||
92 | |||
93 | return 'DATEADD(' . $unit->value . ', ' . $factorClause . $interval . ', ' . $date . ')'; | ||
94 | } | ||
95 | |||
96 | public function getDateDiffExpression(string $date1, string $date2): string | ||
97 | { | ||
98 | return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')'; | ||
99 | } | ||
100 | |||
101 | /** | ||
102 | * {@inheritDoc} | ||
103 | * | ||
104 | * Microsoft SQL Server supports this through AUTO_INCREMENT columns. | ||
105 | */ | ||
106 | public function supportsIdentityColumns(): bool | ||
107 | { | ||
108 | return true; | ||
109 | } | ||
110 | |||
111 | public function supportsReleaseSavepoints(): bool | ||
112 | { | ||
113 | return false; | ||
114 | } | ||
115 | |||
116 | public function supportsSchemas(): bool | ||
117 | { | ||
118 | return true; | ||
119 | } | ||
120 | |||
121 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
122 | public function supportsColumnCollation(): bool | ||
123 | { | ||
124 | return true; | ||
125 | } | ||
126 | |||
127 | public function supportsSequences(): bool | ||
128 | { | ||
129 | return true; | ||
130 | } | ||
131 | |||
132 | public function getAlterSequenceSQL(Sequence $sequence): string | ||
133 | { | ||
134 | return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . | ||
135 | ' INCREMENT BY ' . $sequence->getAllocationSize(); | ||
136 | } | ||
137 | |||
138 | public function getCreateSequenceSQL(Sequence $sequence): string | ||
139 | { | ||
140 | return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . | ||
141 | ' START WITH ' . $sequence->getInitialValue() . | ||
142 | ' INCREMENT BY ' . $sequence->getAllocationSize() . | ||
143 | ' MINVALUE ' . $sequence->getInitialValue(); | ||
144 | } | ||
145 | |||
146 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
147 | public function getListSequencesSQL(string $database): string | ||
148 | { | ||
149 | return 'SELECT seq.name, | ||
150 | CAST( | ||
151 | seq.increment AS VARCHAR(MAX) | ||
152 | ) AS increment, -- CAST avoids driver error for sql_variant type | ||
153 | CAST( | ||
154 | seq.start_value AS VARCHAR(MAX) | ||
155 | ) AS start_value -- CAST avoids driver error for sql_variant type | ||
156 | FROM sys.sequences AS seq'; | ||
157 | } | ||
158 | |||
159 | public function getSequenceNextValSQL(string $sequence): string | ||
160 | { | ||
161 | return 'SELECT NEXT VALUE FOR ' . $sequence; | ||
162 | } | ||
163 | |||
164 | public function getDropForeignKeySQL(string $foreignKey, string $table): string | ||
165 | { | ||
166 | return $this->getDropConstraintSQL($foreignKey, $table); | ||
167 | } | ||
168 | |||
169 | public function getDropIndexSQL(string $name, string $table): string | ||
170 | { | ||
171 | return 'DROP INDEX ' . $name . ' ON ' . $table; | ||
172 | } | ||
173 | |||
174 | /** | ||
175 | * {@inheritDoc} | ||
176 | */ | ||
177 | protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array | ||
178 | { | ||
179 | $defaultConstraintsSql = []; | ||
180 | $commentsSql = []; | ||
181 | |||
182 | $tableComment = $options['comment'] ?? null; | ||
183 | if ($tableComment !== null) { | ||
184 | $commentsSql[] = $this->getCommentOnTableSQL($name, $tableComment); | ||
185 | } | ||
186 | |||
187 | // @todo does other code breaks because of this? | ||
188 | // force primary keys to be not null | ||
189 | foreach ($columns as &$column) { | ||
190 | if (! empty($column['primary'])) { | ||
191 | $column['notnull'] = true; | ||
192 | } | ||
193 | |||
194 | // Build default constraints SQL statements. | ||
195 | if (isset($column['default'])) { | ||
196 | $defaultConstraintsSql[] = 'ALTER TABLE ' . $name . | ||
197 | ' ADD' . $this->getDefaultConstraintDeclarationSQL($column); | ||
198 | } | ||
199 | |||
200 | if (empty($column['comment']) && ! is_numeric($column['comment'])) { | ||
201 | continue; | ||
202 | } | ||
203 | |||
204 | $commentsSql[] = $this->getCreateColumnCommentSQL($name, $column['name'], $column['comment']); | ||
205 | } | ||
206 | |||
207 | $columnListSql = $this->getColumnDeclarationListSQL($columns); | ||
208 | |||
209 | if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { | ||
210 | foreach ($options['uniqueConstraints'] as $definition) { | ||
211 | $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition); | ||
212 | } | ||
213 | } | ||
214 | |||
215 | if (isset($options['primary']) && ! empty($options['primary'])) { | ||
216 | $flags = ''; | ||
217 | if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) { | ||
218 | $flags = ' NONCLUSTERED'; | ||
219 | } | ||
220 | |||
221 | $columnListSql .= ', PRIMARY KEY' . $flags | ||
222 | . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')'; | ||
223 | } | ||
224 | |||
225 | $query = 'CREATE TABLE ' . $name . ' (' . $columnListSql; | ||
226 | |||
227 | $check = $this->getCheckDeclarationSQL($columns); | ||
228 | if (! empty($check)) { | ||
229 | $query .= ', ' . $check; | ||
230 | } | ||
231 | |||
232 | $query .= ')'; | ||
233 | |||
234 | $sql = [$query]; | ||
235 | |||
236 | if (isset($options['indexes']) && ! empty($options['indexes'])) { | ||
237 | foreach ($options['indexes'] as $index) { | ||
238 | $sql[] = $this->getCreateIndexSQL($index, $name); | ||
239 | } | ||
240 | } | ||
241 | |||
242 | if (isset($options['foreignKeys'])) { | ||
243 | foreach ($options['foreignKeys'] as $definition) { | ||
244 | $sql[] = $this->getCreateForeignKeySQL($definition, $name); | ||
245 | } | ||
246 | } | ||
247 | |||
248 | return array_merge($sql, $commentsSql, $defaultConstraintsSql); | ||
249 | } | ||
250 | |||
251 | public function getCreatePrimaryKeySQL(Index $index, string $table): string | ||
252 | { | ||
253 | $sql = 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY'; | ||
254 | |||
255 | if ($index->hasFlag('nonclustered')) { | ||
256 | $sql .= ' NONCLUSTERED'; | ||
257 | } | ||
258 | |||
259 | return $sql . ' (' . implode(', ', $index->getQuotedColumns($this)) . ')'; | ||
260 | } | ||
261 | |||
262 | private function unquoteSingleIdentifier(string $possiblyQuotedName): string | ||
263 | { | ||
264 | return str_starts_with($possiblyQuotedName, '[') && str_ends_with($possiblyQuotedName, ']') | ||
265 | ? substr($possiblyQuotedName, 1, -1) | ||
266 | : $possiblyQuotedName; | ||
267 | } | ||
268 | |||
269 | /** | ||
270 | * Returns the SQL statement for creating a column comment. | ||
271 | * | ||
272 | * SQL Server does not support native column comments, | ||
273 | * therefore the extended properties functionality is used | ||
274 | * as a workaround to store them. | ||
275 | * The property name used to store column comments is "MS_Description" | ||
276 | * which provides compatibility with SQL Server Management Studio, | ||
277 | * as column comments are stored in the same property there when | ||
278 | * specifying a column's "Description" attribute. | ||
279 | * | ||
280 | * @param string $tableName The quoted table name to which the column belongs. | ||
281 | * @param string $columnName The quoted column name to create the comment for. | ||
282 | * @param string $comment The column's comment. | ||
283 | */ | ||
284 | protected function getCreateColumnCommentSQL(string $tableName, string $columnName, string $comment): string | ||
285 | { | ||
286 | if (str_contains($tableName, '.')) { | ||
287 | [$schemaName, $tableName] = explode('.', $tableName); | ||
288 | } else { | ||
289 | $schemaName = 'dbo'; | ||
290 | } | ||
291 | |||
292 | return $this->getAddExtendedPropertySQL( | ||
293 | 'MS_Description', | ||
294 | $comment, | ||
295 | 'SCHEMA', | ||
296 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), | ||
297 | 'TABLE', | ||
298 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), | ||
299 | 'COLUMN', | ||
300 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), | ||
301 | ); | ||
302 | } | ||
303 | |||
304 | /** | ||
305 | * Returns the SQL snippet for declaring a default constraint. | ||
306 | * | ||
307 | * @param mixed[] $column Column definition. | ||
308 | */ | ||
309 | protected function getDefaultConstraintDeclarationSQL(array $column): string | ||
310 | { | ||
311 | if (! isset($column['default'])) { | ||
312 | throw new InvalidArgumentException('Incomplete column definition. "default" required.'); | ||
313 | } | ||
314 | |||
315 | $columnName = new Identifier($column['name']); | ||
316 | |||
317 | return $this->getDefaultValueDeclarationSQL($column) . ' FOR ' . $columnName->getQuotedName($this); | ||
318 | } | ||
319 | |||
320 | public function getCreateIndexSQL(Index $index, string $table): string | ||
321 | { | ||
322 | $constraint = parent::getCreateIndexSQL($index, $table); | ||
323 | |||
324 | if ($index->isUnique() && ! $index->isPrimary()) { | ||
325 | $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); | ||
326 | } | ||
327 | |||
328 | return $constraint; | ||
329 | } | ||
330 | |||
331 | protected function getCreateIndexSQLFlags(Index $index): string | ||
332 | { | ||
333 | $type = ''; | ||
334 | if ($index->isUnique()) { | ||
335 | $type .= 'UNIQUE '; | ||
336 | } | ||
337 | |||
338 | if ($index->hasFlag('clustered')) { | ||
339 | $type .= 'CLUSTERED '; | ||
340 | } elseif ($index->hasFlag('nonclustered')) { | ||
341 | $type .= 'NONCLUSTERED '; | ||
342 | } | ||
343 | |||
344 | return $type; | ||
345 | } | ||
346 | |||
347 | /** | ||
348 | * Extend unique key constraint with required filters | ||
349 | */ | ||
350 | private function _appendUniqueConstraintDefinition(string $sql, Index $index): string | ||
351 | { | ||
352 | $fields = []; | ||
353 | |||
354 | foreach ($index->getQuotedColumns($this) as $field) { | ||
355 | $fields[] = $field . ' IS NOT NULL'; | ||
356 | } | ||
357 | |||
358 | return $sql . ' WHERE ' . implode(' AND ', $fields); | ||
359 | } | ||
360 | |||
361 | /** | ||
362 | * {@inheritDoc} | ||
363 | */ | ||
364 | public function getAlterTableSQL(TableDiff $diff): array | ||
365 | { | ||
366 | $queryParts = []; | ||
367 | $sql = []; | ||
368 | $columnSql = []; | ||
369 | $commentsSql = []; | ||
370 | |||
371 | $table = $diff->getOldTable(); | ||
372 | |||
373 | $tableName = $table->getName(); | ||
374 | |||
375 | foreach ($diff->getAddedColumns() as $column) { | ||
376 | $columnProperties = $column->toArray(); | ||
377 | |||
378 | $addColumnSql = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnProperties); | ||
379 | |||
380 | if (isset($columnProperties['default'])) { | ||
381 | $addColumnSql .= $this->getDefaultValueDeclarationSQL($columnProperties); | ||
382 | } | ||
383 | |||
384 | $queryParts[] = $addColumnSql; | ||
385 | |||
386 | $comment = $column->getComment(); | ||
387 | |||
388 | if ($comment === '') { | ||
389 | continue; | ||
390 | } | ||
391 | |||
392 | $commentsSql[] = $this->getCreateColumnCommentSQL( | ||
393 | $tableName, | ||
394 | $column->getQuotedName($this), | ||
395 | $comment, | ||
396 | ); | ||
397 | } | ||
398 | |||
399 | foreach ($diff->getDroppedColumns() as $column) { | ||
400 | if ($column->getDefault() !== null) { | ||
401 | $queryParts[] = $this->getAlterTableDropDefaultConstraintClause($column); | ||
402 | } | ||
403 | |||
404 | $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); | ||
405 | } | ||
406 | |||
407 | foreach ($diff->getModifiedColumns() as $columnDiff) { | ||
408 | $newColumn = $columnDiff->getNewColumn(); | ||
409 | $newComment = $newColumn->getComment(); | ||
410 | $hasNewComment = $newComment !== ''; | ||
411 | |||
412 | $oldColumn = $columnDiff->getOldColumn(); | ||
413 | $oldComment = $oldColumn->getComment(); | ||
414 | $hasOldComment = $oldComment !== ''; | ||
415 | |||
416 | if ($hasOldComment && $hasNewComment && $newComment !== $oldComment) { | ||
417 | $commentsSql[] = $this->getAlterColumnCommentSQL( | ||
418 | $tableName, | ||
419 | $newColumn->getQuotedName($this), | ||
420 | $newComment, | ||
421 | ); | ||
422 | } elseif ($hasOldComment && ! $hasNewComment) { | ||
423 | $commentsSql[] = $this->getDropColumnCommentSQL( | ||
424 | $tableName, | ||
425 | $newColumn->getQuotedName($this), | ||
426 | ); | ||
427 | } elseif (! $hasOldComment && $hasNewComment) { | ||
428 | $commentsSql[] = $this->getCreateColumnCommentSQL( | ||
429 | $tableName, | ||
430 | $newColumn->getQuotedName($this), | ||
431 | $newComment, | ||
432 | ); | ||
433 | } | ||
434 | |||
435 | $columnNameSQL = $newColumn->getQuotedName($this); | ||
436 | |||
437 | $oldDeclarationSQL = $this->getColumnDeclarationSQL($columnNameSQL, $oldColumn->toArray()); | ||
438 | $newDeclarationSQL = $this->getColumnDeclarationSQL($columnNameSQL, $newColumn->toArray()); | ||
439 | |||
440 | $declarationSQLChanged = $newDeclarationSQL !== $oldDeclarationSQL; | ||
441 | $defaultChanged = $columnDiff->hasDefaultChanged(); | ||
442 | |||
443 | if (! $declarationSQLChanged && ! $defaultChanged) { | ||
444 | continue; | ||
445 | } | ||
446 | |||
447 | $requireDropDefaultConstraint = $this->alterColumnRequiresDropDefaultConstraint($columnDiff); | ||
448 | |||
449 | if ($requireDropDefaultConstraint) { | ||
450 | $queryParts[] = $this->getAlterTableDropDefaultConstraintClause($oldColumn); | ||
451 | } | ||
452 | |||
453 | if ($declarationSQLChanged) { | ||
454 | $queryParts[] = 'ALTER COLUMN ' . $newDeclarationSQL; | ||
455 | } | ||
456 | |||
457 | if ( | ||
458 | $newColumn->getDefault() === null | ||
459 | || (! $requireDropDefaultConstraint && ! $defaultChanged) | ||
460 | ) { | ||
461 | continue; | ||
462 | } | ||
463 | |||
464 | $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($tableName, $newColumn); | ||
465 | } | ||
466 | |||
467 | $tableNameSQL = $table->getQuotedName($this); | ||
468 | |||
469 | foreach ($diff->getRenamedColumns() as $oldColumnName => $newColumn) { | ||
470 | $oldColumnName = new Identifier($oldColumnName); | ||
471 | |||
472 | $sql[] = sprintf( | ||
473 | "sp_rename '%s.%s', '%s', 'COLUMN'", | ||
474 | $tableNameSQL, | ||
475 | $oldColumnName->getQuotedName($this), | ||
476 | $newColumn->getQuotedName($this), | ||
477 | ); | ||
478 | } | ||
479 | |||
480 | foreach ($queryParts as $query) { | ||
481 | $sql[] = 'ALTER TABLE ' . $tableNameSQL . ' ' . $query; | ||
482 | } | ||
483 | |||
484 | return array_merge( | ||
485 | $this->getPreAlterTableIndexForeignKeySQL($diff), | ||
486 | $sql, | ||
487 | $commentsSql, | ||
488 | $this->getPostAlterTableIndexForeignKeySQL($diff), | ||
489 | $columnSql, | ||
490 | ); | ||
491 | } | ||
492 | |||
493 | public function getRenameTableSQL(string $oldName, string $newName): string | ||
494 | { | ||
495 | return sprintf( | ||
496 | 'sp_rename %s, %s', | ||
497 | $this->quoteStringLiteral($oldName), | ||
498 | $this->quoteStringLiteral($newName), | ||
499 | ); | ||
500 | } | ||
501 | |||
502 | /** | ||
503 | * Returns the SQL clause for adding a default constraint in an ALTER TABLE statement. | ||
504 | * | ||
505 | * @param string $tableName The name of the table to generate the clause for. | ||
506 | * @param Column $column The column to generate the clause for. | ||
507 | */ | ||
508 | private function getAlterTableAddDefaultConstraintClause(string $tableName, Column $column): string | ||
509 | { | ||
510 | $columnDef = $column->toArray(); | ||
511 | $columnDef['name'] = $column->getQuotedName($this); | ||
512 | |||
513 | return 'ADD' . $this->getDefaultConstraintDeclarationSQL($columnDef); | ||
514 | } | ||
515 | |||
516 | /** | ||
517 | * Returns the SQL clause for dropping an existing default constraint in an ALTER TABLE statement. | ||
518 | */ | ||
519 | private function getAlterTableDropDefaultConstraintClause(Column $column): string | ||
520 | { | ||
521 | if (! $column->hasPlatformOption(self::OPTION_DEFAULT_CONSTRAINT_NAME)) { | ||
522 | throw new InvalidArgumentException( | ||
523 | 'Column ' . $column->getName() . ' was not properly introspected as it has a default value' | ||
524 | . ' but does not have the default constraint name.', | ||
525 | ); | ||
526 | } | ||
527 | |||
528 | return 'DROP CONSTRAINT ' . $this->quoteIdentifier( | ||
529 | $column->getPlatformOption(self::OPTION_DEFAULT_CONSTRAINT_NAME), | ||
530 | ); | ||
531 | } | ||
532 | |||
533 | /** | ||
534 | * Checks whether a column alteration requires dropping its default constraint first. | ||
535 | * | ||
536 | * Different to other database vendors SQL Server implements column default values | ||
537 | * as constraints and therefore changes in a column's default value as well as changes | ||
538 | * in a column's type require dropping the default constraint first before being to | ||
539 | * alter the particular column to the new definition. | ||
540 | */ | ||
541 | private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff): bool | ||
542 | { | ||
543 | // We only need to drop an existing default constraint if we know the | ||
544 | // column was defined with a default value before. | ||
545 | if ($columnDiff->getOldColumn()->getDefault() === null) { | ||
546 | return false; | ||
547 | } | ||
548 | |||
549 | // We need to drop an existing default constraint if the column was | ||
550 | // defined with a default value before and it has changed. | ||
551 | if ($columnDiff->hasDefaultChanged()) { | ||
552 | return true; | ||
553 | } | ||
554 | |||
555 | // We need to drop an existing default constraint if the column was | ||
556 | // defined with a default value before and the native column type has changed. | ||
557 | return $columnDiff->hasTypeChanged() || $columnDiff->hasFixedChanged(); | ||
558 | } | ||
559 | |||
560 | /** | ||
561 | * Returns the SQL statement for altering a column comment. | ||
562 | * | ||
563 | * SQL Server does not support native column comments, | ||
564 | * therefore the extended properties functionality is used | ||
565 | * as a workaround to store them. | ||
566 | * The property name used to store column comments is "MS_Description" | ||
567 | * which provides compatibility with SQL Server Management Studio, | ||
568 | * as column comments are stored in the same property there when | ||
569 | * specifying a column's "Description" attribute. | ||
570 | * | ||
571 | * @param string $tableName The quoted table name to which the column belongs. | ||
572 | * @param string $columnName The quoted column name to alter the comment for. | ||
573 | * @param string $comment The column's comment. | ||
574 | */ | ||
575 | protected function getAlterColumnCommentSQL(string $tableName, string $columnName, string $comment): string | ||
576 | { | ||
577 | if (str_contains($tableName, '.')) { | ||
578 | [$schemaName, $tableName] = explode('.', $tableName); | ||
579 | } else { | ||
580 | $schemaName = 'dbo'; | ||
581 | } | ||
582 | |||
583 | return $this->getUpdateExtendedPropertySQL( | ||
584 | 'MS_Description', | ||
585 | $comment, | ||
586 | 'SCHEMA', | ||
587 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), | ||
588 | 'TABLE', | ||
589 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), | ||
590 | 'COLUMN', | ||
591 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), | ||
592 | ); | ||
593 | } | ||
594 | |||
595 | /** | ||
596 | * Returns the SQL statement for dropping a column comment. | ||
597 | * | ||
598 | * SQL Server does not support native column comments, | ||
599 | * therefore the extended properties functionality is used | ||
600 | * as a workaround to store them. | ||
601 | * The property name used to store column comments is "MS_Description" | ||
602 | * which provides compatibility with SQL Server Management Studio, | ||
603 | * as column comments are stored in the same property there when | ||
604 | * specifying a column's "Description" attribute. | ||
605 | * | ||
606 | * @param string $tableName The quoted table name to which the column belongs. | ||
607 | * @param string $columnName The quoted column name to drop the comment for. | ||
608 | */ | ||
609 | protected function getDropColumnCommentSQL(string $tableName, string $columnName): string | ||
610 | { | ||
611 | if (str_contains($tableName, '.')) { | ||
612 | [$schemaName, $tableName] = explode('.', $tableName); | ||
613 | } else { | ||
614 | $schemaName = 'dbo'; | ||
615 | } | ||
616 | |||
617 | return $this->getDropExtendedPropertySQL( | ||
618 | 'MS_Description', | ||
619 | 'SCHEMA', | ||
620 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($schemaName)), | ||
621 | 'TABLE', | ||
622 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), | ||
623 | 'COLUMN', | ||
624 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($columnName)), | ||
625 | ); | ||
626 | } | ||
627 | |||
628 | /** | ||
629 | * {@inheritDoc} | ||
630 | */ | ||
631 | protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array | ||
632 | { | ||
633 | return [sprintf( | ||
634 | "EXEC sp_rename N'%s.%s', N'%s', N'INDEX'", | ||
635 | $tableName, | ||
636 | $oldIndexName, | ||
637 | $index->getQuotedName($this), | ||
638 | ), | ||
639 | ]; | ||
640 | } | ||
641 | |||
642 | /** | ||
643 | * Returns the SQL statement for adding an extended property to a database object. | ||
644 | * | ||
645 | * @link http://msdn.microsoft.com/en-us/library/ms180047%28v=sql.90%29.aspx | ||
646 | * | ||
647 | * @param string $name The name of the property to add. | ||
648 | * @param string|null $value The value of the property to add. | ||
649 | * @param string|null $level0Type The type of the object at level 0 the property belongs to. | ||
650 | * @param string|null $level0Name The name of the object at level 0 the property belongs to. | ||
651 | * @param string|null $level1Type The type of the object at level 1 the property belongs to. | ||
652 | * @param string|null $level1Name The name of the object at level 1 the property belongs to. | ||
653 | * @param string|null $level2Type The type of the object at level 2 the property belongs to. | ||
654 | * @param string|null $level2Name The name of the object at level 2 the property belongs to. | ||
655 | */ | ||
656 | protected function getAddExtendedPropertySQL( | ||
657 | string $name, | ||
658 | ?string $value = null, | ||
659 | ?string $level0Type = null, | ||
660 | ?string $level0Name = null, | ||
661 | ?string $level1Type = null, | ||
662 | ?string $level1Name = null, | ||
663 | ?string $level2Type = null, | ||
664 | ?string $level2Name = null, | ||
665 | ): string { | ||
666 | return 'EXEC sp_addextendedproperty ' . | ||
667 | 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . | ||
668 | 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . | ||
669 | 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . | ||
670 | ($level2Type !== null || $level2Name !== null | ||
671 | ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name | ||
672 | : '' | ||
673 | ); | ||
674 | } | ||
675 | |||
676 | /** | ||
677 | * Returns the SQL statement for dropping an extended property from a database object. | ||
678 | * | ||
679 | * @link http://technet.microsoft.com/en-gb/library/ms178595%28v=sql.90%29.aspx | ||
680 | * | ||
681 | * @param string $name The name of the property to drop. | ||
682 | * @param string|null $level0Type The type of the object at level 0 the property belongs to. | ||
683 | * @param string|null $level0Name The name of the object at level 0 the property belongs to. | ||
684 | * @param string|null $level1Type The type of the object at level 1 the property belongs to. | ||
685 | * @param string|null $level1Name The name of the object at level 1 the property belongs to. | ||
686 | * @param string|null $level2Type The type of the object at level 2 the property belongs to. | ||
687 | * @param string|null $level2Name The name of the object at level 2 the property belongs to. | ||
688 | */ | ||
689 | protected function getDropExtendedPropertySQL( | ||
690 | string $name, | ||
691 | ?string $level0Type = null, | ||
692 | ?string $level0Name = null, | ||
693 | ?string $level1Type = null, | ||
694 | ?string $level1Name = null, | ||
695 | ?string $level2Type = null, | ||
696 | ?string $level2Name = null, | ||
697 | ): string { | ||
698 | return 'EXEC sp_dropextendedproperty ' . | ||
699 | 'N' . $this->quoteStringLiteral($name) . ', ' . | ||
700 | 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . | ||
701 | 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . | ||
702 | ($level2Type !== null || $level2Name !== null | ||
703 | ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name | ||
704 | : '' | ||
705 | ); | ||
706 | } | ||
707 | |||
708 | /** | ||
709 | * Returns the SQL statement for updating an extended property of a database object. | ||
710 | * | ||
711 | * @link http://msdn.microsoft.com/en-us/library/ms186885%28v=sql.90%29.aspx | ||
712 | * | ||
713 | * @param string $name The name of the property to update. | ||
714 | * @param string|null $value The value of the property to update. | ||
715 | * @param string|null $level0Type The type of the object at level 0 the property belongs to. | ||
716 | * @param string|null $level0Name The name of the object at level 0 the property belongs to. | ||
717 | * @param string|null $level1Type The type of the object at level 1 the property belongs to. | ||
718 | * @param string|null $level1Name The name of the object at level 1 the property belongs to. | ||
719 | * @param string|null $level2Type The type of the object at level 2 the property belongs to. | ||
720 | * @param string|null $level2Name The name of the object at level 2 the property belongs to. | ||
721 | */ | ||
722 | protected function getUpdateExtendedPropertySQL( | ||
723 | string $name, | ||
724 | ?string $value = null, | ||
725 | ?string $level0Type = null, | ||
726 | ?string $level0Name = null, | ||
727 | ?string $level1Type = null, | ||
728 | ?string $level1Name = null, | ||
729 | ?string $level2Type = null, | ||
730 | ?string $level2Name = null, | ||
731 | ): string { | ||
732 | return 'EXEC sp_updateextendedproperty ' . | ||
733 | 'N' . $this->quoteStringLiteral($name) . ', N' . $this->quoteStringLiteral($value ?? '') . ', ' . | ||
734 | 'N' . $this->quoteStringLiteral($level0Type ?? '') . ', ' . $level0Name . ', ' . | ||
735 | 'N' . $this->quoteStringLiteral($level1Type ?? '') . ', ' . $level1Name . | ||
736 | ($level2Type !== null || $level2Name !== null | ||
737 | ? ', N' . $this->quoteStringLiteral($level2Type ?? '') . ', ' . $level2Name | ||
738 | : '' | ||
739 | ); | ||
740 | } | ||
741 | |||
742 | public function getEmptyIdentityInsertSQL(string $quotedTableName, string $quotedIdentifierColumnName): string | ||
743 | { | ||
744 | return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; | ||
745 | } | ||
746 | |||
747 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
748 | public function getListViewsSQL(string $database): string | ||
749 | { | ||
750 | return "SELECT name, definition FROM sysobjects | ||
751 | INNER JOIN sys.sql_modules ON sysobjects.id = sys.sql_modules.object_id | ||
752 | WHERE type = 'V' ORDER BY name"; | ||
753 | } | ||
754 | |||
755 | public function getLocateExpression(string $string, string $substring, ?string $start = null): string | ||
756 | { | ||
757 | if ($start === null) { | ||
758 | return sprintf('CHARINDEX(%s, %s)', $substring, $string); | ||
759 | } | ||
760 | |||
761 | return sprintf('CHARINDEX(%s, %s, %s)', $substring, $string, $start); | ||
762 | } | ||
763 | |||
764 | public function getModExpression(string $dividend, string $divisor): string | ||
765 | { | ||
766 | return $dividend . ' % ' . $divisor; | ||
767 | } | ||
768 | |||
769 | public function getTrimExpression( | ||
770 | string $str, | ||
771 | TrimMode $mode = TrimMode::UNSPECIFIED, | ||
772 | ?string $char = null, | ||
773 | ): string { | ||
774 | if ($char === null) { | ||
775 | return match ($mode) { | ||
776 | TrimMode::LEADING => 'LTRIM(' . $str . ')', | ||
777 | TrimMode::TRAILING => 'RTRIM(' . $str . ')', | ||
778 | default => 'LTRIM(RTRIM(' . $str . '))', | ||
779 | }; | ||
780 | } | ||
781 | |||
782 | $pattern = "'%[^' + " . $char . " + ']%'"; | ||
783 | |||
784 | if ($mode === TrimMode::LEADING) { | ||
785 | return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)'; | ||
786 | } | ||
787 | |||
788 | if ($mode === TrimMode::TRAILING) { | ||
789 | return 'reverse(stuff(reverse(' . $str . '), 1, ' | ||
790 | . 'patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))'; | ||
791 | } | ||
792 | |||
793 | return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, ' | ||
794 | . 'patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str | ||
795 | . ') - 1, null))) - 1, null))'; | ||
796 | } | ||
797 | |||
798 | public function getConcatExpression(string ...$string): string | ||
799 | { | ||
800 | return sprintf('CONCAT(%s)', implode(', ', $string)); | ||
801 | } | ||
802 | |||
803 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
804 | public function getListDatabasesSQL(): string | ||
805 | { | ||
806 | return 'SELECT * FROM sys.databases'; | ||
807 | } | ||
808 | |||
809 | public function getSubstringExpression(string $string, string $start, ?string $length = null): string | ||
810 | { | ||
811 | if ($length === null) { | ||
812 | return sprintf('SUBSTRING(%s, %s, LEN(%s) - %s + 1)', $string, $start, $string, $start); | ||
813 | } | ||
814 | |||
815 | return sprintf('SUBSTRING(%s, %s, %s)', $string, $start, $length); | ||
816 | } | ||
817 | |||
818 | public function getLengthExpression(string $string): string | ||
819 | { | ||
820 | return 'LEN(' . $string . ')'; | ||
821 | } | ||
822 | |||
823 | public function getCurrentDatabaseExpression(): string | ||
824 | { | ||
825 | return 'DB_NAME()'; | ||
826 | } | ||
827 | |||
828 | public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string | ||
829 | { | ||
830 | return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); | ||
831 | } | ||
832 | |||
833 | /** | ||
834 | * {@inheritDoc} | ||
835 | */ | ||
836 | public function getIntegerTypeDeclarationSQL(array $column): string | ||
837 | { | ||
838 | return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
839 | } | ||
840 | |||
841 | /** | ||
842 | * {@inheritDoc} | ||
843 | */ | ||
844 | public function getBigIntTypeDeclarationSQL(array $column): string | ||
845 | { | ||
846 | return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
847 | } | ||
848 | |||
849 | /** | ||
850 | * {@inheritDoc} | ||
851 | */ | ||
852 | public function getSmallIntTypeDeclarationSQL(array $column): string | ||
853 | { | ||
854 | return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
855 | } | ||
856 | |||
857 | /** | ||
858 | * {@inheritDoc} | ||
859 | */ | ||
860 | public function getGuidTypeDeclarationSQL(array $column): string | ||
861 | { | ||
862 | return 'UNIQUEIDENTIFIER'; | ||
863 | } | ||
864 | |||
865 | /** | ||
866 | * {@inheritDoc} | ||
867 | */ | ||
868 | public function getDateTimeTzTypeDeclarationSQL(array $column): string | ||
869 | { | ||
870 | return 'DATETIMEOFFSET(6)'; | ||
871 | } | ||
872 | |||
873 | protected function getCharTypeDeclarationSQLSnippet(?int $length): string | ||
874 | { | ||
875 | $sql = 'NCHAR'; | ||
876 | |||
877 | if ($length !== null) { | ||
878 | $sql .= sprintf('(%d)', $length); | ||
879 | } | ||
880 | |||
881 | return $sql; | ||
882 | } | ||
883 | |||
884 | protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string | ||
885 | { | ||
886 | if ($length === null) { | ||
887 | throw ColumnLengthRequired::new($this, 'NVARCHAR'); | ||
888 | } | ||
889 | |||
890 | return sprintf('NVARCHAR(%d)', $length); | ||
891 | } | ||
892 | |||
893 | /** | ||
894 | * {@inheritDoc} | ||
895 | */ | ||
896 | public function getAsciiStringTypeDeclarationSQL(array $column): string | ||
897 | { | ||
898 | $length = $column['length'] ?? null; | ||
899 | |||
900 | if (empty($column['fixed'])) { | ||
901 | return parent::getVarcharTypeDeclarationSQLSnippet($length); | ||
902 | } | ||
903 | |||
904 | return parent::getCharTypeDeclarationSQLSnippet($length); | ||
905 | } | ||
906 | |||
907 | /** | ||
908 | * {@inheritDoc} | ||
909 | */ | ||
910 | public function getClobTypeDeclarationSQL(array $column): string | ||
911 | { | ||
912 | return 'VARCHAR(MAX)'; | ||
913 | } | ||
914 | |||
915 | /** | ||
916 | * {@inheritDoc} | ||
917 | */ | ||
918 | protected function _getCommonIntegerTypeDeclarationSQL(array $column): string | ||
919 | { | ||
920 | return ! empty($column['autoincrement']) ? ' IDENTITY' : ''; | ||
921 | } | ||
922 | |||
923 | /** | ||
924 | * {@inheritDoc} | ||
925 | */ | ||
926 | public function getDateTimeTypeDeclarationSQL(array $column): string | ||
927 | { | ||
928 | // 3 - microseconds precision length | ||
929 | // http://msdn.microsoft.com/en-us/library/ms187819.aspx | ||
930 | return 'DATETIME2(6)'; | ||
931 | } | ||
932 | |||
933 | /** | ||
934 | * {@inheritDoc} | ||
935 | */ | ||
936 | public function getDateTypeDeclarationSQL(array $column): string | ||
937 | { | ||
938 | return 'DATE'; | ||
939 | } | ||
940 | |||
941 | /** | ||
942 | * {@inheritDoc} | ||
943 | */ | ||
944 | public function getTimeTypeDeclarationSQL(array $column): string | ||
945 | { | ||
946 | return 'TIME(0)'; | ||
947 | } | ||
948 | |||
949 | /** | ||
950 | * {@inheritDoc} | ||
951 | */ | ||
952 | public function getBooleanTypeDeclarationSQL(array $column): string | ||
953 | { | ||
954 | return 'BIT'; | ||
955 | } | ||
956 | |||
957 | protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string | ||
958 | { | ||
959 | if ($limit === null && $offset <= 0) { | ||
960 | return $query; | ||
961 | } | ||
962 | |||
963 | if ($this->shouldAddOrderBy($query)) { | ||
964 | if (preg_match('/^SELECT\s+DISTINCT/im', $query) > 0) { | ||
965 | // SQL Server won't let us order by a non-selected column in a DISTINCT query, | ||
966 | // so we have to do this madness. This says, order by the first column in the | ||
967 | // result. SQL Server's docs say that a nonordered query's result order is non- | ||
968 | // deterministic anyway, so this won't do anything that a bunch of update and | ||
969 | // deletes to the table wouldn't do anyway. | ||
970 | $query .= ' ORDER BY 1'; | ||
971 | } else { | ||
972 | // In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you | ||
973 | // use constant expressions in the order by list. | ||
974 | $query .= ' ORDER BY (SELECT 0)'; | ||
975 | } | ||
976 | } | ||
977 | |||
978 | // This looks somewhat like MYSQL, but limit/offset are in inverse positions | ||
979 | // Supposedly SQL:2008 core standard. | ||
980 | // Per TSQL spec, FETCH NEXT n ROWS ONLY is not valid without OFFSET n ROWS. | ||
981 | $query .= sprintf(' OFFSET %d ROWS', $offset); | ||
982 | |||
983 | if ($limit !== null) { | ||
984 | $query .= sprintf(' FETCH NEXT %d ROWS ONLY', $limit); | ||
985 | } | ||
986 | |||
987 | return $query; | ||
988 | } | ||
989 | |||
990 | public function convertBooleans(mixed $item): mixed | ||
991 | { | ||
992 | if (is_array($item)) { | ||
993 | foreach ($item as $key => $value) { | ||
994 | if (! is_bool($value) && ! is_numeric($value)) { | ||
995 | continue; | ||
996 | } | ||
997 | |||
998 | $item[$key] = (int) (bool) $value; | ||
999 | } | ||
1000 | } elseif (is_bool($item) || is_numeric($item)) { | ||
1001 | $item = (int) (bool) $item; | ||
1002 | } | ||
1003 | |||
1004 | return $item; | ||
1005 | } | ||
1006 | |||
1007 | public function getCreateTemporaryTableSnippetSQL(): string | ||
1008 | { | ||
1009 | return 'CREATE TABLE'; | ||
1010 | } | ||
1011 | |||
1012 | public function getTemporaryTableName(string $tableName): string | ||
1013 | { | ||
1014 | return '#' . $tableName; | ||
1015 | } | ||
1016 | |||
1017 | public function getDateTimeFormatString(): string | ||
1018 | { | ||
1019 | return 'Y-m-d H:i:s.u'; | ||
1020 | } | ||
1021 | |||
1022 | public function getDateFormatString(): string | ||
1023 | { | ||
1024 | return 'Y-m-d'; | ||
1025 | } | ||
1026 | |||
1027 | public function getTimeFormatString(): string | ||
1028 | { | ||
1029 | return 'H:i:s'; | ||
1030 | } | ||
1031 | |||
1032 | public function getDateTimeTzFormatString(): string | ||
1033 | { | ||
1034 | return 'Y-m-d H:i:s.u P'; | ||
1035 | } | ||
1036 | |||
1037 | protected function initializeDoctrineTypeMappings(): void | ||
1038 | { | ||
1039 | $this->doctrineTypeMapping = [ | ||
1040 | 'bigint' => Types::BIGINT, | ||
1041 | 'binary' => Types::BINARY, | ||
1042 | 'bit' => Types::BOOLEAN, | ||
1043 | 'blob' => Types::BLOB, | ||
1044 | 'char' => Types::STRING, | ||
1045 | 'date' => Types::DATE_MUTABLE, | ||
1046 | 'datetime' => Types::DATETIME_MUTABLE, | ||
1047 | 'datetime2' => Types::DATETIME_MUTABLE, | ||
1048 | 'datetimeoffset' => Types::DATETIMETZ_MUTABLE, | ||
1049 | 'decimal' => Types::DECIMAL, | ||
1050 | 'double' => Types::FLOAT, | ||
1051 | 'double precision' => Types::FLOAT, | ||
1052 | 'float' => Types::FLOAT, | ||
1053 | 'image' => Types::BLOB, | ||
1054 | 'int' => Types::INTEGER, | ||
1055 | 'money' => Types::INTEGER, | ||
1056 | 'nchar' => Types::STRING, | ||
1057 | 'ntext' => Types::TEXT, | ||
1058 | 'numeric' => Types::DECIMAL, | ||
1059 | 'nvarchar' => Types::STRING, | ||
1060 | 'real' => Types::FLOAT, | ||
1061 | 'smalldatetime' => Types::DATETIME_MUTABLE, | ||
1062 | 'smallint' => Types::SMALLINT, | ||
1063 | 'smallmoney' => Types::INTEGER, | ||
1064 | 'text' => Types::TEXT, | ||
1065 | 'time' => Types::TIME_MUTABLE, | ||
1066 | 'tinyint' => Types::SMALLINT, | ||
1067 | 'uniqueidentifier' => Types::GUID, | ||
1068 | 'varbinary' => Types::BINARY, | ||
1069 | 'varchar' => Types::STRING, | ||
1070 | ]; | ||
1071 | } | ||
1072 | |||
1073 | public function createSavePoint(string $savepoint): string | ||
1074 | { | ||
1075 | return 'SAVE TRANSACTION ' . $savepoint; | ||
1076 | } | ||
1077 | |||
1078 | public function releaseSavePoint(string $savepoint): string | ||
1079 | { | ||
1080 | return ''; | ||
1081 | } | ||
1082 | |||
1083 | public function rollbackSavePoint(string $savepoint): string | ||
1084 | { | ||
1085 | return 'ROLLBACK TRANSACTION ' . $savepoint; | ||
1086 | } | ||
1087 | |||
1088 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
1089 | public function getForeignKeyReferentialActionSQL(string $action): string | ||
1090 | { | ||
1091 | // RESTRICT is not supported, therefore falling back to NO ACTION. | ||
1092 | if (strtoupper($action) === 'RESTRICT') { | ||
1093 | return 'NO ACTION'; | ||
1094 | } | ||
1095 | |||
1096 | return parent::getForeignKeyReferentialActionSQL($action); | ||
1097 | } | ||
1098 | |||
1099 | public function appendLockHint(string $fromClause, LockMode $lockMode): string | ||
1100 | { | ||
1101 | return match ($lockMode) { | ||
1102 | LockMode::NONE, | ||
1103 | LockMode::OPTIMISTIC => $fromClause, | ||
1104 | LockMode::PESSIMISTIC_READ => $fromClause . ' WITH (HOLDLOCK, ROWLOCK)', | ||
1105 | LockMode::PESSIMISTIC_WRITE => $fromClause . ' WITH (UPDLOCK, ROWLOCK)', | ||
1106 | }; | ||
1107 | } | ||
1108 | |||
1109 | protected function createReservedKeywordsList(): KeywordList | ||
1110 | { | ||
1111 | return new SQLServerKeywords(); | ||
1112 | } | ||
1113 | |||
1114 | public function quoteSingleIdentifier(string $str): string | ||
1115 | { | ||
1116 | return '[' . str_replace(']', ']]', $str) . ']'; | ||
1117 | } | ||
1118 | |||
1119 | public function getTruncateTableSQL(string $tableName, bool $cascade = false): string | ||
1120 | { | ||
1121 | $tableIdentifier = new Identifier($tableName); | ||
1122 | |||
1123 | return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this); | ||
1124 | } | ||
1125 | |||
1126 | /** | ||
1127 | * {@inheritDoc} | ||
1128 | */ | ||
1129 | public function getBlobTypeDeclarationSQL(array $column): string | ||
1130 | { | ||
1131 | return 'VARBINARY(MAX)'; | ||
1132 | } | ||
1133 | |||
1134 | /** | ||
1135 | * {@inheritDoc} | ||
1136 | * | ||
1137 | * @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. | ||
1138 | */ | ||
1139 | public function getColumnDeclarationSQL(string $name, array $column): string | ||
1140 | { | ||
1141 | if (isset($column['columnDefinition'])) { | ||
1142 | $declaration = $column['columnDefinition']; | ||
1143 | } else { | ||
1144 | $collation = ! empty($column['collation']) ? | ||
1145 | ' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : ''; | ||
1146 | |||
1147 | $notnull = ! empty($column['notnull']) ? ' NOT NULL' : ''; | ||
1148 | |||
1149 | $typeDecl = $column['type']->getSQLDeclaration($column, $this); | ||
1150 | $declaration = $typeDecl . $collation . $notnull; | ||
1151 | } | ||
1152 | |||
1153 | return $name . ' ' . $declaration; | ||
1154 | } | ||
1155 | |||
1156 | /** | ||
1157 | * SQL Server does not support quoting collation identifiers. | ||
1158 | */ | ||
1159 | public function getColumnCollationDeclarationSQL(string $collation): string | ||
1160 | { | ||
1161 | return 'COLLATE ' . $collation; | ||
1162 | } | ||
1163 | |||
1164 | public function columnsEqual(Column $column1, Column $column2): bool | ||
1165 | { | ||
1166 | if (! parent::columnsEqual($column1, $column2)) { | ||
1167 | return false; | ||
1168 | } | ||
1169 | |||
1170 | return $this->getDefaultValueDeclarationSQL($column1->toArray()) | ||
1171 | === $this->getDefaultValueDeclarationSQL($column2->toArray()); | ||
1172 | } | ||
1173 | |||
1174 | protected function getLikeWildcardCharacters(): string | ||
1175 | { | ||
1176 | return parent::getLikeWildcardCharacters() . '[]^'; | ||
1177 | } | ||
1178 | |||
1179 | protected function getCommentOnTableSQL(string $tableName, string $comment): string | ||
1180 | { | ||
1181 | return $this->getAddExtendedPropertySQL( | ||
1182 | 'MS_Description', | ||
1183 | $comment, | ||
1184 | 'SCHEMA', | ||
1185 | $this->quoteStringLiteral('dbo'), | ||
1186 | 'TABLE', | ||
1187 | $this->quoteStringLiteral($this->unquoteSingleIdentifier($tableName)), | ||
1188 | ); | ||
1189 | } | ||
1190 | |||
1191 | private function shouldAddOrderBy(string $query): bool | ||
1192 | { | ||
1193 | // Find the position of the last instance of ORDER BY and ensure it is not within a parenthetical statement | ||
1194 | // but can be in a newline | ||
1195 | $matches = []; | ||
1196 | $matchesCount = preg_match_all('/[\\s]+order\\s+by\\s/im', $query, $matches, PREG_OFFSET_CAPTURE); | ||
1197 | if ($matchesCount === 0) { | ||
1198 | return true; | ||
1199 | } | ||
1200 | |||
1201 | // ORDER BY instance may be in a subquery after ORDER BY | ||
1202 | // e.g. SELECT col1 FROM test ORDER BY (SELECT col2 from test ORDER BY col2) | ||
1203 | // if in the searched query ORDER BY clause was found where | ||
1204 | // number of open parentheses after the occurrence of the clause is equal to | ||
1205 | // number of closed brackets after the occurrence of the clause, | ||
1206 | // it means that ORDER BY is included in the query being checked | ||
1207 | while ($matchesCount > 0) { | ||
1208 | $orderByPos = $matches[0][--$matchesCount][1]; | ||
1209 | $openBracketsCount = substr_count($query, '(', $orderByPos); | ||
1210 | $closedBracketsCount = substr_count($query, ')', $orderByPos); | ||
1211 | if ($openBracketsCount === $closedBracketsCount) { | ||
1212 | return false; | ||
1213 | } | ||
1214 | } | ||
1215 | |||
1216 | return true; | ||
1217 | } | ||
1218 | |||
1219 | public function createSchemaManager(Connection $connection): SQLServerSchemaManager | ||
1220 | { | ||
1221 | return new SQLServerSchemaManager($connection, $this); | ||
1222 | } | ||
1223 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php b/vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php new file mode 100644 index 0000000..f27e1b4 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php | |||
@@ -0,0 +1,52 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms\SQLite; | ||
6 | |||
7 | use Doctrine\DBAL\Platforms\SQLitePlatform; | ||
8 | use Doctrine\DBAL\Schema\Comparator as BaseComparator; | ||
9 | use Doctrine\DBAL\Schema\Table; | ||
10 | use Doctrine\DBAL\Schema\TableDiff; | ||
11 | |||
12 | use function strcasecmp; | ||
13 | |||
14 | /** | ||
15 | * Compares schemas in the context of SQLite platform. | ||
16 | * | ||
17 | * BINARY is the default column collation and should be ignored if specified explicitly. | ||
18 | */ | ||
19 | class Comparator extends BaseComparator | ||
20 | { | ||
21 | /** @internal The comparator can be only instantiated by a schema manager. */ | ||
22 | public function __construct(SQLitePlatform $platform) | ||
23 | { | ||
24 | parent::__construct($platform); | ||
25 | } | ||
26 | |||
27 | public function compareTables(Table $oldTable, Table $newTable): TableDiff | ||
28 | { | ||
29 | return parent::compareTables( | ||
30 | $this->normalizeColumns($oldTable), | ||
31 | $this->normalizeColumns($newTable), | ||
32 | ); | ||
33 | } | ||
34 | |||
35 | private function normalizeColumns(Table $table): Table | ||
36 | { | ||
37 | $table = clone $table; | ||
38 | |||
39 | foreach ($table->getColumns() as $column) { | ||
40 | $options = $column->getPlatformOptions(); | ||
41 | |||
42 | if (! isset($options['collation']) || strcasecmp($options['collation'], 'binary') !== 0) { | ||
43 | continue; | ||
44 | } | ||
45 | |||
46 | unset($options['collation']); | ||
47 | $column->setPlatformOptions($options); | ||
48 | } | ||
49 | |||
50 | return $table; | ||
51 | } | ||
52 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/SQLitePlatform.php b/vendor/doctrine/dbal/src/Platforms/SQLitePlatform.php new file mode 100644 index 0000000..acec163 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/SQLitePlatform.php | |||
@@ -0,0 +1,994 @@ | |||
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\Exception\NotSupported; | ||
10 | use Doctrine\DBAL\Platforms\Keywords\KeywordList; | ||
11 | use Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords; | ||
12 | use Doctrine\DBAL\Schema\Column; | ||
13 | use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist; | ||
14 | use Doctrine\DBAL\Schema\ForeignKeyConstraint; | ||
15 | use Doctrine\DBAL\Schema\Identifier; | ||
16 | use Doctrine\DBAL\Schema\Index; | ||
17 | use Doctrine\DBAL\Schema\SQLiteSchemaManager; | ||
18 | use Doctrine\DBAL\Schema\Table; | ||
19 | use Doctrine\DBAL\Schema\TableDiff; | ||
20 | use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder; | ||
21 | use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder; | ||
22 | use Doctrine\DBAL\TransactionIsolationLevel; | ||
23 | use Doctrine\DBAL\Types; | ||
24 | use InvalidArgumentException; | ||
25 | |||
26 | use function array_combine; | ||
27 | use function array_keys; | ||
28 | use function array_merge; | ||
29 | use function array_search; | ||
30 | use function array_unique; | ||
31 | use function array_values; | ||
32 | use function count; | ||
33 | use function explode; | ||
34 | use function implode; | ||
35 | use function sprintf; | ||
36 | use function str_replace; | ||
37 | use function strpos; | ||
38 | use function strtolower; | ||
39 | use function substr; | ||
40 | use function trim; | ||
41 | |||
42 | /** | ||
43 | * The SQLitePlatform class describes the specifics and dialects of the SQLite | ||
44 | * database platform. | ||
45 | */ | ||
46 | class SQLitePlatform extends AbstractPlatform | ||
47 | { | ||
48 | public function getCreateDatabaseSQL(string $name): string | ||
49 | { | ||
50 | throw NotSupported::new(__METHOD__); | ||
51 | } | ||
52 | |||
53 | public function getDropDatabaseSQL(string $name): string | ||
54 | { | ||
55 | throw NotSupported::new(__METHOD__); | ||
56 | } | ||
57 | |||
58 | public function getRegexpExpression(): string | ||
59 | { | ||
60 | return 'REGEXP'; | ||
61 | } | ||
62 | |||
63 | public function getModExpression(string $dividend, string $divisor): string | ||
64 | { | ||
65 | return $dividend . ' % ' . $divisor; | ||
66 | } | ||
67 | |||
68 | public function getTrimExpression( | ||
69 | string $str, | ||
70 | TrimMode $mode = TrimMode::UNSPECIFIED, | ||
71 | ?string $char = null, | ||
72 | ): string { | ||
73 | $trimFn = match ($mode) { | ||
74 | TrimMode::UNSPECIFIED, | ||
75 | TrimMode::BOTH => 'TRIM', | ||
76 | TrimMode::LEADING => 'LTRIM', | ||
77 | TrimMode::TRAILING => 'RTRIM', | ||
78 | }; | ||
79 | |||
80 | $arguments = [$str]; | ||
81 | |||
82 | if ($char !== null) { | ||
83 | $arguments[] = $char; | ||
84 | } | ||
85 | |||
86 | return sprintf('%s(%s)', $trimFn, implode(', ', $arguments)); | ||
87 | } | ||
88 | |||
89 | public function getSubstringExpression(string $string, string $start, ?string $length = null): string | ||
90 | { | ||
91 | if ($length === null) { | ||
92 | return sprintf('SUBSTR(%s, %s)', $string, $start); | ||
93 | } | ||
94 | |||
95 | return sprintf('SUBSTR(%s, %s, %s)', $string, $start, $length); | ||
96 | } | ||
97 | |||
98 | public function getLocateExpression(string $string, string $substring, ?string $start = null): string | ||
99 | { | ||
100 | if ($start === null || $start === '1') { | ||
101 | return sprintf('INSTR(%s, %s)', $string, $substring); | ||
102 | } | ||
103 | |||
104 | return sprintf( | ||
105 | 'CASE WHEN INSTR(SUBSTR(%1$s, %3$s), %2$s) > 0 THEN INSTR(SUBSTR(%1$s, %3$s), %2$s) + %3$s - 1 ELSE 0 END', | ||
106 | $string, | ||
107 | $substring, | ||
108 | $start, | ||
109 | ); | ||
110 | } | ||
111 | |||
112 | protected function getDateArithmeticIntervalExpression( | ||
113 | string $date, | ||
114 | string $operator, | ||
115 | string $interval, | ||
116 | DateIntervalUnit $unit, | ||
117 | ): string { | ||
118 | switch ($unit) { | ||
119 | case DateIntervalUnit::WEEK: | ||
120 | $interval = $this->multiplyInterval($interval, 7); | ||
121 | $unit = DateIntervalUnit::DAY; | ||
122 | break; | ||
123 | |||
124 | case DateIntervalUnit::QUARTER: | ||
125 | $interval = $this->multiplyInterval($interval, 3); | ||
126 | $unit = DateIntervalUnit::MONTH; | ||
127 | break; | ||
128 | } | ||
129 | |||
130 | return 'DATETIME(' . $date . ',' . $this->getConcatExpression( | ||
131 | $this->quoteStringLiteral($operator), | ||
132 | $interval, | ||
133 | $this->quoteStringLiteral(' ' . $unit->value), | ||
134 | ) . ')'; | ||
135 | } | ||
136 | |||
137 | public function getDateDiffExpression(string $date1, string $date2): string | ||
138 | { | ||
139 | return sprintf("JULIANDAY(%s, 'start of day') - JULIANDAY(%s, 'start of day')", $date1, $date2); | ||
140 | } | ||
141 | |||
142 | /** | ||
143 | * {@inheritDoc} | ||
144 | * | ||
145 | * The DBAL doesn't support databases on the SQLite platform. The expression here always returns a fixed string | ||
146 | * as an indicator of an implicitly selected database. | ||
147 | * | ||
148 | * @link https://www.sqlite.org/lang_select.html | ||
149 | * @see Connection::getDatabase() | ||
150 | */ | ||
151 | public function getCurrentDatabaseExpression(): string | ||
152 | { | ||
153 | return "'main'"; | ||
154 | } | ||
155 | |||
156 | /** @link https://www2.sqlite.org/cvstrac/wiki?p=UnsupportedSql */ | ||
157 | public function createSelectSQLBuilder(): SelectSQLBuilder | ||
158 | { | ||
159 | return new DefaultSelectSQLBuilder($this, null, null); | ||
160 | } | ||
161 | |||
162 | protected function _getTransactionIsolationLevelSQL(TransactionIsolationLevel $level): string | ||
163 | { | ||
164 | return match ($level) { | ||
165 | TransactionIsolationLevel::READ_UNCOMMITTED => '0', | ||
166 | TransactionIsolationLevel::READ_COMMITTED, | ||
167 | TransactionIsolationLevel::REPEATABLE_READ, | ||
168 | TransactionIsolationLevel::SERIALIZABLE => '1', | ||
169 | }; | ||
170 | } | ||
171 | |||
172 | public function getSetTransactionIsolationSQL(TransactionIsolationLevel $level): string | ||
173 | { | ||
174 | return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level); | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * {@inheritDoc} | ||
179 | */ | ||
180 | public function getBooleanTypeDeclarationSQL(array $column): string | ||
181 | { | ||
182 | return 'BOOLEAN'; | ||
183 | } | ||
184 | |||
185 | /** | ||
186 | * {@inheritDoc} | ||
187 | */ | ||
188 | public function getIntegerTypeDeclarationSQL(array $column): string | ||
189 | { | ||
190 | return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * {@inheritDoc} | ||
195 | */ | ||
196 | public function getBigIntTypeDeclarationSQL(array $column): string | ||
197 | { | ||
198 | // SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields. | ||
199 | if (! empty($column['autoincrement'])) { | ||
200 | return $this->getIntegerTypeDeclarationSQL($column); | ||
201 | } | ||
202 | |||
203 | return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
204 | } | ||
205 | |||
206 | /** | ||
207 | * {@inheritDoc} | ||
208 | */ | ||
209 | public function getSmallIntTypeDeclarationSQL(array $column): string | ||
210 | { | ||
211 | // SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields. | ||
212 | if (! empty($column['autoincrement'])) { | ||
213 | return $this->getIntegerTypeDeclarationSQL($column); | ||
214 | } | ||
215 | |||
216 | return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($column); | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * {@inheritDoc} | ||
221 | */ | ||
222 | public function getDateTimeTypeDeclarationSQL(array $column): string | ||
223 | { | ||
224 | return 'DATETIME'; | ||
225 | } | ||
226 | |||
227 | /** | ||
228 | * {@inheritDoc} | ||
229 | */ | ||
230 | public function getDateTypeDeclarationSQL(array $column): string | ||
231 | { | ||
232 | return 'DATE'; | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * {@inheritDoc} | ||
237 | */ | ||
238 | public function getTimeTypeDeclarationSQL(array $column): string | ||
239 | { | ||
240 | return 'TIME'; | ||
241 | } | ||
242 | |||
243 | /** | ||
244 | * {@inheritDoc} | ||
245 | */ | ||
246 | protected function _getCommonIntegerTypeDeclarationSQL(array $column): string | ||
247 | { | ||
248 | // sqlite autoincrement is only possible for the primary key | ||
249 | if (! empty($column['autoincrement'])) { | ||
250 | return ' PRIMARY KEY AUTOINCREMENT'; | ||
251 | } | ||
252 | |||
253 | return ! empty($column['unsigned']) ? ' UNSIGNED' : ''; | ||
254 | } | ||
255 | |||
256 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
257 | public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey): string | ||
258 | { | ||
259 | return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint( | ||
260 | $foreignKey->getQuotedLocalColumns($this), | ||
261 | $foreignKey->getQuotedForeignTableName($this), | ||
262 | $foreignKey->getQuotedForeignColumns($this), | ||
263 | $foreignKey->getName(), | ||
264 | $foreignKey->getOptions(), | ||
265 | )); | ||
266 | } | ||
267 | |||
268 | /** | ||
269 | * {@inheritDoc} | ||
270 | */ | ||
271 | protected function _getCreateTableSQL(string $name, array $columns, array $options = []): array | ||
272 | { | ||
273 | $queryFields = $this->getColumnDeclarationListSQL($columns); | ||
274 | |||
275 | if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { | ||
276 | foreach ($options['uniqueConstraints'] as $definition) { | ||
277 | $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($definition); | ||
278 | } | ||
279 | } | ||
280 | |||
281 | $queryFields .= $this->getNonAutoincrementPrimaryKeyDefinition($columns, $options); | ||
282 | |||
283 | if (isset($options['foreignKeys'])) { | ||
284 | foreach ($options['foreignKeys'] as $foreignKey) { | ||
285 | $queryFields .= ', ' . $this->getForeignKeyDeclarationSQL($foreignKey); | ||
286 | } | ||
287 | } | ||
288 | |||
289 | $tableComment = ''; | ||
290 | if (isset($options['comment'])) { | ||
291 | $comment = trim($options['comment'], " '"); | ||
292 | |||
293 | $tableComment = $this->getInlineTableCommentSQL($comment); | ||
294 | } | ||
295 | |||
296 | $query = ['CREATE TABLE ' . $name . ' ' . $tableComment . '(' . $queryFields . ')']; | ||
297 | |||
298 | if (isset($options['alter']) && $options['alter'] === true) { | ||
299 | return $query; | ||
300 | } | ||
301 | |||
302 | if (isset($options['indexes']) && ! empty($options['indexes'])) { | ||
303 | foreach ($options['indexes'] as $indexDef) { | ||
304 | $query[] = $this->getCreateIndexSQL($indexDef, $name); | ||
305 | } | ||
306 | } | ||
307 | |||
308 | if (isset($options['unique']) && ! empty($options['unique'])) { | ||
309 | foreach ($options['unique'] as $indexDef) { | ||
310 | $query[] = $this->getCreateIndexSQL($indexDef, $name); | ||
311 | } | ||
312 | } | ||
313 | |||
314 | return $query; | ||
315 | } | ||
316 | |||
317 | /** | ||
318 | * Generate a PRIMARY KEY definition if no autoincrement value is used | ||
319 | * | ||
320 | * @param mixed[][] $columns | ||
321 | * @param mixed[] $options | ||
322 | */ | ||
323 | private function getNonAutoincrementPrimaryKeyDefinition(array $columns, array $options): string | ||
324 | { | ||
325 | if (empty($options['primary'])) { | ||
326 | return ''; | ||
327 | } | ||
328 | |||
329 | $keyColumns = array_unique(array_values($options['primary'])); | ||
330 | |||
331 | foreach ($keyColumns as $keyColumn) { | ||
332 | foreach ($columns as $column) { | ||
333 | if ($column['name'] === $keyColumn && ! empty($column['autoincrement'])) { | ||
334 | return ''; | ||
335 | } | ||
336 | } | ||
337 | } | ||
338 | |||
339 | return ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; | ||
340 | } | ||
341 | |||
342 | protected function getBinaryTypeDeclarationSQLSnippet(?int $length): string | ||
343 | { | ||
344 | return 'BLOB'; | ||
345 | } | ||
346 | |||
347 | protected function getVarcharTypeDeclarationSQLSnippet(?int $length): string | ||
348 | { | ||
349 | $sql = 'VARCHAR'; | ||
350 | |||
351 | if ($length !== null) { | ||
352 | $sql .= sprintf('(%d)', $length); | ||
353 | } | ||
354 | |||
355 | return $sql; | ||
356 | } | ||
357 | |||
358 | protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length): string | ||
359 | { | ||
360 | return 'BLOB'; | ||
361 | } | ||
362 | |||
363 | /** | ||
364 | * {@inheritDoc} | ||
365 | */ | ||
366 | public function getClobTypeDeclarationSQL(array $column): string | ||
367 | { | ||
368 | return 'CLOB'; | ||
369 | } | ||
370 | |||
371 | /** @internal The method should be only used from within the {@see AbstractSchemaManager} class hierarchy. */ | ||
372 | public function getListViewsSQL(string $database): string | ||
373 | { | ||
374 | return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; | ||
375 | } | ||
376 | |||
377 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
378 | public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey): string | ||
379 | { | ||
380 | $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey); | ||
381 | |||
382 | if (! $foreignKey->hasOption('deferrable') || $foreignKey->getOption('deferrable') === false) { | ||
383 | $query .= ' NOT'; | ||
384 | } | ||
385 | |||
386 | $query .= ' DEFERRABLE'; | ||
387 | $query .= ' INITIALLY'; | ||
388 | |||
389 | if ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) { | ||
390 | $query .= ' DEFERRED'; | ||
391 | } else { | ||
392 | $query .= ' IMMEDIATE'; | ||
393 | } | ||
394 | |||
395 | return $query; | ||
396 | } | ||
397 | |||
398 | public function supportsIdentityColumns(): bool | ||
399 | { | ||
400 | return true; | ||
401 | } | ||
402 | |||
403 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
404 | public function supportsColumnCollation(): bool | ||
405 | { | ||
406 | return true; | ||
407 | } | ||
408 | |||
409 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
410 | public function supportsInlineColumnComments(): bool | ||
411 | { | ||
412 | return true; | ||
413 | } | ||
414 | |||
415 | public function getTruncateTableSQL(string $tableName, bool $cascade = false): string | ||
416 | { | ||
417 | $tableIdentifier = new Identifier($tableName); | ||
418 | |||
419 | return 'DELETE FROM ' . $tableIdentifier->getQuotedName($this); | ||
420 | } | ||
421 | |||
422 | /** @internal The method should be only used from within the {@see AbstractPlatform} class hierarchy. */ | ||
423 | public function getInlineColumnCommentSQL(string $comment): string | ||
424 | { | ||
425 | if ($comment === '') { | ||
426 | return ''; | ||
427 | } | ||
428 | |||
429 | return '--' . str_replace("\n", "\n--", $comment) . "\n"; | ||
430 | } | ||
431 | |||
432 | private function getInlineTableCommentSQL(string $comment): string | ||
433 | { | ||
434 | return $this->getInlineColumnCommentSQL($comment); | ||
435 | } | ||
436 | |||
437 | protected function initializeDoctrineTypeMappings(): void | ||
438 | { | ||
439 | $this->doctrineTypeMapping = [ | ||
440 | 'bigint' => 'bigint', | ||
441 | 'bigserial' => 'bigint', | ||
442 | 'blob' => 'blob', | ||
443 | 'boolean' => 'boolean', | ||
444 | 'char' => 'string', | ||
445 | 'clob' => 'text', | ||
446 | 'date' => 'date', | ||
447 | 'datetime' => 'datetime', | ||
448 | 'decimal' => 'decimal', | ||
449 | 'double' => 'float', | ||
450 | 'double precision' => 'float', | ||
451 | 'float' => 'float', | ||
452 | 'image' => 'string', | ||
453 | 'int' => 'integer', | ||
454 | 'integer' => 'integer', | ||
455 | 'longtext' => 'text', | ||
456 | 'longvarchar' => 'string', | ||
457 | 'mediumint' => 'integer', | ||
458 | 'mediumtext' => 'text', | ||
459 | 'ntext' => 'string', | ||
460 | 'numeric' => 'decimal', | ||
461 | 'nvarchar' => 'string', | ||
462 | 'real' => 'float', | ||
463 | 'serial' => 'integer', | ||
464 | 'smallint' => 'smallint', | ||
465 | 'string' => 'string', | ||
466 | 'text' => 'text', | ||
467 | 'time' => 'time', | ||
468 | 'timestamp' => 'datetime', | ||
469 | 'tinyint' => 'boolean', | ||
470 | 'tinytext' => 'text', | ||
471 | 'varchar' => 'string', | ||
472 | 'varchar2' => 'string', | ||
473 | ]; | ||
474 | } | ||
475 | |||
476 | protected function createReservedKeywordsList(): KeywordList | ||
477 | { | ||
478 | return new SQLiteKeywords(); | ||
479 | } | ||
480 | |||
481 | /** | ||
482 | * {@inheritDoc} | ||
483 | */ | ||
484 | protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff): array | ||
485 | { | ||
486 | return []; | ||
487 | } | ||
488 | |||
489 | /** | ||
490 | * {@inheritDoc} | ||
491 | */ | ||
492 | protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff): array | ||
493 | { | ||
494 | $table = $diff->getOldTable(); | ||
495 | |||
496 | $sql = []; | ||
497 | |||
498 | foreach ($this->getIndexesInAlteredTable($diff, $table) as $index) { | ||
499 | if ($index->isPrimary()) { | ||
500 | continue; | ||
501 | } | ||
502 | |||
503 | $sql[] = $this->getCreateIndexSQL($index, $table->getQuotedName($this)); | ||
504 | } | ||
505 | |||
506 | return $sql; | ||
507 | } | ||
508 | |||
509 | protected function doModifyLimitQuery(string $query, ?int $limit, int $offset): string | ||
510 | { | ||
511 | if ($limit === null && $offset > 0) { | ||
512 | $limit = -1; | ||
513 | } | ||
514 | |||
515 | return parent::doModifyLimitQuery($query, $limit, $offset); | ||
516 | } | ||
517 | |||
518 | /** | ||
519 | * {@inheritDoc} | ||
520 | */ | ||
521 | public function getBlobTypeDeclarationSQL(array $column): string | ||
522 | { | ||
523 | return 'BLOB'; | ||
524 | } | ||
525 | |||
526 | public function getTemporaryTableName(string $tableName): string | ||
527 | { | ||
528 | return $tableName; | ||
529 | } | ||
530 | |||
531 | /** | ||
532 | * {@inheritDoc} | ||
533 | */ | ||
534 | public function getCreateTablesSQL(array $tables): array | ||
535 | { | ||
536 | $sql = []; | ||
537 | |||
538 | foreach ($tables as $table) { | ||
539 | $sql = array_merge($sql, $this->getCreateTableSQL($table)); | ||
540 | } | ||
541 | |||
542 | return $sql; | ||
543 | } | ||
544 | |||
545 | /** {@inheritDoc} */ | ||
546 | public function getCreateIndexSQL(Index $index, string $table): string | ||
547 | { | ||
548 | $name = $index->getQuotedName($this); | ||
549 | $columns = $index->getColumns(); | ||
550 | |||
551 | if (count($columns) === 0) { | ||
552 | throw new InvalidArgumentException(sprintf( | ||
553 | 'Incomplete or invalid index definition %s on table %s', | ||
554 | $name, | ||
555 | $table, | ||
556 | )); | ||
557 | } | ||
558 | |||
559 | if ($index->isPrimary()) { | ||
560 | return $this->getCreatePrimaryKeySQL($index, $table); | ||
561 | } | ||
562 | |||
563 | if (strpos($table, '.') !== false) { | ||
564 | [$schema, $table] = explode('.', $table); | ||
565 | $name = $schema . '.' . $name; | ||
566 | } | ||
567 | |||
568 | $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; | ||
569 | $query .= ' (' . implode(', ', $index->getQuotedColumns($this)) . ')' . $this->getPartialIndexSQL($index); | ||
570 | |||
571 | return $query; | ||
572 | } | ||
573 | |||
574 | /** | ||
575 | * {@inheritDoc} | ||
576 | */ | ||
577 | public function getDropTablesSQL(array $tables): array | ||
578 | { | ||
579 | $sql = []; | ||
580 | |||
581 | foreach ($tables as $table) { | ||
582 | $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); | ||
583 | } | ||
584 | |||
585 | return $sql; | ||
586 | } | ||
587 | |||
588 | public function getCreatePrimaryKeySQL(Index $index, string $table): string | ||
589 | { | ||
590 | throw NotSupported::new(__METHOD__); | ||
591 | } | ||
592 | |||
593 | public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, string $table): string | ||
594 | { | ||
595 | throw NotSupported::new(__METHOD__); | ||
596 | } | ||
597 | |||
598 | public function getDropForeignKeySQL(string $foreignKey, string $table): string | ||
599 | { | ||
600 | throw NotSupported::new(__METHOD__); | ||
601 | } | ||
602 | |||
603 | /** | ||
604 | * {@inheritDoc} | ||
605 | */ | ||
606 | public function getAlterTableSQL(TableDiff $diff): array | ||
607 | { | ||
608 | $sql = $this->getSimpleAlterTableSQL($diff); | ||
609 | if ($sql !== false) { | ||
610 | return $sql; | ||
611 | } | ||
612 | |||
613 | $table = $diff->getOldTable(); | ||
614 | |||
615 | $columns = []; | ||
616 | $oldColumnNames = []; | ||
617 | $newColumnNames = []; | ||
618 | $columnSql = []; | ||
619 | |||
620 | foreach ($table->getColumns() as $column) { | ||
621 | $columnName = strtolower($column->getName()); | ||
622 | $columns[$columnName] = $column; | ||
623 | $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this); | ||
624 | } | ||
625 | |||
626 | foreach ($diff->getDroppedColumns() as $column) { | ||
627 | $columnName = strtolower($column->getName()); | ||
628 | if (! isset($columns[$columnName])) { | ||
629 | continue; | ||
630 | } | ||
631 | |||
632 | unset( | ||
633 | $columns[$columnName], | ||
634 | $oldColumnNames[$columnName], | ||
635 | $newColumnNames[$columnName], | ||
636 | ); | ||
637 | } | ||
638 | |||
639 | foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { | ||
640 | $oldColumnName = strtolower($oldColumnName); | ||
641 | |||
642 | $columns = $this->replaceColumn( | ||
643 | $table->getName(), | ||
644 | $columns, | ||
645 | $oldColumnName, | ||
646 | $column, | ||
647 | ); | ||
648 | |||
649 | if (! isset($newColumnNames[$oldColumnName])) { | ||
650 | continue; | ||
651 | } | ||
652 | |||
653 | $newColumnNames[$oldColumnName] = $column->getQuotedName($this); | ||
654 | } | ||
655 | |||
656 | foreach ($diff->getModifiedColumns() as $columnDiff) { | ||
657 | $oldColumnName = strtolower($columnDiff->getOldColumn()->getName()); | ||
658 | $newColumn = $columnDiff->getNewColumn(); | ||
659 | |||
660 | $columns = $this->replaceColumn( | ||
661 | $table->getName(), | ||
662 | $columns, | ||
663 | $oldColumnName, | ||
664 | $newColumn, | ||
665 | ); | ||
666 | |||
667 | if (! isset($newColumnNames[$oldColumnName])) { | ||
668 | continue; | ||
669 | } | ||
670 | |||
671 | $newColumnNames[$oldColumnName] = $newColumn->getQuotedName($this); | ||
672 | } | ||
673 | |||
674 | foreach ($diff->getAddedColumns() as $column) { | ||
675 | $columns[strtolower($column->getName())] = $column; | ||
676 | } | ||
677 | |||
678 | $tableName = $table->getName(); | ||
679 | $pos = strpos($tableName, '.'); | ||
680 | if ($pos !== false) { | ||
681 | $tableName = substr($tableName, $pos + 1); | ||
682 | } | ||
683 | |||
684 | $dataTable = new Table('__temp__' . $tableName); | ||
685 | |||
686 | $newTable = new Table( | ||
687 | $table->getQuotedName($this), | ||
688 | $columns, | ||
689 | $this->getPrimaryIndexInAlteredTable($diff, $table), | ||
690 | [], | ||
691 | $this->getForeignKeysInAlteredTable($diff, $table), | ||
692 | $table->getOptions(), | ||
693 | ); | ||
694 | |||
695 | $newTable->addOption('alter', true); | ||
696 | |||
697 | $sql = $this->getPreAlterTableIndexForeignKeySQL($diff); | ||
698 | |||
699 | $sql[] = sprintf( | ||
700 | 'CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', | ||
701 | $dataTable->getQuotedName($this), | ||
702 | implode(', ', $oldColumnNames), | ||
703 | $table->getQuotedName($this), | ||
704 | ); | ||
705 | $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); | ||
706 | |||
707 | $sql = array_merge($sql, $this->getCreateTableSQL($newTable)); | ||
708 | $sql[] = sprintf( | ||
709 | 'INSERT INTO %s (%s) SELECT %s FROM %s', | ||
710 | $newTable->getQuotedName($this), | ||
711 | implode(', ', $newColumnNames), | ||
712 | implode(', ', $oldColumnNames), | ||
713 | $dataTable->getQuotedName($this), | ||
714 | ); | ||
715 | $sql[] = $this->getDropTableSQL($dataTable->getQuotedName($this)); | ||
716 | |||
717 | return array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff), $columnSql); | ||
718 | } | ||
719 | |||
720 | /** | ||
721 | * Replace the column with the given name with the new column. | ||
722 | * | ||
723 | * @param array<string,Column> $columns | ||
724 | * | ||
725 | * @return array<string,Column> | ||
726 | * | ||
727 | * @throws Exception | ||
728 | */ | ||
729 | private function replaceColumn(string $tableName, array $columns, string $columnName, Column $column): array | ||
730 | { | ||
731 | $keys = array_keys($columns); | ||
732 | $index = array_search($columnName, $keys, true); | ||
733 | |||
734 | if ($index === false) { | ||
735 | throw ColumnDoesNotExist::new($columnName, $tableName); | ||
736 | } | ||
737 | |||
738 | $values = array_values($columns); | ||
739 | |||
740 | $keys[$index] = strtolower($column->getName()); | ||
741 | $values[$index] = $column; | ||
742 | |||
743 | return array_combine($keys, $values); | ||
744 | } | ||
745 | |||
746 | /** | ||
747 | * @return list<string>|false | ||
748 | * | ||
749 | * @throws Exception | ||
750 | */ | ||
751 | private function getSimpleAlterTableSQL(TableDiff $diff): array|false | ||
752 | { | ||
753 | if ( | ||
754 | count($diff->getModifiedColumns()) > 0 | ||
755 | || count($diff->getDroppedColumns()) > 0 | ||
756 | || count($diff->getRenamedColumns()) > 0 | ||
757 | || count($diff->getAddedIndexes()) > 0 | ||
758 | || count($diff->getModifiedIndexes()) > 0 | ||
759 | || count($diff->getDroppedIndexes()) > 0 | ||
760 | || count($diff->getRenamedIndexes()) > 0 | ||
761 | || count($diff->getAddedForeignKeys()) > 0 | ||
762 | || count($diff->getModifiedForeignKeys()) > 0 | ||
763 | || count($diff->getDroppedForeignKeys()) > 0 | ||
764 | ) { | ||
765 | return false; | ||
766 | } | ||
767 | |||
768 | $table = $diff->getOldTable(); | ||
769 | |||
770 | $sql = []; | ||
771 | $columnSql = []; | ||
772 | |||
773 | foreach ($diff->getAddedColumns() as $column) { | ||
774 | $definition = array_merge([ | ||
775 | 'unique' => null, | ||
776 | 'autoincrement' => null, | ||
777 | 'default' => null, | ||
778 | ], $column->toArray()); | ||
779 | |||
780 | $type = $definition['type']; | ||
781 | |||
782 | /** @psalm-suppress RiskyTruthyFalsyComparison */ | ||
783 | switch (true) { | ||
784 | case isset($definition['columnDefinition']) || $definition['autoincrement'] || $definition['unique']: | ||
785 | case $type instanceof Types\DateTimeType && $definition['default'] === $this->getCurrentTimestampSQL(): | ||
786 | case $type instanceof Types\DateType && $definition['default'] === $this->getCurrentDateSQL(): | ||
787 | case $type instanceof Types\TimeType && $definition['default'] === $this->getCurrentTimeSQL(): | ||
788 | return false; | ||
789 | } | ||
790 | |||
791 | $definition['name'] = $column->getQuotedName($this); | ||
792 | |||
793 | $sql[] = 'ALTER TABLE ' . $table->getQuotedName($this) . ' ADD COLUMN ' | ||
794 | . $this->getColumnDeclarationSQL($definition['name'], $definition); | ||
795 | } | ||
796 | |||
797 | return array_merge($sql, $columnSql); | ||
798 | } | ||
799 | |||
800 | /** @return string[] */ | ||
801 | private function getColumnNamesInAlteredTable(TableDiff $diff, Table $oldTable): array | ||
802 | { | ||
803 | $columns = []; | ||
804 | |||
805 | foreach ($oldTable->getColumns() as $column) { | ||
806 | $columnName = $column->getName(); | ||
807 | $columns[strtolower($columnName)] = $columnName; | ||
808 | } | ||
809 | |||
810 | foreach ($diff->getDroppedColumns() as $column) { | ||
811 | $columnName = strtolower($column->getName()); | ||
812 | if (! isset($columns[$columnName])) { | ||
813 | continue; | ||
814 | } | ||
815 | |||
816 | unset($columns[$columnName]); | ||
817 | } | ||
818 | |||
819 | foreach ($diff->getRenamedColumns() as $oldColumnName => $column) { | ||
820 | $columnName = $column->getName(); | ||
821 | $columns[strtolower($oldColumnName)] = $columnName; | ||
822 | $columns[strtolower($columnName)] = $columnName; | ||
823 | } | ||
824 | |||
825 | foreach ($diff->getModifiedColumns() as $columnDiff) { | ||
826 | $oldColumnName = $columnDiff->getOldColumn()->getName(); | ||
827 | $newColumnName = $columnDiff->getNewColumn()->getName(); | ||
828 | $columns[strtolower($oldColumnName)] = $newColumnName; | ||
829 | $columns[strtolower($newColumnName)] = $newColumnName; | ||
830 | } | ||
831 | |||
832 | foreach ($diff->getAddedColumns() as $column) { | ||
833 | $columnName = $column->getName(); | ||
834 | $columns[strtolower($columnName)] = $columnName; | ||
835 | } | ||
836 | |||
837 | return $columns; | ||
838 | } | ||
839 | |||
840 | /** @return Index[] */ | ||
841 | private function getIndexesInAlteredTable(TableDiff $diff, Table $oldTable): array | ||
842 | { | ||
843 | $indexes = $oldTable->getIndexes(); | ||
844 | $columnNames = $this->getColumnNamesInAlteredTable($diff, $oldTable); | ||
845 | |||
846 | foreach ($indexes as $key => $index) { | ||
847 | foreach ($diff->getRenamedIndexes() as $oldIndexName => $renamedIndex) { | ||
848 | if (strtolower($key) !== strtolower($oldIndexName)) { | ||
849 | continue; | ||
850 | } | ||
851 | |||
852 | unset($indexes[$key]); | ||
853 | } | ||
854 | |||
855 | $changed = false; | ||
856 | $indexColumns = []; | ||
857 | foreach ($index->getColumns() as $columnName) { | ||
858 | $normalizedColumnName = strtolower($columnName); | ||
859 | if (! isset($columnNames[$normalizedColumnName])) { | ||
860 | unset($indexes[$key]); | ||
861 | continue 2; | ||
862 | } | ||
863 | |||
864 | $indexColumns[] = $columnNames[$normalizedColumnName]; | ||
865 | if ($columnName === $columnNames[$normalizedColumnName]) { | ||
866 | continue; | ||
867 | } | ||
868 | |||
869 | $changed = true; | ||
870 | } | ||
871 | |||
872 | if (! $changed) { | ||
873 | continue; | ||
874 | } | ||
875 | |||
876 | $indexes[$key] = new Index( | ||
877 | $index->getName(), | ||
878 | $indexColumns, | ||
879 | $index->isUnique(), | ||
880 | $index->isPrimary(), | ||
881 | $index->getFlags(), | ||
882 | ); | ||
883 | } | ||
884 | |||
885 | foreach ($diff->getDroppedIndexes() as $index) { | ||
886 | $indexName = $index->getName(); | ||
887 | |||
888 | if ($indexName === '') { | ||
889 | continue; | ||
890 | } | ||
891 | |||
892 | unset($indexes[strtolower($indexName)]); | ||
893 | } | ||
894 | |||
895 | foreach ( | ||
896 | array_merge( | ||
897 | $diff->getModifiedIndexes(), | ||
898 | $diff->getAddedIndexes(), | ||
899 | $diff->getRenamedIndexes(), | ||
900 | ) as $index | ||
901 | ) { | ||
902 | $indexName = $index->getName(); | ||
903 | |||
904 | if ($indexName !== '') { | ||
905 | $indexes[strtolower($indexName)] = $index; | ||
906 | } else { | ||
907 | $indexes[] = $index; | ||
908 | } | ||
909 | } | ||
910 | |||
911 | return $indexes; | ||
912 | } | ||
913 | |||
914 | /** @return ForeignKeyConstraint[] */ | ||
915 | private function getForeignKeysInAlteredTable(TableDiff $diff, Table $oldTable): array | ||
916 | { | ||
917 | $foreignKeys = $oldTable->getForeignKeys(); | ||
918 | $columnNames = $this->getColumnNamesInAlteredTable($diff, $oldTable); | ||
919 | |||
920 | foreach ($foreignKeys as $key => $constraint) { | ||
921 | $changed = false; | ||
922 | $localColumns = []; | ||
923 | foreach ($constraint->getLocalColumns() as $columnName) { | ||
924 | $normalizedColumnName = strtolower($columnName); | ||
925 | if (! isset($columnNames[$normalizedColumnName])) { | ||
926 | unset($foreignKeys[$key]); | ||
927 | continue 2; | ||
928 | } | ||
929 | |||
930 | $localColumns[] = $columnNames[$normalizedColumnName]; | ||
931 | if ($columnName === $columnNames[$normalizedColumnName]) { | ||
932 | continue; | ||
933 | } | ||
934 | |||
935 | $changed = true; | ||
936 | } | ||
937 | |||
938 | if (! $changed) { | ||
939 | continue; | ||
940 | } | ||
941 | |||
942 | $foreignKeys[$key] = new ForeignKeyConstraint( | ||
943 | $localColumns, | ||
944 | $constraint->getForeignTableName(), | ||
945 | $constraint->getForeignColumns(), | ||
946 | $constraint->getName(), | ||
947 | $constraint->getOptions(), | ||
948 | ); | ||
949 | } | ||
950 | |||
951 | foreach ($diff->getDroppedForeignKeys() as $constraint) { | ||
952 | $constraintName = $constraint->getName(); | ||
953 | |||
954 | if ($constraintName === '') { | ||
955 | continue; | ||
956 | } | ||
957 | |||
958 | unset($foreignKeys[strtolower($constraintName)]); | ||
959 | } | ||
960 | |||
961 | foreach (array_merge($diff->getModifiedForeignKeys(), $diff->getAddedForeignKeys()) as $constraint) { | ||
962 | $constraintName = $constraint->getName(); | ||
963 | |||
964 | if ($constraintName !== '') { | ||
965 | $foreignKeys[strtolower($constraintName)] = $constraint; | ||
966 | } else { | ||
967 | $foreignKeys[] = $constraint; | ||
968 | } | ||
969 | } | ||
970 | |||
971 | return $foreignKeys; | ||
972 | } | ||
973 | |||
974 | /** @return Index[] */ | ||
975 | private function getPrimaryIndexInAlteredTable(TableDiff $diff, Table $oldTable): array | ||
976 | { | ||
977 | $primaryIndex = []; | ||
978 | |||
979 | foreach ($this->getIndexesInAlteredTable($diff, $oldTable) as $index) { | ||
980 | if (! $index->isPrimary()) { | ||
981 | continue; | ||
982 | } | ||
983 | |||
984 | $primaryIndex = [$index->getName() => $index]; | ||
985 | } | ||
986 | |||
987 | return $primaryIndex; | ||
988 | } | ||
989 | |||
990 | public function createSchemaManager(Connection $connection): SQLiteSchemaManager | ||
991 | { | ||
992 | return new SQLiteSchemaManager($connection, $this); | ||
993 | } | ||
994 | } | ||
diff --git a/vendor/doctrine/dbal/src/Platforms/TrimMode.php b/vendor/doctrine/dbal/src/Platforms/TrimMode.php new file mode 100644 index 0000000..31c2375 --- /dev/null +++ b/vendor/doctrine/dbal/src/Platforms/TrimMode.php | |||
@@ -0,0 +1,13 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Doctrine\DBAL\Platforms; | ||
6 | |||
7 | enum TrimMode | ||
8 | { | ||
9 | case UNSPECIFIED; | ||
10 | case LEADING; | ||
11 | case TRAILING; | ||
12 | case BOTH; | ||
13 | } | ||