Completed
Push — 2.10.x ( 61a6b9...f20ba1 )
by Grégoire
13:37 queued 11s
created

PostgreSqlPlatform   F

Complexity

Total Complexity 170

Size/Duplication

Total Lines 1232
Duplicated Lines 0 %

Test Coverage

Coverage 92%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 170
eloc 360
dl 0
loc 1232
ccs 345
cts 375
cp 0.92
rs 2
c 1
b 0
f 0

83 Methods

Rating   Name   Duplication   Size   Complexity  
B isUnchangedBinaryColumn() 0 25 8
A getRenameIndexSQL() 0 8 2
A isNumericType() 0 3 2
A typeChangeBreaksDefaultValue() 0 12 5
A setUseBooleanTrueFalseStrings() 0 3 1
A getReservedKeywordsClass() 0 3 1
A getBlobTypeDeclarationSQL() 0 3 1
A getDefaultValueDeclarationSQL() 0 7 2
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 getListTableIndexesSQL() 0 10 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
B getAdvancedForeignKeyOptionsSQL() 0 25 8
A getListDatabasesSQL() 0 3 1
A hasNativeGuidType() 0 3 1
A prefersSequences() 0 3 1
A getCloseActiveDatabaseConnectionsSQL() 0 4 1
A initializeDoctrineTypeMappings() 0 42 1
A convertFromBoolean() 0 7 2
A getAlterSequenceSQL() 0 5 1
A getBigIntTypeDeclarationSQL() 0 7 2
A getIntegerTypeDeclarationSQL() 0 7 2
A getCommentOnColumnSQL() 0 11 2
A convertBooleansToDatabaseValue() 0 10 3
A getCreateSequenceSQL() 0 7 1
A getBinaryTypeDeclarationSQLSnippet() 0 3 1
A getGuidExpression() 0 3 1
A getVarcharTypeDeclarationSQLSnippet() 0 4 4
A _getCommonIntegerTypeDeclarationSQL() 0 3 1
A getReadLockSQL() 0 3 1
A getGuidTypeDeclarationSQL() 0 3 1
A getDropForeignKeySQL() 0 3 1
A getName() 0 3 1
A getTimeTypeDeclarationSQL() 0 3 1
A getCreateSchemaSQL() 0 3 1
A getDropSequenceSQL() 0 7 2
A getBinaryMaxLength() 0 3 1
A doConvertBooleans() 0 11 3
A getDateTimeTzTypeDeclarationSQL() 0 3 1
A getSmallIntTypeDeclarationSQL() 0 3 1
A getDateTypeDeclarationSQL() 0 3 1
A getDateTimeTzFormatString() 0 3 1
B _getCreateTableSQL() 0 26 8
A getEmptyIdentityInsertSQL() 0 3 1
A getSequenceNextValSQL() 0 3 1
A getSQLResultCasing() 0 3 1
A getSetTransactionIsolationSQL() 0 4 1
F getAlterTableSQL() 0 142 29
A getVarcharMaxLength() 0 3 1
A getOldColumnComment() 0 3 2
A getSequenceCacheSQL() 0 7 2
A convertBooleans() 0 14 4
A isSerialField() 0 5 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
A getListTableMetadataSQL() 0 12 2
B convertSingleBooleanValue() 0 26 7

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

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