Passed
Pull Request — master (#3225)
by Šimon
12:57
created

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

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

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