Failed Conditions
Pull Request — master (#2850)
by Adrien
33:12 queued 23:11
created

PostgreSqlPlatform::quoteDefaultStringLiteral()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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

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