Failed Conditions
Pull Request — master (#2893)
by Šimon
38:04
created

_getPortableTableColumnDefinition()   F

Complexity

Conditions 49
Paths > 20000

Size

Total Lines 151
Code Lines 110

Duplication

Lines 15
Ratio 9.93 %

Code Coverage

Tests 0
CRAP Score 2450

Importance

Changes 0
Metric Value
dl 15
loc 151
rs 2
c 0
b 0
f 0
ccs 0
cts 107
cp 0
cc 49
eloc 110
nc 688128
nop 1
crap 2450

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Schema;
21
22
use Doctrine\DBAL\Exception\DriverException;
23
use Doctrine\DBAL\Types\Type;
24
25
/**
26
 * PostgreSQL Schema Manager.
27
 *
28
 * @author Konsta Vesterinen <[email protected]>
29
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
30
 * @author Benjamin Eberlei <[email protected]>
31
 * @since  2.0
32
 */
33
class PostgreSqlSchemaManager extends AbstractSchemaManager
34
{
35
    /**
36
     * @var array
37
     */
38
    private $existingSchemaPaths;
39
40
    /**
41
     * Gets all the existing schema names.
42
     *
43
     * @return array
44
     */
45
    public function getSchemaNames()
46
    {
47
        $rows = $this->_conn->fetchAll("SELECT nspname as schema_name FROM pg_namespace WHERE nspname !~ '^pg_.*' and nspname != 'information_schema'");
48
49
        return array_map(function ($v) { return $v['schema_name']; }, $rows);
50
    }
51
52
    /**
53
     * Returns an array of schema search paths.
54
     *
55
     * This is a PostgreSQL only function.
56
     *
57
     * @return array
58
     */
59
    public function getSchemaSearchPaths()
60
    {
61
        $params = $this->_conn->getParams();
62
        $schema = explode(",", $this->_conn->fetchColumn('SHOW search_path'));
63
64
        if (isset($params['user'])) {
65
            $schema = str_replace('"$user"', $params['user'], $schema);
66
        }
67
68
        return array_map('trim', $schema);
69
    }
70
71
    /**
72
     * Gets names of all existing schemas in the current users search path.
73
     *
74
     * This is a PostgreSQL only function.
75
     *
76
     * @return array
77
     */
78
    public function getExistingSchemaSearchPaths()
79
    {
80
        if ($this->existingSchemaPaths === null) {
81
            $this->determineExistingSchemaSearchPaths();
82
        }
83
84
        return $this->existingSchemaPaths;
85
    }
86
87
    /**
88
     * Sets or resets the order of the existing schemas in the current search path of the user.
89
     *
90
     * This is a PostgreSQL only function.
91
     *
92
     * @return void
93
     */
94
    public function determineExistingSchemaSearchPaths()
95
    {
96
        $names = $this->getSchemaNames();
97
        $paths = $this->getSchemaSearchPaths();
98
99
        $this->existingSchemaPaths = array_filter($paths, function ($v) use ($names) {
100
            return in_array($v, $names);
101
        });
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function dropDatabase($database)
108
    {
109
        try {
110
            parent::dropDatabase($database);
111
        } catch (DriverException $exception) {
112
            // If we have a SQLSTATE 55006, the drop database operation failed
113
            // because of active connections on the database.
114
            // To force dropping the database, we first have to close all active connections
115
            // on that database and issue the drop database operation again.
116
            if ($exception->getSQLState() !== '55006') {
117
                throw $exception;
118
            }
119
120
            $this->_execSql(
121
                [
122
                    $this->_platform->getDisallowDatabaseConnectionsSQL($database),
0 ignored issues
show
Bug introduced by
The method getDisallowDatabaseConnectionsSQL() does not exist on Doctrine\DBAL\Platforms\AbstractPlatform. It seems like you code against a sub-type of Doctrine\DBAL\Platforms\AbstractPlatform such as Doctrine\DBAL\Platforms\PostgreSqlPlatform. ( Ignorable by Annotation )

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

122
                    $this->_platform->/** @scrutinizer ignore-call */ 
123
                                      getDisallowDatabaseConnectionsSQL($database),
Loading history...
123
                    $this->_platform->getCloseActiveDatabaseConnectionsSQL($database),
0 ignored issues
show
Bug introduced by
The method getCloseActiveDatabaseConnectionsSQL() does not exist on Doctrine\DBAL\Platforms\AbstractPlatform. It seems like you code against a sub-type of Doctrine\DBAL\Platforms\AbstractPlatform such as Doctrine\DBAL\Platforms\PostgreSqlPlatform. ( Ignorable by Annotation )

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

123
                    $this->_platform->/** @scrutinizer ignore-call */ 
124
                                      getCloseActiveDatabaseConnectionsSQL($database),
Loading history...
124
                ]
125
            );
126
127
            parent::dropDatabase($database);
128
        }
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
135
    {
136
        $onUpdate = null;
137
        $onDelete = null;
138
139
        if (preg_match('(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) {
140
            $onUpdate = $match[1];
141
        }
142
        if (preg_match('(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) {
143
            $onDelete = $match[1];
144
        }
145
146
        if (preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values)) {
147
            // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get
148
            // the idea to trim them here.
149
            $localColumns = array_map('trim', explode(",", $values[1]));
150
            $foreignColumns = array_map('trim', explode(",", $values[3]));
151
            $foreignTable = $values[2];
152
        }
153
154
        return new ForeignKeyConstraint(
155
            $localColumns, $foreignTable, $foreignColumns, $tableForeignKey['conname'],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $foreignColumns does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $localColumns does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $foreignTable does not seem to be defined for all execution paths leading up to this point.
Loading history...
156
            ['onUpdate' => $onUpdate, 'onDelete' => $onDelete]
157
        );
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    protected function _getPortableTriggerDefinition($trigger)
164
    {
165
        return $trigger['trigger_name'];
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    protected function _getPortableViewDefinition($view)
172
    {
173
        return new View($view['schemaname'].'.'.$view['viewname'], $view['definition']);
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179
    protected function _getPortableUserDefinition($user)
180
    {
181
        return [
182
            'user' => $user['usename'],
183
            'password' => $user['passwd']
184
        ];
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    protected function _getPortableTableDefinition($table)
191
    {
192
        $schemas = $this->getExistingSchemaSearchPaths();
193
        $firstSchema = array_shift($schemas);
194
195
        if ($table['schema_name'] == $firstSchema) {
196
            return $table['table_name'];
197
        }
198
199
        return $table['schema_name'] . "." . $table['table_name'];
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     *
205
     * @license New BSD License
206
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
207
     */
208
    protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
209
    {
210
        $buffer = [];
211
        foreach ($tableIndexes as $row) {
212
            $colNumbers = explode(' ', $row['indkey']);
213
            $colNumbersSql = 'IN (' . join(' ,', $colNumbers) . ' )';
214
            $columnNameSql = "SELECT attnum, attname FROM pg_attribute
215
                WHERE attrelid={$row['indrelid']} AND attnum $colNumbersSql ORDER BY attnum ASC;";
216
217
            $stmt = $this->_conn->executeQuery($columnNameSql);
218
            $indexColumns = $stmt->fetchAll();
219
220
            // required for getting the order of the columns right.
221
            foreach ($colNumbers as $colNum) {
222
                foreach ($indexColumns as $colRow) {
223
                    if ($colNum == $colRow['attnum']) {
224
                        $buffer[] = [
225
                            'key_name' => $row['relname'],
226
                            'column_name' => trim($colRow['attname']),
227
                            'non_unique' => !$row['indisunique'],
228
                            'primary' => $row['indisprimary'],
229
                            'where' => $row['where'],
230
                        ];
231
                    }
232
                }
233
            }
234
        }
235
236
        return parent::_getPortableTableIndexesList($buffer, $tableName);
237
    }
238
239
    /**
240
     * {@inheritdoc}
241
     */
242
    protected function _getPortableDatabaseDefinition($database)
243
    {
244
        return $database['datname'];
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250
    protected function _getPortableSequencesList($sequences)
251
    {
252
        $sequenceDefinitions = [];
253
254
        foreach ($sequences as $sequence) {
255 View Code Duplication
            if ($sequence['schemaname'] != 'public') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
256
                $sequenceName = $sequence['schemaname'] . "." . $sequence['relname'];
257
            } else {
258
                $sequenceName = $sequence['relname'];
259
            }
260
261
            $sequenceDefinitions[$sequenceName] = $sequence;
262
        }
263
264
        $list = [];
265
266
        foreach ($this->filterAssetNames(array_keys($sequenceDefinitions)) as $sequenceName) {
267
            $list[] = $this->_getPortableSequenceDefinition($sequenceDefinitions[$sequenceName]);
268
        }
269
270
        return $list;
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276
    protected function getPortableNamespaceDefinition(array $namespace)
277
    {
278
        return $namespace['nspname'];
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284
    protected function _getPortableSequenceDefinition($sequence)
285
    {
286 View Code Duplication
        if ($sequence['schemaname'] !== 'public') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
287
            $sequenceName = $sequence['schemaname'] . "." . $sequence['relname'];
288
        } else {
289
            $sequenceName = $sequence['relname'];
290
        }
291
292
        if ( ! isset($sequence['increment_by'], $sequence['min_value'])) {
293
            $data     = $this->_conn->fetchAssoc('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName));
294
            $sequence = array_merge($sequence, $data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type boolean; however, parameter $array2 of array_merge() does only seem to accept null|array, 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

294
            $sequence = array_merge($sequence, /** @scrutinizer ignore-type */ $data);
Loading history...
295
        }
296
297
        return new Sequence($sequenceName, $sequence['increment_by'], $sequence['min_value']);
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303
    protected function _getPortableTableColumnDefinition($tableColumn)
304
    {
305
        $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
306
307
        if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') {
308
            // get length from varchar definition
309
            $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']);
310
            $tableColumn['length'] = $length;
311
        }
312
313
        $matches = [];
314
315
        $autoincrement = false;
316
        if (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) {
317
            $tableColumn['sequence'] = $matches[1];
318
            $tableColumn['default'] = null;
319
            $autoincrement = true;
320
        }
321
322
        if (preg_match("/^['(](.*)[')]::.*$/", $tableColumn['default'], $matches)) {
323
            $tableColumn['default'] = $matches[1];
324
        }
325
326
        if (stripos($tableColumn['default'], 'NULL') === 0) {
327
            $tableColumn['default'] = null;
328
        }
329
330
        $length = (isset($tableColumn['length'])) ? $tableColumn['length'] : null;
331
        if ($length == '-1' && isset($tableColumn['atttypmod'])) {
332
            $length = $tableColumn['atttypmod'] - 4;
333
        }
334
        if ((int) $length <= 0) {
335
            $length = null;
336
        }
337
        $fixed = null;
338
339
        if (!isset($tableColumn['name'])) {
340
            $tableColumn['name'] = '';
341
        }
342
343
        $precision = null;
344
        $scale = null;
345
        $jsonb = null;
346
347
        $dbType = strtolower($tableColumn['type']);
348
        if (strlen($tableColumn['domain_type']) && !$this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])) {
349
            $dbType = strtolower($tableColumn['domain_type']);
350
            $tableColumn['complete_type'] = $tableColumn['domain_complete_type'];
351
        }
352
353
        $type = $this->_platform->getDoctrineTypeMapping($dbType);
354
        $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
355
        $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
356
357
        switch ($dbType) {
358
            case 'smallint':
359 View Code Duplication
            case 'int2':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
360
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
361
                $length = null;
362
                break;
363
            case 'int':
364
            case 'int4':
365 View Code Duplication
            case 'integer':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
366
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
367
                $length = null;
368
                break;
369
            case 'bigint':
370 View Code Duplication
            case 'int8':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
371
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
372
                $length = null;
373
                break;
374
            case 'bool':
375
            case 'boolean':
376
                if ($tableColumn['default'] === 'true') {
377
                    $tableColumn['default'] = true;
378
                }
379
380
                if ($tableColumn['default'] === 'false') {
381
                    $tableColumn['default'] = false;
382
                }
383
384
                $length = null;
385
                break;
386
            case 'text':
387
                $fixed = false;
388
                break;
389
            case 'varchar':
390
            case 'interval':
391
            case '_varchar':
392
                $fixed = false;
393
                break;
394
            case 'char':
395
            case 'bpchar':
396
                $fixed = true;
397
                break;
398
            case 'float':
399
            case 'float4':
400
            case 'float8':
401
            case 'double':
402
            case 'double precision':
403
            case 'real':
404
            case 'decimal':
405
            case 'money':
406
            case 'numeric':
407
                $tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
408
409
                if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) {
410
                    $precision = $match[1];
411
                    $scale = $match[2];
412
                    $length = null;
413
                }
414
                break;
415
            case 'year':
416
                $length = null;
417
                break;
418
419
            // PostgreSQL 9.4+ only
420
            case 'jsonb':
421
                $jsonb = true;
422
                break;
423
        }
424
425
        if ($tableColumn['default'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) {
426
            $tableColumn['default'] = $match[1];
427
        }
428
429
        $options = [
430
            'length'        => $length,
431
            'notnull'       => (bool) $tableColumn['isnotnull'],
432
            'default'       => $tableColumn['default'],
433
            'precision'     => $precision,
434
            'scale'         => $scale,
435
            'fixed'         => $fixed,
436
            'unsigned'      => false,
437
            'autoincrement' => $autoincrement,
438
            'comment'       => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
439
                ? $tableColumn['comment']
440
                : null,
441
        ];
442
443
        $column = new Column($tableColumn['field'], Type::getType($type), $options);
444
445 View Code Duplication
        if (isset($tableColumn['collation']) && !empty($tableColumn['collation'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
446
            $column->setPlatformOption('collation', $tableColumn['collation']);
447
        }
448
449
        if (in_array($column->getType()->getName(), [Type::JSON_ARRAY, Type::JSON], true)) {
450
            $column->setPlatformOption('jsonb', $jsonb);
451
        }
452
453
        return $column;
454
    }
455
456
    /**
457
     * PostgreSQL 9.4 puts parentheses around negative numeric default values that need to be stripped eventually.
458
     *
459
     * @param mixed $defaultValue
460
     *
461
     * @return mixed
462
     */
463
    private function fixVersion94NegativeNumericDefaultValue($defaultValue)
464
    {
465
        if (strpos($defaultValue, '(') === 0) {
466
            return trim($defaultValue, '()');
467
        }
468
469
        return $defaultValue;
470
    }
471
}
472