Passed
Pull Request — 2.11.x (#3971)
by Grégoire
03:18
created

PostgreSqlPlatform::getAlterTableSQL()   F

Complexity

Conditions 29
Paths > 20000

Size

Total Lines 143
Code Lines 83

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 77
CRAP Score 29.1013

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 83
c 1
b 0
f 0
dl 0
loc 143
ccs 77
cts 81
cp 0.9506
rs 0
cc 29
nc 23652
nop 1
crap 29.1013

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Doctrine\DBAL\Platforms;
4
5
use Doctrine\DBAL\Schema\Column;
6
use Doctrine\DBAL\Schema\ColumnDiff;
7
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
8
use Doctrine\DBAL\Schema\Identifier;
9
use Doctrine\DBAL\Schema\Index;
10
use Doctrine\DBAL\Schema\Sequence;
11
use Doctrine\DBAL\Schema\TableDiff;
12
use Doctrine\DBAL\Types\BigIntType;
13
use Doctrine\DBAL\Types\BinaryType;
14
use Doctrine\DBAL\Types\BlobType;
15
use Doctrine\DBAL\Types\IntegerType;
16
use Doctrine\DBAL\Types\Type;
17
use UnexpectedValueException;
18
use function array_diff;
19
use function array_merge;
20
use function array_unique;
21
use function array_values;
22
use function count;
23
use function explode;
24
use function implode;
25
use function in_array;
26
use function is_array;
27
use function is_bool;
28
use function is_numeric;
29
use function is_string;
30
use function sprintf;
31
use function strpos;
32
use function strtolower;
33
use function trim;
34
35
/**
36
 * PostgreSqlPlatform.
37
 *
38
 * @deprecated Use PostgreSQL 9.4 or newer
39
 *
40
 * @todo   Rename: PostgreSQLPlatform
41
 */
42
class PostgreSqlPlatform extends AbstractPlatform
43
{
44
    /** @var bool */
45
    private $useBooleanTrueFalseStrings = true;
46
47
    /** @var string[][] PostgreSQL booleans literals */
48
    private $booleanLiterals = [
49
        'true' => [
50
            't',
51
            'true',
52
            'y',
53
            'yes',
54
            'on',
55
            '1',
56
        ],
57
        'false' => [
58
            'f',
59
            'false',
60
            'n',
61
            'no',
62
            'off',
63
            '0',
64
        ],
65
    ];
66
67
    /**
68
     * PostgreSQL has different behavior with some drivers
69
     * with regard to how booleans have to be handled.
70
     *
71
     * Enables use of 'true'/'false' or otherwise 1 and 0 instead.
72
     *
73
     * @param bool $flag
74
     *
75
     * @return void
76
     */
77 10912
    public function setUseBooleanTrueFalseStrings($flag)
78
    {
79 10912
        $this->useBooleanTrueFalseStrings = (bool) $flag;
80 10912
    }
81
82
    /**
83
     * {@inheritDoc}
84
     */
85 12760
    public function getSubstringExpression($value, $from, $length = null)
86
    {
87 12760
        if ($length === null) {
88 12760
            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
89
        }
90
91 11367
        return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')';
92
    }
93
94
    /**
95
     * {@inheritDoc}
96
     */
97
    public function getNowExpression()
98
    {
99
        return 'LOCALTIMESTAMP(0)';
100
    }
101
102
    /**
103
     * {@inheritDoc}
104
     */
105 11367
    public function getRegexpExpression()
106
    {
107 11367
        return 'SIMILAR TO';
108
    }
109
110
    /**
111
     * {@inheritDoc}
112
     */
113 4851
    public function getLocateExpression($str, $substr, $startPos = false)
114
    {
115 4851
        if ($startPos !== false) {
116 4851
            $str = $this->getSubstringExpression($str, $startPos);
117
118 4851
            return 'CASE WHEN (POSITION(' . $substr . ' IN ' . $str . ') = 0) THEN 0 ELSE (POSITION(' . $substr . ' IN ' . $str . ') + ' . ($startPos-1) . ') END';
119
        }
120
121 4851
        return 'POSITION(' . $substr . ' IN ' . $str . ')';
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127 4858
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
128
    {
129 4858
        if ($unit === DateIntervalUnit::QUARTER) {
130 4858
            $interval *= 3;
131 4858
            $unit      = DateIntervalUnit::MONTH;
132
        }
133
134 4858
        return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit . "')::interval)";
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     */
140 4662
    public function getDateDiffExpression($date1, $date2)
141
    {
142 4662
        return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))';
143
    }
144
145
    /**
146
     * {@inheritDoc}
147
     */
148 4287
    public function supportsSequences()
149
    {
150 4287
        return true;
151
    }
152
153
    /**
154
     * {@inheritDoc}
155
     */
156 4261
    public function supportsSchemas()
157
    {
158 4261
        return true;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 4086
    public function getDefaultSchemaName()
165
    {
166 4086
        return 'public';
167
    }
168
169
    /**
170
     * {@inheritDoc}
171
     */
172 4168
    public function supportsIdentityColumns()
173
    {
174 4168
        return true;
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180 11864
    public function supportsPartialIndexes()
181
    {
182 11864
        return true;
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188 10913
    public function usesSequenceEmulatedIdentityColumns()
189
    {
190 10913
        return true;
191
    }
192
193
    /**
194
     * {@inheritdoc}
195
     */
196 11836
    public function getIdentitySequenceName($tableName, $columnName)
197
    {
198 11836
        return $tableName . '_' . $columnName . '_seq';
199
    }
200
201
    /**
202
     * {@inheritDoc}
203
     */
204 12861
    public function supportsCommentOnStatement()
205
    {
206 12861
        return true;
207
    }
208
209
    /**
210
     * {@inheritDoc}
211
     */
212 5
    public function prefersSequences()
213
    {
214 5
        return true;
215
    }
216
217
    /**
218
     * {@inheritDoc}
219
     */
220 13036
    public function hasNativeGuidType()
221
    {
222 13036
        return true;
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     */
228 4289
    public function getListDatabasesSQL()
229
    {
230 4289
        return 'SELECT datname FROM pg_database';
231
    }
232
233
    /**
234
     * {@inheritDoc}
235
     */
236 4261
    public function getListNamespacesSQL()
237
    {
238 4261
        return "SELECT schema_name AS nspname
239
                FROM   information_schema.schemata
240
                WHERE  schema_name NOT LIKE 'pg\_%'
241
                AND    schema_name != 'information_schema'";
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247 3057
    public function getListSequencesSQL($database)
248
    {
249 3057
        return "SELECT sequence_name AS relname,
250
                       sequence_schema AS schemaname
251
                FROM   information_schema.sequences
252
                WHERE  sequence_schema NOT LIKE 'pg\_%'
253
                AND    sequence_schema != 'information_schema'";
254
    }
255
256
    /**
257
     * {@inheritDoc}
258
     */
259 4718
    public function getListTablesSQL()
260
    {
261 4718
        return "SELECT quote_ident(table_name) AS table_name,
262
                       table_schema AS schema_name
263
                FROM   information_schema.tables
264
                WHERE  table_schema NOT LIKE 'pg\_%'
265
                AND    table_schema != 'information_schema'
266
                AND    table_name != 'geometry_columns'
267
                AND    table_name != 'spatial_ref_sys'
268
                AND    table_type != 'VIEW'";
269
    }
270
271
    /**
272
     * {@inheritDoc}
273
     */
274 4170
    public function getListViewsSQL($database)
275
    {
276 4170
        return 'SELECT quote_ident(table_name) AS viewname,
277
                       table_schema AS schemaname,
278
                       view_definition AS definition
279
                FROM   information_schema.views
280
                WHERE  view_definition IS NOT NULL';
281
    }
282
283
    /**
284
     * @param string      $table
285
     * @param string|null $database
286
     *
287
     * @return string
288
     */
289 11750
    public function getListTableForeignKeysSQL($table, $database = null)
0 ignored issues
show
Unused Code introduced by
The parameter $database is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

289
    public function getListTableForeignKeysSQL($table, /** @scrutinizer ignore-unused */ $database = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
290
    {
291
        return 'SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef
292
                  FROM pg_catalog.pg_constraint r
293
                  WHERE r.conrelid =
294
                  (
295
                      SELECT c.oid
296
                      FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n
297 11750
                      WHERE ' . $this->getTableWhereClause($table) . " AND n.oid = c.relnamespace
298
                  )
299
                  AND r.contype = 'f'";
300
    }
301
302
    /**
303
     * {@inheritDoc}
304
     */
305 4362
    public function getCreateViewSQL($name, $sql)
306
    {
307 4362
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
308
    }
309
310
    /**
311
     * {@inheritDoc}
312
     */
313 4362
    public function getDropViewSQL($name)
314
    {
315 4362
        return 'DROP VIEW ' . $name;
316
    }
317
318
    /**
319
     * {@inheritDoc}
320
     */
321 10355
    public function getListTableConstraintsSQL($table)
322
    {
323 10355
        $table = new Identifier($table);
324 10355
        $table = $this->quoteStringLiteral($table->getName());
325
326 10355
        return sprintf(
327
            <<<'SQL'
328 5
SELECT
329
    quote_ident(relname) as relname
330
FROM
331
    pg_class
332
WHERE oid IN (
333
    SELECT indexrelid
334
    FROM pg_index, pg_class
335
    WHERE pg_class.relname = %s
336
        AND pg_class.oid = pg_index.indrelid
337
        AND (indisunique = 't' OR indisprimary = 't')
338
    )
339
SQL
340
            ,
341 10355
            $table
342
        );
343
    }
344
345
    /**
346
     * {@inheritDoc}
347
     *
348
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
349
     */
350 11702
    public function getListTableIndexesSQL($table, $currentDatabase = null)
351
    {
352
        return 'SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary,
353
                       pg_index.indkey, pg_index.indrelid,
354
                       pg_get_expr(indpred, indrelid) AS where
355
                 FROM pg_class, pg_index
356
                 WHERE oid IN (
357
                    SELECT indexrelid
358
                    FROM pg_index si, pg_class sc, pg_namespace sn
359 11702
                    WHERE ' . $this->getTableWhereClause($table, 'sc', 'sn') . ' AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid
360
                 ) AND pg_index.indexrelid = oid';
361
    }
362
363
    /**
364
     * @param string $table
365
     * @param string $classAlias
366
     * @param string $namespaceAlias
367
     *
368
     * @return string
369
     */
370 11770
    private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n')
371
    {
372 11770
        $whereClause = $namespaceAlias . ".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND ";
373 11770
        if (strpos($table, '.') !== false) {
374 11656
            [$schema, $table] = explode('.', $table);
375 11656
            $schema           = $this->quoteStringLiteral($schema);
376
        } else {
377 11755
            $schema = 'ANY(current_schemas(false))';
378
        }
379
380 11770
        $table = new Identifier($table);
381 11770
        $table = $this->quoteStringLiteral($table->getName());
382
383 11770
        return $whereClause . sprintf(
384 30
            '%s.relname = %s AND %s.nspname = %s',
385 11770
            $classAlias,
386 11770
            $table,
387 11770
            $namespaceAlias,
388 11770
            $schema
389
        );
390
    }
391
392
    /**
393
     * {@inheritDoc}
394
     */
395 11670
    public function getListTableColumnsSQL($table, $database = null)
396
    {
397
        return "SELECT
398
                    a.attnum,
399
                    quote_ident(a.attname) AS field,
400
                    t.typname AS type,
401
                    format_type(a.atttypid, a.atttypmod) AS complete_type,
402
                    (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
403
                    (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM
404
                      pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type,
405
                    a.attnotnull AS isnotnull,
406
                    (SELECT 't'
407
                     FROM pg_index
408
                     WHERE c.oid = pg_index.indrelid
409
                        AND pg_index.indkey[0] = a.attnum
410
                        AND pg_index.indisprimary = 't'
411
                    ) AS pri,
412
                    (SELECT pg_get_expr(adbin, adrelid)
413
                     FROM pg_attrdef
414
                     WHERE c.oid = pg_attrdef.adrelid
415
                        AND pg_attrdef.adnum=a.attnum
416
                    ) AS default,
417
                    (SELECT pg_description.description
418
                        FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
419
                    ) AS comment
420
                    FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n
421 11670
                    WHERE " . $this->getTableWhereClause($table, 'c', 'n') . '
422
                        AND a.attnum > 0
423
                        AND a.attrelid = c.oid
424
                        AND a.atttypid = t.oid
425
                        AND n.oid = c.relnamespace
426
                    ORDER BY a.attnum';
427
    }
428
429
    /**
430
     * {@inheritDoc}
431
     */
432 12805
    public function getCreateDatabaseSQL($name)
433
    {
434 12805
        return 'CREATE DATABASE ' . $name;
435
    }
436
437
    /**
438
     * Returns the SQL statement for disallowing new connections on the given database.
439
     *
440
     * This is useful to force DROP DATABASE operations which could fail because of active connections.
441
     *
442
     * @param string $database The name of the database to disallow new connections for.
443
     *
444
     * @return string
445
     */
446 11542
    public function getDisallowDatabaseConnectionsSQL($database)
447
    {
448 11542
        return "UPDATE pg_database SET datallowconn = 'false' WHERE datname = " . $this->quoteStringLiteral($database);
449
    }
450
451
    /**
452
     * Returns the SQL statement for closing currently active connections on the given database.
453
     *
454
     * This is useful to force DROP DATABASE operations which could fail because of active connections.
455
     *
456
     * @param string $database The name of the database to close currently active connections for.
457
     *
458
     * @return string
459
     */
460 7985
    public function getCloseActiveDatabaseConnectionsSQL($database)
461
    {
462
        return 'SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = '
463 7985
            . $this->quoteStringLiteral($database);
464
    }
465
466
    /**
467
     * {@inheritDoc}
468
     */
469 12775
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey)
470
    {
471 12775
        $query = '';
472
473 12775
        if ($foreignKey->hasOption('match')) {
474 11390
            $query .= ' MATCH ' . $foreignKey->getOption('match');
475
        }
476
477 12775
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
478
479 12775
        if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) {
480 11390
            $query .= ' DEFERRABLE';
481
        } else {
482 12775
            $query .= ' NOT DEFERRABLE';
483
        }
484
485 12775
        if (($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false)
486 12775
            || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false)
487
        ) {
488 11390
            $query .= ' INITIALLY DEFERRED';
489
        } else {
490 12775
            $query .= ' INITIALLY IMMEDIATE';
491
        }
492
493 12775
        return $query;
494
    }
495
496
    /**
497
     * {@inheritDoc}
498
     */
499 11974
    public function getAlterTableSQL(TableDiff $diff)
500
    {
501 11974
        $sql         = [];
502 11974
        $commentsSQL = [];
503 11974
        $columnSql   = [];
504
505 11974
        foreach ($diff->addedColumns as $column) {
506 11116
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
507
                continue;
508
            }
509
510 11116
            $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
511 11116
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
512
513 11116
            $comment = $this->getColumnComment($column);
514
515 11116
            if ($comment === null || $comment === '') {
516 11111
                continue;
517
            }
518
519 9849
            $commentsSQL[] = $this->getCommentOnColumnSQL(
520 9849
                $diff->getName($this)->getQuotedName($this),
521 9849
                $column->getQuotedName($this),
522
                $comment
523
            );
524
        }
525
526 11974
        foreach ($diff->removedColumns as $column) {
527 11628
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
528
                continue;
529
            }
530
531 11628
            $query = 'DROP ' . $column->getQuotedName($this);
532 11628
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
533
        }
534
535 11974
        foreach ($diff->changedColumns as $columnDiff) {
536
            /** @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
537 11934
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
538
                continue;
539
            }
540
541 11934
            if ($this->isUnchangedBinaryColumn($columnDiff)) {
542 10562
                continue;
543
            }
544
545 11929
            $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this);
546 11929
            $column        = $columnDiff->column;
547
548 11929
            if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed')) {
549 11768
                $type = $column->getType();
550
551
                // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type
552 11768
                $columnDefinition                  = $column->toArray();
553 11768
                $columnDefinition['autoincrement'] = false;
554
555
                // here was a server version check before, but DBAL API does not support this anymore.
556 11768
                $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this);
557 11768
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
558
            }
559
560 11929
            if ($columnDiff->hasChanged('default') || $this->typeChangeBreaksDefaultValue($columnDiff)) {
561 11409
                $defaultClause = $column->getDefault() === null
562 11383
                    ? ' DROP DEFAULT'
563 11409
                    : ' SET' . $this->getDefaultValueDeclarationSQL($column->toArray());
564 11409
                $query         = 'ALTER ' . $oldColumnName . $defaultClause;
565 11409
                $sql[]         = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
566
            }
567
568 11929
            if ($columnDiff->hasChanged('notnull')) {
569 9946
                $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL';
570 9946
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
571
            }
572
573 11929
            if ($columnDiff->hasChanged('autoincrement')) {
574 4439
                if ($column->getAutoincrement()) {
575
                    // add autoincrement
576 4439
                    $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName);
577
578 4439
                    $sql[] = 'CREATE SEQUENCE ' . $seqName;
579 4439
                    $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ') FROM ' . $diff->getName($this)->getQuotedName($this) . '))';
580 4439
                    $query = 'ALTER ' . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')";
581 4439
                    $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
582
                } else {
583
                    // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have
584 4432
                    $query = 'ALTER ' . $oldColumnName . ' DROP DEFAULT';
585 4432
                    $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
586
                }
587
            }
588
589 11929
            $newComment = $this->getColumnComment($column);
590 11929
            $oldComment = $this->getOldColumnComment($columnDiff);
591
592 11929
            if ($columnDiff->hasChanged('comment') || ($columnDiff->fromColumn !== null && $oldComment !== $newComment)) {
593 11446
                $commentsSQL[] = $this->getCommentOnColumnSQL(
594 11446
                    $diff->getName($this)->getQuotedName($this),
595 11446
                    $column->getQuotedName($this),
596
                    $newComment
597
                );
598
            }
599
600 11929
            if (! $columnDiff->hasChanged('length')) {
601 11924
                continue;
602
            }
603
604 9412
            $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $column->getType()->getSQLDeclaration($column->toArray(), $this);
605 9412
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
606
        }
607
608 11974
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
609 11049
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
610
                continue;
611
            }
612
613 11049
            $oldColumnName = new Identifier($oldColumnName);
614
615 11049
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
616 11049
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
617
        }
618
619 11974
        $tableSql = [];
620
621 11974
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
622 11974
            $sql = array_merge($sql, $commentsSQL);
623
624 11974
            $newName = $diff->getNewName();
625
626 11974
            if ($newName !== false) {
627 9946
                $sql[] = sprintf(
628 10
                    'ALTER TABLE %s RENAME TO %s',
629 9946
                    $diff->getName($this)->getQuotedName($this),
630 9946
                    $newName->getQuotedName($this)
631
                );
632
            }
633
634 11974
            $sql = array_merge(
635 11974
                $this->getPreAlterTableIndexForeignKeySQL($diff),
636 11974
                $sql,
637 11974
                $this->getPostAlterTableIndexForeignKeySQL($diff)
638
            );
639
        }
640
641 11974
        return array_merge($sql, $tableSql, $columnSql);
642
    }
643
644
    /**
645
     * Checks whether a given column diff is a logically unchanged binary type column.
646
     *
647
     * Used to determine whether a column alteration for a binary type column can be skipped.
648
     * Doctrine's {@link \Doctrine\DBAL\Types\BinaryType} and {@link \Doctrine\DBAL\Types\BlobType}
649
     * are mapped to the same database column type on this platform as this platform
650
     * does not have a native VARBINARY/BINARY column type. Therefore the {@link \Doctrine\DBAL\Schema\Comparator}
651
     * might detect differences for binary type columns which do not have to be propagated
652
     * to database as there actually is no difference at database level.
653
     *
654
     * @param ColumnDiff $columnDiff The column diff to check against.
655
     *
656
     * @return bool True if the given column diff is an unchanged binary type column, false otherwise.
657
     */
658 11934
    private function isUnchangedBinaryColumn(ColumnDiff $columnDiff)
659
    {
660 11934
        $columnType = $columnDiff->column->getType();
661
662 11934
        if (! $columnType instanceof BinaryType && ! $columnType instanceof BlobType) {
663 11929
            return false;
664
        }
665
666 10562
        $fromColumn = $columnDiff->fromColumn instanceof Column ? $columnDiff->fromColumn : null;
667
668 10562
        if ($fromColumn) {
669 10562
            $fromColumnType = $fromColumn->getType();
670
671 10562
            if (! $fromColumnType instanceof BinaryType && ! $fromColumnType instanceof BlobType) {
672
                return false;
673
            }
674
675 10562
            return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0;
676
        }
677
678
        if ($columnDiff->hasChanged('type')) {
679
            return false;
680
        }
681
682
        return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0;
683
    }
684
685
    /**
686
     * {@inheritdoc}
687
     */
688 10801
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
689
    {
690 10801
        if (strpos($tableName, '.') !== false) {
691 9394
            [$schema]     = explode('.', $tableName);
692 9394
            $oldIndexName = $schema . '.' . $oldIndexName;
693
        }
694
695 10801
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
696
    }
697
698
    /**
699
     * {@inheritdoc}
700
     */
701 11658
    public function getCommentOnColumnSQL($tableName, $columnName, $comment)
702
    {
703 11658
        $tableName  = new Identifier($tableName);
704 11658
        $columnName = new Identifier($columnName);
705 11658
        $comment    = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
706
707 11658
        return sprintf(
708 45
            'COMMENT ON COLUMN %s.%s IS %s',
709 11658
            $tableName->getQuotedName($this),
710 11658
            $columnName->getQuotedName($this),
711 11658
            $comment
712
        );
713
    }
714
715
    /**
716
     * {@inheritDoc}
717
     */
718 12004
    public function getCreateSequenceSQL(Sequence $sequence)
719
    {
720 12004
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
721 12004
            ' INCREMENT BY ' . $sequence->getAllocationSize() .
722 12004
            ' MINVALUE ' . $sequence->getInitialValue() .
723 12004
            ' START ' . $sequence->getInitialValue() .
724 12004
            $this->getSequenceCacheSQL($sequence);
725
    }
726
727
    /**
728
     * {@inheritDoc}
729
     */
730
    public function getAlterSequenceSQL(Sequence $sequence)
731
    {
732
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
733
            ' INCREMENT BY ' . $sequence->getAllocationSize() .
734
            $this->getSequenceCacheSQL($sequence);
735
    }
736
737
    /**
738
     * Cache definition for sequences
739
     *
740
     * @return string
741
     */
742 12004
    private function getSequenceCacheSQL(Sequence $sequence)
743
    {
744 12004
        if ($sequence->getCache() > 1) {
745 10608
            return ' CACHE ' . $sequence->getCache();
746
        }
747
748 11999
        return '';
749
    }
750
751
    /**
752
     * {@inheritDoc}
753
     */
754 11999
    public function getDropSequenceSQL($sequence)
755
    {
756 11999
        if ($sequence instanceof Sequence) {
757
            $sequence = $sequence->getQuotedName($this);
758
        }
759
760 11999
        return 'DROP SEQUENCE ' . $sequence . ' CASCADE';
761
    }
762
763
    /**
764
     * {@inheritDoc}
765
     */
766 11722
    public function getCreateSchemaSQL($schemaName)
767
    {
768 11722
        return 'CREATE SCHEMA ' . $schemaName;
769
    }
770
771
    /**
772
     * {@inheritDoc}
773
     */
774 11588
    public function getDropForeignKeySQL($foreignKey, $table)
775
    {
776 11588
        return $this->getDropConstraintSQL($foreignKey, $table);
777
    }
778
779
    /**
780
     * {@inheritDoc}
781
     */
782 12856
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
783
    {
784 12856
        $queryFields = $this->getColumnDeclarationListSQL($columns);
785
786 12856
        if (isset($options['primary']) && ! empty($options['primary'])) {
787 11956
            $keyColumns   = array_unique(array_values($options['primary']));
788 11956
            $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
789
        }
790
791 12856
        $query = 'CREATE TABLE ' . $tableName . ' (' . $queryFields . ')';
792
793 12856
        $sql = [$query];
794
795 12856
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
796 11837
            foreach ($options['indexes'] as $index) {
797 11837
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
798
            }
799
        }
800
801 12856
        if (isset($options['foreignKeys'])) {
802 11637
            foreach ((array) $options['foreignKeys'] as $definition) {
803 11539
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
804
            }
805
        }
806
807 12856
        return $sql;
808
    }
809
810
    /**
811
     * Converts a single boolean value.
812
     *
813
     * First converts the value to its native PHP boolean type
814
     * and passes it to the given callback function to be reconverted
815
     * into any custom representation.
816
     *
817
     * @param mixed    $value    The value to convert.
818
     * @param callable $callback The callback function to use for converting the real boolean value.
819
     *
820
     * @return mixed
821
     *
822
     * @throws UnexpectedValueException
823
     */
824 12357
    private function convertSingleBooleanValue($value, $callback)
825
    {
826 12357
        if ($value === null) {
827 11340
            return $callback(null);
828
        }
829
830 12347
        if (is_bool($value) || is_numeric($value)) {
831 12292
            return $callback((bool) $value);
832
        }
833
834 11479
        if (! is_string($value)) {
835
            return $callback(true);
836
        }
837
838
        /**
839
         * Better safe than sorry: http://php.net/in_array#106319
840
         */
841 11479
        if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) {
842 11401
            return $callback(false);
843
        }
844
845 11070
        if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) {
846 11065
            return $callback(true);
847
        }
848
849 10746
        throw new UnexpectedValueException("Unrecognized boolean literal '${value}'");
850
    }
851
852
    /**
853
     * Converts one or multiple boolean values.
854
     *
855
     * First converts the value(s) to their native PHP boolean type
856
     * and passes them to the given callback function to be reconverted
857
     * into any custom representation.
858
     *
859
     * @param mixed    $item     The value(s) to convert.
860
     * @param callable $callback The callback function to use for converting the real boolean value(s).
861
     *
862
     * @return mixed
863
     */
864 12357
    private function doConvertBooleans($item, $callback)
865
    {
866 12357
        if (is_array($item)) {
867
            foreach ($item as $key => $value) {
868
                $item[$key] = $this->convertSingleBooleanValue($value, $callback);
869
            }
870
871
            return $item;
872
        }
873
874 12357
        return $this->convertSingleBooleanValue($item, $callback);
875
    }
876
877
    /**
878
     * {@inheritDoc}
879
     *
880
     * Postgres wants boolean values converted to the strings 'true'/'false'.
881
     */
882 12287
    public function convertBooleans($item)
883
    {
884 12287
        if (! $this->useBooleanTrueFalseStrings) {
885 10912
            return parent::convertBooleans($item);
886
        }
887
888 12277
        return $this->doConvertBooleans(
889 12277
            $item,
890
            static function ($boolean) {
891 12277
                if ($boolean === null) {
892 10930
                    return 'NULL';
893
                }
894
895 12272
                return $boolean === true ? 'true' : 'false';
896 12277
            }
897
        );
898
    }
899
900
    /**
901
     * {@inheritDoc}
902
     */
903 11397
    public function convertBooleansToDatabaseValue($item)
904
    {
905 11397
        if (! $this->useBooleanTrueFalseStrings) {
906 10838
            return parent::convertBooleansToDatabaseValue($item);
907
        }
908
909 11392
        return $this->doConvertBooleans(
910 11392
            $item,
911
            static function ($boolean) {
912 11387
                return $boolean === null ? null : (int) $boolean;
913 11392
            }
914
        );
915
    }
916
917
    /**
918
     * {@inheritDoc}
919
     */
920 11304
    public function convertFromBoolean($item)
921
    {
922 11304
        if (in_array(strtolower($item), $this->booleanLiterals['false'], true)) {
923 10817
            return false;
924
        }
925
926 11274
        return parent::convertFromBoolean($item);
927
    }
928
929
    /**
930
     * {@inheritDoc}
931
     */
932 11245
    public function getSequenceNextValSQL($sequenceName)
933
    {
934 11245
        return "SELECT NEXTVAL('" . $sequenceName . "')";
935
    }
936
937
    /**
938
     * {@inheritDoc}
939
     */
940 11344
    public function getSetTransactionIsolationSQL($level)
941
    {
942
        return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL '
943 11344
            . $this->_getTransactionIsolationLevelSQL($level);
944
    }
945
946
    /**
947
     * {@inheritDoc}
948
     */
949 11418
    public function getBooleanTypeDeclarationSQL(array $field)
950
    {
951 11418
        return 'BOOLEAN';
952
    }
953
954
    /**
955
     * {@inheritDoc}
956
     */
957 12839
    public function getIntegerTypeDeclarationSQL(array $field)
958
    {
959 12839
        if (! empty($field['autoincrement'])) {
960 12648
            return 'SERIAL';
961
        }
962
963 12707
        return 'INT';
964
    }
965
966
    /**
967
     * {@inheritDoc}
968
     */
969 12172
    public function getBigIntTypeDeclarationSQL(array $field)
970
    {
971 12172
        if (! empty($field['autoincrement'])) {
972 12158
            return 'BIGSERIAL';
973
        }
974
975 4338
        return 'BIGINT';
976
    }
977
978
    /**
979
     * {@inheritDoc}
980
     */
981 12277
    public function getSmallIntTypeDeclarationSQL(array $field)
982
    {
983 12277
        return 'SMALLINT';
984
    }
985
986
    /**
987
     * {@inheritDoc}
988
     */
989 10516
    public function getGuidTypeDeclarationSQL(array $field)
990
    {
991 10516
        return 'UUID';
992
    }
993
994
    /**
995
     * {@inheritDoc}
996
     */
997 12150
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
998
    {
999 12150
        return 'TIMESTAMP(0) WITHOUT TIME ZONE';
1000
    }
1001
1002
    /**
1003
     * {@inheritDoc}
1004
     */
1005 3709
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
1006
    {
1007 3709
        return 'TIMESTAMP(0) WITH TIME ZONE';
1008
    }
1009
1010
    /**
1011
     * {@inheritDoc}
1012
     */
1013 4648
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
1014
    {
1015 4648
        return 'DATE';
1016
    }
1017
1018
    /**
1019
     * {@inheritDoc}
1020
     */
1021 4641
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
1022
    {
1023 4641
        return 'TIME(0) WITHOUT TIME ZONE';
1024
    }
1025
1026
    /**
1027
     * {@inheritDoc}
1028
     *
1029
     * @deprecated Use application-generated UUIDs instead
1030
     */
1031
    public function getGuidExpression()
1032
    {
1033
        return 'UUID_GENERATE_V4()';
1034
    }
1035
1036
    /**
1037
     * {@inheritDoc}
1038
     */
1039
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
1040
    {
1041
        return '';
1042
    }
1043
1044
    /**
1045
     * {@inheritDoc}
1046
     */
1047 12655
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
1048
    {
1049 12655
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
1050 12655
            : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
1051
    }
1052
1053
    /**
1054
     * {@inheritdoc}
1055
     */
1056 11748
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
1057
    {
1058 11748
        return 'BYTEA';
1059
    }
1060
1061
    /**
1062
     * {@inheritDoc}
1063
     */
1064 11767
    public function getClobTypeDeclarationSQL(array $field)
1065
    {
1066 11767
        return 'TEXT';
1067
    }
1068
1069
    /**
1070
     * {@inheritDoc}
1071
     */
1072 12016
    public function getName()
1073
    {
1074 12016
        return 'postgresql';
1075
    }
1076
1077
    /**
1078
     * {@inheritDoc}
1079
     *
1080
     * PostgreSQL returns all column names in SQL result sets in lowercase.
1081
     */
1082
    public function getSQLResultCasing($column)
1083
    {
1084
        return strtolower($column);
1085
    }
1086
1087
    /**
1088
     * {@inheritDoc}
1089
     */
1090 3626
    public function getDateTimeTzFormatString()
1091
    {
1092 3626
        return 'Y-m-d H:i:sO';
1093
    }
1094
1095
    /**
1096
     * {@inheritDoc}
1097
     */
1098 3500
    public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
1099
    {
1100 3500
        return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)';
1101
    }
1102
1103
    /**
1104
     * {@inheritDoc}
1105
     */
1106 11486
    public function getTruncateTableSQL($tableName, $cascade = false)
1107
    {
1108 11486
        $tableIdentifier = new Identifier($tableName);
1109 11486
        $sql             = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
1110
1111 11486
        if ($cascade) {
1112
            $sql .= ' CASCADE';
1113
        }
1114
1115 11486
        return $sql;
1116
    }
1117
1118
    /**
1119
     * {@inheritDoc}
1120
     */
1121
    public function getReadLockSQL()
1122
    {
1123
        return 'FOR SHARE';
1124
    }
1125
1126
    /**
1127
     * {@inheritDoc}
1128
     */
1129 12488
    protected function initializeDoctrineTypeMappings()
1130
    {
1131 12488
        $this->doctrineTypeMapping = [
1132
            'smallint'      => 'smallint',
1133
            'int2'          => 'smallint',
1134
            'serial'        => 'integer',
1135
            'serial4'       => 'integer',
1136
            'int'           => 'integer',
1137
            'int4'          => 'integer',
1138
            'integer'       => 'integer',
1139
            'bigserial'     => 'bigint',
1140
            'serial8'       => 'bigint',
1141
            'bigint'        => 'bigint',
1142
            'int8'          => 'bigint',
1143
            'bool'          => 'boolean',
1144
            'boolean'       => 'boolean',
1145
            'text'          => 'text',
1146
            'tsvector'      => 'text',
1147
            'varchar'       => 'string',
1148
            'interval'      => 'string',
1149
            '_varchar'      => 'string',
1150
            'char'          => 'string',
1151
            'bpchar'        => 'string',
1152
            'inet'          => 'string',
1153
            'date'          => 'date',
1154
            'datetime'      => 'datetime',
1155
            'timestamp'     => 'datetime',
1156
            'timestamptz'   => 'datetimetz',
1157
            'time'          => 'time',
1158
            'timetz'        => 'time',
1159
            'float'         => 'float',
1160
            'float4'        => 'float',
1161
            'float8'        => 'float',
1162
            'double'        => 'float',
1163
            'double precision' => 'float',
1164
            'real'          => 'float',
1165
            'decimal'       => 'decimal',
1166
            'money'         => 'decimal',
1167
            'numeric'       => 'decimal',
1168
            'year'          => 'date',
1169
            'uuid'          => 'guid',
1170
            'bytea'         => 'blob',
1171
        ];
1172 12488
    }
1173
1174
    /**
1175
     * {@inheritDoc}
1176
     */
1177 12655
    public function getVarcharMaxLength()
1178
    {
1179 12655
        return 65535;
1180
    }
1181
1182
    /**
1183
     * {@inheritdoc}
1184
     */
1185 11753
    public function getBinaryMaxLength()
1186
    {
1187 11753
        return 0;
1188
    }
1189
1190
    /**
1191
     * {@inheritdoc}
1192
     */
1193 11753
    public function getBinaryDefaultLength()
1194
    {
1195 11753
        return 0;
1196
    }
1197
1198
    /**
1199
     * {@inheritDoc}
1200
     */
1201 2399
    protected function getReservedKeywordsClass()
1202
    {
1203 2399
        return Keywords\PostgreSQLKeywords::class;
1204
    }
1205
1206
    /**
1207
     * {@inheritDoc}
1208
     */
1209 12293
    public function getBlobTypeDeclarationSQL(array $field)
1210
    {
1211 12293
        return 'BYTEA';
1212
    }
1213
1214
    /**
1215
     * {@inheritdoc}
1216
     */
1217 12906
    public function getDefaultValueDeclarationSQL($field)
1218
    {
1219 12906
        if ($this->isSerialField($field)) {
1220 12663
            return '';
1221
        }
1222
1223 12023
        return parent::getDefaultValueDeclarationSQL($field);
1224
    }
1225
1226
    /**
1227
     * @param mixed[] $field
1228
     */
1229 12906
    private function isSerialField(array $field) : bool
1230
    {
1231 12906
        return isset($field['type'], $field['autoincrement'])
1232 12906
            && $field['autoincrement'] === true
1233 12906
            && $this->isNumericType($field['type']);
1234
    }
1235
1236
    /**
1237
     * Check whether the type of a column is changed in a way that invalidates the default value for the column
1238
     */
1239 11924
    private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff) : bool
1240
    {
1241 11924
        if (! $columnDiff->fromColumn) {
1242 10720
            return $columnDiff->hasChanged('type');
1243
        }
1244
1245 11755
        $oldTypeIsNumeric = $this->isNumericType($columnDiff->fromColumn->getType());
1246 11755
        $newTypeIsNumeric = $this->isNumericType($columnDiff->column->getType());
1247
1248
        // default should not be changed when switching between numeric types and the default comes from a sequence
1249 11755
        return $columnDiff->hasChanged('type')
1250 11755
            && ! ($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement());
1251
    }
1252
1253 12683
    private function isNumericType(Type $type) : bool
1254
    {
1255 12683
        return $type instanceof IntegerType || $type instanceof BigIntType;
1256
    }
1257
1258 11929
    private function getOldColumnComment(ColumnDiff $columnDiff) : ?string
1259
    {
1260 11929
        return $columnDiff->fromColumn ? $this->getColumnComment($columnDiff->fromColumn) : null;
1261
    }
1262
1263 4508
    public function getListTableMetadataSQL(string $table, ?string $schema = null) : string
1264
    {
1265 4508
        if ($schema !== null) {
1266
            $table = $schema . '.' . $table;
1267
        }
1268
1269 4508
        return sprintf(
1270
            <<<'SQL'
1271
SELECT obj_description(%s::regclass) AS table_comment;
1272
SQL
1273
            ,
1274 4508
            $this->quoteStringLiteral($table)
1275
        );
1276
    }
1277
}
1278