Completed
Push — master ( 822d97...ff6cbd )
by Sergei
13:05
created

PostgreSqlPlatform   F

Complexity

Total Complexity 169

Size/Duplication

Total Lines 1207
Duplicated Lines 0 %

Test Coverage

Coverage 92.33%

Importance

Changes 0
Metric Value
wmc 169
eloc 348
dl 0
loc 1207
ccs 337
cts 365
cp 0.9233
rs 2
c 0
b 0
f 0

82 Methods

Rating   Name   Duplication   Size   Complexity  
A getDropForeignKeySQL() 0 3 1
A getAlterSequenceSQL() 0 5 1
A getDateArithmeticIntervalExpression() 0 8 2
A getTableWhereClause() 0 19 2
A getCommentOnColumnSQL() 0 11 2
A getLocateExpression() 0 9 2
A getCreateSequenceSQL() 0 7 1
A supportsPartialIndexes() 0 3 1
A getIdentitySequenceName() 0 3 1
A getListSequencesSQL() 0 3 1
B isUnchangedBinaryColumn() 0 25 8
A supportsSequences() 0 3 1
A getRegexpExpression() 0 3 1
A getDefaultSchemaName() 0 3 1
A getSubstringExpression() 0 7 2
A getDateDiffExpression() 0 3 1
A getRenameIndexSQL() 0 8 2
A supportsSchemas() 0 3 1
A getDisallowDatabaseConnectionsSQL() 0 3 1
A getListTableForeignKeysSQL() 0 9 1
A getCreateSchemaSQL() 0 3 1
A getListTablesSQL() 0 3 1
A getDropSequenceSQL() 0 7 2
A getListTableColumnsSQL() 0 27 1
A getCreateViewSQL() 0 3 1
B _getCreateTableSQL() 0 26 8
A usesSequenceEmulatedIdentityColumns() 0 3 1
A getListTableIndexesSQL() 0 10 1
A getListNamespacesSQL() 0 3 1
F getAlterTableSQL() 0 137 29
A getSequenceCacheSQL() 0 7 2
A getNowExpression() 0 3 1
A getListTableConstraintsSQL() 0 21 1
A getDropViewSQL() 0 3 1
A getListViewsSQL() 0 3 1
A getCreateDatabaseSQL() 0 3 1
A supportsIdentityColumns() 0 3 1
A supportsCommentOnStatement() 0 3 1
A setUseBooleanTrueFalseStrings() 0 3 1
B getAdvancedForeignKeyOptionsSQL() 0 25 8
A getListDatabasesSQL() 0 3 1
A hasNativeGuidType() 0 3 1
A prefersSequences() 0 3 1
B convertSingleBooleanValue() 0 26 8
A getCloseActiveDatabaseConnectionsSQL() 0 4 1
A initializeDoctrineTypeMappings() 0 42 1
A isSerialField() 0 4 3
A convertFromBoolean() 0 7 2
A getBigIntTypeDeclarationSQL() 0 7 2
A getIntegerTypeDeclarationSQL() 0 7 2
A convertBooleansToDatabaseValue() 0 10 3
A getBinaryTypeDeclarationSQLSnippet() 0 3 1
A getGuidExpression() 0 3 1
A getVarcharTypeDeclarationSQLSnippet() 0 4 4
A _getCommonIntegerTypeDeclarationSQL() 0 3 1
A isNumericType() 0 3 2
A getReadLockSQL() 0 3 1
A getGuidTypeDeclarationSQL() 0 3 1
A typeChangeBreaksDefaultValue() 0 12 5
A getName() 0 3 1
A getTimeTypeDeclarationSQL() 0 3 1
A getBinaryMaxLength() 0 3 1
A doConvertBooleans() 0 11 3
A getReservedKeywordsClass() 0 3 1
A getDateTimeTzTypeDeclarationSQL() 0 3 1
A getSmallIntTypeDeclarationSQL() 0 3 1
A getDateTypeDeclarationSQL() 0 3 1
A getDateTimeTzFormatString() 0 3 1
A getBlobTypeDeclarationSQL() 0 3 1
A getEmptyIdentityInsertSQL() 0 3 1
A getDefaultValueDeclarationSQL() 0 7 2
A getSequenceNextValSQL() 0 3 1
A getSQLResultCasing() 0 3 1
A getSetTransactionIsolationSQL() 0 4 1
A getVarcharMaxLength() 0 3 1
A getOldColumnComment() 0 3 2
A convertBooleans() 0 14 4
A getClobTypeDeclarationSQL() 0 3 1
A getTruncateTableSQL() 0 10 2
A getBooleanTypeDeclarationSQL() 0 3 1
A getBinaryDefaultLength() 0 3 1
A getDateTimeTypeDeclarationSQL() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like PostgreSqlPlatform often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PostgreSqlPlatform, and based on these observations, apply Extract Interface, too.

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
 * @todo   Rename: PostgreSQLPlatform
39
 */
