Completed
Pull Request — master (#3628)
by Dallas
61:55
created

PostgreSqlPlatform::getAlterTableAlterNotNullSQL()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 5
c 1
b 0
f 1
dl 0
loc 7
ccs 3
cts 4
cp 0.75
rs 10
cc 2
nc 1
nop 3
crap 2.0625
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Platforms;
6
7
use Doctrine\DBAL\Schema\ColumnDiff;
8
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
9
use Doctrine\DBAL\Schema\Identifier;
10
use Doctrine\DBAL\Schema\Index;
11
use Doctrine\DBAL\Schema\Sequence;
12
use Doctrine\DBAL\Schema\TableDiff;
13
use Doctrine\DBAL\Types\BigIntType;
14
use Doctrine\DBAL\Types\BinaryType;
15
use Doctrine\DBAL\Types\BlobType;
16
use Doctrine\DBAL\Types\IntegerType;
17
use Doctrine\DBAL\Types\Type;
18
use UnexpectedValueException;
19
use function array_diff;
20
use function array_merge;
21
use function array_unique;
22
use function array_values;
23
use function count;
24
use function explode;
25
use function implode;
26
use function in_array;
27
use function is_array;
28
use function is_bool;
29
use function is_numeric;
30
use function is_string;
31
use function sprintf;
32
use function strpos;
33
use function strtolower;
34
use function trim;
35
36
/**
37
 * Provides the behavior, features and SQL dialect of the PostgreSQL 9.4+ database platform.
38
 *
39
 * @todo   Rename: PostgreSQLPlatform
40
 */
41
class PostgreSqlPlatform extends AbstractPlatform
42
{
43
    /** @var bool */
44
    private $useBooleanTrueFalseStrings = true;
45
46
    /** @var string[][] PostgreSQL booleans literals */
47
    private $booleanLiterals = [
48
        'true' => [
49
            't',
50
            'true',
51
            'y',
52
            'yes',
53
            'on',
54
            '1',
55
        ],
56
        'false' => [
57
            'f',
58
            'false',
59
            'n',
60
            'no',
61
            'off',
62
            '0',
63
        ],
64
    ];
65
66
    /**
67
     * PostgreSQL has different behavior with some drivers
68
     * with regard to how booleans have to be handled.
69
     *
70
     * Enables use of 'true'/'false' or otherwise 1 and 0 instead.
71
     */
72
    public function setUseBooleanTrueFalseStrings(bool $flag) : void
73 11870
    {
74
        $this->useBooleanTrueFalseStrings = $flag;
75 11870
    }
76 11870
77
    /**
78
     * {@inheritDoc}
79
     */
80
    public function getNowExpression() : string
81 13774
    {
82
        return 'LOCALTIMESTAMP(0)';
83 13774
    }
84 13774
85
    /**
86
     * {@inheritDoc}
87 12360
     */
88
    public function getRegexpExpression() : string
89
    {
90
        return 'SIMILAR TO';
91
    }
92
93
    /**
94
     * {@inheritDoc}
95
     */
96
    public function getLocateExpression(string $string, string $substring, ?string $start = null) : string
97
    {
98
        if ($start !== null) {
99
            $string = $this->getSubstringExpression($string, $start);
100
101 12360
            return 'CASE WHEN (POSITION(' . $substring . ' IN ' . $string . ') = 0) THEN 0 ELSE (POSITION(' . $substring . ' IN ' . $string . ') + ' . $start . ' - 1) END';
102
        }
103 12360
104
        return sprintf('POSITION(%s IN %s)', $substring, $string);
105
    }
106
107
    /**
108
     * {@inheritdoc}
109 4872
     */
110
    protected function getDateArithmeticIntervalExpression(string $date, string $operator, string $interval, string $unit) : string
111 4872
    {
112 4872
        if ($unit === DateIntervalUnit::QUARTER) {
113
            $interval = $this->multiplyInterval($interval, 3);
114 4872
            $unit     = DateIntervalUnit::MONTH;
115
        }
116
117 4872
        return '(' . $date . ' ' . $operator . ' (' . $interval . " || ' " . $unit . "')::interval)";
118
    }
119
120
    /**
121
     * {@inheritDoc}
122
     */
123 4879
    public function getDateDiffExpression(string $date1, string $date2) : string
124
    {
125 4879
        return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))';
126 4879
    }
127 4879
128
    /**
129
     * {@inheritDoc}
130 4879
     */
131
    public function getCurrentDatabaseExpression() : string
132
    {
133
        return 'CURRENT_DATABASE()';
134
    }
135
136 4683
    /**
137
     * {@inheritDoc}
138 4683
     */
139
    public function supportsSequences() : bool
140
    {
141
        return true;
142
    }
143
144 4291
    /**
145
     * {@inheritDoc}
146 4291
     */
147
    public function supportsSchemas() : bool
148
    {
149
        return true;
150
    }
151
152 4260
    /**
153
     * {@inheritdoc}
154 4260
     */
155
    public function getDefaultSchemaName() : string
156
    {
157
        return 'public';
158
    }
159
160 4085
    /**
161
     * {@inheritDoc}
162 4085
     */
163
    public function supportsIdentityColumns() : bool
164
    {
165
        return true;
166
    }
167
168 4172
    /**
169
     * {@inheritdoc}
170 4172
     */
171
    public function supportsPartialIndexes() : bool
172
    {
173
        return true;
174
    }
175
176 12801
    /**
177
     * {@inheritdoc}
178 12801
     */
179
    public function usesSequenceEmulatedIdentityColumns() : bool
180
    {
181
        return true;
182
    }
183
184 11844
    /**
185
     * {@inheritdoc}
186 11844
     */
187
    public function getIdentitySequenceName(string $tableName, string $columnName) : string
