PostgreSqlPlatform::getListTableColumnsSQL()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 33
rs 9.392
c 0
b 0
f 0
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
    public function setUseBooleanTrueFalseStrings($flag)
97
    {
98
        $this->useBooleanTrueFalseStrings = (bool) $flag;
99
    }
100
101
    /**
102
     * {@inheritDoc}
103
     */
104 View Code Duplication
    public function getSubstringExpression($value, $from, $length = null)
105
    {
106
        if ($length === null) {
107
            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
108
        }
109
110
        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
    public function getRegexpExpression()
125
    {
126
        return 'SIMILAR TO';
127
    }
128
129
    /**
130
     * {@inheritDoc}
131
     */
132
    public function getLocateExpression($str, $substr, $startPos = false)
133
    {
134
        if ($startPos !== false) {
135
            $str = $this->getSubstringExpression($str, $startPos);
136
137
            return 'CASE WHEN (POSITION('.$substr.' IN '.$str.') = 0) THEN 0 ELSE (POSITION('.$substr.' IN '.$str.') + '.($startPos-1).') END';
138
        }
139
140
        return 'POSITION('.$substr.' IN '.$str.')';
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
147
    {
148
        if ($unit === DateIntervalUnit::QUARTER) {
149
            $interval *= 3;
150
            $unit      = DateIntervalUnit::MONTH;
151
        }
152
153
        return "(" . $date ." " . $operator . " (" . $interval . " || ' " . $unit . "')::interval)";
154
    }
155
156
    /**
157
     * {@inheritDoc}
158
     */
159
    public function getDateDiffExpression($date1, $date2)
160
    {
161
        return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))';
162
    }
163
164
    /**
165
     * {@inheritDoc}
166
     */
167
    public function supportsSequences()
