Completed
Push — master ( e828bc...51bf0a )
by Luís
18s
created

PostgreSqlPlatform::getDropViewSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

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