188
    {
189
        return $tableName . '_' . $columnName . '_seq';
190
    }
191
192 12757
    /**
193
     * {@inheritDoc}
194 12757
     */
195
    public function supportsCommentOnStatement() : bool
196
    {
197
        return true;
198
    }
199
200 13941
    /**
201
     * {@inheritDoc}
202 13941
     */
203
    public function prefersSequences() : bool
204
    {
205
        return true;
206
    }
207
208 10
    /**
209
     * {@inheritDoc}
210 10
     */
211
    public function hasNativeGuidType() : bool
212
    {
213
        return true;
214
    }
215
216 14291
    /**
217
     * {@inheritDoc}
218 14291
     */
219
    public function getListDatabasesSQL() : string
220
    {
221
        return 'SELECT datname FROM pg_database';
222
    }
223
224 4288
    /**
225
     * {@inheritDoc}
226 4288
     */
227
    public function getListNamespacesSQL() : string
228
    {
229
        return "SELECT schema_name AS nspname
230
                FROM   information_schema.schemata
231
                WHERE  schema_name NOT LIKE 'pg\_%'
232 4260
                AND    schema_name != 'information_schema'";
233
    }
234 4260
235
    /**
236
     * {@inheritDoc}
237
     */
238
    public function getListSequencesSQL(string $database) : string
239
    {
240
        return "SELECT sequence_name AS relname,
241
                       sequence_schema AS schemaname
242
                FROM   information_schema.sequences
243 3058
                WHERE  sequence_schema NOT LIKE 'pg\_%'
244
                AND    sequence_schema != 'information_schema'";
245 3058
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250
    public function getListTablesSQL() : string
251
    {
252
        return "SELECT quote_ident(table_name) AS table_name,
253
                       table_schema AS schema_name
254
                FROM   information_schema.tables
255 4739
                WHERE  table_schema NOT LIKE 'pg\_%'
256
                AND    table_schema != 'information_schema'
257 4739
                AND    table_name != 'geometry_columns'
258
                AND    table_name != 'spatial_ref_sys'
259
                AND    table_type != 'VIEW'";
260
    }
261
262
    /**
263
     * {@inheritDoc}
264
     */
265
    public function getListViewsSQL(string $database) : string
266
    {
267
        return 'SELECT quote_ident(table_name) AS viewname,
268
                       table_schema AS schemaname,
269
                       view_definition AS definition
270 4169
                FROM   information_schema.views
271
                WHERE  view_definition IS NOT NULL';
272 4169
    }
273
274
    /**
275
     * {@inheritDoc}
276
     */
277
    public function getListTableForeignKeysSQL(string $table, ?string $database = null) : string
278
    {
279
        return 'SELECT quote_ident(r.conname) as conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef
280
                  FROM pg_catalog.pg_constraint r
281
                  WHERE r.conrelid =
282 12685
                  (
283
                      SELECT c.oid
284
                      FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n
285
                      WHERE ' . $this->getTableWhereClause($table) . " AND n.oid = c.relnamespace
286
                  )
287
                  AND r.contype = 'f'";
288
    }
289
290 12685
    /**
291
     * {@inheritDoc}
292
     */
293
    public function getCreateViewSQL(string $name, string $sql) : string
294
    {
295
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
296
    }
297
298 4361
    /**
299
     * {@inheritDoc}
300 4361
     */
301
    public function getDropViewSQL(string $name) : string
302
    {
303
        return 'DROP VIEW ' . $name;
304
    }
305
306 4361
    /**
307
     * {@inheritDoc}
308 4361
     */
309
    public function getListTableConstraintsSQL(string $table) : string
310
    {
311
        $table = new Identifier($table);
312
        $table = $this->quoteStringLiteral($table->getName());
313
314 11260
        return sprintf(
315
            <<<'SQL'
316 11260
SELECT
317 11260
    quote_ident(relname) as relname
318
FROM
319 11260
    pg_class
320
WHERE oid IN (
321 10
    SELECT indexrelid
322
    FROM pg_index, pg_class
323
    WHERE pg_class.relname = %s
324
        AND pg_class.oid = pg_index.indrelid
325
        AND (indisunique = 't' OR indisprimary = 't')
326
    )
327
SQL
328
            ,
329
            $table
330
        );
331
    }
332
333
    /**
334 11260
     * {@inheritDoc}
335
     *
336
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
337
     */
338
    public function getListTableIndexesSQL(string $table, ?string $currentDatabase = null) : string
339
    {
340
        return 'SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary,
341
                       pg_index.indkey, pg_index.indrelid,
342
                       pg_get_expr(indpred, indrelid) AS where
343 12631
                 FROM pg_class, pg_index
344
                 WHERE oid IN (
345
                    SELECT indexrelid
346
                    FROM pg_index si, pg_class sc, pg_namespace sn
347
                    WHERE ' . $this->getTableWhereClause($table, 'sc', 'sn') . ' AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid
348
                 ) AND pg_index.indexrelid = oid';
349
    }
350
351
    private function getTableWhereClause(string $table, string $classAlias = 'c', string $namespaceAlias = 'n') : string
352 12631
    {
353
        $whereClause = $namespaceAlias . ".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND ";
354
        if (strpos($table, '.') !== false) {
355
            [$schema, $table] = explode('.', $table);
356
            $schema           = $this->quoteStringLiteral($schema);
357
        } else {
358
            $schema = "ANY(string_to_array((select replace(replace(setting,'\"\$user\"',user),' ','') from pg_catalog.pg_settings where name = 'search_path'),','))";
359
        }
360
361
        $table = new Identifier($table);
362
        $table = $this->quoteStringLiteral($table->getName());
363 12725
364
        return $whereClause . sprintf(
365 12725
            '%s.relname = %s AND %s.nspname = %s',
366 12725
            $classAlias,
367 12565
            $table,
368 12565
            $namespaceAlias,
369
            $schema
370 12695
        );
371
    }
372
373 12725
    /**
374 12725
     * {@inheritDoc}
375
     */
376 12725
    public function getListTableColumnsSQL(string $table, ?string $database = null) : string
377 60
    {
378 12725
        return "SELECT
379 12725
                    a.attnum,
380 12725
                    quote_ident(a.attname) AS field,
381 12725
                    t.typname AS type,
382
                    format_type(a.atttypid, a.atttypmod) AS complete_type,
383
                    (SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,
384
                    (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
385
                    (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM
386
                      pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type,
387
                    a.attnotnull AS isnotnull,
388 12595
                    (SELECT 't'
389
                     FROM pg_index
390
                     WHERE c.oid = pg_index.indrelid
391
                        AND pg_index.indkey[0] = a.attnum
392
                        AND pg_index.indisprimary = 't'
393
                    ) AS pri,
394
                    (SELECT pg_get_expr(adbin, adrelid)
395
                     FROM pg_attrdef
396
                     WHERE c.oid = pg_attrdef.adrelid
397
                        AND pg_attrdef.adnum=a.attnum
398
                    ) AS default,
399
                    (SELECT pg_description.description
400
                        FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
401
                    ) AS comment
402
                    FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n
403
                    WHERE " . $this->getTableWhereClause($table, 'c', 'n') . '
404
                        AND a.attnum > 0
405
                        AND a.attrelid = c.oid
406
                        AND a.atttypid = t.oid
407
                        AND n.oid = c.relnamespace
408
                    ORDER BY a.attnum';
409
    }
410
411
    /**
412
     * {@inheritDoc}
413
     */
414 12595
    public function getCreateDatabaseSQL(string $database) : string
415
    {
416
        return 'CREATE DATABASE ' . $database;
417
    }
418
419
    /**
420
     * Returns the SQL statement for disallowing new connections on the given database.
421
     *
422
     * This is useful to force DROP DATABASE operations which could fail because of active connections.
423
     *
424
     * @param string $database The name of the database to disallow new connections for.
425 13815
     */
426
    public function getDisallowDatabaseConnectionsSQL(string $database) : string
427 13815
    {
428
        return "UPDATE pg_database SET datallowconn = 'false' WHERE datname = " . $this->quoteStringLiteral($database);
429
    }
430
431
    /**
432
     * Returns the SQL statement for closing currently active connections on the given database.
433
     *
434
     * This is useful to force DROP DATABASE operations which could fail because of active connections.
435
     *
436
     * @param string $database The name of the database to close currently active connections for.
437
     */
438
    public function getCloseActiveDatabaseConnectionsSQL(string $database) : string
439 12452
    {
440
        return 'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '
441 12452
            . $this->quoteStringLiteral($database);
442
    }
443
444
    /**
445
     * {@inheritDoc}
446
     */
447
    public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) : string
448
    {
449
        $query = '';
450
451
        if ($foreignKey->hasOption('match')) {
452
            $query .= ' MATCH ' . $foreignKey->getOption('match');
453 8683
        }
454
455
        $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey);
456 8683
457
        if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) {
458
            $query .= ' DEFERRABLE';
459
        } else {
460
            $query .= ' NOT DEFERRABLE';
461
        }
462 13811
463
        if ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) {
464 13811
            $query .= ' INITIALLY DEFERRED';
465
        } else {
466 13811
            $query .= ' INITIALLY IMMEDIATE';
467 12385
        }
468
469
        return $query;
470 13811
    }
