Passed
Pull Request — master (#3645)
by Matthew
14:01
created

_getPortableTableColumnDefinition()   F

Complexity

Conditions 48
Paths > 20000

Size

Total Lines 150
Code Lines 111

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 101
CRAP Score 48.9111

Importance

Changes 0
Metric Value
eloc 111
c 0
b 0
f 0
dl 0
loc 150
ccs 101
cts 109
cp 0.9266
rs 0
cc 48
nc 258048
nop 1
crap 48.9111

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Doctrine\DBAL\Schema;
4
5
use Doctrine\DBAL\Exception\DriverException;
6
use Doctrine\DBAL\FetchMode;
7
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
8
use Doctrine\DBAL\Types\Type;
9
use Doctrine\DBAL\Types\Types;
10
use const CASE_LOWER;
11
use function array_change_key_case;
12
use function array_filter;
13
use function array_keys;
14
use function array_map;
15
use function array_shift;
16
use function assert;
17
use function explode;
18
use function implode;
19
use function in_array;
20
use function preg_match;
21
use function preg_replace;
22
use function sprintf;
23
use function str_replace;
24
use function strlen;
25
use function strpos;
26
use function strtolower;
27
use function trim;
28
29
/**
30
 * PostgreSQL Schema Manager.
31
 */
32
class PostgreSqlSchemaManager extends AbstractSchemaManager
33
{
34
    /** @var string[] */
35
    private $existingSchemaPaths;
36
37
    /**
38
     * Gets all the existing schema names.
39
     *
40
     * @return string[]
41
     */
42 662
    public function getSchemaNames()
43
    {
44 662
        $statement = $this->_conn->executeQuery("SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' AND nspname != 'information_schema'");
45
46 662
        return $statement->fetchAll(FetchMode::COLUMN);
47
    }
48
49
    /**
50
     * Returns an array of schema search paths.
51
     *
52
     * This is a PostgreSQL only function.
53
     *
54
     * @return string[]
55
     */
56 662
    public function getSchemaSearchPaths()
57
    {
58 662
        $params = $this->_conn->getParams();
59 662
        $schema = explode(',', $this->_conn->fetchColumn('SHOW search_path'));
0 ignored issues
show
Bug introduced by
It seems like $this->_conn->fetchColumn('SHOW search_path') can also be of type false; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

59
        $schema = explode(',', /** @scrutinizer ignore-type */ $this->_conn->fetchColumn('SHOW search_path'));
Loading history...
60
61 662
        if (isset($params['user'])) {
62 662
            $schema = str_replace('"$user"', $params['user'], $schema);
63
        }
64
65 662
        return array_map('trim', $schema);
66
    }
67
68
    /**
69
     * Gets names of all existing schemas in the current users search path.
70
     *
71
     * This is a PostgreSQL only function.
72
     *
73
     * @return string[]
74
     */
75 662
    public function getExistingSchemaSearchPaths()
76
    {
77 662
        if ($this->existingSchemaPaths === null) {
78 662
            $this->determineExistingSchemaSearchPaths();
79
        }
80
81 662
        return $this->existingSchemaPaths;
82
    }
83
84
    /**
85
     * Sets or resets the order of the existing schemas in the current search path of the user.
86
     *
87
     * This is a PostgreSQL only function.
88
     *
89
     * @return void
90
     */
91 662
    public function determineExistingSchemaSearchPaths()
92
    {
93 662
        $names = $this->getSchemaNames();
94 662
        $paths = $this->getSchemaSearchPaths();
95
96
        $this->existingSchemaPaths = array_filter($paths, static function ($v) use ($names) {
97 662
            return in_array($v, $names);
98 662
        });
99 662
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104 662
    public function dropDatabase($database)
105
    {
106
        try {
107 662
            parent::dropDatabase($database);
108 662
        } catch (DriverException $exception) {
109
            // If we have a SQLSTATE 55006, the drop database operation failed
110
            // because of active connections on the database.
111
            // To force dropping the database, we first have to close all active connections
112
            // on that database and issue the drop database operation again.
113 662
            if ($exception->getSQLState() !== '55006') {
114 662
                throw $exception;
115
            }
116
117 380
            assert($this->_platform instanceof PostgreSqlPlatform);
118
119 380
            $this->_execSql(
120
                [
121 380
                    $this->_platform->getDisallowDatabaseConnectionsSQL($database),
122 380
                    $this->_platform->getCloseActiveDatabaseConnectionsSQL($database),
123
                ]
124
            );
125
126 380
            parent::dropDatabase($database);
127
        }
128 380
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133 509
    protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
134
    {
135 509
        $onUpdate       = null;
136 509
        $onDelete       = null;
137 509
        $localColumns   = [];
138 509
        $foreignColumns = [];
139 509
        $foreignTable   = null;
140
141 509
        if (preg_match('(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) {
142 502
            $onUpdate = $match[1];
143
        }
144 509
        if (preg_match('(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) {
145 488
            $onDelete = $match[1];
146
        }
147
148 509
        if (preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values)) {
149
            // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get
150
            // the idea to trim them here.
151 509
            $localColumns   = array_map('trim', explode(',', $values[1]));
152 509
            $foreignColumns = array_map('trim', explode(',', $values[3]));
153 509
            $foreignTable   = $values[2];
154
        }
155
156 509
        return new ForeignKeyConstraint(
157 509
            $localColumns,
158
            $foreignTable,
159
            $foreignColumns,
160 509
            $tableForeignKey['conname'],
161 509
            ['onUpdate' => $onUpdate, 'onDelete' => $onDelete]
162
        );
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168
    protected function _getPortableTriggerDefinition($trigger)
169
    {
170
        return $trigger['trigger_name'];
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176 261
    protected function _getPortableViewDefinition($view)
177
    {
178 261
        return new View($view['schemaname'] . '.' . $view['viewname'], $view['definition']);
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    protected function _getPortableUserDefinition($user)
185
    {
186
        return [
187
            'user' => $user['usename'],
188
            'password' => $user['passwd'],
189
        ];
190
    }
191
192
    /**
193
     * {@inheritdoc}
194
     */
195 662
    protected function _getPortableTableDefinition($table)
196
    {
197 662
        $schemas     = $this->getExistingSchemaSearchPaths();
198 662
        $firstSchema = array_shift($schemas);
199
200 662
        if ($table['schema_name'] === $firstSchema) {
201 662
            return $table['table_name'];
202
        }
203
204 509
        return $table['schema_name'] . '.' . $table['table_name'];
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     *
210
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
211
     */
212 613
    protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
213
    {
214 613
        $buffer = [];
215 613
        foreach ($tableIndexes as $row) {
216 537
            $colNumbers    = array_map('intval', explode(' ', $row['indkey']));
217 537
            $columnNameSql = sprintf(
218
                'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC',
219 537
                $row['indrelid'],
220 537
                implode(' ,', $colNumbers)
221
            );
222
223 537
            $stmt         = $this->_conn->executeQuery($columnNameSql);
224 537
            $indexColumns = $stmt->fetchAll();
225
226
            // required for getting the order of the columns right.
227 537
            foreach ($colNumbers as $colNum) {
228 537
                foreach ($indexColumns as $colRow) {
229 537
                    if ($colNum !== $colRow['attnum']) {
230 446
                        continue;
231
                    }
232
233 537
                    $buffer[] = [
234 537
                        'key_name' => $row['relname'],
235 537
                        'column_name' => trim($colRow['attname']),
236 537
                        'non_unique' => ! $row['indisunique'],
237 537
                        'primary' => $row['indisprimary'],
238 537
                        'where' => $row['where'],
239
                    ];
240
                }
241
            }
242
        }
243
244 613
        return parent::_getPortableTableIndexesList($buffer, $tableName);
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250 380
    protected function _getPortableDatabaseDefinition($database)
251
    {
252 380
        return $database['datname'];
253
    }
254
255
    /**
256
     * {@inheritdoc}
257
     */
258 373
    protected function _getPortableSequencesList($sequences)
259
    {
260 373
        $sequenceDefinitions = [];
261
262 373
        foreach ($sequences as $sequence) {
263 373
            if ($sequence['schemaname'] !== 'public') {
264 373
                $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname'];
265
            } else {
266 373
                $sequenceName = $sequence['relname'];
267
            }
268
269 373
            $sequenceDefinitions[$sequenceName] = $sequence;
270
        }
271
272 373
        $list = [];
273
274 373
        foreach ($this->filterAssetNames(array_keys($sequenceDefinitions)) as $sequenceName) {
275 373
            $list[] = $this->_getPortableSequenceDefinition($sequenceDefinitions[$sequenceName]);
276
        }
277
278 373
        return $list;
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284 352
    protected function getPortableNamespaceDefinition(array $namespace)
285
    {
286 352
        return $namespace['nspname'];
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292 373
    protected function _getPortableSequenceDefinition($sequence)
293
    {
294 373
        if ($sequence['schemaname'] !== 'public') {
295 373
            $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname'];
296
        } else {
297 373
            $sequenceName = $sequence['relname'];
298
        }
299
300 373
        if (! isset($sequence['increment_by'], $sequence['min_value'])) {
301
            /** @var string[] $data */
302 267
            $data = $this->_conn->fetchAssoc('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName));
303
304 267
            $sequence += $data;
305
        }
306
307 373
        return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']);
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313 613
    protected function _getPortableTableColumnDefinition($tableColumn)
314
    {
315 613
        $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
316
317 613
        if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') {
318
            // get length from varchar definition
319 599
            $length                = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']);
320 599
            $tableColumn['length'] = $length;
321
        }
322
323 613
        $matches = [];
324
325 613
        $autoincrement = false;
326 613
        if (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) {
327 530
            $tableColumn['sequence'] = $matches[1];
328 530
            $tableColumn['default']  = null;
329 530
            $autoincrement           = true;
330
        }
331
332 613
        if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches)) {
333 599
            $tableColumn['default'] = $matches[1];
334 613
        } elseif (preg_match('/^NULL::/', $tableColumn['default'])) {
335 599
            $tableColumn['default'] = null;
336
        }
337
338 613
        $length = $tableColumn['length'] ?? null;
339 613
        if ($length === '-1' && isset($tableColumn['atttypmod'])) {
340
            $length = $tableColumn['atttypmod'] - 4;
341
        }
342 613
        if ((int) $length <= 0) {
343 613
            $length = null;
344
        }
345 613
        $fixed = null;
346
347 613
        if (! isset($tableColumn['name'])) {
348 613
            $tableColumn['name'] = '';
349
        }
350
351 613
        $precision = null;
352 613
        $scale     = null;
353 613
        $jsonb     = null;
354
355 613
        $dbType = strtolower($tableColumn['type']);
356 613
        if (strlen($tableColumn['domain_type']) && ! $this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])) {
357 537
            $dbType                       = strtolower($tableColumn['domain_type']);
358 537
            $tableColumn['complete_type'] = $tableColumn['domain_complete_type'];
359
        }
360
361 613
        $type                   = $this->_platform->getDoctrineTypeMapping($dbType);
362 613
        $type                   = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
363 613
        $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
364
365
        switch ($dbType) {
366 613
            case 'smallint':
367 613
            case 'int2':
368 429
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
369 429
                $length                 = null;
370 429
                break;
371 613
            case 'int':
372 613
            case 'int4':
373 606
            case 'integer':
374 613
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
375 613
                $length                 = null;
376 613
                break;
377 606
            case 'bigint':
378 606
            case 'int8':
379 429
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
380 429
                $length                 = null;
381 429
                break;
382 606
            case 'bool':
383 599
            case 'boolean':
384 606
                if ($tableColumn['default'] === 'true') {
385
                    $tableColumn['default'] = true;
386
                }
387
388 606
                if ($tableColumn['default'] === 'false') {
389 606
                    $tableColumn['default'] = false;
390
                }
391
392 606
                $length = null;
393 606
                break;
394 599
            case 'text':
395 599
            case '_varchar':
396 599
            case 'varchar':
397 599
                $tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']);
398 599
                $fixed                  = false;
399 599
                break;
400 537
            case 'interval':
401
                $fixed = false;
402
                break;
403 537
            case 'char':
404 537
            case 'bpchar':
405 453
                $fixed = true;
406 453
                break;
407 537
            case 'float':
408 537
            case 'float4':
409 537
            case 'float8':
410 537
            case 'double':
411 537
            case 'double precision':
412 537
            case 'real':
413 537
            case 'decimal':
414 537
            case 'money':
415 537
            case 'numeric':
416 537
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
417
418 537
                if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) {
419 537
                    $precision = $match[1];
420 537
                    $scale     = $match[2];
421 537
                    $length    = null;
422
                }
423 537
                break;
424 537
            case 'year':
425
                $length = null;
426
                break;
427
428
            // PostgreSQL 9.4+ only
429 537
            case 'jsonb':
430 316
                $jsonb = true;
431 316
                break;
432
        }
433
434 613
        if ($tableColumn['default'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) {
435 599
            $tableColumn['default'] = $match[1];
436
        }
437
438
        $options = [
439 613
            'length'        => $length,
440 613
            'notnull'       => (bool) $tableColumn['isnotnull'],
441 613
            'default'       => $tableColumn['default'],
442 613
            'precision'     => $precision,
443 613
            'scale'         => $scale,
444 613
            'fixed'         => $fixed,
445
            'unsigned'      => false,
446 613
            'autoincrement' => $autoincrement,
447 613
            'comment'       => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
448 226
                ? $tableColumn['comment']
449
                : null,
450
        ];
451
452 613
        $column = new Column($tableColumn['field'], Type::getType($type), $options);
453
454 613
        if (isset($tableColumn['collation']) && ! empty($tableColumn['collation'])) {
455
            $column->setPlatformOption('collation', $tableColumn['collation']);
456
        }
457
458 613
        if (in_array($column->getType()->getName(), [Types::JSON_ARRAY, Types::JSON], true)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Types::JSON_ARRAY has been deprecated: json_array type is deprecated, use {@see DefaultTypes::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

458
        if (in_array($column->getType()->getName(), [/** @scrutinizer ignore-deprecated */ Types::JSON_ARRAY, Types::JSON], true)) {

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...
459 343
            $column->setPlatformOption('jsonb', $jsonb);
460
        }
461
462 613
        return $column;
463
    }
464
465
    /**
466
     * PostgreSQL 9.4 puts parentheses around negative numeric default values that need to be stripped eventually.
467
     *
468
     * @param mixed $defaultValue
469
     *
470
     * @return mixed
471
     */
472 613
    private function fixVersion94NegativeNumericDefaultValue($defaultValue)
473
    {
474 613
        if (strpos($defaultValue, '(') === 0) {
475 184
            return trim($defaultValue, '()');
476
        }
477
478 613
        return $defaultValue;
479
    }
480
481
    /**
482
     * Parses a default value expression as given by PostgreSQL
483
     */
484 599
    private function parseDefaultExpression(?string $default) : ?string
485
    {
486 599
        if ($default === null) {
487 599
            return $default;
488
        }
489
490 599
        return str_replace("''", "'", $default);
491
    }
492
493 613
    public function listTableDetails($tableName) : Table
494
    {
495 613
        $table = parent::listTableDetails($tableName);
496
497
        /** @var PostgreSqlPlatform $platform */
498 613
        $platform = $this->_platform;
499 613
        $sql      = $platform->getListTableMetadataSQL($tableName);
500
501 613
        $tableOptions = $this->_conn->fetchAssoc($sql);
502
503 613
        $table->addOption('comment', $tableOptions['table_comment']);
504
505 613
        return $table;
506
    }
507
}
508