168
    {
169
        return true;
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175
    public function supportsSchemas()
176
    {
177
        return true;
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function getDefaultSchemaName()
184
    {
185
        return 'public';
186
    }
187
188
    /**
189
     * {@inheritDoc}
190
     */
191
    public function supportsIdentityColumns()
192
    {
193
        return true;
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    public function supportsPartialIndexes()
200
    {
201
        return true;
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function usesSequenceEmulatedIdentityColumns()
208
    {
209
        return true;
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     */
215
    public function getIdentitySequenceName($tableName, $columnName)
216
    {
217
        return $tableName . '_' . $columnName . '_seq';
218
    }
219
220
    /**
221
     * {@inheritDoc}
222
     */
223
    public function supportsCommentOnStatement()
224
    {
225
        return true;
226
    }
227
228
    /**
229
     * {@inheritDoc}
230
     */
231
    public function prefersSequences()
232
    {
233
        return true;
234
    }
235
236
    /**
237
     * {@inheritDoc}
238
     */
239
    public function hasNativeGuidType()
240
    {
241
        return true;
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247
    public function getListDatabasesSQL()
248
    {
249
        return 'SELECT datname FROM pg_database';
250
    }
251
252
    /**
253
     * {@inheritDoc}
254
     */
255
    public function getListNamespacesSQL()
256
    {
257
        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
    public function getListSequencesSQL($database)
267
    {
268
        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
    public function getListTablesSQL()
279
    {
280
        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
    public function getListViewsSQL($database)
294
    {
295
        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
    public function getListTableForeignKeysSQL($table, $database = null)
0 ignored issues
show
Unused Code introduced by
The parameter $database is not used and could be removed.

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

Loading history...
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
                      WHERE " .$this->getTableWhereClause($table) ." AND n.oid = c.relnamespace
314
                  )
315
                  AND r.contype = 'f'";
316
    }
317
318
    /**
319
     * {@inheritDoc}
320
     */
321
    public function getCreateViewSQL($name, $sql)
322
    {
323
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
324
    }
325
326
    /**
327
     * {@inheritDoc}
328
     */
329
    public function getDropViewSQL($name)
330
    {
331
        return 'DROP VIEW '. $name;
332
    }
333
334
    /**
335
     * {@inheritDoc}
336
     */
337
    public function getListTableConstraintsSQL($table)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
338
    {
339
        $table = new Identifier($table);
340
        $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
                    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
    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
                    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
    private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n')
382
    {
383
        $whereClause = $namespaceAlias.".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND ";
384 View Code Duplication
        if (strpos($table, ".") !== false) {
385
            list($schema, $table) = explode(".", $table);
386
            $schema = $this->quoteStringLiteral($schema);
387
        } else {
388
            $schema = "ANY(string_to_array((select replace(replace(setting,'\"\$user\"',user),' ','') from pg_catalog.pg_settings where name = 'search_path'),','))";
389
        }
390
391
        $table = new Identifier($table);
392
        $table = $this->quoteStringLiteral($table->getName());
393
        $whereClause .= "$classAlias.relname = " . $table . " AND $namespaceAlias.nspname = $schema";
394
395
        return $whereClause;
396
    }
397
398
    /**
399
     * {@inheritDoc}
400
     */
401
    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
                    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
    public function getCreateDatabaseSQL($name)
439
    {
440
        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
    public function getDisallowDatabaseConnectionsSQL($database)
453
    {
454
        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
    public function getCloseActiveDatabaseConnectionsSQL($database)
467
    {
468
        $database = $this->quoteStringLiteral($database);
469
470
        return "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = $database";
471
    }
472
473
    /**
474
     * {@inheritDoc}
475
     */
476
    public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey)
477
    {
478
        $query = '';
479
480
        if ($foreignKey->hasOption('match')) {
481
            $query .= ' MATCH ' . $foreignKey->getOption('match');
482
        }
483
484
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
485
486
        if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) {
487
            $query .= ' DEFERRABLE';
488
        } else {
489
            $query .= ' NOT DEFERRABLE';
490
        }
491
492
        if (($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false)
493
            || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false)
494
        ) {
495
            $query .= ' INITIALLY DEFERRED';
496
        } else {
497
            $query .= ' INITIALLY IMMEDIATE';
498
        }
499
500
        return $query;
501
    }
502
503
    /**
504
     * {@inheritDoc}
505
     */
506
    public function getAlterTableSQL(TableDiff $diff)
507
    {
508
        $sql = [];
509
        $commentsSQL = [];
510
        $columnSql = [];
511
512
        foreach ($diff->addedColumns as $column) {
513
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
514
                continue;
515
            }
516
517
            $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
518
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
519
520
            $comment = $this->getColumnComment($column);
521
522 View Code Duplication
            if (null !== $comment && '' !== $comment) {
523
                $commentsSQL[] = $this->getCommentOnColumnSQL(
524
                    $diff->getName($this)->getQuotedName($this),
525
                    $column->getQuotedName($this),
526
                    $comment
527
                );
528
            }
529
        }
530
531
        foreach ($diff->removedColumns as $column) {
532
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
533
                continue;
534
            }
535
536
            $query = 'DROP ' . $column->getQuotedName($this);
537
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
538
        }
539
540
        foreach ($diff->changedColumns as $columnDiff) {
541
            /** @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
542
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
543
                continue;
544
            }
545
546
            if ($this->isUnchangedBinaryColumn($columnDiff)) {
547
                continue;
548
            }
549
550
            $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this);
551
            $column = $columnDiff->column;
552
553
            if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed')) {
554
                $type = $column->getType();
555
556
                // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type
557
                $columnDefinition = $column->toArray();
558
                $columnDefinition['autoincrement'] = false;
559
560
                // here was a server version check before, but DBAL API does not support this anymore.
561
                $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($columnDefinition, $this);
562
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
563
            }
564
565
            if ($columnDiff->hasChanged('default') || $this->typeChangeBreaksDefaultValue($columnDiff)) {
566
                $defaultClause = null === $column->getDefault()
567
                    ? ' DROP DEFAULT'
568
                    : ' SET' . $this->getDefaultValueDeclarationSQL($column->toArray());
569
                $query = 'ALTER ' . $oldColumnName . $defaultClause;
570
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
571
            }
572
573
            if ($columnDiff->hasChanged('notnull')) {
574
                $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL';
575
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
576
            }
577
578
            if ($columnDiff->hasChanged('autoincrement')) {
579
                if ($column->getAutoincrement()) {
580
                    // add autoincrement
581
                    $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName);
582
583
                    $sql[] = "CREATE SEQUENCE " . $seqName;
584
                    $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ") FROM " . $diff->getName($this)->getQuotedName($this) . "))";
585
                    $query = "ALTER " . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')";
586
                    $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
                    $query = "ALTER " . $oldColumnName . " " . "DROP DEFAULT";
590
                    $sql[] = "ALTER TABLE " . $diff->getName($this)->getQuotedName($this) . " " . $query;
591
                }
592
            }
593
594 View Code Duplication
            if ($columnDiff->hasChanged('comment')) {
595
                $commentsSQL[] = $this->getCommentOnColumnSQL(
596
                    $diff->getName($this)->getQuotedName($this),
597
                    $column->getQuotedName($this),
598
                    $this->getColumnComment($column)
599
                );
600
            }
601
602
            if ($columnDiff->hasChanged('length')) {
603
                $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $column->getType()->getSQLDeclaration($column->toArray(), $this);
604
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
605
            }
606
        }
607
608 View Code Duplication
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
609
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
610
                continue;
611
            }
612
613
            $oldColumnName = new Identifier($oldColumnName);
614
615
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
616
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
617
        }
618
619
        $tableSql = [];
620
621 View Code Duplication
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
622
            $sql = array_merge($sql, $commentsSQL);
623
624
            if ($diff->newName !== false) {
625
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this);
626
            }
627
628
            $sql = array_merge(
629
                $this->getPreAlterTableIndexForeignKeySQL($diff),
630
                $sql,
631
                $this->getPostAlterTableIndexForeignKeySQL($diff)
632
            );
633
        }
634
635
        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
    private function isUnchangedBinaryColumn(ColumnDiff $columnDiff)
653
    {
654
        $columnType = $columnDiff->column->getType();
655
656
        if ( ! $columnType instanceof BinaryType && ! $columnType instanceof BlobType) {
657
            return false;
658
        }
659
660
        $fromColumn = $columnDiff->fromColumn instanceof Column ? $columnDiff->fromColumn : null;
661
662
        if ($fromColumn) {
663
            $fromColumnType = $fromColumn->getType();
664
665
            if ( ! $fromColumnType instanceof BinaryType && ! $fromColumnType instanceof BlobType) {
666
                return false;
667
            }
668
669
            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 View Code Duplication
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
683
    {
684
        if (strpos($tableName, '.') !== false) {
685
            list($schema) = explode('.', $tableName);
686
            $oldIndexName = $schema . '.' . $oldIndexName;
687
        }
688
689
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
690
    }
691
692
    /**
693
     * {@inheritdoc}
694
     */
695 View Code Duplication
    public function getCommentOnColumnSQL($tableName, $columnName, $comment)
696
    {
697
        $tableName = new Identifier($tableName);
698
        $columnName = new Identifier($columnName);
699
        $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
700
701
        return "COMMENT ON COLUMN " . $tableName->getQuotedName($this) . "." . $columnName->getQuotedName($this) .
702
            " IS $comment";
703
    }
704
705
    /**
706
     * {@inheritDoc}
707
     */
708 View Code Duplication
    public function getCreateSequenceSQL(Sequence $sequence)
709
    {
710
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
711
            ' INCREMENT BY ' . $sequence->getAllocationSize() .
712
            ' MINVALUE ' . $sequence->getInitialValue() .
713
            ' START ' . $sequence->getInitialValue() .
714
            $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
    private function getSequenceCacheSQL(Sequence $sequence)
735
    {
736
        if ($sequence->getCache() > 1) {
737
            return ' CACHE ' . $sequence->getCache();
738
        }
739
740
        return '';
741
    }
742
743
    /**
744
     * {@inheritDoc}
745
     */
746
    public function getDropSequenceSQL($sequence)
747
    {
748
        if ($sequence instanceof Sequence) {
749
            $sequence = $sequence->getQuotedName($this);
750
        }
751
752
        return 'DROP SEQUENCE ' . $sequence . ' CASCADE';
753
    }
754
755
    /**
756
     * {@inheritDoc}
757
     */
758
    public function getCreateSchemaSQL($schemaName)
759
    {
760
        return 'CREATE SCHEMA ' . $schemaName;
761
    }
762
763
    /**
764
     * {@inheritDoc}
765
     */
766
    public function getDropForeignKeySQL($foreignKey, $table)
767
    {
768
        return $this->getDropConstraintSQL($foreignKey, $table);
769
    }
770
771
    /**
772
     * {@inheritDoc}
773
     */
774
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
775
    {
776
        $queryFields = $this->getColumnDeclarationListSQL($columns);
777
778 View Code Duplication
        if (isset($options['primary']) && ! empty($options['primary'])) {
779
            $keyColumns = array_unique(array_values($options['primary']));
780
            $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
781
        }
782
783
        $query = 'CREATE TABLE ' . $tableName . ' (' . $queryFields . ')';
784
785
        $sql = [$query];
786
787
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
788
            foreach ($options['indexes'] as $index) {
789
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
790
            }
791
        }
792
793 View Code Duplication
        if (isset($options['foreignKeys'])) {
794
            foreach ((array) $options['foreignKeys'] as $definition) {
795
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
796
            }
797
        }
798
799
        return $sql;
800
    }
801
802
    /**
803
     * Converts a single boolean value.
804
     *
805
     * First converts the value to its native PHP boolean type
806
     * and passes it to the given callback function to be reconverted
807
     * into any custom representation.
808
     *
809
     * @param mixed    $value    The value to convert.
810
     * @param callable $callback The callback function to use for converting the real boolean value.
811
     *
812
     * @return mixed
813
     * @throws \UnexpectedValueException
814
     */
815
    private function convertSingleBooleanValue($value, $callback)
816
    {
817
        if (null === $value) {
818
            return $callback(null);
819
        }
820
821
        if (is_bool($value) || is_numeric($value)) {
822
            return $callback($value ? true : false);
823
        }
824
825
        if (!is_string($value)) {
826
            return $callback(true);
827
        }
828
829
        /**
830
         * Better safe than sorry: http://php.net/in_array#106319
831
         */
832 View Code Duplication
        if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) {
833
            return $callback(false);
834
        }
835
836 View Code Duplication
        if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) {
837
            return $callback(true);
838
        }
839
840
        throw new \UnexpectedValueException("Unrecognized boolean literal '${value}'");
841
    }
842
843
    /**
844
     * Converts one or multiple boolean values.
845
     *
846
     * First converts the value(s) to their native PHP boolean type
847
     * and passes them to the given callback function to be reconverted
848
     * into any custom representation.
849
     *
850
     * @param mixed    $item     The value(s) to convert.
851
     * @param callable $callback The callback function to use for converting the real boolean value(s).
852
     *
853
     * @return mixed
854
     */
855
    private function doConvertBooleans($item, $callback)
856
    {
857
        if (is_array($item)) {
858
            foreach ($item as $key => $value) {
859
                $item[$key] = $this->convertSingleBooleanValue($value, $callback);
860
            }
861
862
            return $item;
863
        }
864
865
        return $this->convertSingleBooleanValue($item, $callback);
866
    }
867
868
    /**
869
     * {@inheritDoc}
870
     *
871
     * Postgres wants boolean values converted to the strings 'true'/'false'.
872
     */
873
    public function convertBooleans($item)
874
    {
875
        if ( ! $this->useBooleanTrueFalseStrings) {
876
            return parent::convertBooleans($item);
877
        }
878
879
        return $this->doConvertBooleans(
880
            $item,
881
            function ($boolean) {
882
                if (null === $boolean) {
883
                    return 'NULL';
884
                }
885
886
                return true === $boolean ? 'true' : 'false';
887
            }
888
        );
889
    }
890
891
    /**
892
     * {@inheritDoc}
893
     */
894
    public function convertBooleansToDatabaseValue($item)
895
    {
896
        if ( ! $this->useBooleanTrueFalseStrings) {
897
            return parent::convertBooleansToDatabaseValue($item);
898
        }
899
900
        return $this->doConvertBooleans(
901
            $item,
902
            function ($boolean) {
903
                return null === $boolean ? null : (int) $boolean;
904
            }
905
        );
906
    }
907
908
    /**
909
     * {@inheritDoc}
910
     */
911
    public function convertFromBoolean($item)
912
    {
913
        if (in_array(strtolower($item), $this->booleanLiterals['false'], true)) {
914
            return false;
915
        }
916
917
        return parent::convertFromBoolean($item);
918
    }
919
920
    /**
921
     * {@inheritDoc}
922
     */
923
    public function getSequenceNextValSQL($sequenceName)
924
    {
925
        return "SELECT NEXTVAL('" . $sequenceName . "')";
926
    }
927
928
    /**
929
     * {@inheritDoc}
930
     */
931
    public function getSetTransactionIsolationSQL($level)
932
    {
933
        return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL '
934
            . $this->_getTransactionIsolationLevelSQL($level);
935
    }
936
937
    /**
938
     * {@inheritDoc}
939
     */
940
    public function getBooleanTypeDeclarationSQL(array $field)
941
    {
942
        return 'BOOLEAN';
943
    }
944
945
    /**
946
     * {@inheritDoc}
947
     */
948
    public function getIntegerTypeDeclarationSQL(array $field)
949
    {
950
        if ( ! empty($field['autoincrement'])) {
951
            return 'SERIAL';
952
        }
953
954
        return 'INT';
955
    }
956
957
    /**
958
     * {@inheritDoc}
959
     */
960
    public function getBigIntTypeDeclarationSQL(array $field)
961
    {
962
        if ( ! empty($field['autoincrement'])) {
963
            return 'BIGSERIAL';
964
        }
965
966
        return 'BIGINT';
967
    }
968
969
    /**
970
     * {@inheritDoc}
971
     */
972
    public function getSmallIntTypeDeclarationSQL(array $field)
973
    {
974
        return 'SMALLINT';
975
    }
976
977
    /**
978
     * {@inheritDoc}
979
     */
980
    public function getGuidTypeDeclarationSQL(array $field)
981
    {
982
        return 'UUID';
983
    }
984
985
    /**
986
     * {@inheritDoc}
987
     */
988
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
989
    {
990
        return 'TIMESTAMP(0) WITHOUT TIME ZONE';
991
    }
992
993
    /**
994
     * {@inheritDoc}
995
     */
996
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
997
    {
998
        return 'TIMESTAMP(0) WITH TIME ZONE';
999
    }
1000
1001
    /**
1002
     * {@inheritDoc}
1003
     */
1004
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
1005
    {
1006
        return 'DATE';
1007
    }
1008
1009
    /**
1010
     * {@inheritDoc}
1011
     */
1012
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
1013
    {
1014
        return 'TIME(0) WITHOUT TIME ZONE';
1015
    }
1016
1017
    /**
1018
     * {@inheritDoc}
1019
     *
1020
     * @deprecated Use application-generated UUIDs instead
1021
     */
1022
    public function getGuidExpression()
1023
    {
1024
        return 'UUID_GENERATE_V4()';
1025
    }
1026
1027
    /**
1028
     * {@inheritDoc}
1029
     */
1030
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
1031
    {
1032
        return '';
1033
    }
1034
1035
    /**
1036
     * {@inheritDoc}
1037
     */
1038
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
1039
    {
1040
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
1041
            : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
1042
    }
1043
1044
    /**
1045
     * {@inheritdoc}
1046
     */
1047
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
1048
    {
1049
        return 'BYTEA';
1050
    }
1051
1052
    /**
1053
     * {@inheritDoc}
1054
     */
1055
    public function getClobTypeDeclarationSQL(array $field)
1056
    {
1057
        return 'TEXT';
1058
    }
1059
1060
    /**
1061
     * {@inheritDoc}
1062
     */
1063
    public function getName()
1064
    {
1065
        return 'postgresql';
1066
    }
1067
1068
    /**
1069
     * {@inheritDoc}
1070
     *
1071
     * PostgreSQL returns all column names in SQL result sets in lowercase.
1072
     */
1073
    public function getSQLResultCasing($column)
1074
    {
1075
        return strtolower($column);
1076
    }
1077
1078
    /**
1079
     * {@inheritDoc}
1080
     */
1081
    public function getDateTimeTzFormatString()
1082
    {
1083
        return 'Y-m-d H:i:sO';
1084
    }
1085
1086
    /**
1087
     * {@inheritDoc}
1088
     */
1089
    public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
1090
    {
1091
        return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)';
1092
    }
1093
1094
    /**
1095
     * {@inheritDoc}
1096
     */
1097
    public function getTruncateTableSQL($tableName, $cascade = false)
1098
    {
1099
        $tableIdentifier = new Identifier($tableName);
1100
        $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
1101
1102
        if ($cascade) {
1103
            $sql .= ' CASCADE';
1104
        }
1105
1106
        return $sql;
1107
    }
1108
1109
    /**
1110
     * {@inheritDoc}
1111
     */
1112
    public function getReadLockSQL()
1113
    {
1114
        return 'FOR SHARE';
1115
    }
1116
1117
    /**
1118
     * {@inheritDoc}
1119
     */
1120 View Code Duplication
    protected function initializeDoctrineTypeMappings()
1121
    {
1122
        $this->doctrineTypeMapping = [
1123
            'smallint'      => 'smallint',
1124
            'int2'          => 'smallint',
1125
            'serial'        => 'integer',
1126
            'serial4'       => 'integer',
1127
            'int'           => 'integer',
1128
            'int4'          => 'integer',
1129
            'integer'       => 'integer',
1130
            'bigserial'     => 'bigint',
1131
            'serial8'       => 'bigint',
1132
            'bigint'        => 'bigint',
1133
            'int8'          => 'bigint',
1134
            'bool'          => 'boolean',
1135
            'boolean'       => 'boolean',
1136
            'text'          => 'text',
1137
            'tsvector'      => 'text',
1138
            'varchar'       => 'string',
1139
            'interval'      => 'string',
1140
            '_varchar'      => 'string',
1141
            'char'          => 'string',
1142
            'bpchar'        => 'string',
1143
            'inet'          => 'string',
1144
            'date'          => 'date',
1145
            'datetime'      => 'datetime',
1146
            'timestamp'     => 'datetime',
1147
            'timestamptz'   => 'datetimetz',
1148
            'time'          => 'time',
1149
            'timetz'        => 'time',
1150
            'float'         => 'float',
1151
            'float4'        => 'float',
1152
            'float8'        => 'float',
1153
            'double'        => 'float',
1154
            'double precision' => 'float',
1155
            'real'          => 'float',
1156
            'decimal'       => 'decimal',
1157
            'money'         => 'decimal',
1158
            'numeric'       => 'decimal',
1159
            'year'          => 'date',
1160
            'uuid'          => 'guid',
1161
            'bytea'         => 'blob',
1162
        ];
1163
    }
1164
1165
    /**
1166
     * {@inheritDoc}
1167
     */
1168
    public function getVarcharMaxLength()
1169
    {
1170
        return 65535;
1171
    }
1172
1173
    /**
1174
     * {@inheritdoc}
1175
     */
1176
    public function getBinaryMaxLength()
1177
    {
1178
        return 0;
1179
    }
1180
1181
    /**
1182
     * {@inheritdoc}
1183
     */
1184
    public function getBinaryDefaultLength()
1185
    {
1186
        return 0;
1187
    }
1188
1189
    /**
1190
     * {@inheritDoc}
1191
     */
1192
    protected function getReservedKeywordsClass()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
1193
    {
1194
        return Keywords\PostgreSQLKeywords::class;
1195
    }
1196
1197
    /**
1198
     * {@inheritDoc}
1199
     */
1200
    public function getBlobTypeDeclarationSQL(array $field)
1201
    {
1202
        return 'BYTEA';
1203
    }
1204
1205
    /**
1206
     * {@inheritdoc}
1207
     */
1208
    public function quoteStringLiteral($str)
1209
    {
1210
        $str = str_replace('\\', '\\\\', $str); // PostgreSQL requires backslashes to be escaped aswell.
1211
1212
        return parent::quoteStringLiteral($str);
1213
    }
1214
1215
    /**
1216
     * {@inheritdoc}
1217
     */
1218
    public function getDefaultValueDeclarationSQL($field)
1219
    {
1220
        if ($this->isSerialField($field)) {
1221
            return '';
1222
        }
1223
1224
        return parent::getDefaultValueDeclarationSQL($field);
1225
    }
1226
1227
    private function isSerialField(array $field) : bool
1228
    {
1229
        return $field['autoincrement'] ?? false === true && isset($field['type'])
1230
            && $this->isNumericType($field['type']);
1231
    }
1232
1233
    /**
1234
     * Check whether the type of a column is changed in a way that invalidates the default value for the column
1235
     *
1236
     * @param ColumnDiff $columnDiff
1237
     * @return bool
1238
     */
1239
    private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff) : bool
1240
    {
1241
        if (! $columnDiff->fromColumn) {
1242
            return $columnDiff->hasChanged('type');
1243
        }
1244
1245
        $oldTypeIsNumeric = $this->isNumericType($columnDiff->fromColumn->getType());
1246
        $newTypeIsNumeric = $this->isNumericType($columnDiff->column->getType());
1247
1248
        // default should not be changed when switching between numeric types and the default comes from a sequence
1249
        return $columnDiff->hasChanged('type')
1250
            && ! ($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement());
1251
    }
1252
1253
    private function isNumericType(Type $type) : bool
1254
    {
1255
        return $type instanceof IntegerType || $type instanceof BigIntType;
1256
    }
1257
}
1258