471
472 13811
    public function getAlterTableAddColumnSQL(string $table, string $column) : string
473 12385
    {
474
        return sprintf('ALTER TABLE %s ADD %s', $table, $column);
475 13811
    }
476
477
    public function getAlterTableDropColumnSQL(string $table, string $column) : string
478 13811
    {
479 13811
        return sprintf('ALTER TABLE %s DROP %s', $table, $column);
480
    }
481 12385
482
    /**
483 13811
     * @param string[] $columnDefinition
484
     */
485
    public function getAlterTableAlterTypeColumnSQL(string $table, string $column, string $type, array $columnDefinition) : string
486 13811
    {
487
        $using = '';
488
489
        if ($columnDefinition['using'] ?? false) {
490
            $using = 'USING ' . $columnDefinition['using'];
491
        }
492 12991
493
        return trim(sprintf('ALTER TABLE %s ALTER %s TYPE %s %s', $table, $column, $type, $using));
494 12991
    }
495 12991
496 12991
    public function getAlterTableAlterDefaultSQL(string $table, string $columnName, Column $column) : string
0 ignored issues
show
Bug introduced by
The type Doctrine\DBAL\Platforms\Column was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
497
    {
498 12991
        if ($column->getDefault() === null) {
499 11999
            return sprintf('ALTER TABLE %s ALTER %s DROP DEFAULT', $table, $columnName);
500
        }
501
502
        return sprintf(
503 11999
            'ALTER TABLE %s ALTER %s SET %s',
504 11999
            $table,
505
            $columnName,
506 11999
            trim($this->getDefaultValueDeclarationSQL($column->toArray()))
507
        );
508 11999
    }
509 11989
510
    public function getAlterTableRenameColumnSQL(string $table, string $oldColumn, string $newColumn) : string
511
    {
512 10710
        return sprintf('ALTER TABLE %s RENAME COLUMN %s TO %s', $table, $oldColumn, $newColumn);
513 10710
    }
514 10710
515 10
    public function getAlterTableAlterNotNullSQL(string $table, string $columnName, Column $column) : string
516
    {
517
        return sprintf(
518
            'ALTER TABLE %s ALTER %s %s NOT NULL',
519 12991
            $table,
520 12575
            $columnName,
521
            $column->getNotnull() ? 'SET' : 'DROP'
522
        );
523
    }