40
class PostgreSqlPlatform extends AbstractPlatform
41
{
42
    /** @var bool */
43
    private $useBooleanTrueFalseStrings = true;
44
45
    /** @var string[][] PostgreSQL booleans literals */
46
    private $booleanLiterals = [
47
        'true' => [
48
            't',
49
            'true',
50
            'y',
51
            'yes',
52
            'on',
53
            '1',
54
        ],
55
        'false' => [
56
            'f',
57
            'false',
58
            'n',
59
            'no',
60
            'off',
61
            '0',
62
        ],
63
    ];
64
65
    /**
66
     * PostgreSQL has different behavior with some drivers
67
     * with regard to how booleans have to be handled.
68
     *
69
     * Enables use of 'true'/'false' or otherwise 1 and 0 instead.
70
     *
71
     * @param bool $flag
72
     */
73 190
    public function setUseBooleanTrueFalseStrings($flag)
74
    {
75 190
        $this->useBooleanTrueFalseStrings = (bool) $flag;
76 190
    }
77
78
    /**
79
     * {@inheritDoc}
80
     */
81 101
    public function getSubstringExpression($value, $from, $length = null)
82
    {
83 101
        if ($length === null) {
84 101
            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
85
        }
86
87 95
        return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')';
88
    }
89
90
    /**
91
     * {@inheritDoc}
92
     */
93
    public function getNowExpression()
94
    {
95
        return 'LOCALTIMESTAMP(0)';
96
    }
97
98
    /**
99
     * {@inheritDoc}
100
     */
101 95
    public function getRegexpExpression()
102
    {
103 95
        return 'SIMILAR TO';
104
    }
105
106
    /**
107
     * {@inheritDoc}
108
     */
109 6
    public function getLocateExpression($str, $substr, $startPos = false)
110
    {
111 6
        if ($startPos !== false) {
112 6
            $str = $this->getSubstringExpression($str, $startPos);
113
114 6
            return 'CASE WHEN (POSITION(' . $substr . ' IN ' . $str . ') = 0) THEN 0 ELSE (POSITION(' . $substr . ' IN ' . $str . ') + ' . ($startPos-1) . ') END';
115
        }
116
117 6
        return 'POSITION(' . $substr . ' IN ' . $str . ')';
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123 6
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
124
    {
125 6
        if ($unit === DateIntervalUnit::QUARTER) {
126 6
            $interval *= 3;
127 6
            $unit      = DateIntervalUnit::MONTH;
128
        }
129
130 6
        return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit . "')::interval)";
131
    }
132
133
    /**
134
     * {@inheritDoc}
135
     */
136 18
    public function getDateDiffExpression($date1, $date2)
137
    {
138 18
        return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))';
139
    }
140
141
    /**
142
     * {@inheritDoc}
143
     */
144 137
    public function supportsSequences()
145
    {
146 137
        return true;
147
    }
148
149
    /**
150
     * {@inheritDoc}
151
     */
152 24
    public function supportsSchemas()
153
    {
154 24
        return true;
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160 6
    public function getDefaultSchemaName()
161
    {
162 6
        return 'public';
163
    }
164
165
    /**
166
     * {@inheritDoc}
167
     */
168 113
    public function supportsIdentityColumns()
169
    {
170 113
        return true;
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176 906
    public function supportsPartialIndexes()
177
    {
178 906
        return true;
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184 101
    public function usesSequenceEmulatedIdentityColumns()
185
    {
186 101
        return true;
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192 107
    public function getIdentitySequenceName($tableName, $columnName)
193
    {
194 107
        return $tableName . '_' . $columnName . '_seq';
195
    }
196
197
    /**
198
     * {@inheritDoc}
199
     */
200 2388
    public function supportsCommentOnStatement()
201
    {
202 2388
        return true;
203
    }
204
205
    /**
206
     * {@inheritDoc}
207
     */
208 95
    public function prefersSequences()
209
    {
210 95
        return true;
211
    }
212
213
    /**
214
     * {@inheritDoc}
215
     */
216 4817
    public function hasNativeGuidType()
217
    {
218 4817
        return true;
219
    }
220
221
    /**
222
     * {@inheritDoc}
223
     */
224 12
    public function getListDatabasesSQL()
225
    {
226 12
        return 'SELECT datname FROM pg_database';
227
    }
228
229
    /**
230
     * {@inheritDoc}
231
     */
232 12
    public function getListNamespacesSQL()
233
    {
234 12
        return "SELECT schema_name AS nspname
235
                FROM   information_schema.schemata
236
                WHERE  schema_name NOT LIKE 'pg\_%'
237
                AND    schema_name != 'information_schema'";
238
    }
239
240
    /**
241
     * {@inheritDoc}
242
     */
243 30
    public function getListSequencesSQL($database)
244
    {
245 30
        return "SELECT sequence_name AS relname,
246
                       sequence_schema AS schemaname
247
                FROM   information_schema.sequences
248
                WHERE  sequence_schema NOT LIKE 'pg\_%'
249
                AND    sequence_schema != 'information_schema'";
250
    }
251
252
    /**
253
     * {@inheritDoc}
254
     */
255 524
    public function getListTablesSQL()
256
    {
257 524
        return "SELECT quote_ident(table_name) AS table_name,
258
                       table_schema AS schema_name
259
                FROM   information_schema.tables
260
                WHERE  table_schema NOT LIKE 'pg\_%'
261
                AND    table_schema != 'information_schema'
262
                AND    table_name != 'geometry_columns'
263
                AND    table_name != 'spatial_ref_sys'
264
                AND    table_type != 'VIEW'";
265
    }
266
267
    /**
268
     * {@inheritDoc}
269
     */
270 6
    public function getListViewsSQL($database)
271
    {
272 6
        return 'SELECT quote_ident(table_name) AS viewname,
273
                       table_schema AS schemaname,
274
                       view_definition AS definition
275
                FROM   information_schema.views
276
                WHERE  view_definition IS NOT NULL';
277
    }
278
279
    /**
280
     * {@inheritDoc}
281
     */
282 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

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