Completed
Push — master ( c7757e...39cb21 )
by Luís
16s
created

lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php (2 issues)

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\BlobType;
30
31
/**
32
 * PostgreSqlPlatform.
33
 *
34
 * @since  2.0
35
 * @author Roman Borschel <[email protected]>
36
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
37
 * @author Benjamin Eberlei <[email protected]>
38
 * @todo   Rename: PostgreSQLPlatform
39
 */
40
class PostgreSqlPlatform extends AbstractPlatform
41
{
42
    /**
43
     * @var bool
44
     */
45
    private $useBooleanTrueFalseStrings = true;
46
47
    /**
48
     * @var array PostgreSQL booleans literals
49
     */
50
    private $booleanLiterals = [
51
        'true' => [
52
            't',
53
            'true',
54
            'y',
55
            'yes',
56
            'on',
57
            '1'
58
        ],
59
        'false' => [
60
            'f',
61
            'false',
62
            'n',
63
            'no',
64
            'off',
65
            '0'
66
        ]
67
    ];
68
69
    /**
70
     * PostgreSQL has different behavior with some drivers
71
     * with regard to how booleans have to be handled.
72
     *
73
     * Enables use of 'true'/'false' or otherwise 1 and 0 instead.
74
     *
75
     * @param bool $flag
76
     */
77 8
    public function setUseBooleanTrueFalseStrings($flag)
78
    {
79 8
        $this->useBooleanTrueFalseStrings = (bool) $flag;
80 8
    }
81
82
    /**
83
     * {@inheritDoc}
84
     */
85 4 View Code Duplication
    public function getSubstringExpression($value, $from, $length = null)
86
    {
87 4
        if ($length === null) {
88 4
            return 'SUBSTRING(' . $value . ' FROM ' . $from . ')';
89
        }
90
91 4
        return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')';
92
    }
93
94
    /**
95
     * {@inheritDoc}
96
     */
97
    public function getNowExpression()
98
    {
99
        return 'LOCALTIMESTAMP(0)';
100
    }
101
102
    /**
103
     * {@inheritDoc}
104
     */
105 4
    public function getRegexpExpression()
106
    {
107 4
        return 'SIMILAR TO';
108
    }
109
110
    /**
111
     * {@inheritDoc}
112
     */
113
    public function getLocateExpression($str, $substr, $startPos = false)
114
    {
115
        if ($startPos !== false) {
116
            $str = $this->getSubstringExpression($str, $startPos);
117
118
            return 'CASE WHEN (POSITION('.$substr.' IN '.$str.') = 0) THEN 0 ELSE (POSITION('.$substr.' IN '.$str.') + '.($startPos-1).') END';
119
        }
120
121
        return 'POSITION('.$substr.' IN '.$str.')';
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
128
    {
129
        if (self::DATE_INTERVAL_UNIT_QUARTER === $unit) {
130
            $interval *= 3;
131
            $unit = self::DATE_INTERVAL_UNIT_MONTH;
132
        }
133
134
        return "(" . $date ." " . $operator . " (" . $interval . " || ' " . $unit . "')::interval)";
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     */
140
    public function getDateDiffExpression($date1, $date2)
141
    {
142
        return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))';
143
    }
144
145
    /**
146
     * {@inheritDoc}
147
     */
148 4
    public function supportsSequences()
149
    {
150 4
        return true;
151
    }
152
153
    /**
154
     * {@inheritDoc}
155
     */
156
    public function supportsSchemas()
157
    {
158
        return true;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function getDefaultSchemaName()
165
    {
166
        return 'public';
167
    }
168
169
    /**
170
     * {@inheritDoc}
171
     */
172 4
    public function supportsIdentityColumns()
173
    {
174 4
        return true;
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180 34
    public function supportsPartialIndexes()
181
    {
182 34
        return true;
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188 4
    public function usesSequenceEmulatedIdentityColumns()
189
    {
190 4
        return true;
191
    }
192
193
    /**
194
     * {@inheritdoc}
195
     */
196 4
    public function getIdentitySequenceName($tableName, $columnName)
197
    {
198 4
        return $tableName . '_' . $columnName . '_seq';
199
    }
200
201
    /**
202
     * {@inheritDoc}
203
     */
204 44
    public function supportsCommentOnStatement()
205
    {
206 44
        return true;
207
    }
208
209
    /**
210
     * {@inheritDoc}
211
     */
212 4
    public function prefersSequences()
213
    {
214 4
        return true;
215
    }
216
217
    /**
218
     * {@inheritDoc}
219
     */
220 164
    public function hasNativeGuidType()
221
    {
222 164
        return true;
223
    }
224
225
    /**
226
     * {@inheritDoc}
227
     */
228
    public function getListDatabasesSQL()
229
    {
230
        return 'SELECT datname FROM pg_database';
231
    }
232
233
    /**
234
     * {@inheritDoc}
235
     */
236
    public function getListNamespacesSQL()
237
    {
238
        return "SELECT schema_name AS nspname
239
                FROM   information_schema.schemata
240
                WHERE  schema_name NOT LIKE 'pg\_%'
241
                AND    schema_name != 'information_schema'";
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247
    public function getListSequencesSQL($database)
248
    {
249
        return "SELECT sequence_name AS relname,
250
                       sequence_schema AS schemaname
251
                FROM   information_schema.sequences
252
                WHERE  sequence_schema NOT LIKE 'pg\_%'
253
                AND    sequence_schema != 'information_schema'";
254
    }
255
256
    /**
257
     * {@inheritDoc}
258
     */
259
    public function getListTablesSQL()
260
    {
261
        return "SELECT quote_ident(table_name) AS table_name,
262
                       table_schema AS schema_name
263
                FROM   information_schema.tables
264
                WHERE  table_schema NOT LIKE 'pg\_%'
265
                AND    table_schema != 'information_schema'
266
                AND    table_name != 'geometry_columns'
267
                AND    table_name != 'spatial_ref_sys'
268
                AND    table_type != 'VIEW'";
269
    }
270
271
    /**
272
     * {@inheritDoc}
273
     */
274
    public function getListViewsSQL($database)
275
    {
276
        return 'SELECT quote_ident(table_name) AS viewname,
277
                       table_schema AS schemaname,
278
                       view_definition AS definition
279
                FROM   information_schema.views
280
                WHERE  view_definition IS NOT NULL';
281
    }
282
283
    /**
284
     * {@inheritDoc}
285
     */
286 8
    public function getListTableForeignKeysSQL($table, $database = null)
287
    {
288
        return "SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef
289
                  FROM pg_catalog.pg_constraint r
290
                  WHERE r.conrelid =
291
                  (
292
                      SELECT c.oid
293
                      FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n
294 8
                      WHERE " .$this->getTableWhereClause($table) ." AND n.oid = c.relnamespace
295
                  )
296
                  AND r.contype = 'f'";
297
    }
298
299
    /**
300
     * {@inheritDoc}
301
     */
302
    public function getCreateViewSQL($name, $sql)
303
    {
304
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
305
    }
306
307
    /**
308
     * {@inheritDoc}
309
     */
310
    public function getDropViewSQL($name)
311
    {
312
        return 'DROP VIEW '. $name;
313
    }
314
315
    /**
316
     * {@inheritDoc}
317
     */
318 4
    public function getListTableConstraintsSQL($table)
319
    {
320 4
        $table = new Identifier($table);
321 4
        $table = $this->quoteStringLiteral($table->getName());
322
323
        return "SELECT
324
                    quote_ident(relname) as relname
325
                FROM
326
                    pg_class
327
                WHERE oid IN (
328
                    SELECT indexrelid
329
                    FROM pg_index, pg_class
330 4
                    WHERE pg_class.relname = $table
331
                        AND pg_class.oid = pg_index.indrelid
332
                        AND (indisunique = 't' OR indisprimary = 't')
333
                        )";
334
    }
335
336
    /**
337
     * {@inheritDoc}
338
     *
339
     * @license New BSD License
340
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
341
     */
342 8
    public function getListTableIndexesSQL($table, $currentDatabase = null)
343
    {
344
        return "SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary,
345
                       pg_index.indkey, pg_index.indrelid,
346
                       pg_get_expr(indpred, indrelid) AS where
347
                 FROM pg_class, pg_index
348
                 WHERE oid IN (
349
                    SELECT indexrelid
350
                    FROM pg_index si, pg_class sc, pg_namespace sn
351 8
                    WHERE " . $this->getTableWhereClause($table, 'sc', 'sn')." AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid
352
                 ) AND pg_index.indexrelid = oid";
353
    }
354
355
    /**
356
     * @param string $table
357
     * @param string $classAlias
358
     * @param string $namespaceAlias
359
     *
360
     * @return string
361
     */
362 24
    private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n')
363
    {
364 24
        $whereClause = $namespaceAlias.".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND ";
365 24 View Code Duplication
        if (strpos($table, ".") !== false) {
366 12
            list($schema, $table) = explode(".", $table);
367 12
            $schema = $this->quoteStringLiteral($schema);
368
        } else {
369 12
            $schema = "ANY(string_to_array((select replace(replace(setting,'\"\$user\"',user),' ','') from pg_catalog.pg_settings where name = 'search_path'),','))";
370
        }
371
372 24
        $table = new Identifier($table);
373 24
        $table = $this->quoteStringLiteral($table->getName());
374 24
        $whereClause .= "$classAlias.relname = " . $table . " AND $namespaceAlias.nspname = $schema";
375
376 24
        return $whereClause;
377
    }
378
379
    /**
380
     * {@inheritDoc}
381
     */
382 8
    public function getListTableColumnsSQL($table, $database = null)
383
    {
384
        return "SELECT
385
                    a.attnum,
386
                    quote_ident(a.attname) AS field,
387
                    t.typname AS type,
388
                    format_type(a.atttypid, a.atttypmod) AS complete_type,
389
                    (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
390
                    (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM
391
                      pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type,
392
                    a.attnotnull AS isnotnull,
393
                    (SELECT 't'
394
                     FROM pg_index
395
                     WHERE c.oid = pg_index.indrelid
396
                        AND pg_index.indkey[0] = a.attnum
397
                        AND pg_index.indisprimary = 't'
398
                    ) AS pri,
399
                    (SELECT pg_get_expr(adbin, adrelid)
400
                     FROM pg_attrdef
401
                     WHERE c.oid = pg_attrdef.adrelid
402
                        AND pg_attrdef.adnum=a.attnum
403
                    ) AS default,
404
                    (SELECT pg_description.description
405
                        FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
406
                    ) AS comment
407
                    FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n
408 8
                    WHERE ".$this->getTableWhereClause($table, 'c', 'n') ."
409
                        AND a.attnum > 0
410
                        AND a.attrelid = c.oid
411
                        AND a.atttypid = t.oid
412
                        AND n.oid = c.relnamespace
413
                    ORDER BY a.attnum";
414
    }
415
416
    /**
417
     * {@inheritDoc}
418
     */
419 4
    public function getCreateDatabaseSQL($name)
420
    {
421 4
        return 'CREATE DATABASE ' . $name;
422
    }
423
424
    /**
425
     * Returns the SQL statement for disallowing new connections on the given database.
426
     *
427
     * This is useful to force DROP DATABASE operations which could fail because of active connections.
428
     *
429
     * @param string $database The name of the database to disallow new connections for.
430
     *
431
     * @return string
432
     */
433 4
    public function getDisallowDatabaseConnectionsSQL($database)
434
    {
435 4
        return "UPDATE pg_database SET datallowconn = 'false' WHERE datname = '$database'";
436
    }
437
438
    /**
439
     * Returns the SQL statement for closing currently active connections on the given database.
440
     *
441
     * This is useful to force DROP DATABASE operations which could fail because of active connections.
442
     *
443
     * @param string $database The name of the database to close currently active connections for.
444
     *
445
     * @return string
446
     */
447 4
    public function getCloseActiveDatabaseConnectionsSQL($database)
448
    {
449 4
        $database = $this->quoteStringLiteral($database);
450
451 4
        return "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = $database";
452
    }
453
454
    /**
455
     * {@inheritDoc}
456
     */
457 20
    public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey)
458
    {
459 20
        $query = '';
460
461 20
        if ($foreignKey->hasOption('match')) {
462 4
            $query .= ' MATCH ' . $foreignKey->getOption('match');
463
        }
464
465 20
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
466
467 20
        if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) {
468 4
            $query .= ' DEFERRABLE';
469
        } else {
470 20
            $query .= ' NOT DEFERRABLE';
471
        }
472
473 20
        if (($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false)
474 20
            || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false)
475
        ) {
476 4
            $query .= ' INITIALLY DEFERRED';
477
        } else {
478 20
            $query .= ' INITIALLY IMMEDIATE';
479
        }
480
481 20
        return $query;
482
    }
483
484
    /**
485
     * {@inheritDoc}
486
     */
487 72
    public function getAlterTableSQL(TableDiff $diff)
488
    {
489 72
        $sql = [];
490 72
        $commentsSQL = [];
491 72
        $columnSql = [];
492
493 72
        foreach ($diff->addedColumns as $column) {
494 16
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
495
                continue;
496
            }
497
498 16
            $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
499 16
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
500
501 16
            $comment = $this->getColumnComment($column);
502
503 16 View Code Duplication
            if (null !== $comment && '' !== $comment) {
504 4
                $commentsSQL[] = $this->getCommentOnColumnSQL(
505 4
                    $diff->getName($this)->getQuotedName($this),
506 4
                    $column->getQuotedName($this),
507 16
                    $comment
508
                );
509
            }
510
        }
511
512 72
        foreach ($diff->removedColumns as $column) {
513 16
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
514
                continue;
515
            }
516
517 16
            $query = 'DROP ' . $column->getQuotedName($this);
518 16
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
519
        }
520
521 72
        foreach ($diff->changedColumns as $columnDiff) {
522
            /** @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
523 40
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
524
                continue;
525
            }
526
527 40
            if ($this->isUnchangedBinaryColumn($columnDiff)) {
528 4
                continue;
529
            }
530
531 36
            $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this);
532 36
            $column = $columnDiff->column;
533
534 36
            if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed')) {
535 16
                $type = $column->getType();
536
537
                // here was a server version check before, but DBAL API does not support this anymore.
538 16
                $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSQLDeclaration($column->toArray(), $this);
539 16
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
540
            }
541
542 36
            if ($columnDiff->hasChanged('default') || $columnDiff->hasChanged('type')) {
543 8
                $defaultClause = null === $column->getDefault()
544 4
                    ? ' DROP DEFAULT'
545 8
                    : ' SET' . $this->getDefaultValueDeclarationSQL($column->toArray());
546 8
                $query = 'ALTER ' . $oldColumnName . $defaultClause;
547 8
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
548
            }
549
550 36
            if ($columnDiff->hasChanged('notnull')) {
551 8
                $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL';
552 8
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
553
            }
554
555 36
            if ($columnDiff->hasChanged('autoincrement')) {
556
                if ($column->getAutoincrement()) {
557
                    // add autoincrement
558
                    $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName);
559
560
                    $sql[] = "CREATE SEQUENCE " . $seqName;
561
                    $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ") FROM " . $diff->getName($this)->getQuotedName($this) . "))";
562
                    $query = "ALTER " . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')";
563
                    $sql[] = "ALTER TABLE " . $diff->getName($this)->getQuotedName($this) . " " . $query;
564
                } else {
565
                    // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have
566
                    $query = "ALTER " . $oldColumnName . " " . "DROP DEFAULT";
567
                    $sql[] = "ALTER TABLE " . $diff->getName($this)->getQuotedName($this) . " " . $query;
568
                }
569
            }
570
571 36 View Code Duplication
            if ($columnDiff->hasChanged('comment')) {
572 8
                $commentsSQL[] = $this->getCommentOnColumnSQL(
573 8
                    $diff->getName($this)->getQuotedName($this),
574 8
                    $column->getQuotedName($this),
575 8
                    $this->getColumnComment($column)
576
                );
577
            }
578
579 36
            if ($columnDiff->hasChanged('length')) {
580 4
                $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $column->getType()->getSQLDeclaration($column->toArray(), $this);
581 36
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
582
            }
583
        }
584
585 72 View Code Duplication
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
586 16
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
587
                continue;
588
            }
589
590 16
            $oldColumnName = new Identifier($oldColumnName);
591
592 16
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) .
593 16
                ' RENAME COLUMN ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
594
        }
595
596 72
        $tableSql = [];
597
598 72 View Code Duplication
        if ( ! $this->onSchemaAlterTable($diff, $tableSql)) {
599 72
            $sql = array_merge($sql, $commentsSQL);
600
601 72
            if ($diff->newName !== false) {
602 8
                $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' RENAME TO ' . $diff->getNewName()->getQuotedName($this);
603
            }
604
605 72
            $sql = array_merge(
606 72
                $this->getPreAlterTableIndexForeignKeySQL($diff),
607 72
                $sql,
608 72
                $this->getPostAlterTableIndexForeignKeySQL($diff)
609
            );
610
        }
611
612 72
        return array_merge($sql, $tableSql, $columnSql);
613
    }
614
615
    /**
616
     * Checks whether a given column diff is a logically unchanged binary type column.
617
     *
618
     * Used to determine whether a column alteration for a binary type column can be skipped.
619
     * Doctrine's {@link \Doctrine\DBAL\Types\BinaryType} and {@link \Doctrine\DBAL\Types\BlobType}
620
     * are mapped to the same database column type on this platform as this platform
621
     * does not have a native VARBINARY/BINARY column type. Therefore the {@link \Doctrine\DBAL\Schema\Comparator}
622
     * might detect differences for binary type columns which do not have to be propagated
623
     * to database as there actually is no difference at database level.
624
     *
625
     * @param ColumnDiff $columnDiff The column diff to check against.
626
     *
627
     * @return boolean True if the given column diff is an unchanged binary type column, false otherwise.
628
     */
629 40
    private function isUnchangedBinaryColumn(ColumnDiff $columnDiff)
630
    {
631 40
        $columnType = $columnDiff->column->getType();
632
633 40
        if ( ! $columnType instanceof BinaryType && ! $columnType instanceof BlobType) {
634 36
            return false;
635
        }
636
637 4
        $fromColumn = $columnDiff->fromColumn instanceof Column ? $columnDiff->fromColumn : null;
638
639 4
        if ($fromColumn) {
640 4
            $fromColumnType = $fromColumn->getType();
641
642 4
            if ( ! $fromColumnType instanceof BinaryType && ! $fromColumnType instanceof BlobType) {
643
                return false;
644
            }
645
646 4
            return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0;
647
        }
648
649
        if ($columnDiff->hasChanged('type')) {
650
            return false;
651
        }
652
653
        return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0;
654
    }
655
656
    /**
657
     * {@inheritdoc}
658
     */
659 20 View Code Duplication
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
660
    {
661 20
        if (strpos($tableName, '.') !== false) {
662 8
            list($schema) = explode('.', $tableName);
663 8
            $oldIndexName = $schema . '.' . $oldIndexName;
664
        }
665
666 20
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
667
    }
668
669
    /**
670
     * {@inheritdoc}
671
     */
672 32 View Code Duplication
    public function getCommentOnColumnSQL($tableName, $columnName, $comment)
673
    {
674 32
        $tableName = new Identifier($tableName);
675 32
        $columnName = new Identifier($columnName);
676 32
        $comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
677
678 32
        return "COMMENT ON COLUMN " . $tableName->getQuotedName($this) . "." . $columnName->getQuotedName($this) .
679 32
            " IS $comment";
680
    }
681
682
    /**
683
     * {@inheritDoc}
684
     */
685 8 View Code Duplication
    public function getCreateSequenceSQL(Sequence $sequence)
686
    {
687 8
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
688 8
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
689 8
               ' MINVALUE ' . $sequence->getInitialValue() .
690 8
               ' START ' . $sequence->getInitialValue() .
691 8
               $this->getSequenceCacheSQL($sequence);
692
    }
693
694
    /**
695
     * {@inheritDoc}
696
     */
697
    public function getAlterSequenceSQL(Sequence $sequence)
698
    {
699
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
700
               ' INCREMENT BY ' . $sequence->getAllocationSize() .
701
               $this->getSequenceCacheSQL($sequence);
702
    }
703
704
    /**
705
     * Cache definition for sequences
706
     *
707
     * @param Sequence $sequence
708
     *
709
     * @return string
710
     */
711 8
    private function getSequenceCacheSQL(Sequence $sequence)
712
    {
713 8
        if ($sequence->getCache() > 1) {
714 4
            return ' CACHE ' . $sequence->getCache();
715
        }
716
717 4
        return '';
718
    }
719
720
    /**
721
     * {@inheritDoc}
722
     */
723 4
    public function getDropSequenceSQL($sequence)
724
    {
725 4
        if ($sequence instanceof Sequence) {
726
            $sequence = $sequence->getQuotedName($this);
727
        }
728
729 4
        return 'DROP SEQUENCE ' . $sequence . ' CASCADE';
730
    }
731
732
    /**
733
     * {@inheritDoc}
734
     */
735 4
    public function getCreateSchemaSQL($schemaName)
736
    {
737 4
        return 'CREATE SCHEMA ' . $schemaName;
738
    }
739
740
    /**
741
     * {@inheritDoc}
742
     */
743 12
    public function getDropForeignKeySQL($foreignKey, $table)
744
    {
745 12
        return $this->getDropConstraintSQL($foreignKey, $table);
746
    }
747
748
    /**
749
     * {@inheritDoc}
750
     */
751 40
    protected function _getCreateTableSQL($tableName, array $columns, array $options = [])
752
    {
753 40
        $queryFields = $this->getColumnDeclarationListSQL($columns);
754
755 40 View Code Duplication
        if (isset($options['primary']) && ! empty($options['primary'])) {
756 16
            $keyColumns = array_unique(array_values($options['primary']));
757 16
            $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
758
        }
759
760 40
        $query = 'CREATE TABLE ' . $tableName . ' (' . $queryFields . ')';
761
762 40
        $sql[] = $query;
763
764 40 View Code Duplication
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
765 12
            foreach ($options['indexes'] as $index) {
766 12
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
767
            }
768
        }
769
770 40 View Code Duplication
        if (isset($options['foreignKeys'])) {
771 4
            foreach ((array) $options['foreignKeys'] as $definition) {
772 4
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
773
            }
774
        }
775
776 40
        return $sql;
777
    }
778
779
    /**
780
     * Converts a single boolean value.
781
     *
782
     * First converts the value to its native PHP boolean type
783
     * and passes it to the given callback function to be reconverted
784
     * into any custom representation.
785
     *
786
     * @param mixed    $value    The value to convert.
787
     * @param callable $callback The callback function to use for converting the real boolean value.
788
     *
789
     * @return mixed
790
     * @throws \UnexpectedValueException
791
     */
792 128
    private function convertSingleBooleanValue($value, $callback)
793
    {
794 128
        if (null === $value) {
795 8
            return $callback(null);
796
        }
797
798 120
        if (is_bool($value) || is_numeric($value)) {
799 76
            return $callback($value ? true : false);
800
        }
801
802 44
        if (!is_string($value)) {
803
            return $callback(true);
804
        }
805
806
        /**
807
         * Better safe than sorry: http://php.net/in_array#106319
808
         */
809 44 View Code Duplication
        if (in_array(trim(strtolower($value)), $this->booleanLiterals['false'], true)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
810 20
            return $callback(false);
811
        }
812
813 24 View Code Duplication
        if (in_array(trim(strtolower($value)), $this->booleanLiterals['true'], true)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
814 20
            return $callback(true);
815
        }
816
817 4
        throw new \UnexpectedValueException("Unrecognized boolean literal '${value}'");
818
    }
819
820
    /**
821
     * Converts one or multiple boolean values.
822
     *
823
     * First converts the value(s) to their native PHP boolean type
824
     * and passes them to the given callback function to be reconverted
825
     * into any custom representation.
826
     *
827
     * @param mixed    $item     The value(s) to convert.
828
     * @param callable $callback The callback function to use for converting the real boolean value(s).
829
     *
830
     * @return mixed
831
     */
832
    private function doConvertBooleans($item, $callback)
833
    {
834 128
        if (is_array($item)) {
835
            foreach ($item as $key => $value) {
836
                $item[$key] = $this->convertSingleBooleanValue($value, $callback);
837
            }
838
839
            return $item;
840
        }
841
842 128
        return $this->convertSingleBooleanValue($item, $callback);
843
    }
844
845
    /**
846
     * {@inheritDoc}
847
     *
848
     * Postgres wants boolean values converted to the strings 'true'/'false'.
849
     */
850
    public function convertBooleans($item)
851
    {
852 72
        if ( ! $this->useBooleanTrueFalseStrings) {
853 8
            return parent::convertBooleans($item);
854
        }
855
856 64
        return $this->doConvertBooleans(
857 64
            $item,
858
            function ($boolean) {
859 64
                if (null === $boolean) {
860 4
                    return 'NULL';
861
                }
862
863 60
                return true === $boolean ? 'true' : 'false';
864 64
            }
865
        );
866
    }
867
868
    /**
869
     * {@inheritDoc}
870
     */
871
    public function convertBooleansToDatabaseValue($item)
872
    {
873 68
        if ( ! $this->useBooleanTrueFalseStrings) {
874 4
            return parent::convertBooleansToDatabaseValue($item);
875
        }
876
877 64
        return $this->doConvertBooleans(
878 64
            $item,
879
            function ($boolean) {
880 60
                return null === $boolean ? null : (int) $boolean;
881 64
            }
882
        );
883
    }
884
885
    /**
886
     * {@inheritDoc}
887
     */
888
    public function convertFromBoolean($item)
889
    {
890 60
        if (in_array(strtolower($item), $this->booleanLiterals['false'], true)) {
891 24
            return false;
892
        }
893
894 36
        return parent::convertFromBoolean($item);
895
    }
896
897
    /**
898
     * {@inheritDoc}
899
     */
900
    public function getSequenceNextValSQL($sequenceName)
901
    {
902 4
        return "SELECT NEXTVAL('" . $sequenceName . "')";
903
    }
904
905
    /**
906
     * {@inheritDoc}
907
     */
908
    public function getSetTransactionIsolationSQL($level)
909
    {
910
        return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL '
911 4
                . $this->_getTransactionIsolationLevelSQL($level);
912
    }
913
914
    /**
915
     * {@inheritDoc}
916
     */
917
    public function getBooleanTypeDeclarationSQL(array $field)
918
    {
919 4
        return 'BOOLEAN';
920
    }
921
922
    /**
923
     * {@inheritDoc}
924
     */
925
    public function getIntegerTypeDeclarationSQL(array $field)
926
    {
927 36
        if ( ! empty($field['autoincrement'])) {
928 12
            return 'SERIAL';
929
        }
930
931 28
        return 'INT';
932
    }
933
934
    /**
935
     * {@inheritDoc}
936
     */
937
    public function getBigIntTypeDeclarationSQL(array $field)
938
    {
939
        if ( ! empty($field['autoincrement'])) {
940
            return 'BIGSERIAL';
941
        }
942
943
        return 'BIGINT';
944
    }
945
946
    /**
947
     * {@inheritDoc}
948
     */
949
    public function getSmallIntTypeDeclarationSQL(array $field)
950
    {
951 2
        return 'SMALLINT';
952
    }
953
954
    /**
955
     * {@inheritDoc}
956
     */
957
    public function getGuidTypeDeclarationSQL(array $field)
958
    {
959 4
        return 'UUID';
960
    }
961
962
    /**
963
     * {@inheritDoc}
964
     */
965
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
966
    {
967
        return 'TIMESTAMP(0) WITHOUT TIME ZONE';
968
    }
969
970
    /**
971
     * {@inheritDoc}
972
     */
973
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
974
    {
975
        return 'TIMESTAMP(0) WITH TIME ZONE';
976
    }
977
978
    /**
979
     * {@inheritDoc}
980
     */
981
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
982
    {
983
        return 'DATE';
984
    }
985
986
    /**
987
     * {@inheritDoc}
988
     */
989
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
990
    {
991
        return 'TIME(0) WITHOUT TIME ZONE';
992
    }
993
994
    /**
995
     * {@inheritDoc}
996
     */
997
    public function getGuidExpression()
998
    {
999
        return 'UUID_GENERATE_V4()';
1000
    }
1001
1002
    /**
1003
     * {@inheritDoc}
1004
     */
1005
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
1006
    {
1007
        return '';
1008
    }
1009
1010
    /**
1011
     * {@inheritDoc}
1012
     */
1013
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
1014
    {
1015 48
        return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)')
1016 48
                : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)');
1017
    }
1018
1019
    /**
1020
     * {@inheritdoc}
1021
     */
1022
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
1023
    {
1024 4
        return 'BYTEA';
1025
    }
1026
1027
    /**
1028
     * {@inheritDoc}
1029
     */
1030
    public function getClobTypeDeclarationSQL(array $field)
1031
    {
1032 6
        return 'TEXT';
1033
    }
1034
1035
    /**
1036
     * {@inheritDoc}
1037
     */
1038
    public function getName()
1039
    {
1040 8
        return 'postgresql';
1041
    }
1042
1043
    /**
1044
     * {@inheritDoc}
1045
     *
1046
     * PostgreSQL returns all column names in SQL result sets in lowercase.
1047
     */
1048
    public function getSQLResultCasing($column)
1049
    {
1050
        return strtolower($column);
1051
    }
1052
1053
    /**
1054
     * {@inheritDoc}
1055
     */
1056
    public function getDateTimeTzFormatString()
1057
    {
1058
        return 'Y-m-d H:i:sO';
1059
    }
1060
1061
    /**
1062
     * {@inheritDoc}
1063
     */
1064
    public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
1065
    {
1066
        return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)';
1067
    }