524 12575
525 12575
    public function getAlterTableRenameToSQL(string $table, string $name) : string
526
    {
527
        return sprintf('ALTER TABLE %s RENAME TO %s', $table, $name);
528 12991
    }
529
530 12911
    public function getSetSequenceValueSQL(string $table, string $column, string $sequence) : string
531
    {
532
        return 'SELECT setval(' . "'" . $sequence . "'" . ', (SELECT MAX(' . $column . ') FROM ' . $table . '))';
533
    }
534 12911
535 11485
    public function getCreateSimpleSequenceSQL(string $sequence) : string
536
    {
537
        return sprintf('CREATE SEQUENCE %s', $sequence);
538 12901
    }
539 12901
540
    /**
541 12901
     * {@inheritDoc}
542 12722
     */
543
    public function getAlterTableSQL(TableDiff $diff) : array
544
    {
545 12722
        $sql         = [];
546 12722
        $commentsSQL = [];
547
        $columnSql   = [];
548
549 12722
        foreach ($diff->addedColumns as $column) {
550 12722
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
551
                continue;
552
            }
553 12901
554 12333
            $sql[] = $this->getAlterTableAddColumnSQL(
555 12302
                $diff->getName($this)->getQuotedName($this),
556 12333
                $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray())
557 12333
            );
558 12333
559
            $comment = $this->getColumnComment($column);
560
561 12901
            if ($comment === null || $comment === '') {
562 10820
                continue;
563 10820
            }
564
565
            $commentsSQL[] = $this->getCommentOnColumnSQL(
566 12901
                $diff->getName($this)->getQuotedName($this),
567 4431
                $column->getQuotedName($this),
568
                $comment
569 4431
            );
570
        }
571 4431
572 4431
        foreach ($diff->removedColumns as $column) {
573 4431
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
574 4431
                continue;
575
            }
576
577 4424
            $sql[] = $this->getAlterTableDropColumnSQL(
578 4424
                $diff->getName($this)->getQuotedName($this),
579
                $column->getQuotedName($this)
580
            );
581
        }
582 12901
583 12901
        foreach ($diff->changedColumns as $columnDiff) {
584
            /** @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
585 12901
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
586 12372
                continue;
587 12372
            }
588 12372
589 30
            if ($this->isUnchangedBinaryColumn($columnDiff)) {
590
                continue;
591
            }
592
593 12901
            $oldColumnName = $columnDiff->getOldColumnName()->getQuotedName($this);
594 12891
            $column        = $columnDiff->column;
595
596
            if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('precision') || $columnDiff->hasChanged('scale') || $columnDiff->hasChanged('fixed')) {
597 10235
                $type = $column->getType();
598 10235
599
                // SERIAL/BIGSERIAL are not "real" types and we can't alter a column to that type
600
                $columnDefinition                  = $column->toArray();
601 12991
                $columnDefinition['autoincrement'] = false;
602 11928
603
                // here was a server version check before, but DBAL API does not support this anymore.
604
                $sql[] = $this->getAlterTableAlterTypeColumnSQL(
605
                    $diff->getName($this)->getQuotedName($this),
606 11928
                    $oldColumnName,
607
                    $type->getSQLDeclaration($columnDefinition, $this),
608 11928
                    $columnDefinition
609 11928
                );
610
            }
611
612 12991
            if ($columnDiff->hasChanged('default') || $this->typeChangeBreaksDefaultValue($columnDiff)) {
613
                $sql[] = $this->getAlterTableAlterDefaultSQL(
614 12991
                    $diff->getName($this)->getQuotedName($this),
615 12991
                    $oldColumnName,
616
                    $column
617 12991
                );
618
            }
619 12991
620 10820
            if ($columnDiff->hasChanged('notnull')) {
621 20
                $sql[] = $this->getAlterTableAlterNotNullSQL(
622 10820
                    $diff->getName($this)->getQuotedName($this),
623 10820
                    $oldColumnName,
624
                    $column
625
                );
626
            }
627 12991
628 12991
            if ($columnDiff->hasChanged('autoincrement')) {
629 12991
                if ($column->getAutoincrement()) {
630 12991
                    // add autoincrement
631
                    $seqName = $this->getIdentitySequenceName($diff->name, $oldColumnName);
632
633
                    $sql[] = $this->getCreateSimpleSequenceSQL($seqName);
634 12991
                    $sql[] = $this->getSetSequenceValueSQL(
635
                        $diff->getName($this)->getQuotedName($this),
636
                        $oldColumnName,
637
                        $seqName
638
                    );
639
                    $sql[] = $this->getAlterTableAlterDefaultSQL(
640
                        $diff->getName($this)->getQuotedName($this),
641
                        $oldColumnName,
642
                        $column->setDefault('nextval(' . "'" . $seqName . "'" . ')')
643
                    );
644
                } else {
645
                    // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have
646
                    $sql[] = $this->getAlterTableAlterDefaultSQL(
647
                        $diff->getName($this)->getQuotedName($this),
648
                        $oldColumnName,
649
                        $column->setDefault(null)
650
                    );
651 12911
                }
652
            }
653 12911
654
            $newComment = $this->getColumnComment($column);
655 12911
            $oldComment = $this->getOldColumnComment($columnDiff);
656 12901
657
            if ($columnDiff->hasChanged('comment') || ($columnDiff->fromColumn !== null && $oldComment !== $newComment)) {
658
                $commentsSQL[] = $this->getCommentOnColumnSQL(
659 11485
                    $diff->getName($this)->getQuotedName($this),
660
                    $column->getQuotedName($this),
661 11485
                    $newComment
662 11485
                );
663
            }
664 11485
665
            if (! $columnDiff->hasChanged('length')) {
666
                continue;
667
            }
668 11485
669
            $sql[] = $this->getAlterTableAlterTypeColumnSQL(
670
                $diff->getName($this)->getQuotedName($this),
671
                $oldColumnName,
672
                $column->getType()->getSQLDeclaration($column->toArray(), $this),
673
                $column->toArray()
674
            );
675
        }
676
677
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
678
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
679
                continue;
680
            }
681 11649
682
            $oldColumnName = new Identifier($oldColumnName);
683 11649
684 10220
            $sql[] = $this->getAlterTableRenameColumnSQL(
685 10220
                $diff->getName($this)->getQuotedName($this),
686
                $oldColumnName->getQuotedName($this),
687
                $column->getQuotedName($this)
688 11649
            );
689
        }
690
691
        $tableSql = [];
692
693
        if (! $this->onSchemaAlterTable($diff, $tableSql)) {
694 12618
            $sql = array_merge($sql, $commentsSQL);
695
696 12618
            $newName = $diff->getNewName();
697 12618
698 12618
            if ($newName !== null) {
699
                $sql[] = $this->getAlterTableRenameToSQL(
700 12618
                    $diff->getName($this)->getQuotedName($this),
701 90
                    $newName->getQuotedName($this)
702 12618
                );
703 12618
            }
704 12618
705
            $sql = array_merge(
706
                $this->getPreAlterTableIndexForeignKeySQL($diff),
707
                $sql,
708
                $this->getPostAlterTableIndexForeignKeySQL($diff)
709
            );
710
        }
711 12977
712
        return array_merge($sql, $tableSql, $columnSql);
713 12977
    }
714 12977
715 12977
    /**
716 12977
     * Checks whether a given column diff is a logically unchanged binary type column.
717 12977
     *
718
     * Used to determine whether a column alteration for a binary type column can be skipped.
719
     * Doctrine's {@link \Doctrine\DBAL\Types\BinaryType} and {@link \Doctrine\DBAL\Types\BlobType}
720
     * are mapped to the same database column type on this platform as this platform
721
     * does not have a native VARBINARY/BINARY column type. Therefore the {@link \Doctrine\DBAL\Schema\Comparator}
722
     * might detect differences for binary type columns which do not have to be propagated
723
     * to database as there actually is no difference at database level.
724
     *
725
     * @param ColumnDiff $columnDiff The column diff to check against.
726
     *
727
     * @return bool True if the given column diff is an unchanged binary type column, false otherwise.
728
     */
729
    private function isUnchangedBinaryColumn(ColumnDiff $columnDiff) : bool
730
    {
731
        $columnType = $columnDiff->column->getType();
732
733
        if (! $columnType instanceof BinaryType && ! $columnType instanceof BlobType) {
734
            return false;
735 12977
        }
736
737 12977
        $fromColumn = $columnDiff->fromColumn;
738 11535
739
        if ($fromColumn !== null) {
740
            $fromColumnType = $fromColumn->getType();
741 12967
742
            if (! $fromColumnType instanceof BinaryType && ! $fromColumnType instanceof BlobType) {
743
                return false;
744
            }
745
746
            return count(array_diff($columnDiff->changedProperties, ['type', 'length', 'fixed'])) === 0;
747 12967
        }
748
749 12967
        if ($columnDiff->hasChanged('type')) {
750
            return false;
751
        }
752
753 12967
        return count(array_diff($columnDiff->changedProperties, ['length', 'fixed'])) === 0;
754
    }
755
756
    /**
757
     * {@inheritdoc}
758
     */
759 12658
    protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName) : array
