summaryrefslogtreecommitdiff
path: root/vendor/doctrine/dbal/src/Platforms
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/dbal/src/Platforms')
-rw-r--r--vendor/doctrine/dbal/src/Platforms/AbstractMySQLPlatform.php842
-rw-r--r--vendor/doctrine/dbal/src/Platforms/AbstractPlatform.php2219
-rw-r--r--vendor/doctrine/dbal/src/Platforms/DB2Platform.php593
-rw-r--r--vendor/doctrine/dbal/src/Platforms/DateIntervalUnit.php17
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Exception/InvalidPlatformVersion.php28
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Exception/NoColumnsSpecifiedForTable.php18
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Exception/NotSupported.php18
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Exception/PlatformException.php11
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/DB2Keywords.php414
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/KeywordList.php42
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/MariaDBKeywords.php264
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/MySQL80Keywords.php59
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/MySQLKeywords.php257
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/OracleKeywords.php133
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/PostgreSQLKeywords.php119
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/SQLServerKeywords.php207
-rw-r--r--vendor/doctrine/dbal/src/Platforms/Keywords/SQLiteKeywords.php141
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MariaDB1052Platform.php38
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MariaDB1060Platform.php18
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MariaDBPlatform.php165
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider.php11
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/CachingCharsetMetadataProvider.php29
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CharsetMetadataProvider/ConnectionCharsetMetadataProvider.php37
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider.php11
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/CachingCollationMetadataProvider.php29
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/CollationMetadataProvider/ConnectionCollationMetadataProvider.php37
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/Comparator.php93
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL/DefaultTableOptions.php23
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQL80Platform.php25
-rw-r--r--vendor/doctrine/dbal/src/Platforms/MySQLPlatform.php49
-rw-r--r--vendor/doctrine/dbal/src/Platforms/OraclePlatform.php784
-rw-r--r--vendor/doctrine/dbal/src/Platforms/PostgreSQLPlatform.php784
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLServer/Comparator.php50
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLServer/SQL/Builder/SQLServerSelectSQLBuilder.php84
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLServerPlatform.php1223
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLite/Comparator.php52
-rw-r--r--vendor/doctrine/dbal/src/Platforms/SQLitePlatform.php994
-rw-r--r--vendor/doctrine/dbal/src/Platforms/TrimMode.php13
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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Platforms\Keywords\KeywordList;
10use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords;
11use Doctrine\DBAL\Schema\AbstractAsset;
12use Doctrine\DBAL\Schema\ForeignKeyConstraint;
13use Doctrine\DBAL\Schema\Identifier;
14use Doctrine\DBAL\Schema\Index;
15use Doctrine\DBAL\Schema\MySQLSchemaManager;
16use Doctrine\DBAL\Schema\TableDiff;
17use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
18use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
19use Doctrine\DBAL\TransactionIsolationLevel;
20use Doctrine\DBAL\Types\Types;
21
22use function array_merge;
23use function array_unique;
24use function array_values;
25use function count;
26use function implode;
27use function in_array;
28use function is_numeric;
29use function sprintf;
30use function str_replace;
31use function strtolower;
32
33/**
34 * Provides the base implementation for the lowest versions of supported MySQL-like database platforms.
35 */
36abstract 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Exception\InvalidArgumentException;
10use Doctrine\DBAL\Exception\InvalidColumnDeclaration;
11use Doctrine\DBAL\Exception\InvalidColumnType;
12use Doctrine\DBAL\Exception\InvalidColumnType\ColumnLengthRequired;
13use Doctrine\DBAL\Exception\InvalidColumnType\ColumnPrecisionRequired;
14use Doctrine\DBAL\Exception\InvalidColumnType\ColumnScaleRequired;
15use Doctrine\DBAL\LockMode;
16use Doctrine\DBAL\Platforms\Exception\NoColumnsSpecifiedForTable;
17use Doctrine\DBAL\Platforms\Exception\NotSupported;
18use Doctrine\DBAL\Platforms\Keywords\KeywordList;
19use Doctrine\DBAL\Schema\AbstractSchemaManager;
20use Doctrine\DBAL\Schema\Column;
21use Doctrine\DBAL\Schema\ForeignKeyConstraint;
22use Doctrine\DBAL\Schema\Identifier;
23use Doctrine\DBAL\Schema\Index;
24use Doctrine\DBAL\Schema\SchemaDiff;
25use Doctrine\DBAL\Schema\Sequence;
26use Doctrine\DBAL\Schema\Table;
27use Doctrine\DBAL\Schema\TableDiff;
28use Doctrine\DBAL\Schema\UniqueConstraint;
29use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
30use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
31use Doctrine\DBAL\SQL\Parser;
32use Doctrine\DBAL\TransactionIsolationLevel;
33use Doctrine\DBAL\Types;
34use Doctrine\DBAL\Types\Exception\TypeNotFound;
35use Doctrine\DBAL\Types\Type;
36
37use function addcslashes;
38use function array_map;
39use function array_merge;
40use function array_unique;
41use function array_values;
42use function assert;
43use function count;
44use function explode;
45use function implode;
46use function in_array;
47use function is_array;
48use function is_bool;
49use function is_float;
50use function is_int;
51use function is_string;
52use function preg_quote;
53use function preg_replace;
54use function sprintf;
55use function str_contains;
56use function str_replace;
57use function strlen;
58use function strtolower;
59use 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 */
68abstract 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Platforms\Exception\NotSupported;
9use Doctrine\DBAL\Platforms\Keywords\DB2Keywords;
10use Doctrine\DBAL\Platforms\Keywords\KeywordList;
11use Doctrine\DBAL\Schema\ColumnDiff;
12use Doctrine\DBAL\Schema\DB2SchemaManager;
13use Doctrine\DBAL\Schema\Identifier;
14use Doctrine\DBAL\Schema\Index;
15use Doctrine\DBAL\Schema\TableDiff;
16use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
17use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
18use Doctrine\DBAL\TransactionIsolationLevel;
19use Doctrine\DBAL\Types\Types;
20
21use function array_merge;
22use function count;
23use function current;
24use function explode;
25use function implode;
26use function sprintf;
27use 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 */
32class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7enum 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Exception;
6
7use Exception;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Exception;
6
7use LogicException;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Exception;
6
7use LogicException;
8
9use function sprintf;
10
11/** @psalm-immutable */
12final 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Exception;
6
7use Doctrine\DBAL\Exception;
8
9interface 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * DB2 Keywords.
9 */
10class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7use function array_flip;
8use function array_map;
9use function strtoupper;
10
11/**
12 * Abstract interface for a SQL reserved keyword dictionary.
13 */
14abstract 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7use function array_merge;
8
9/**
10 * MySQL 8.0 reserved keywords list.
11 */
12class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * MySQL Keywordlist.
9 */
10class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * Oracle Keywordlist.
9 */
10class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * Reserved keywords list corresponding to the PostgreSQL database platform of the oldest supported version.
9 */
10class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * Reserved keywords list corresponding to the Microsoft SQL Server database platform of the oldest supported version.
9 */
10class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\Keywords;
6
7/**
8 * SQLite Keywordlist.
9 */
10class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Schema\Index;
8use Doctrine\DBAL\Schema\TableDiff;
9
10/**
11 * Provides the behavior, features and SQL dialect of the MariaDB 10.5 database platform.
12 */
13class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
8
9/**
10 * Provides the behavior, features and SQL dialect of the MariaDB 10.6 database platform.
11 */
12class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Platforms\Keywords\KeywordList;
8use Doctrine\DBAL\Platforms\Keywords\MariaDBKeywords;
9use Doctrine\DBAL\Schema\ForeignKeyConstraint;
10use Doctrine\DBAL\Schema\TableDiff;
11use Doctrine\DBAL\Types\JsonType;
12
13use function array_diff_key;
14use function array_merge;
15use function count;
16use function in_array;
17
18/**
19 * Provides the behavior, features and SQL dialect of the MariaDB database platform of the oldest supported version.
20 */
21class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL;
6
7/** @internal */
8interface 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;
6
7use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;
8
9use function array_key_exists;
10
11/** @internal */
12final 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Platforms\MySQL\CharsetMetadataProvider;
10
11/** @internal */
12final 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL;
6
7/** @internal */
8interface 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;
6
7use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;
8
9use function array_key_exists;
10
11/** @internal */
12final 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider;
10
11/** @internal */
12final 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'
23SELECT CHARACTER_SET_NAME
24FROM information_schema.COLLATIONS
25WHERE COLLATION_NAME = ?;
26SQL
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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL;
6
7use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
8use Doctrine\DBAL\Schema\Comparator as BaseComparator;
9use Doctrine\DBAL\Schema\Table;
10use Doctrine\DBAL\Schema\TableDiff;
11
12use 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 */
21class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\MySQL;
6
7/** @internal */
8final 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Platforms\Keywords\KeywordList;
8use Doctrine\DBAL\Platforms\Keywords\MySQL80Keywords;
9use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
10
11/**
12 * Provides the behavior, features and SQL dialect of the MySQL 8.0 database platform.
13 */
14class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Platforms\Keywords\KeywordList;
8use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords;
9use Doctrine\DBAL\Schema\Index;
10use Doctrine\DBAL\Types\BlobType;
11use 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 */
17class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception\InvalidColumnType\ColumnLengthRequired;
9use Doctrine\DBAL\Platforms\Keywords\KeywordList;
10use Doctrine\DBAL\Platforms\Keywords\OracleKeywords;
11use Doctrine\DBAL\Schema\ForeignKeyConstraint;
12use Doctrine\DBAL\Schema\Identifier;
13use Doctrine\DBAL\Schema\Index;
14use Doctrine\DBAL\Schema\OracleSchemaManager;
15use Doctrine\DBAL\Schema\Sequence;
16use Doctrine\DBAL\Schema\TableDiff;
17use Doctrine\DBAL\TransactionIsolationLevel;
18use Doctrine\DBAL\Types\Types;
19use InvalidArgumentException;
20
21use function array_merge;
22use function count;
23use function explode;
24use function implode;
25use function sprintf;
26use function str_contains;
27use function strlen;
28use function strtoupper;
29use function substr;
30
31/**
32 * OraclePlatform.
33 */
34class 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;
365BEGIN
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;
373END;";
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
385DECLARE
386 last_Sequence NUMBER;
387 last_InsertID NUMBER;
388BEGIN
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;
401END;';
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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Platforms\Keywords\KeywordList;
9use Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords;
10use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11use Doctrine\DBAL\Schema\Identifier;
12use Doctrine\DBAL\Schema\Index;
13use Doctrine\DBAL\Schema\PostgreSQLSchemaManager;
14use Doctrine\DBAL\Schema\Sequence;
15use Doctrine\DBAL\Schema\TableDiff;
16use Doctrine\DBAL\TransactionIsolationLevel;
17use Doctrine\DBAL\Types\Types;
18use UnexpectedValueException;
19
20use function array_merge;
21use function array_unique;
22use function array_values;
23use function explode;
24use function implode;
25use function in_array;
26use function is_array;
27use function is_bool;
28use function is_numeric;
29use function is_string;
30use function sprintf;
31use function str_contains;
32use function strtolower;
33use function trim;
34
35/**
36 * Provides the behavior, features and SQL dialect of the PostgreSQL 9.4+ database platform.
37 */
38class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\SQLServer;
6
7use Doctrine\DBAL\Platforms\SQLServerPlatform;
8use Doctrine\DBAL\Schema\Comparator as BaseComparator;
9use Doctrine\DBAL\Schema\Table;
10use 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 */
17class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\SQLServer\SQL\Builder;
6
7use Doctrine\DBAL\Platforms\SQLServerPlatform;
8use Doctrine\DBAL\Query\ForUpdate\ConflictResolutionMode;
9use Doctrine\DBAL\Query\SelectQuery;
10use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
11
12use function count;
13use function implode;
14
15final 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception\InvalidColumnType\ColumnLengthRequired;
9use Doctrine\DBAL\LockMode;
10use Doctrine\DBAL\Platforms\Keywords\KeywordList;
11use Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords;
12use Doctrine\DBAL\Platforms\SQLServer\SQL\Builder\SQLServerSelectSQLBuilder;
13use Doctrine\DBAL\Schema\Column;
14use Doctrine\DBAL\Schema\ColumnDiff;
15use Doctrine\DBAL\Schema\Identifier;
16use Doctrine\DBAL\Schema\Index;
17use Doctrine\DBAL\Schema\Sequence;
18use Doctrine\DBAL\Schema\SQLServerSchemaManager;
19use Doctrine\DBAL\Schema\TableDiff;
20use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
21use Doctrine\DBAL\TransactionIsolationLevel;
22use Doctrine\DBAL\Types\Types;
23use InvalidArgumentException;
24
25use function array_merge;
26use function array_unique;
27use function array_values;
28use function explode;
29use function implode;
30use function is_array;
31use function is_bool;
32use function is_numeric;
33use function preg_match;
34use function preg_match_all;
35use function sprintf;
36use function str_contains;
37use function str_ends_with;
38use function str_replace;
39use function str_starts_with;
40use function strtoupper;
41use function substr;
42use function substr_count;
43
44use 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 */
50class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms\SQLite;
6
7use Doctrine\DBAL\Platforms\SQLitePlatform;
8use Doctrine\DBAL\Schema\Comparator as BaseComparator;
9use Doctrine\DBAL\Schema\Table;
10use Doctrine\DBAL\Schema\TableDiff;
11
12use 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 */
19class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7use Doctrine\DBAL\Connection;
8use Doctrine\DBAL\Exception;
9use Doctrine\DBAL\Platforms\Exception\NotSupported;
10use Doctrine\DBAL\Platforms\Keywords\KeywordList;
11use Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords;
12use Doctrine\DBAL\Schema\Column;
13use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist;
14use Doctrine\DBAL\Schema\ForeignKeyConstraint;
15use Doctrine\DBAL\Schema\Identifier;
16use Doctrine\DBAL\Schema\Index;
17use Doctrine\DBAL\Schema\SQLiteSchemaManager;
18use Doctrine\DBAL\Schema\Table;
19use Doctrine\DBAL\Schema\TableDiff;
20use Doctrine\DBAL\SQL\Builder\DefaultSelectSQLBuilder;
21use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
22use Doctrine\DBAL\TransactionIsolationLevel;
23use Doctrine\DBAL\Types;
24use InvalidArgumentException;
25
26use function array_combine;
27use function array_keys;
28use function array_merge;
29use function array_search;
30use function array_unique;
31use function array_values;
32use function count;
33use function explode;
34use function implode;
35use function sprintf;
36use function str_replace;
37use function strpos;
38use function strtolower;
39use function substr;
40use function trim;
41
42/**
43 * The SQLitePlatform class describes the specifics and dialects of the SQLite
44 * database platform.
45 */
46class 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
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Platforms;
6
7enum TrimMode
8{
9 case UNSPECIFIED;
10 case LEADING;
11 case TRAILING;
12 case BOTH;
13}