Passed
Pull Request — master (#3158)
by Ben
14:19
created

PostgreSqlPlatform::getAlterTableSQL()   F

Complexity

Conditions 30
Paths > 20000

Size

Total Lines 136
Code Lines 79

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 75
CRAP Score 30.1165

Importance

Changes 0
Metric Value
dl 0
loc 136
rs 2
c 0
b 0
f 0
ccs 75
cts 79
cp 0.9494
cc 30
eloc 79
nc 46980
nop 1
crap 30.1165

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Platforms;
21
22
use Doctrine\DBAL\Schema\Column;
23
use Doctrine\DBAL\Schema\ColumnDiff;
24
use Doctrine\DBAL\Schema\Identifier;
25
use Doctrine\DBAL\Schema\Index;
26
use Doctrine\DBAL\Schema\Sequence;
27
use Doctrine\DBAL\Schema\TableDiff;
28
use Doctrine\DBAL\Types\BigIntType;
29
use Doctrine\DBAL\Types\BinaryType;
30
use Doctrine\DBAL\Types\BlobType;
31
use Doctrine\DBAL\Types\IntegerType;
32
use Doctrine\DBAL\Types\Type;
33
use function array_diff;
34
use function array_merge;
35
use function array_unique;
36
use function array_values;
37
use function count;
38
use function explode;
39
use function implode;
40
use function in_array;
41
use function is_array;
42
use function is_bool;
43
use function is_numeric;
44
use function is_string;
45
use function str_replace;
46
use function strpos;
47
use function strtolower;
48
use function trim;
49
50
/**
51
 * PostgreSqlPlatform.
52
 *
53
 * @since  2.0
54
 * @author Roman Borschel <[email protected]>
55
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
56
 * @author Benjamin Eberlei <[email protected]>
57
 * @todo   Rename: PostgreSQLPlatform
58
 */
59
class PostgreSqlPlatform extends AbstractPlatform
60
{
61
    /**
62
     * @var bool
63
     */
64
    private $useBooleanTrueFalseStrings = true;
65
66
    /**
67
     * @var array PostgreSQL booleans literals
68
     */
69
    private $booleanLiterals = [
70
        'true' => [
71
            't',
72
            'true',
73
            'y',
74
            'yes',
75
            'on',
76
            '1'
77
        ],
78
        'false' => [
79
            'f',
80
            'false',
81
            'n',
82
            'no',
83
            'off',
84
            '0'
85
        ]
86
    ];
87
88
    /**
89
     * PostgreSQL has different behavior with some drivers
90
     * with regard to how booleans have to be handled.
91
     *
92
     * Enables use of 'true'/'false' or otherwise 1 and 0 instead.
93
     *
94
     * @param bool $flag
95
     */
96 190
    public function setUseBooleanTrueFalseStrings($flag)
97
    {
98 190
        $this->useBooleanTrueFalseStrings = (bool) $flag;
99 190
    }
100
101
    /**
102
     * {@inheritDoc}
103
     */
104 101
    public function getSubstringExpression($value, $from, $length = null)
105
    {
106 101
        if ($length === null) {
107 101
            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
108
        }
109
110 95
        return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')';
111
    }
112
113
    /**
114
     * {@inheritDoc}
115
     */
116
    public function getNowExpression()
117
    {
118
        return 'LOCALTIMESTAMP(0)';
119
    }
120
121
    /**
122
     * {@inheritDoc}
123
     */
124 95
    public function getRegexpExpression()
125
    {
126 95
        return 'SIMILAR TO';
127
    }
128
129
    /**
130
     * {@inheritDoc}
131
     */
132 6
    public function getLocateExpression($str, $substr, $startPos = false)
133
    {
134 6
        if ($startPos !== false) {
135 6
            $str = $this->getSubstringExpression($str, $startPos);
136
137 6
            return 'CASE WHEN (POSITION('.$substr.' IN '.$str.') = 0) THEN 0 ELSE (POSITION('.$substr.' IN '.$str.') + '.($startPos-1).') END';
138
        }
139
140 6
        return 'POSITION('.$substr.' IN '.$str.')';
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146 6
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
147
    {
148 6
        if ($unit === DateIntervalUnit::QUARTER) {
149 6
            $interval *= 3;
150 6
            $unit      = DateIntervalUnit::MONTH;
151
        }
152
153 6
        return "(" . $date ." " . $operator . " (" . $interval . " || ' " . $unit . "')::interval)";
154
    }
155
156
    /**
157
     * {@inheritDoc}
158
     */
159 18
    public function getDateDiffExpression($date1, $date2)
160
    {
161 18
        return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))';
162
    }
163
164
    /**
165
     * {@inheritDoc}
166
     */
167 137
    public function supportsSequences()
168
    {
169 137
        return true;
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175 24
    public function supportsSchemas()
176
    {
177 24
        return true;
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183 6
    public function getDefaultSchemaName()
184
    {
185 6
        return 'public';
186
    }
187
188
    /**
189
     * {@inheritDoc}
190
     */
191 113
    public function supportsIdentityColumns()
192
    {
193 113
        return true;
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 906
    public function supportsPartialIndexes()
200
    {
201 906
        return true;
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207 101
    public function usesSequenceEmulatedIdentityColumns()
208
    {
209 101
        return true;
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215 107
    public function getIdentitySequenceName($tableName, $columnName)
216
    {
217 107
        return $tableName . '_' . $columnName . '_seq';
218
    }
219
220
    /**
221
     * {@inheritDoc}
222
     */
223 2370
    public function supportsCommentOnStatement()
224
    {
225 2370
        return true;
226
    }
227
228
    /**
229
     * {@inheritDoc}
230
     */
231 95
    public function prefersSequences()
232
    {
233 95
        return true;
234
    }
235
236
    /**
237
     * {@inheritDoc}
238
     */
239 4817
    public function hasNativeGuidType()
240
    {
241 4817
        return true;
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247 12
    public function getListDatabasesSQL()
248
    {
249 12
        return 'SELECT datname FROM pg_database';
250
    }
251
252
    /**
253
     * {@inheritDoc}
254
     */
255 12
    public function getListNamespacesSQL()
256
    {
257 12
        return "SELECT schema_name AS nspname
258
                FROM   information_schema.schemata
259
                WHERE  schema_name NOT LIKE 'pg\_%'
260
                AND    schema_name != 'information_schema'";
261
    }
262
263
    /**
264
     * {@inheritDoc}
265
     */
266 30
    public function getListSequencesSQL($database)
267
    {
268 30
        return "SELECT sequence_name AS relname,
269
                       sequence_schema AS schemaname
270
                FROM   information_schema.sequences
271
                WHERE  sequence_schema NOT LIKE 'pg\_%'
272
                AND    sequence_schema != 'information_schema'";
273
    }
274
275
    /**
276
     * {@inheritDoc}
277
     */
278 524
    public function getListTablesSQL()
279
    {
280 524
        return "SELECT quote_ident(table_name) AS table_name,
281
                       table_schema AS schema_name
282
                FROM   information_schema.tables
283
                WHERE  table_schema NOT LIKE 'pg\_%'
284
                AND    table_schema != 'information_schema'
285
                AND    table_name != 'geometry_columns'
286
                AND    table_name != 'spatial_ref_sys'
287
                AND    table_type != 'VIEW'";
288
    }
289
290
    /**
291
     * {@inheritDoc}
292
     */
293 6
    public function getListViewsSQL($database)
294
    {
295 6
        return 'SELECT quote_ident(table_name) AS viewname,
296
                       table_schema AS schemaname,
297
                       view_definition AS definition
298
                FROM   information_schema.views
299
                WHERE  view_definition IS NOT NULL';
300
    }
301
302
    /**
303
     * {@inheritDoc}
304
     */
305 448
    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

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