760
    {
761 12658
        if (strpos($tableName, '.') !== false) {
762
            [$schema]     = explode('.', $tableName);
763
            $oldIndexName = $schema . '.' . $oldIndexName;
764
        }
765
766
        return ['ALTER INDEX ' . $oldIndexName . ' RENAME TO ' . $index->getQuotedName($this)];
767 12530
    }
768
769 12530
    /**
770
     * {@inheritdoc}
771
     */
772
    public function getCommentOnColumnSQL(string $tableName, string $columnName, ?string $comment) : string
773
    {
774
        $tableName  = new Identifier($tableName);
775 13931
        $columnName = new Identifier($columnName);
776
        $comment    = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
777 13931
778
        return sprintf(
779 13931
            'COMMENT ON COLUMN %s.%s IS %s',
780 12873
            $tableName->getQuotedName($this),
781 12873
            $columnName->getQuotedName($this),
782
            $comment
783
        );
784 13931
    }
785
786 13931
    /**
787
     * {@inheritDoc}
788 13931
     */
789 12747
    public function getCreateSequenceSQL(Sequence $sequence) : string
790 12747
    {
791
        return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) .
792
            ' INCREMENT BY ' . $sequence->getAllocationSize() .
793
            ' MINVALUE ' . $sequence->getInitialValue() .
794 13931
            ' START ' . $sequence->getInitialValue() .
795 12501
            $this->getSequenceCacheSQL($sequence);
796 12403
    }
797
798
    /**
799
     * {@inheritDoc}
800 13931
     */
801
    public function getAlterSequenceSQL(Sequence $sequence) : string
802
    {
803
        return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) .
804
            ' INCREMENT BY ' . $sequence->getAllocationSize() .
805
            $this->getSequenceCacheSQL($sequence);
806
    }
807
808
    /**
809
     * Cache definition for sequences
810
     */
811
    private function getSequenceCacheSQL(Sequence $sequence) : string
812
    {
813
        if ($sequence->getCache() > 1) {
814
            return ' CACHE ' . $sequence->getCache();
815
        }
816
817 13500
        return '';
818
    }
819 13500
820 12299
    /**
821
     * {@inheritDoc}
822
     */
