Completed
Push — 2.8 ( c0195c...8d2399 )
by Sergei
21:38
created

PostgreSqlPlatform::getCreateDatabaseSQL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

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