1068
1069
    /**
1070
     * {@inheritDoc}
1071
     */
1072
    public function getTruncateTableSQL($tableName, $cascade = false)
1073
    {
1074 4
        $tableIdentifier = new Identifier($tableName);
1075 4
        $sql = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
1076
1077 4
        if ($cascade) {
1078
            $sql .= ' CASCADE';
1079
        }
1080
1081 4
        return $sql;
1082
    }
1083
1084
    /**
1085
     * {@inheritDoc}
1086
     */
1087
    public function getReadLockSQL()
1088
    {
1089
        return 'FOR SHARE';
1090
    }
1091
1092
    /**
1093
     * {@inheritDoc}
1094
     */
1095 View Code Duplication
    protected function initializeDoctrineTypeMappings()
1096
    {
1097 22
        $this->doctrineTypeMapping = [
1098
            'smallint'      => 'smallint',
1099
            'int2'          => 'smallint',
1100
            'serial'        => 'integer',
1101
            'serial4'       => 'integer',
1102
            'int'           => 'integer',
1103
            'int4'          => 'integer',
1104
            'integer'       => 'integer',
1105
            'bigserial'     => 'bigint',
1106
            'serial8'       => 'bigint',
1107
            'bigint'        => 'bigint',
1108
            'int8'          => 'bigint',
1109
            'bool'          => 'boolean',
1110
            'boolean'       => 'boolean',
1111
            'text'          => 'text',
1112
            'tsvector'      => 'text',
1113
            'varchar'       => 'string',
1114
            'interval'      => 'string',
1115
            '_varchar'      => 'string',
1116
            'char'          => 'string',
1117
            'bpchar'        => 'string',
1118
            'inet'          => 'string',
1119
            'date'          => 'date',
1120
            'datetime'      => 'datetime',
1121
            'timestamp'     => 'datetime',
1122
            'timestamptz'   => 'datetimetz',
1123
            'time'          => 'time',
1124
            'timetz'        => 'time',
1125
            'float'         => 'float',
1126
            'float4'        => 'float',
1127
            'float8'        => 'float',
1128
            'double'        => 'float',
1129
            'double precision' => 'float',
1130
            'real'          => 'float',
1131
            'decimal'       => 'decimal',
1132
            'money'         => 'decimal',
1133
            'numeric'       => 'decimal',
1134
            'year'          => 'date',
1135
            'uuid'          => 'guid',
1136
            'bytea'         => 'blob',
1137
        ];
1138 22
    }