823 13480
    public function getDropSequenceSQL($sequence) : string
824 13370
    {
825
        if ($sequence instanceof Sequence) {
826
            $sequence = $sequence->getQuotedName($this);
827 12493
        }
828
829
        return 'DROP SEQUENCE ' . $sequence . ' CASCADE';
830
    }
831
832
    /**
833
     * {@inheritDoc}
834 12493
     */
835 12379
    public function getCreateSchemaSQL(string $schemaName) : string
836
    {
837
        return 'CREATE SCHEMA ' . $schemaName;
838 12060
    }
839 12050
840
    /**
841
     * {@inheritDoc}
842 11685
     */
843
    public function getDropForeignKeySQL($foreignKey, $table) : string
844
    {
845
        return $this->getDropConstraintSQL($foreignKey, $table);
846
    }
847
848
    /**
849
     * {@inheritDoc}
850
     */
851
    protected function _getCreateTableSQL(string $tableName, array $columns, array $options = []) : array
852
    {
853
        $queryFields = $this->getColumnDeclarationListSQL($columns);
854
855
        if (isset($options['primary']) && ! empty($options['primary'])) {
856
            $keyColumns   = array_unique(array_values($options['primary']));
857 13500
            $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
858
        }
859 13500
860
        $query = 'CREATE TABLE ' . $tableName . ' (' . $queryFields . ')';
861
862
        $sql = [$query];
863
864
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
865
            foreach ($options['indexes'] as $index) {
866
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
867 13500
            }
868
        }
869
870
        if (isset($options['foreignKeys'])) {
871
            foreach ((array) $options['foreignKeys'] as $definition) {
872
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
873
            }
874
        }
875 13360
876
        return $sql;
877 13360
    }
878 11870
879
    /**
880
     * Converts a single boolean value.
881 13340
     *
882 13340
     * First converts the value to its native PHP boolean type
883
     * and passes it to the given callback function to be reconverted
884 13340
     * into any custom representation.
885 11885
     *
886
     * @param mixed    $value    The value to convert.
887
     * @param callable $callback The callback function to use for converting the real boolean value.
888 13330
     *
889 13340
     * @return mixed
890
     *
891
     * @throws UnexpectedValueException
892
     */
893
    private function convertSingleBooleanValue($value, callable $callback)
894
    {
895
        if ($value === null) {
896 12427
            return $callback(null);
897
        }
898 12427
899 11785
        if (is_bool($value) || is_numeric($value)) {
900
            return $callback((bool) $value);
901
        }
902 12417
903 12417
        if (! is_string($value)) {
904
            return $callback(true);
905 12407
        }
906 12417
907
        /**
908
         * Better safe than sorry: http://php.net/in_array#106319
909
         */
910
        if (in_array(strtolower(trim($value)), $this->booleanLiterals['false'], true)) {
911
            return $callback(false);
912
        }
913 12318
914
        if (in_array(strtolower(trim($value)), $this->booleanLiterals['true'], true)) {
915 12318
            return $callback(true);
916 11785
        }
917
918
        throw new UnexpectedValueException(sprintf(
919 12258
            'Unrecognized boolean literal, %s given.',
920
            $value
921
        ));
922
    }
923
924
    /**
925 12214
     * Converts one or multiple boolean values.
926
     *
927 12214
     * First converts the value(s) to their native PHP boolean type
928
     * and passes them to the given callback function to be reconverted
929
     * into any custom representation.
930
     *
931
     * @param mixed    $item     The value(s) to convert.
932
     * @param callable $callback The callback function to use for converting the real boolean value(s).
933 12335
     *
934
     * @return mixed
935
     */
936 12335
    private function doConvertBooleans($item, callable $callback)
937
    {
938
        if (is_array($item)) {
939
            foreach ($item as $key => $value) {
940
                $item[$key] = $this->convertSingleBooleanValue($value, $callback);
941
            }
942 12308
943
            return $item;
944 12308
        }
945
946
        return $this->convertSingleBooleanValue($item, $callback);
947
    }
948
949
    /**
950 13897
     * {@inheritDoc}
951
     *
952 13897
     * Postgres wants boolean values converted to the strings 'true'/'false'.
953 13676
     */
954
    public function convertBooleans($item)
955
    {
956 13731
        if (! $this->useBooleanTrueFalseStrings) {
957
            return parent::convertBooleans($item);
958
        }
959
960
        return $this->doConvertBooleans(
961
            $item,
962 13159
            static function ($boolean) : string {
963
                if ($boolean === null) {
964 13159
                    return 'NULL';
965 13145
                }
966
967
                return $boolean === true ? 'true' : 'false';
968 4337
            }
969
        );
970
    }
971
972
    /**
973
     * {@inheritDoc}
974 13271
     */
975
    public function convertBooleansToDatabaseValue($item)
976 13271
    {
977
        if (! $this->useBooleanTrueFalseStrings) {
978
            return parent::convertBooleansToDatabaseValue($item);
979
        }
980
981
        return $this->doConvertBooleans(
982 11435
            $item,
983
            static function ($boolean) : ?int {
984 11435
                return $boolean === null ? null : (int) $boolean;
985
            }
986
        );
987
    }
988
989
    /**
990 13086
     * {@inheritDoc}
991
     */
992 13086
    public function convertFromBoolean($item) : ?bool
993
    {
994
        if (in_array($item, $this->booleanLiterals['false'], true)) {
995
            return false;
996
        }
997
998 3708
        return parent::convertFromBoolean($item);
999
    }
1000 3708
1001
    /**
1002
     * {@inheritDoc}
1003
     */
1004
    public function getSequenceNextValSQL(string $sequenceName) : string
1005
    {
1006 4669
        return "SELECT NEXTVAL('" . $sequenceName . "')";
1007
    }
