Failed Conditions
Pull Request — master (#2916)
by Robin
17:37 queued 08:37
created

PostgreSqlPlatform::getAlterTableSQL()   F

Complexity

Conditions 33
Paths > 20000

Size

Total Lines 141
Code Lines 82

Duplication

Lines 37
Ratio 26.24 %

Code Coverage

Tests 68
CRAP Score 36.6753

Importance

Changes 0
Metric Value
dl 37
loc 141
ccs 68
cts 80
cp 0.85
rs 2
c 0
b 0
f 0
cc 33
eloc 82
nc 396900
nop 1
crap 36.6753

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\BinaryType;
29
use Doctrine\DBAL\Types\BlobType;
30
use Doctrine\DBAL\Types\Type;
31
32
/**
33
 * PostgreSqlPlatform.
34
 *
35
 * @since  2.0
36
 * @author Roman Borschel <[email protected]>
37
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
38
 * @author Benjamin Eberlei <[email protected]>
39
 * @todo   Rename: PostgreSQLPlatform
40
 */
41
class PostgreSqlPlatform extends AbstractPlatform
42
{
43
    /**
44
     * @var bool
45
     */
46
    private $useBooleanTrueFalseStrings = true;
47
48
    /**
49
     * @var array PostgreSQL booleans literals
50
     */
51
    private $booleanLiterals = [
52
        'true' => [
53
            't',
54
            'true',
55
            'y',
56
            'yes',
57
            'on',
58
            '1'
59
        ],
60
        'false' => [
61
            'f',
62
            'false',
63
            'n',
64
            'no',
65
            'off',
66
            '0'
67
        ]
68
    ];
69
70
    /**
71
     * PostgreSQL has different behavior with some drivers
72
     * with regard to how booleans have to be handled.
73
     *
74
     * Enables use of 'true'/'false' or otherwise 1 and 0 instead.
75
     *
76
     * @param bool $flag
77
     */
78 8
    public function setUseBooleanTrueFalseStrings($flag)
79
    {
80 8
        $this->useBooleanTrueFalseStrings = (bool) $flag;
81 8
    }
82
83
    /**
84
     * {@inheritDoc}
85
     */
86 4 View Code Duplication
    public function getSubstringExpression($value, $from, $length = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
87
    {
88 4
        if ($length === null) {
89 4
            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
90
        }
91
92 4
        return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')';
93
    }
94
95
    /**
96
     * {@inheritDoc}
97
     */
98
    public function getNowExpression()
99
    {
100
        return 'LOCALTIMESTAMP(0)';
101
    }
102
103
    /**
104
     * {@inheritDoc}
105
     */
106 4
    public function getRegexpExpression()
107
    {
108 4
        return 'SIMILAR TO';
109
    }
110
111
    /**
112
     * {@inheritDoc}
113
     */
114
    public function getLocateExpression($str, $substr, $startPos = false)
115
    {
116
        if ($startPos !== false) {
117
            $str = $this->getSubstringExpression($str, $startPos);
118
119
            return 'CASE WHEN (POSITION('.$substr.' IN '.$str.') = 0) THEN 0 ELSE (POSITION('.$substr.' IN '.$str.') + '.($startPos-1).') END';
120
        }
121
122
        return 'POSITION('.$substr.' IN '.$str.')';
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
129
    {
130
        if (self::DATE_INTERVAL_UNIT_QUARTER === $unit) {
131
            $interval *= 3;
132
            $unit = self::DATE_INTERVAL_UNIT_MONTH;
133
        }
134
135
        return "(" . $date ." " . $operator . " (" . $interval . " || ' " . $unit . "')::interval)";
136
    }
137
138
    /**
139
     * {@inheritDoc}
140
     */
141
    public function getDateDiffExpression($date1, $date2)
142
    {
143
        return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))';
144
    }
145
146
    /**
147
     * {@inheritDoc}
148
     */
149 4
    public function supportsSequences()
150
    {
151 4
        return true;
152
    }
153
154
    /**
155
     * {@inheritDoc}
156
     */
157
    public function supportsSchemas()
158
    {
159
        return true;
160
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function getDefaultSchemaName()
166
    {
167
        return 'public';
168
    }
169
170
    /**
171
     * {@inheritDoc}
172
     */
173 4
    public function supportsIdentityColumns()
174
    {
175 4
        return true;
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181 34
    public function supportsPartialIndexes()
182
    {
183 34
        return true;
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 4
    public function usesSequenceEmulatedIdentityColumns()
190
    {
191 4
        return true;
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197 4
    public function getIdentitySequenceName($tableName, $columnName)
198
    {
199 4
        return $tableName . '_' . $columnName . '_seq';
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     */
205 44
    public function supportsCommentOnStatement()
206
    {
207 44
        return true;
208
    }
209
210
    /**
211
     * {@inheritDoc}
212
     */
213 4
    public function prefersSequences()
214
    {
215 4
        return true;
216
    }
217
218
    /**
219
     * {@inheritDoc}
220
     */
221 164
    public function hasNativeGuidType()
222
    {
223 164
        return true;
224
    }
225
226
    /**
227
     * {@inheritDoc}
228
     */
229
    public function getListDatabasesSQL()
230
    {
231
        return 'SELECT datname FROM pg_database';
232
    }
233
234
    /**
235
     * {@inheritDoc}
236
     */
237
    public function getListNamespacesSQL()
238
    {
239
        return "SELECT schema_name AS nspname
240
                FROM   information_schema.schemata
241
                WHERE  schema_name NOT LIKE 'pg\_%'
242
                AND    schema_name != 'information_schema'";
243
    }
244
245
    /**
246
     * {@inheritDoc}
247
     */
248
    public function getListSequencesSQL($database)
249
    {
250
        return "SELECT sequence_name AS relname,
251
                       sequence_schema AS schemaname
252
                FROM   information_schema.sequences
253
                WHERE  sequence_schema NOT LIKE 'pg\_%'
254
                AND    sequence_schema != 'information_schema'";
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260
    public function getListTablesSQL()
261
    {
262
        return "SELECT quote_ident(table_name) AS table_name,
263
                       table_schema AS schema_name
264
                FROM   information_schema.tables
265
                WHERE  table_schema NOT LIKE 'pg\_%'
266
                AND    table_schema != 'information_schema'
267
                AND    table_name != 'geometry_columns'
268
                AND    table_name != 'spatial_ref_sys'
269
                AND    table_type != 'VIEW'";
270
    }
271
272
    /**
273
     * {@inheritDoc}
274
     */
275
    public function getListViewsSQL($database)
276
    {
277
        return 'SELECT quote_ident(table_name) AS viewname,
278
                       table_schema AS schemaname,
279
                       view_definition AS definition
280
                FROM   information_schema.views
281
                WHERE  view_definition IS NOT NULL';
282
    }
283
284
    /**
285
     * {@inheritDoc}
286
     */
287 8
    public function getListTableForeignKeysSQL($table, $database = null)
0 ignored issues
show
Unused Code introduced by
The parameter $database is not used and could be removed.

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

Loading history...
288
    {
289
        return "SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef
290
                  FROM pg_catalog.pg_constraint r
291
                  WHERE r.conrelid =
292
                  (
293
                      SELECT c.oid
294
                      FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n
295 8
                      WHERE " .$this->getTableWhereClause($table) ." AND n.oid = c.relnamespace
296
                  )
297
                  AND r.contype = 'f'";
298
    }
299
300
    /**
301
     * {@inheritDoc}
302
     */
303
    public function getCreateViewSQL($name, $sql)
304
    {
305
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
306
    }
307
308
    /**
309
     * {@inheritDoc}
310
     */
311
    public function getDropViewSQL($name)
312
    {
313
        return 'DROP VIEW '. $name;
314
    }
315
316
    /**
317
     * {@inheritDoc}
318
     */
319 4
    public function getListTableConstraintsSQL($table)
320
    {
321 4
        $table = new Identifier($table);
322 4
        $table = $this->quoteStringLiteral($table->getName());
323
324
        return "SELECT
325
                    quote_ident(relname) as relname
326
                FROM
327
                    pg_class
328
                WHERE oid IN (
329
                    SELECT indexrelid
330
                    FROM pg_index, pg_class
331 4
                    WHERE pg_class.relname = $table
332
                        AND pg_class.oid = pg_index.indrelid
333
                        AND (indisunique = 't' OR indisprimary = 't')
334
                        )";
335
    }
336
337
    /**
338
     * {@inheritDoc}
339
     *
340
     * @license New BSD License
341
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
342
     */
343 8
    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 8
                    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 24
    private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n')
364
    {
365 24
        $whereClause = $namespaceAlias.".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND ";
366 24 View Code Duplication
        if (strpos($table, ".") !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
367 12
            list($schema, $table) = explode(".", $table);
368 12
            $schema = $this->quoteStringLiteral($schema);
369
        } else {
370 12
            $schema = "ANY(string_to_array((select replace(replace(setting,'\"\$user\"',user),' ','') from pg_catalog.pg_settings where name = 'search_path'),','))";
371
        }
372
373 24
        $table = new Identifier($table);
374 24
        $table = $this->quoteStringLiteral($table->getName());
375 24
        $whereClause .= "$classAlias.relname = " . $table . " AND $namespaceAlias.nspname = $schema";
376
377 24
        return $whereClause;
378
    }
379
380
    /**
381
     * {@inheritDoc}
382
     */
383 8
    public function getListTableColumnsSQL($table, $database = null)
384
    {
385
        return "SELECT
386
                    a.attnum,
387
                    quote_ident(a.attname) AS field,
388
                    t.typname AS type,
389
                    format_type(a.atttypid, a.atttypmod) AS complete_type,
390
                    (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
391
                    (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM
392
                      pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type,
393
                    a.attnotnull AS isnotnull,
394
                    (SELECT 't'
395
                     FROM pg_index
396
                     WHERE c.oid = pg_index.indrelid
397
                        AND pg_index.indkey[0] = a.attnum
398
                        AND pg_index.indisprimary = 't'
399
                    ) AS pri,
400
                    (SELECT pg_get_expr(adbin, adrelid)
401
                     FROM pg_attrdef
402
                     WHERE c.oid = pg_attrdef.adrelid
403
                        AND pg_attrdef.adnum=a.attnum
404
                    ) AS default,
405
                    (SELECT pg_description.description
406
                        FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
407
                    ) AS comment
408
                    FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n
409 8
                    WHERE ".$this->getTableWhereClause($table, 'c', 'n') ."
410
                        AND a.attnum > 0
411
                        AND a.attrelid = c.oid
412
                        AND a.atttypid = t.oid
413
                        AND n.oid = c.relnamespace
414
                    ORDER BY a.attnum";
415
    }
416
417
    /**
418
     * {@inheritDoc}
419
     */
420 4
    public function getCreateDatabaseSQL($name)
421
    {
422 4
        return 'CREATE DATABASE ' . $name;
423
    }
424
425
    /**
426
     * Returns the SQL statement for disallowing new connections on the given database.
427
     *
428
     * This is useful to force DROP DATABASE operations which could fail because of active connections.
429
     *
430
     * @param string $database The name of the database to disallow new connections for.
431
     *
432
     * @return string
433
     */
434 4
    public function getDisallowDatabaseConnectionsSQL($database)
435
    {
436 4
        return "UPDATE pg_database SET datallowconn = 'false' WHERE datname = '$database'";
437
    }
438
439
    /**
440
     * Returns the SQL statement for closing currently active connections on the given database.
441
     *
442
     * This is useful to force DROP DATABASE operations which could fail because of active connections.
443
     *
444
     * @param string $database The name of the database to close currently active connections for.
445
     *
446
     * @return string
447
     */
448 4
    public function getCloseActiveDatabaseConnectionsSQL($database)
449
    {
450 4
        $database = $this->quoteStringLiteral($database);
451
452 4
        return "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = $database";
453
    }
454
455
    /**
456
     * {@inheritDoc}
457
     */
458 20
    public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey)
459
    {
460 20
        $query = '';
461
462 20
        if ($foreignKey->hasOption('match')) {
463 4
            $query .= ' MATCH ' . $foreignKey->getOption('match');
464
        }
465
466 20
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
467
468 20
        if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) {
469 4
            $query .= ' DEFERRABLE';
470
        } else {
471 20
            $query .= ' NOT DEFERRABLE';
472
        }
473
474 20
        if (($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false)
475 20
            || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false)
476
        ) {
477 4
            $query .= ' INITIALLY DEFERRED';
478
        } else {
479 20
            $query .= ' INITIALLY IMMEDIATE';
480
        }
481
482 20
        return $query;
483
    }
484
485
    /**
486
     * {@inheritDoc}
487
     */
488 72
    public function getAlterTableSQL(TableDiff $diff)
489
    {
490 72
        $sql = [];
491 72
        $commentsSQL = [];
492 72
        $columnSql = [];
493
494 72
        foreach ($diff->addedColumns as $column) {
495 16
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
496
                continue;
497
            }
498
499 16
            $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
500 16
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
501
502 16
            $comment = $this->getColumnComment($column);
503
504 16 View Code Duplication
            if (null !== $comment && '' !== $comment) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
505 4
                $commentsSQL[] = $this->getCommentOnColumnSQL(
506 4
                    $diff->getName($this)->getQuotedName($this),
507 4
                    $column->getQuotedName($this),
508 16
                    $comment
509
                );
510
            }
511
        }
512
513 72
        foreach ($diff->removedColumns as $column) {
514 16
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
515
                continue;
516
            }
517
518 16
            $query = 'DROP ' . $column->getQuotedName($this);
519 16
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
520
        }
521
522 72
        foreach ($diff->changedColumns as $columnDiff) {
523
            /** @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
524 40
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
525
                continue;
526
            }
527
528 40
            if ($this->isUnchangedBinaryColumn($columnDiff)) {
529 4
                continue;
530
            }
531
532 36
            $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this);
533 36
            $column = $columnDiff->column;
534
535 36
            if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed')) {
536 16
                $type = $column->getType();
537
538
                // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type
539 16
                $columnDefinition = $column->toArray();
540 16
                $columnDefinition['autoincrement'] = false;
541
542
                // here was a server version check before, but DBAL API does not support this anymore.
543 16
                $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this);
544 16
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
545
            }
546
547 36
            if ($columnDiff->fromColumn) {
548 12
                $oldTypeIsNumeric = $columnDiff->fromColumn->getType()->getName() === Type::INTEGER || $columnDiff->fromColumn->getType()->getName() === Type::BIGINT;
549 12
                $newTypeIsNumeric = $column->getType()->getName() === Type::INTEGER || $column->getType()->getName() === Type::BIGINT;
550
551
                // default should not be changed when switching between numeric types and the default comes from a sequence
552 12
                $defaultTypeChanged = $columnDiff->hasChanged('type') && !($oldTypeIsNumeric && $newTypeIsNumeric && $column->getDefault() === null);
553
            } else {
554 24
                $defaultTypeChanged = $columnDiff->hasChanged('type');
555
            }
556
557 36
            if ($columnDiff->hasChanged('default') || $defaultTypeChanged) {
558 8
                $defaultClause = null === $column->getDefault()
559 4
                    ? ' DROP DEFAULT'
560 8
                    : ' SET' . $this->getDefaultValueDeclarationSQL($column->toArray());
561 8
                $query = 'ALTER ' . $oldColumnName . $defaultClause;
562 8
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
563
            }
564
565 36
            if ($columnDiff->hasChanged('notnull')) {
566 8
                $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL';
567 8
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
568
            }
569
570 36
            if ($columnDiff->hasChanged('autoincrement')) {
571
                if ($column->getAutoincrement()) {
572
                    // add autoincrement
573
                    $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName);
574
575
                    $sql[] = "CREATE SEQUENCE " . $seqName;
576
                    $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ") FROM " . $diff->getName($this)->getQuotedName($this) . "))";
577
                    $query = "ALTER " . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')";
578
                    $sql[] = "ALTER TABLE " . $diff->getName($this)->getQuotedName($this) . " " . $query;
579
                } else {
580
                    // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have
581
                    $query = "ALTER " . $oldColumnName . " " . "DROP DEFAULT";
582
                    $sql[] = "ALTER TABLE " . $diff->getName($this)->getQuotedName($this) . " " . $query;
583
                }
584
            }
585
586 36 View Code Duplication
            if ($columnDiff->hasChanged('comment')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
587 8
                $commentsSQL[] = $this->getCommentOnColumnSQL(
588 8
                    $diff->getName($this)->getQuotedName($this),
589 8
                    $column->getQuotedName($this),
590 8
                    $this->getColumnComment($column)
591
                );
592
            }
593
594 36
            if ($columnDiff->hasChanged('length')) {
595 4
                $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $column->getType()->getSQLDeclaration($column->toArray(), $this);
596 36
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
597
            }
598
        }
599
600 72 View Code Duplication
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
601 16
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
602
                continue;
603
            }
604
605 16
            $oldColumnName = new Identifier($oldColumnName);
606
607 16
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
608 16
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
609
        }
610
611 72
        $tableSql = [];
612
613 72 View Code Duplication
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
614 72
            $sql = array_merge($sql, $commentsSQL);
615
616 72
            if ($diff->newName !== false) {
617 8
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this);
618
            }
619
620 72
            $sql = array_merge(
621 72
                $this->getPreAlterTableIndexForeignKeySQL($diff),
622 72
                $sql,
623 72
                $this->getPostAlterTableIndexForeignKeySQL($diff)
624
            );
625
        }
626
627 72
        return array_merge($sql, $tableSql, $columnSql);
628
    }
629
630
    /**
631
     * Checks whether a given column diff is a logically unchanged binary type column.
632
     *
633
     * Used to determine whether a column alteration for a binary type column can be skipped.
634
     * Doctrine's {@link \Doctrine\DBAL\Types\BinaryType} and {@link \Doctrine\DBAL\Types\BlobType}
635
     * are mapped to the same database column type on this platform as this platform
636
     * does not have a native VARBINARY/BINARY column type. Therefore the {@link \Doctrine\DBAL\Schema\Comparator}
637
     * might detect differences for binary type columns which do not have to be propagated
638
     * to database as there actually is no difference at database level.
639
     *
640
     * @param ColumnDiff $columnDiff The column diff to check against.
641
     *
642
     * @return boolean True if the given column diff is an unchanged binary type column, false otherwise.
643
     */
644 40
    private function isUnchangedBinaryColumn(ColumnDiff $columnDiff)
645
    {
646 40
        $columnType = $columnDiff->column->getType();
647
648 40
        if ( ! $columnType instanceof BinaryType && ! $columnType instanceof BlobType) {
649 36
            return false;
650
        }
651
652 4
        $fromColumn = $columnDiff->fromColumn instanceof Column ? $columnDiff->fromColumn : null;
653
654 4
        if ($fromColumn) {
655 4
            $fromColumnType = $fromColumn->getType();
656
657 4
            if ( ! $fromColumnType instanceof BinaryType && ! $fromColumnType instanceof BlobType) {
658
                return false;
659
            }
660
661 4
            return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0;
662
        }
663
664
        if ($columnDiff->hasChanged('type')) {
665
            return false;
666
        }
667
668
        return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0;
669
    }
670
671
    /**
672
     * {@inheritdoc}
673
     */
674 20 View Code Duplication
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
675
    {
676 20
        if (strpos($tableName, '.') !== false) {
677 8
            list($schema) = explode('.', $tableName);
678 8
            $oldIndexName = $schema . '.' . $oldIndexName;
679
        }
680
681 20
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
682
    }
683
684
    /**
685
     * {@inheritdoc}
686
     */
687 32 View Code Duplication
    public function getCommentOnColumnSQL($tableName, $columnName, $comment)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
688
    {
689 32
        $tableName = new Identifier($tableName);
690 32
        $columnName = new Identifier($columnName);
691 32
        $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
692
693 32
        return "COMMENT ON COLUMN " . $tableName->getQuotedName($this) . "." . $columnName->getQuotedName($this) .
694 32
            " IS $comment";
695
    }
696
697
    /**
698
     * {@inheritDoc}
699
     */
700 8 View Code Duplication
    public function getCreateSequenceSQL(Sequence $sequence)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
701
    {
702 8
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
703 8
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
704 8
               ' MINVALUE ' . $sequence->getInitialValue() .
705 8
               ' START ' . $sequence->getInitialValue() .
706 8
               $this->getSequenceCacheSQL($sequence);
707
    }
708
709
    /**
710
     * {@inheritDoc}
711
     */
712
    public function getAlterSequenceSQL(Sequence $sequence)
713
    {
714
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
715
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
716
               $this->getSequenceCacheSQL($sequence);
717
    }
718
719
    /**
720
     * Cache definition for sequences
721
     *
722
     * @param Sequence $sequence
723
     *
724
     * @return string
725
     */
726 8
    private function getSequenceCacheSQL(Sequence $sequence)
727
    {
728 8
        if ($sequence->getCache() > 1) {
729 4
            return ' CACHE ' . $sequence->getCache();
730
        }
731
732 4
        return '';
733
    }
734
735
    /**
736
     * {@inheritDoc}
737
     */
738 4
    public function getDropSequenceSQL($sequence)
739
    {
740 4
        if ($sequence instanceof Sequence) {
741
            $sequence = $sequence->getQuotedName($this);
742
        }
743
744 4
        return 'DROP SEQUENCE ' . $sequence . ' CASCADE';
745
    }
746
747
    /**
748
     * {@inheritDoc}
749
     */
750 4
    public function getCreateSchemaSQL($schemaName)
751
    {
752 4
        return 'CREATE SCHEMA ' . $schemaName;
753
    }
754
755
    /**
756
     * {@inheritDoc}
757
     */
758 12
    public function getDropForeignKeySQL($foreignKey, $table)
759
    {
760 12
        return $this->getDropConstraintSQL($foreignKey, $table);
761
    }
762
763
    /**
764
     * {@inheritDoc}
765
     */
766 40
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
767
    {
768 40
        $queryFields = $this->getColumnDeclarationListSQL($columns);
769
770 40 View Code Duplication
        if (isset($options['primary']) && ! empty($options['primary'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
771 16
            $keyColumns = array_unique(array_values($options['primary']));
772 16
            $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
773
        }
774
775 40
        $query = 'CREATE TABLE ' . $tableName . ' (' . $queryFields . ')';
776
777 40
        $sql[] = $query;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$sql was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sql = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
778
779 40 View Code Duplication
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
780 12
            foreach ($options['indexes'] as $index) {
781 12
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
782
            }
783
        }
784
785 40 View Code Duplication
        if (isset($options['foreignKeys'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
786 4
            foreach ((array) $options['foreignKeys'] as $definition) {
787 4
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
788
            }
789
        }
790
791 40
        return $sql;
792
    }
793
794
    /**
795
     * Converts a single boolean value.
796
     *
797
     * First converts the value to its native PHP boolean type
798
     * and passes it to the given callback function to be reconverted
799
     * into any custom representation.
800
     *
801
     * @param mixed    $value    The value to convert.
802
     * @param callable $callback The callback function to use for converting the real boolean value.
803
     *
804
     * @return mixed
805
     * @throws \UnexpectedValueException
806
     */
807 128
    private function convertSingleBooleanValue($value, $callback)
808
    {
809 128
        if (null === $value) {
810 8
            return $callback(null);
811
        }
812
813 120
        if (is_bool($value) || is_numeric($value)) {
814 76
            return $callback($value ? true : false);
815
        }
816
817 44
        if (!is_string($value)) {
818
            return $callback(true);
819
        }
820
821
        /**
822
         * Better safe than sorry: http://php.net/in_array#106319
823
         */
824 44 View Code Duplication
        if (in_array(trim(strtolower($value)), $this->booleanLiterals['false'], true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
825 20
            return $callback(false);
826
        }
827
828 24 View Code Duplication
        if (in_array(trim(strtolower($value)), $this->booleanLiterals['true'], true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
829 20
            return $callback(true);
830
        }
831
832 4
        throw new \UnexpectedValueException("Unrecognized boolean literal '${value}'");
833
    }
834
835
    /**
836
     * Converts one or multiple boolean values.
837
     *
838
     * First converts the value(s) to their native PHP boolean type
839
     * and passes them to the given callback function to be reconverted
840
     * into any custom representation.
841
     *
842
     * @param mixed    $item     The value(s) to convert.
843
     * @param callable $callback The callback function to use for converting the real boolean value(s).
844
     *
845
     * @return mixed
846
     */
847
    private function doConvertBooleans($item, $callback)
848
    {
849 128
        if (is_array($item)) {
850
            foreach ($item as $key => $value) {
851
                $item[$key] = $this->convertSingleBooleanValue($value, $callback);
852
            }
853
854
            return $item;
855
        }
856
857 128
        return $this->convertSingleBooleanValue($item, $callback);
858
    }
859
860
    /**
861
     * {@inheritDoc}
862
     *
863
     * Postgres wants boolean values converted to the strings 'true'/'false'.
864
     */
865
    public function convertBooleans($item)
866
    {
867 72
        if ( ! $this->useBooleanTrueFalseStrings) {
868 8
            return parent::convertBooleans($item);
869
        }
870
871 64
        return $this->doConvertBooleans(
872 64
            $item,
873
            function ($boolean) {
874 64
                if (null === $boolean) {
875 4
                    return 'NULL';
876
                }
877
878 60
                return true === $boolean ? 'true' : 'false';
879 64
            }
880
        );
881
    }
882
883
    /**
884
     * {@inheritDoc}
885
     */
886
    public function convertBooleansToDatabaseValue($item)
887
    {
888 68
        if ( ! $this->useBooleanTrueFalseStrings) {
889 4
            return parent::convertBooleansToDatabaseValue($item);
890
        }
891
892 64
        return $this->doConvertBooleans(
893 64
            $item,
894
            function ($boolean) {
895 60
                return null === $boolean ? null : (int) $boolean;
896 64
            }
897
        );
898
    }
899
900
    /**
901
     * {@inheritDoc}
902
     */
903
    public function convertFromBoolean($item)
904
    {
905 60
        if (in_array(strtolower($item), $this->booleanLiterals['false'], true)) {
906 24
            return false;
907
        }
908
909 36
        return parent::convertFromBoolean($item);
910
    }
911
912
    /**
913
     * {@inheritDoc}
914
     */
915
    public function getSequenceNextValSQL($sequenceName)
916
    {
917 4
        return "SELECT NEXTVAL('" . $sequenceName . "')";
918
    }
919
920
    /**
921
     * {@inheritDoc}
922
     */
923
    public function getSetTransactionIsolationSQL($level)
924
    {
925
        return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL '
926 4
                . $this->_getTransactionIsolationLevelSQL($level);
927
    }
928
929
    /**
930
     * {@inheritDoc}
931
     */
932
    public function getBooleanTypeDeclarationSQL(array $field)
933
    {
934 4
        return 'BOOLEAN';
935
    }
936
937
    /**
938
     * {@inheritDoc}
939
     */
940
    public function getIntegerTypeDeclarationSQL(array $field)
941
    {
942 36
        if ( ! empty($field['autoincrement'])) {
943 12
            return 'SERIAL';
944
        }
945
946 28
        return 'INT';
947
    }
948
949
    /**
950
     * {@inheritDoc}
951
     */
952
    public function getBigIntTypeDeclarationSQL(array $field)
953
    {
954
        if ( ! empty($field['autoincrement'])) {
955
            return 'BIGSERIAL';
956
        }
957
958
        return 'BIGINT';
959
    }
960
961
    /**
962
     * {@inheritDoc}
963
     */
964
    public function getSmallIntTypeDeclarationSQL(array $field)
965
    {
966 2
        return 'SMALLINT';
967
    }
968
969
    /**
970
     * {@inheritDoc}
971
     */
972
    public function getGuidTypeDeclarationSQL(array $field)
973
    {
974 4
        return 'UUID';
975
    }
976
977
    /**
978
     * {@inheritDoc}
979
     */
980
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
981
    {
982
        return 'TIMESTAMP(0) WITHOUT TIME ZONE';
983
    }
984
985
    /**
986
     * {@inheritDoc}
987
     */
988
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
989
    {
990
        return 'TIMESTAMP(0) WITH TIME ZONE';
991
    }
992
993
    /**
994
     * {@inheritDoc}
995
     */
996
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
997
    {
998
        return 'DATE';
999
    }
1000
1001
    /**
1002
     * {@inheritDoc}
1003
     */
1004
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
1005
    {
1006
        return 'TIME(0) WITHOUT TIME ZONE';
1007
    }
1008
1009
    /**
1010
     * {@inheritDoc}
1011
     */
1012
    public function getGuidExpression()
1013
    {
1014
        return 'UUID_GENERATE_V4()';
1015
    }
1016
1017
    /**
1018
     * {@inheritDoc}
1019
     */
1020
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
1021
    {
1022
        return '';
1023
    }
1024
1025
    /**
1026
     * {@inheritDoc}
1027
     */
1028
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
1029
    {
1030 48
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
1031 48
                : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
1032
    }
1033
1034
    /**
1035
     * {@inheritdoc}
1036
     */
1037
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
1038
    {
1039 4
        return 'BYTEA';
1040
    }
1041
1042
    /**
1043
     * {@inheritDoc}
1044
     */
1045
    public function getClobTypeDeclarationSQL(array $field)
1046
    {
1047 6
        return 'TEXT';
1048
    }
1049
1050
    /**
1051
     * {@inheritDoc}
1052
     */
1053
    public function getName()
1054
    {
1055 8
        return 'postgresql';
1056
    }
1057
1058
    /**
1059
     * {@inheritDoc}
1060
     *
1061
     * PostgreSQL returns all column names in SQL result sets in lowercase.
1062
     */
1063
    public function getSQLResultCasing($column)
1064
    {
1065
        return strtolower($column);
1066
    }
1067
1068
    /**
1069
     * {@inheritDoc}
1070
     */
1071
    public function getDateTimeTzFormatString()
1072
    {
1073
        return 'Y-m-d H:i:sO';
1074
    }
1075
1076
    /**
1077
     * {@inheritDoc}
1078
     */
1079
    public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
1080
    {
1081
        return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)';
1082
    }
1083
1084
    /**
1085
     * {@inheritDoc}
1086
     */
1087
    public function getTruncateTableSQL($tableName, $cascade = false)
1088
    {
1089 4
        $tableIdentifier = new Identifier($tableName);
1090 4
        $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
1091
1092 4
        if ($cascade) {
1093
            $sql .= ' CASCADE';
1094
        }
1095
1096 4
        return $sql;
1097
    }
1098
1099
    /**
1100
     * {@inheritDoc}
1101
     */
1102
    public function getReadLockSQL()
1103
    {
1104
        return 'FOR SHARE';
1105
    }
1106
1107
    /**
1108
     * {@inheritDoc}
1109
     */
1110 View Code Duplication
    protected function initializeDoctrineTypeMappings()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1111
    {
1112 22
        $this->doctrineTypeMapping = [
1113
            'smallint'      => 'smallint',
1114
            'int2'          => 'smallint',
1115
            'serial'        => 'integer',
1116
            'serial4'       => 'integer',
1117
            'int'           => 'integer',
1118
            'int4'          => 'integer',
1119
            'integer'       => 'integer',
1120
            'bigserial'     => 'bigint',
1121
            'serial8'       => 'bigint',
1122
            'bigint'        => 'bigint',
1123
            'int8'          => 'bigint',
1124
            'bool'          => 'boolean',
1125
            'boolean'       => 'boolean',
1126
            'text'          => 'text',
1127
            'tsvector'      => 'text',
1128
            'varchar'       => 'string',
1129
            'interval'      => 'string',
1130
            '_varchar'      => 'string',
1131
            'char'          => 'string',
1132
            'bpchar'        => 'string',
1133
            'inet'          => 'string',
1134
            'date'          => 'date',
1135
            'datetime'      => 'datetime',
1136
            'timestamp'     => 'datetime',
1137
            'timestamptz'   => 'datetimetz',
1138
            'time'          => 'time',
1139
            'timetz'        => 'time',
1140
            'float'         => 'float',
1141
            'float4'        => 'float',
1142
            'float8'        => 'float',
1143
            'double'        => 'float',
1144
            'double precision' => 'float',
1145
            'real'          => 'float',
1146
            'decimal'       => 'decimal',
1147
            'money'         => 'decimal',
1148
            'numeric'       => 'decimal',
1149
            'year'          => 'date',
1150
            'uuid'          => 'guid',
1151
            'bytea'         => 'blob',
1152
        ];
1153 22
    }
1154
1155
    /**
1156
     * {@inheritDoc}
1157
     */
1158
    public function getVarcharMaxLength()
1159
    {
1160 48
        return 65535;
1161
    }
1162
1163
    /**
1164
     * {@inheritdoc}
1165
     */
1166
    public function getBinaryMaxLength()
1167
    {
1168 8
        return 0;
1169
    }
1170
1171
    /**
1172
     * {@inheritdoc}
1173
     */
1174
    public function getBinaryDefaultLength()
1175
    {
1176 8
        return 0;
1177
    }
1178
1179
    /**
1180
     * {@inheritDoc}
1181
     */
1182
    protected function getReservedKeywordsClass()
1183
    {
1184 47
        return Keywords\PostgreSQLKeywords::class;
1185
    }
1186
1187
    /**
1188
     * {@inheritDoc}
1189
     */
1190
    public function getBlobTypeDeclarationSQL(array $field)
1191
    {
1192 4
        return 'BYTEA';
1193
    }
1194
1195
    /**
1196
     * {@inheritdoc}
1197
     */
1198
    public function quoteStringLiteral($str)
1199
    {
1200 68
        $str = str_replace('\\', '\\\\', $str); // PostgreSQL requires backslashes to be escaped aswell.
1201
1202 68
        return parent::quoteStringLiteral($str);
1203
    }
1204
}
1205