Failed Conditions
Pull Request — master (#3546)
by Sergei
14:16
created

_getPortableTableColumnDefinition()   F

Complexity

Conditions 48
Paths > 20000

Size

Total Lines 150
Code Lines 111

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 76
CRAP Score 111.9661

Importance

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

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 703
    public function getSchemaNames()
43
    {
44 703
        $statement = $this->_conn->executeQuery("SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' AND nspname != 'information_schema'");
45
46 703
        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 703
    public function getSchemaSearchPaths()
57
    {
58 703
        $params = $this->_conn->getParams();
59 703
        $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 703
        if (isset($params['user'])) {
62 703
            $schema = str_replace('"$user"', $params['user'], $schema);
63
        }
64
65 703
        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 703
    public function getExistingSchemaSearchPaths()
76
    {
77 703
        if ($this->existingSchemaPaths === null) {
78 703
            $this->determineExistingSchemaSearchPaths();
79
        }
80
81 703
        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 703
    public function determineExistingSchemaSearchPaths()
92
    {
93 703
        $names = $this->getSchemaNames();
94 703
        $paths = $this->getSchemaSearchPaths();
95
96
        $this->existingSchemaPaths = array_filter($paths, static function ($v) use ($names) {
97 703
            return in_array($v, $names);
98 703
        });
99 703
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104 703
    public function dropDatabase($database)
105
    {
106
        try {
107 703
            parent::dropDatabase($database);
108 703
        } 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 703
            if ($exception->getSQLState() !== '55006') {
114 703
                throw $exception;
115
            }
116
117 374
            assert($this->_platform instanceof PostgreSqlPlatform);
118
119 374
            $this->_execSql(
120
                [
121 374
                    $this->_platform->getDisallowDatabaseConnectionsSQL($database),
122 374
                    $this->_platform->getCloseActiveDatabaseConnectionsSQL($database),
123
                ]
124
            );
125
126 374
            parent::dropDatabase($database);
127
        }
128 374
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133 503
    protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
134
    {
135 503
        $onUpdate       = null;
136 503
        $onDelete       = null;
137 503
        $localColumns   = [];
138 503
        $foreignColumns = [];
139 503
        $foreignTable   = null;
140
141 503
        if (preg_match('(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) {
142 496
            $onUpdate = $match[1];
143
        }
144 503
        if (preg_match('(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) {
145 482
            $onDelete = $match[1];
146
        }
147
148 503
        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 503
            $localColumns   = array_map('trim', explode(',', $values[1]));
152 503
            $foreignColumns = array_map('trim', explode(',', $values[3]));
153 503
            $foreignTable   = $values[2];
154
        }
155
156 503
        return new ForeignKeyConstraint(
157 503
            $localColumns,
158
            $foreignTable,
159
            $foreignColumns,
160 503
            $tableForeignKey['conname'],
161 503
            ['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 255
    protected function _getPortableViewDefinition($view)
177
    {
178 255
        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 703
    protected function _getPortableTableDefinition($table)
196
    {
197 703
        $schemas     = $this->getExistingSchemaSearchPaths();
198 703
        $firstSchema = array_shift($schemas);
199
200 703
        if ($table['schema_name'] === $firstSchema) {
201 703
            return $table['table_name'];
202
        }
203
204 503
        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 654
    protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
213
    {
214 654
        $buffer = [];
215 654
        foreach ($tableIndexes as $row) {
216 531
            $colNumbers    = array_map('intval', explode(' ', $row['indkey']));
217 531
            $columnNameSql = sprintf(
218
                'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC',
219 531
                $row['indrelid'],
220 531
                implode(' ,', $colNumbers)
221
            );
222
223 531
            $stmt         = $this->_conn->executeQuery($columnNameSql);
224 531
            $indexColumns = $stmt->fetchAll();
225
226
            // required for getting the order of the columns right.
227 531
            foreach ($colNumbers as $colNum) {
228 531
                foreach ($indexColumns as $colRow) {
229 531
                    if ($colNum !== $colRow['attnum']) {
230 440
                        continue;
231
                    }
232
233 531
                    $buffer[] = [
234 531
                        'key_name' => $row['relname'],
235 531
                        'column_name' => trim($colRow['attname']),
236 531
                        'non_unique' => ! $row['indisunique'],
237 531
                        'primary' => $row['indisprimary'],
238 531
                        'where' => $row['where'],
239
                    ];
240
                }
241
            }
242
        }
243
244 654
        return parent::_getPortableTableIndexesList($buffer, $tableName);
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250 374
    protected function _getPortableDatabaseDefinition($database)
251
    {
252 374
        return $database['datname'];
253
    }
254
255
    /**
256
     * {@inheritdoc}
257
     */
258 367
    protected function _getPortableSequencesList($sequences)
259
    {
260 367
        $sequenceDefinitions = [];
261
262 367
        foreach ($sequences as $sequence) {
263 367
            if ($sequence['schemaname'] !== 'public') {
264 367
                $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname'];
265
            } else {
266 367
                $sequenceName = $sequence['relname'];
267
            }
268
269 367
            $sequenceDefinitions[$sequenceName] = $sequence;
270
        }
271
272 367
        $list = [];
273
274 367
        foreach ($this->filterAssetNames(array_keys($sequenceDefinitions)) as $sequenceName) {
275 367
            $list[] = $this->_getPortableSequenceDefinition($sequenceDefinitions[$sequenceName]);
276
        }
277
278 367
        return $list;
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284 346
    protected function getPortableNamespaceDefinition(array $namespace)
285
    {
286 346
        return $namespace['nspname'];
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292 367
    protected function _getPortableSequenceDefinition($sequence)
293
    {
294 367
        if ($sequence['schemaname'] !== 'public') {
295 367
            $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname'];
296
        } else {
297 367
            $sequenceName = $sequence['relname'];
298
        }
299
300 367
        if (! isset($sequence['increment_by'], $sequence['min_value'])) {
301
            /** @var string[] $data */
302 262
            $data = $this->_conn->fetchAssoc('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName));
303
304 262
            $sequence += $data;
305
        }
306
307 367
        return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']);
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313 654
    protected function _getPortableTableColumnDefinition($tableColumn)
314
    {
315 654
        $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
316
317 654
        if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') {
318
            // get length from varchar definition
319 640
            $length                = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']);
320 640
            $tableColumn['length'] = $length;
321
        }
322
323 654
        $matches = [];
324
325 654
        $autoincrement = false;
326 654
        if (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) {
327 524
            $tableColumn['sequence'] = $matches[1];
328 524
            $tableColumn['default']  = null;
329 524
            $autoincrement           = true;
330
        }
331
332 654
        if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches)) {
333 640
            $tableColumn['default'] = $matches[1];
334 654
        } elseif (preg_match('/^NULL::/', $tableColumn['default'])) {
335 640
            $tableColumn['default'] = null;
336
        }
337
338 654
        $length = $tableColumn['length'] ?? null;
339 654
        if ($length === '-1' && isset($tableColumn['atttypmod'])) {
340
            $length = $tableColumn['atttypmod'] - 4;
341
        }
342 654
        if ((int) $length <= 0) {
343 654
            $length = null;
344
        }
345 654
        $fixed = null;
346
347 654
        if (! isset($tableColumn['name'])) {
348 654
            $tableColumn['name'] = '';
349
        }
350
351 654
        $precision = null;
352 654
        $scale     = null;
353 654
        $jsonb     = null;
354
355 654
        $dbType = strtolower($tableColumn['type']);
356 654
        if (strlen($tableColumn['domain_type']) && ! $this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])) {
357 531
            $dbType                       = strtolower($tableColumn['domain_type']);
358 531
            $tableColumn['complete_type'] = $tableColumn['domain_complete_type'];
359
        }
360
361 654
        $type                   = $this->_platform->getDoctrineTypeMapping($dbType);
362 654
        $type                   = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
363 654
        $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
364
365 654
        switch ($dbType) {
366
            case 'smallint':
367
            case 'int2':
368 423
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
369 423
                $length                 = null;
370 423
                break;
371
            case 'int':
372
            case 'int4':
373
            case 'integer':
374 654
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
375 654
                $length                 = null;
376 654
                break;
377
            case 'bigint':
378
            case 'int8':
379 423
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
380 423
                $length                 = null;
381 423
                break;
382
            case 'bool':
383
            case 'boolean':
384 647
                if ($tableColumn['default'] === 'true') {
385
                    $tableColumn['default'] = true;
386
                }
387
388 647
                if ($tableColumn['default'] === 'false') {
389 647
                    $tableColumn['default'] = false;
390
                }
391
392 647
                $length = null;
393 647
                break;
394
            case 'text':
395
            case '_varchar':
396
            case 'varchar':
397 640
                $tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']);
398 640
                $fixed                  = false;
399 640
                break;
400
            case 'interval':
401
                $fixed = false;
402
                break;
403
            case 'char':
404
            case 'bpchar':
405 447
                $fixed = true;
406 447
                break;
407
            case 'float':
408
            case 'float4':
409
            case 'float8':
410
            case 'double':
411
            case 'double precision':
412
            case 'real':
413
            case 'decimal':
414
            case 'money':
415
            case 'numeric':
416 531
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
417
418 531
                if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) {
419 531
                    $precision = $match[1];
420 531
                    $scale     = $match[2];
421 531
                    $length    = null;
422
                }
423 531
                break;
424
            case 'year':
425
                $length = null;
426
                break;
427
428
            // PostgreSQL 9.4+ only
429
            case 'jsonb':
430 311
                $jsonb = true;
431 311
                break;
432
        }
433
434 654
        if ($tableColumn['default'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) {
435 640
            $tableColumn['default'] = $match[1];
436
        }
437
438
        $options = [
439 654
            'length'        => $length,
440 654
            'notnull'       => (bool) $tableColumn['isnotnull'],
441 654
            'default'       => $tableColumn['default'],
442 654
            'precision'     => $precision,
443 654
            'scale'         => $scale,
444 654
            'fixed'         => $fixed,
445
            'unsigned'      => false,
446 654
            'autoincrement' => $autoincrement,
447 654
            'comment'       => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
448 220
                ? $tableColumn['comment']
449
                : null,
450
        ];
451
452 654
        $column = new Column($tableColumn['field'], Type::getType($type), $options);
453
454 654
        if (isset($tableColumn['collation']) && ! empty($tableColumn['collation'])) {
455
            $column->setPlatformOption('collation', $tableColumn['collation']);
456
        }
457
458 654
        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 341
            $column->setPlatformOption('jsonb', $jsonb);
460
        }
461
462 654
        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 654
    private function fixVersion94NegativeNumericDefaultValue($defaultValue)
473
    {
474 654
        if (strpos($defaultValue, '(') === 0) {
475 183
            return trim($defaultValue, '()');
476
        }
477
478 654
        return $defaultValue;
479
    }
480
481
    /**
482
     * Parses a default value expression as given by PostgreSQL
483
     */
484 640
    private function parseDefaultExpression(?string $default) : ?string
485
    {
486 640
        if ($default === null) {
487 640
            return $default;
488
        }
489
490 640
        return str_replace("''", "'", $default);
491
    }
492
}
493