1008 4669
1009
    /**
1010
     * {@inheritDoc}
1011
     */
1012
    public function getSetTransactionIsolationSQL(int $level) : string
1013
    {
1014 4662
        return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL '
1015
            . $this->_getTransactionIsolationLevelSQL($level);
1016 4662
    }
1017
1018
    /**
1019
     * {@inheritDoc}
1020
     */
1021
    public function getBooleanTypeDeclarationSQL(array $columnDef) : string
1022
    {
1023
        return 'BOOLEAN';
1024
    }
1025
1026
    /**
1027
     * {@inheritDoc}
1028
     */
1029
    public function getIntegerTypeDeclarationSQL(array $columnDef) : string
1030
    {
1031
        if (! empty($columnDef['autoincrement'])) {
1032
            return 'SERIAL';
1033
        }
1034
1035
        return 'INT';
1036
    }
1037
1038
    /**
1039
     * {@inheritDoc}
1040 13704
     */
1041
    public function getBigIntTypeDeclarationSQL(array $columnDef) : string
1042 13704
    {
1043 13704
        if (! empty($columnDef['autoincrement'])) {
1044
            return 'BIGSERIAL';
1045
        }
1046
1047
        return 'BIGINT';
1048
    }
1049 12665
1050
    /**
1051 12665
     * {@inheritDoc}
1052
     */
1053
    public function getSmallIntTypeDeclarationSQL(array $columnDef) : string
1054
    {
1055
        if (! empty($columnDef['autoincrement'])) {
1056
            return 'SMALLSERIAL';
1057 12649
        }
1058
1059 12649
        return 'SMALLINT';
1060
    }
1061
1062
    /**
1063
     * {@inheritDoc}
1064
     */
1065 12937
    public function getGuidTypeDeclarationSQL(array $column) : string
1066
    {
1067 12937
        return 'UUID';
1068
    }
1069
1070
    /**
1071
     * {@inheritDoc}
1072
     */
1073
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) : string
1074
    {
1075
        return 'TIMESTAMP(0) WITHOUT TIME ZONE';
1076
    }
1077
1078
    /**
1079
     * {@inheritDoc}
1080
     */
1081
    public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) : string
1082
    {
1083 3626
        return 'TIMESTAMP(0) WITH TIME ZONE';
1084
    }
1085 3626
1086
    /**
1087
     * {@inheritDoc}
1088
     */
1089
    public function getDateTypeDeclarationSQL(array $fieldDeclaration) : string
1090
    {
1091 3500
        return 'DATE';
1092
    }
1093 3500
1094
    /**
1095
     * {@inheritDoc}
1096
     */
1097
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration) : string
1098
    {
1099 12346
        return 'TIME(0) WITHOUT TIME ZONE';
1100
    }
1101 12346
1102 12346
    /**
1103
     * {@inheritDoc}
1104 12346
     */
1105
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) : string
1106
    {
1107
        return '';
1108 12346
    }
1109
1110
    /**
1111
     * {@inheritDoc}
1112
     */
1113
    protected function getVarcharTypeDeclarationSQLSnippet(?int $length) : string
1114
    {
1115
        $sql = 'VARCHAR';
1116
1117
        if ($length !== null) {
1118
            $sql .= sprintf('(%d)', $length);
1119
        }
1120
1121
        return $sql;
1122 13531
    }
1123
1124 13531
    /**
1125
     * {@inheritDoc}
1126
     */
1127
    protected function getBinaryTypeDeclarationSQLSnippet(?int $length) : string
1128
    {
1129
        return 'BYTEA';
1130
    }
1131
1132
    /**
1133
     * {@inheritDoc}
1134
     */
1135
    protected function getVarbinaryTypeDeclarationSQLSnippet(?int $length) : string
1136
    {
1137
        return 'BYTEA';
1138
    }
1139
1140
    /**
1141
     * {@inheritDoc}
1142
     */
1143
    public function getClobTypeDeclarationSQL(array $field) : string
1144
    {
1145
        return 'TEXT';
1146
    }
1147
1148
    /**
1149
     * {@inheritDoc}
1150
     */
1151
    public function getName() : string
1152
    {
1153
        return 'postgresql';
1154
    }
1155
1156
    /**
1157
     * {@inheritDoc}
1158
     *
1159
     * PostgreSQL returns all column names in SQL result sets in lowercase.
1160
     */
1161
    public function getSQLResultCasing(string $column) : string
1162
    {
1163
        return strtolower($column);
1164
    }
1165 13531
1166
    /**
1167
     * {@inheritDoc}
1168
     */
1169
    public function getDateTimeTzFormatString() : string
1170 13704
    {
1171
        return 'Y-m-d H:i:sO';
1172 13704
    }
1173
1174
    /**
1175
     * {@inheritDoc}
1176
     */
1177
    public function getEmptyIdentityInsertSQL(string $tableName, string $identifierColumnName) : string
1178 12675
    {
1179
        return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)';
1180 12675
    }
1181
1182
    /**
1183
     * {@inheritDoc}
1184
     */
1185
    public function getTruncateTableSQL(string $tableName, bool $cascade = false) : string
1186 12675
    {
1187
        $tableIdentifier = new Identifier($tableName);
1188 12675
        $sql             = 'TRUNCATE ' . $tableIdentifier->getQuotedName($this);
1189
1190
        if ($cascade) {
1191
            $sql .= ' CASCADE';
1192
        }
1193
1194 2656
        return $sql;
1195
    }
1196 2656
1197
    /**
1198
     * {@inheritDoc}
1199
     */
1200
    public function getReadLockSQL() : string
1201
    {
1202 13239
        return 'FOR SHARE';
1203
    }
1204 13239
1205
    /**
1206
     * {@inheritDoc}
1207
     */
1208
    protected function initializeDoctrineTypeMappings() : void
