Completed
Pull Request — master (#2412)
by Benoît
20:15
created

PostgreSqlPlatform   F

Complexity

Total Complexity 170

Size/Duplication

Total Lines 1217
Duplicated Lines 0 %

Test Coverage

Coverage 92.39%

Importance

Changes 0
Metric Value
wmc 170
eloc 350
dl 0
loc 1217
ccs 340
cts 368
cp 0.9239
rs 2
c 0
b 0
f 0

83 Methods

Rating   Name   Duplication   Size   Complexity  
A getListTableIndexesSQL() 0 10 1
A getDateArithmeticIntervalExpression() 0 8 2
A getTableWhereClause() 0 19 2
A getLocateExpression() 0 9 2
A supportsPartialIndexes() 0 3 1
A getIdentitySequenceName() 0 3 1
A getListSequencesSQL() 0 3 1
A supportsSequences() 0 3 1
A getRegexpExpression() 0 3 1
A getDefaultSchemaName() 0 3 1
A getSubstringExpression() 0 7 2
A getDateDiffExpression() 0 3 1
A supportsSchemas() 0 3 1
A getDisallowDatabaseConnectionsSQL() 0 3 1
A getListTableForeignKeysSQL() 0 9 1
A getListTablesSQL() 0 3 1
A getListTableColumnsSQL() 0 27 1
A getCreateViewSQL() 0 3 1
A usesSequenceEmulatedIdentityColumns() 0 3 1
A getListNamespacesSQL() 0 3 1
A getNowExpression() 0 3 1
A getListTableConstraintsSQL() 0 21 1
A getDropViewSQL() 0 3 1
A getListViewsSQL() 0 3 1
A getCreateDatabaseSQL() 0 3 1
A supportsIdentityColumns() 0 3 1
A supportsCommentOnStatement() 0 3 1
A setUseBooleanTrueFalseStrings() 0 3 1
B getAdvancedForeignKeyOptionsSQL() 0 25 8
A hasNativeGuidType() 0 3 1
A getListDatabasesSQL() 0 3 1
A prefersSequences() 0 3 1
A getCloseActiveDatabaseConnectionsSQL() 0 4 1
A getAlterSequenceSQL() 0 5 1
A getCommentOnColumnSQL() 0 11 2
A getCreateSequenceSQL() 0 7 1
B isUnchangedBinaryColumn() 0 25 8
A getRenameIndexSQL() 0 8 2
A getDropForeignKeySQL() 0 3 1
A getCreateSchemaSQL() 0 3 1
A getDropSequenceSQL() 0 7 2
B _getCreateTableSQL() 0 26 8
F getAlterTableSQL() 0 137 29
A getSequenceCacheSQL() 0 7 2
B convertSingleBooleanValue() 0 26 8
A convertFromBoolean() 0 7 2
A getBigIntTypeDeclarationSQL() 0 7 2
A getIntegerTypeDeclarationSQL() 0 7 2
A convertBooleansToDatabaseValue() 0 10 3
A getBinaryTypeDeclarationSQLSnippet() 0 3 1
A getGuidExpression() 0 3 1
A getVarcharTypeDeclarationSQLSnippet() 0 4 4
A _getCommonIntegerTypeDeclarationSQL() 0 3 1
A isNumericType() 0 3 2
A getReadLockSQL() 0 3 1
A getGuidTypeDeclarationSQL() 0 3 1
A typeChangeBreaksDefaultValue() 0 12 5
A getName() 0 3 1
A getTimeTypeDeclarationSQL() 0 3 1
A quoteStringLiteral() 0 5 1
A getBinaryMaxLength() 0 3 1
A doConvertBooleans() 0 11 3
A getReservedKeywordsClass() 0 3 1
A getDateTimeTzTypeDeclarationSQL() 0 3 1
A getSmallIntTypeDeclarationSQL() 0 3 1
A getDateTypeDeclarationSQL() 0 3 1
A getDateTimeTzFormatString() 0 3 1
A getBlobTypeDeclarationSQL() 0 3 1
A getEmptyIdentityInsertSQL() 0 3 1
A getDefaultValueDeclarationSQL() 0 7 2
A getSequenceNextValSQL() 0 3 1
A getSQLResultCasing() 0 3 1
A initializeDoctrineTypeMappings() 0 42 1
A getSetTransactionIsolationSQL() 0 4 1
A getVarcharMaxLength() 0 3 1
A getOldColumnComment() 0 3 2
A convertBooleans() 0 14 4
A isSerialField() 0 4 3
A getClobTypeDeclarationSQL() 0 3 1
A getTruncateTableSQL() 0 10 2
A getBooleanTypeDeclarationSQL() 0 3 1
A getBinaryDefaultLength() 0 3 1
A getDateTimeTypeDeclarationSQL() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like PostgreSqlPlatform often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PostgreSqlPlatform, and based on these observations, apply Extract Interface, too.

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

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