Completed
Pull Request — master (#3143)
by Alessandro
17:39
created

PostgreSqlPlatform::getTruncateTableSQL()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

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

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

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

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

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