1209
    {
1210 14031
        $this->doctrineTypeMapping = [
1211
            'bigint'           => 'bigint',
1212 14031
            'bigserial'        => 'bigint',
1213 13706
            'bool'             => 'boolean',
1214
            'boolean'          => 'boolean',
1215
            'bpchar'           => 'string',
1216 13007
            'bytea'            => 'blob',
1217
            'char'             => 'string',
1218
            'date'             => 'date',
1219
            'datetime'         => 'datetime',
1220
            'decimal'          => 'decimal',
1221
            'double'           => 'float',
1222 14031
            'double precision' => 'float',
1223
            'float'            => 'float',
1224 14031
            'float4'           => 'float',
1225 14031
            'float8'           => 'float',
1226 14031
            'inet'             => 'string',
1227
            'int'              => 'integer',
1228
            'int2'             => 'smallint',
1229
            'int4'             => 'integer',
1230
            'int8'             => 'bigint',
1231
            'integer'          => 'integer',
1232 12891
            'interval'         => 'string',
1233
            '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

1233
            '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...
1234 12891
            '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

1234
            '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...
1235 11675
            'money'            => 'decimal',
1236
            'numeric'          => 'decimal',
1237
            'serial'           => 'integer',
1238 12679
            'serial4'          => 'integer',
1239 12679
            'serial8'          => 'bigint',
1240
            'real'             => 'float',
1241
            'smallint'         => 'smallint',
1242 12679
            'text'             => 'text',
1243 12679
            'time'             => 'time',
1244
            'timestamp'        => 'datetime',
1245
            'timestamptz'      => 'datetimetz',
1246 13746
            'timetz'           => 'time',
1247
            'tsvector'         => 'text',
1248 13746
            'uuid'             => 'guid',
1249
            'varchar'          => 'string',
1250
            'year'             => 'date',
1251 12901
            '_varchar'         => 'string',
1252
        ];
1253 12901
    }
1254
1255
    /**
1256 4529
     * {@inheritdoc}
1257
     */
1258 4529
    public function hasNativeJsonType() : bool
1259
    {
1260
        return true;
1261
    }
1262 4529
1263
    /**
1264
     * {@inheritDoc}
1265
     */
1266
    protected function getReservedKeywordsClass() : string
1267 4529
    {
1268
        return Keywords\PostgreSQLKeywords::class;
1269
    }
1270
1271
    /**
1272
     * {@inheritDoc}
1273
     */
1274
    public function getBlobTypeDeclarationSQL(array $field) : string
1275
    {
1276
        return 'BYTEA';
1277
    }
1278
1279
    /**
1280
     * {@inheritdoc}
1281
     */
1282
    public function getDefaultValueDeclarationSQL(array $field) : string
1283
    {
1284
        if ($this->isSerialField($field)) {
1285
            return '';
1286
        }
1287
1288
        return parent::getDefaultValueDeclarationSQL($field);
1289
    }
1290
1291
    /**
1292
     * {@inheritdoc}
1293
     */
1294
    public function supportsColumnCollation() : bool
1295
    {
1296
        return true;
1297
    }
1298
1299
    /**
1300
     * {@inheritdoc}
1301
     */
1302
    public function getColumnCollationDeclarationSQL(string $collation) : string
1303
    {
1304
        return 'COLLATE ' . $this->quoteSingleIdentifier($collation);
1305
    }
1306
1307
    /**
1308
     * {@inheritdoc}
1309
     */
1310
    public function getJsonTypeDeclarationSQL(array $field) : string
1311
    {
1312
        if (! empty($field['jsonb'])) {
1313
            return 'JSONB';
1314
        }
1315
1316
        return 'JSON';
1317
    }
1318
1319
    /**
1320
     * @param mixed[] $field
1321
     */
1322
    private function isSerialField(array $field) : bool
1323
    {
1324
        return isset($field['type'], $field['autoincrement'])
1325
            && $field['autoincrement'] === true
1326
            && $this->isNumericType($field['type']);
1327
    }
1328
1329
    /**
1330
     * Check whether the type of a column is changed in a way that invalidates the default value for the column
1331
     */
1332
    private function typeChangeBreaksDefaultValue(ColumnDiff $columnDiff) : bool
1333
    {
1334
        if ($columnDiff->fromColumn === null) {
1335
            return $columnDiff->hasChanged('type');
1336
        }
1337
1338
        $oldTypeIsNumeric = $this->isNumericType($columnDiff->fromColumn->getType());
1339
        $newTypeIsNumeric = $this->isNumericType($columnDiff->column->getType());
1340
1341
        // default should not be changed when switching between numeric types and the default comes from a sequence
1342
        return $columnDiff->hasChanged('type')
1343
            && ! ($oldTypeIsNumeric && $newTypeIsNumeric && $columnDiff->column->getAutoincrement());
1344
    }
1345
1346
    private function isNumericType(Type $type) : bool
1347
    {
1348
        return $type instanceof IntegerType || $type instanceof BigIntType;
1349
    }
1350
1351
    private function getOldColumnComment(ColumnDiff $columnDiff) : ?string
1352
    {
1353
        if ($columnDiff->fromColumn === null) {
1354
            return null;
1355
        }
1356
1357
        return $this->getColumnComment($columnDiff->fromColumn);
1358
    }
1359
1360
    public function getListTableMetadataSQL(string $table, ?string $schema = null) : string
1361
    {
1362
        if ($schema !== null) {
1363
            $table = $schema . '.' . $table;
1364
        }
1365
1366
        return sprintf(
1367
            <<<'SQL'
1368
SELECT obj_description(%s::regclass) AS table_comment;
1369
SQL
1370
            ,
1371
            $this->quoteStringLiteral($table)
1372
        );
1373
    }
1374
}
1375