Failed Conditions
Push — 3.0.x ( f82f5c...40cc9b )
by Grégoire
24s queued 15s
created

PostgreSQL94Platform   F

Complexity

Total Complexity 176

Size/Duplication

Total Lines 1271
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 176
eloc 370
dl 0
loc 1271
rs 2
c 0
b 0
f 0

87 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

1150
            'json'             => /** @scrutinizer ignore-deprecated */ Type::JSON,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1151
            'jsonb'            => Type::JSON,
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::JSON has been deprecated: Use {@see Types::JSON} instead. ( Ignorable by Annotation )

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

1151
            'jsonb'            => /** @scrutinizer ignore-deprecated */ Type::JSON,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1152
            'money'            => 'decimal',
1153
            'numeric'          => 'decimal',
1154
            'serial'           => 'integer',
1155
            'serial4'          => 'integer',
1156
            'serial8'          => 'bigint',
1157
            'real'             => 'float',
1158
            'smallint'         => 'smallint',
1159
            'text'             => 'text',
1160
            'time'             => 'time',
1161
            'timestamp'        => 'datetime',
1162
            'timestamptz'      => 'datetimetz',
1163
            'timetz'           => 'time',
1164
            'tsvector'         => 'text',
1165
            'uuid'             => 'guid',
1166
            'varchar'          => 'string',
1167
            'year'             => 'date',
1168
            '_varchar'         => 'string',
1169
        ];
1170
    }
1171
1172
    /**
1173
     * {@inheritDoc}
1174
     */
1175
    public function getVarcharMaxLength()
1176
    {
1177
        return 65535;
1178
    }
1179
1180
    /**
1181
     * {@inheritdoc}
1182
     */
1183
    public function getBinaryMaxLength()
1184
    {
1185
        return 0;
1186
    }
1187
1188
    /**
1189
     * {@inheritdoc}
1190
     */
1191
    public function getBinaryDefaultLength()
1192
    {
1193
        return 0;
1194
    }
1195
1196
    /**
1197
     * {@inheritdoc}
1198
     */
1199
    public function hasNativeJsonType()
1200
    {
1201
        return true;
1202
    }
1203
1204
    /**
1205
     * {@inheritDoc}
1206
     */
1207
    protected function getReservedKeywordsClass()
1208
    {
1209
        return Keywords\PostgreSQL94Keywords::class;
1210
    }
1211
1212
    /**
1213
     * {@inheritDoc}
1214
     */
1215
    public function getBlobTypeDeclarationSQL(array $field)
1216
    {
1217
        return 'BYTEA';
1218
    }
1219
1220
    /**
1221
     * {@inheritdoc}
1222
     */
1223
    public function getDefaultValueDeclarationSQL($field)
1224
    {
1225
        if ($this->isSerialField($field)) {
1226
            return '';
1227
        }
1228
1229
        return parent::getDefaultValueDeclarationSQL($field);
1230
    }
1231
1232
    /**
1233
     * {@inheritdoc}
1234
     */
1235
    public function supportsColumnCollation()
1236
    {
1237
        return true;
1238
    }
1239
1240
    /**
1241
     * {@inheritdoc}
1242
     */
1243
    public function getColumnCollationDeclarationSQL($collation)
1244
    {
1245
        return 'COLLATE ' . $this->quoteSingleIdentifier($collation);
1246
    }
1247
1248
    /**
1249
     * {@inheritdoc}
1250
     */
1251
    public function getJsonTypeDeclarationSQL(array $field)
1252
    {
1253
        if (! empty($field['jsonb'])) {
1254
            return 'JSONB';
1255
        }
1256
1257
        return 'JSON';
1258
    }
1259
1260
    /**
1261
     * @param mixed[] $field
1262
     */
1263
    private function isSerialField(array $field) : bool
1264
    {
1265
        return isset($field['type'], $field['autoincrement'])
1266
            && $field['autoincrement'] === true
1267
            && $this->isNumericType($field['type']);
1268
    }
1269
1270
    /**
1271
     * Check whether the type of a column is changed in a way that invalidates the default value for the column
1272
     */
1273
    private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff) : bool
1274
    {
1275
        if (! $columnDiff->fromColumn) {
1276
            return $columnDiff->hasChanged('type');
1277
        }
1278
1279
        $oldTypeIsNumeric = $this->isNumericType($columnDiff->fromColumn->getType());
1280
        $newTypeIsNumeric = $this->isNumericType($columnDiff->column->getType());
1281
1282
        // default should not be changed when switching between numeric types and the default comes from a sequence
1283
        return $columnDiff->hasChanged('type')
1284
            && ! ($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement());
1285
    }
1286
1287
    private function isNumericType(Type $type) : bool
1288
    {
1289
        return $type instanceof IntegerType || $type instanceof BigIntType;
1290
    }
1291
1292
    private function getOldColumnComment(ColumnDiff $columnDiff) : ?string
1293
    {
1294
        return $columnDiff->fromColumn ? $this->getColumnComment($columnDiff->fromColumn) : null;
1295
    }
1296
1297
    public function getListTableMetadataSQL(string $table, ?string $schema = null) : string
1298
    {
1299
        if ($schema !== null) {
1300
            $table = $schema . '.' . $table;
1301
        }
1302
1303
        return sprintf(
1304
            <<<'SQL'
1305
SELECT obj_description(%s::regclass) AS table_comment;
1306
SQL
1307
            ,
1308
            $this->quoteStringLiteral($table)
1309
        );
1310
    }
1311
}
1312