Completed
Pull Request — 2.8 (#3345)
by
unknown
36:29
created

PostgreSqlPlatform::doConvertBooleans()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4.125

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 11
ccs 3
cts 6
cp 0.5
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 4.125
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 4817
    public function hasNativeGuidType()
240
    {
241 4817
        return true;
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247 12
    public function getListDatabasesSQL()
248
    {
249 12
        return 'SELECT datname FROM pg_database';
250
    }
251
252
    /**
253
     * {@inheritDoc}
254
     */
255 12
    public function getListNamespacesSQL()
256
    {
257 12
        return "SELECT schema_name AS nspname
258
                FROM   information_schema.schemata
259
                WHERE  schema_name NOT LIKE 'pg\_%'
260
                AND    schema_name != 'information_schema'";
261
    }
262
263
    /**
264
     * {@inheritDoc}
265
     */
266 30
    public function getListSequencesSQL($database)
267
    {
268 30
        return "SELECT sequence_name AS relname,
269
                       sequence_schema AS schemaname
270
                FROM   information_schema.sequences
271
                WHERE  sequence_schema NOT LIKE 'pg\_%'
272
                AND    sequence_schema != 'information_schema'";
273
    }
274
275
    /**
276
     * {@inheritDoc}
277
     */
278 524
    public function getListTablesSQL()
279
    {
280 524
        return "SELECT quote_ident(table_name) AS table_name,
281
                       table_schema AS schema_name
282
                FROM   information_schema.tables
283
                WHERE  table_schema NOT LIKE 'pg\_%'
284
                AND    table_schema != 'information_schema'
285
                AND    table_name != 'geometry_columns'
286
                AND    table_name != 'spatial_ref_sys'
287
                AND    table_type != 'VIEW'";
288
    }
289
290
    /**
291
     * {@inheritDoc}
292
     */
293 6
    public function getListViewsSQL($database)
294
    {
295 6
        return 'SELECT quote_ident(table_name) AS viewname,
296
                       table_schema AS schemaname,
297
                       view_definition AS definition
298
                FROM   information_schema.views
299
                WHERE  view_definition IS NOT NULL';
300
    }
301
302
    /**
303
     * {@inheritDoc}
304
     */
305 448
    public function getListTableForeignKeysSQL($table, $database = null)
0 ignored issues
show
Unused Code introduced by
The parameter $database is not used and could be removed. ( Ignorable by Annotation )

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

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

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

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