summaryrefslogtreecommitdiff
path: root/vendor/doctrine/dbal/src/Platforms/DB2Platform.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/dbal/src/Platforms/DB2Platform.php')
-rw-r--r--vendor/doctrine/dbal/src/Platforms/DB2Platform.php593
1 files changed, 593 insertions, 0 deletions
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}