1139
1140
    /**
1141
     * {@inheritDoc}
1142
     */
1143
    public function getVarcharMaxLength()
1144
    {
1145 48
        return 65535;
1146
    }
1147
1148
    /**
1149
     * {@inheritdoc}
1150
     */
1151
    public function getBinaryMaxLength()
1152
    {
1153 8
        return 0;
1154
    }
1155
1156
    /**
1157
     * {@inheritdoc}
1158
     */
1159
    public function getBinaryDefaultLength()
1160
    {
1161 8
        return 0;
1162
    }
1163
1164
    /**
1165
     * {@inheritDoc}
1166
     */
1167
    protected function getReservedKeywordsClass()
1168
    {
1169 47
        return Keywords\PostgreSQLKeywords::class;
1170
    }
1171
1172
    /**
1173
     * {@inheritDoc}
1174
     */
1175
    public function getBlobTypeDeclarationSQL(array $field)
1176
    {
1177 4
        return 'BYTEA';
1178
    }
1179
1180
    /**
1181
     * {@inheritdoc}
1182
     */
1183
    public function quoteStringLiteral($str)
1184
    {
1185 68
        $str = str_replace('\\', '\\\\', $str); // PostgreSQL requires backslashes to be escaped aswell.
1186
1187 68
        return parent::quoteStringLiteral($str);
1188
    }
1189
}
1190