Failed Conditions
Pull Request — develop (#3348)
by Sergei
65:23
created

_getPortableTableColumnDefinition()   D

Complexity

Conditions 10
Paths 384

Size

Total Lines 56
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 10.0158

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 56
ccs 35
cts 37
cp 0.9459
rs 4.5333
c 0
b 0
f 0
cc 10
nc 384
nop 1
crap 10.0158

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
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Schema;
6
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\DriverManager;
9
use Doctrine\DBAL\FetchMode;
10
use Doctrine\DBAL\Types\StringType;
11
use Doctrine\DBAL\Types\TextType;
12
use Doctrine\DBAL\Types\Type;
13
use const CASE_LOWER;
14
use function array_change_key_case;
15
use function array_reverse;
16
use function array_values;
17
use function count;
18
use function file_exists;
19
use function preg_match;
20
use function preg_match_all;
21
use function preg_quote;
22
use function preg_replace;
23
use function rtrim;
24
use function sprintf;
25
use function strpos;
26
use function strtolower;
27
use function unlink;
28
use function usort;
29
30
/**
31
 * Sqlite SchemaManager.
32
 */
33
class SqliteSchemaManager extends AbstractSchemaManager
34
{
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function dropDatabase(string $database) : void
39 86
    {
40
        if (! file_exists($database)) {
41 86
            return;
42 85
        }
43
44
        unlink($database);
45 86
    }
46 86
47
    /**
48
     * {@inheritdoc}
49
     */
50
    public function createDatabase(string $database) : void
51 86
    {
52
        $params  = $this->_conn->getParams();
53 86
        $driver  = $params['driver'];
54 86
        $options = [
55
            'driver' => $driver,
56 86
            'path' => $database,
57 86
        ];
58
        $conn    = DriverManager::getConnection($options);
59 86
        $conn->connect();
60 86
        $conn->close();
61 86
    }
62 86
63
    /**
64
     * {@inheritdoc}
65
     */
66
    public function renameTable(string $name, string $newName) : void
67 84
    {
68
        $tableDiff            = new TableDiff($name);
69 84
        $tableDiff->fromTable = $this->listTableDetails($name);
70 84
        $tableDiff->newName   = $newName;
71 84
        $this->alterTable($tableDiff);
72 84
    }
73 84
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) : void
78
    {
79
        $tableDiff                     = $this->getTableDiffForAlterForeignKey($table);
80
        $tableDiff->addedForeignKeys[] = $foreignKey;
81
82
        $this->alterTable($tableDiff);
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88
    public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) : void
89
    {
90
        $tableDiff                       = $this->getTableDiffForAlterForeignKey($table);
91
        $tableDiff->changedForeignKeys[] = $foreignKey;
92
93
        $this->alterTable($tableDiff);
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function dropForeignKey($foreignKey, $table) : void
100
    {
101
        $tableDiff                       = $this->getTableDiffForAlterForeignKey($table);
102
        $tableDiff->removedForeignKeys[] = $foreignKey;
103
104
        $this->alterTable($tableDiff);
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function listTableForeignKeys(string $table, ?string $database = null) : array
111 83
    {
112
        if ($database === null) {
113 83
            $database = $this->_conn->getDatabase();
114 83
        }
115
        $sql              = $this->_platform->getListTableForeignKeysSQL($table, $database);
116 83
        $tableForeignKeys = $this->_conn->fetchAll($sql);
117 83
118
        if (! empty($tableForeignKeys)) {
119 83
            $createSql = $this->getCreateTableSQL($table);
120 83
121
            if ($createSql !== null && preg_match_all(
122 83
                '#
123
                    (?:CONSTRAINT\s+([^\s]+)\s+)?
124
                    (?:FOREIGN\s+KEY[^\)]+\)\s*)?
125
                    REFERENCES\s+[^\s]+\s+(?:\([^\)]+\))?
126
                    (?:
127
                        [^,]*?
128
                        (NOT\s+DEFERRABLE|DEFERRABLE)
129
                        (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?
130
                    )?#isx',
131
                $createSql,
132 83
                $match
133 83
            )) {
134
                $names      = array_reverse($match[1]);
135 83
                $deferrable = array_reverse($match[2]);
136 83
                $deferred   = array_reverse($match[3]);
137 83
            } else {
138
                $names = $deferrable = $deferred = [];
139
            }
140
141
            foreach ($tableForeignKeys as $key => $value) {
142 83
                $id                                        = $value['id'];
143 83
                $tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && $names[$id] !== '' ? $names[$id] : $id;
144 83
                $tableForeignKeys[$key]['deferrable']      = isset($deferrable[$id]) && strtolower($deferrable[$id]) === 'deferrable';
145 83
                $tableForeignKeys[$key]['deferred']        = isset($deferred[$id]) && strtolower($deferred[$id]) === 'deferred';
146 83
            }
147
        }
148
149
        return $this->_getPortableTableForeignKeysList($tableForeignKeys);
150 83
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    protected function _getPortableTableDefinition($table) : string
156 144
    {
157
        return $table['name'];
158 144
    }
159
160
    /**
161
     * {@inheritdoc}
162
     *
163
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
164
     */
165
    protected function _getPortableTableIndexesList(array $tableIndexRows, string $tableName) : array
166 137
    {
167
        $indexBuffer = [];
168 137
169
        // fetch primary
170
        $stmt       = $this->_conn->executeQuery(sprintf(
171 137
            'PRAGMA TABLE_INFO (%s)',
172 2
            $this->_conn->quote($tableName)
173 137
        ));
174
        $indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
175 137
176
        usort($indexArray, static function ($a, $b) {
177
            if ($a['pk'] === $b['pk']) {
178 84
                return $a['cid'] - $b['cid'];
179 84
            }
180
181
            return $a['pk'] - $b['pk'];
182 84
        });
183 137
        foreach ($indexArray as $indexColumnRow) {
184 137
            if ($indexColumnRow['pk'] === '0') {
185 137
                continue;
186 89
            }
187
188
            $indexBuffer[] = [
189 132
                'key_name' => 'primary',
190 132
                'primary' => true,
191
                'non_unique' => false,
192
                'column_name' => $indexColumnRow['name'],
193 132
            ];
194
        }
195
196
        // fetch regular indexes
197
        foreach ($tableIndexRows as $tableIndex) {
198 137
            // Ignore indexes with reserved names, e.g. autoindexes
199
            if (strpos($tableIndex['name'], 'sqlite_') === 0) {
200 80
                continue;
201 80
            }
202
203
            $keyName           = $tableIndex['name'];
204 71
            $idx               = [];
205 71
            $idx['key_name']   = $keyName;
206 71
            $idx['primary']    = false;
207 71
            $idx['non_unique'] = ! $tableIndex['unique'];
208 71
209
                $stmt       = $this->_conn->executeQuery(sprintf(
210 71
                    'PRAGMA INDEX_INFO (%s)',
211
                    $this->_conn->quote($keyName)
212 71
                ));
213
                $indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
214 71
215
            foreach ($indexArray as $indexColumnRow) {
216 71
                $idx['column_name'] = $indexColumnRow['name'];
217 71
                $indexBuffer[]      = $idx;
218 71
            }
219
        }
220
221
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
222 137
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227
    protected function _getPortableTableIndexDefinition($tableIndex) : array
228
    {
229
        return [
230
            'name' => $tableIndex['name'],
231
            'unique' => (bool) $tableIndex['unique'],
232
        ];
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    protected function _getPortableTableColumnList(string $table, ?string $database, array $tableColumns) : array
239 137
    {
240
        $list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
241 137
242
        // find column with autoincrement
243
        $autoincrementColumn = null;
244 137
        $autoincrementCount  = 0;
245 137
246
        foreach ($tableColumns as $tableColumn) {
247 137
            if ($tableColumn['pk'] === '0') {
248 137
                continue;
249 89
            }
250
251
            $autoincrementCount++;
252 132
            if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') {
253 132
                continue;
254 71
            }
255
256
            $autoincrementColumn = $tableColumn['name'];
257 132
        }
258
259
        if ($autoincrementCount === 1 && $autoincrementColumn !== null) {
0 ignored issues
show
introduced by
The condition $autoincrementColumn !== null is always false.
Loading history...
260 137
            foreach ($list as $column) {
261 132
                if ($autoincrementColumn !== $column->getName()) {
262 132
                    continue;
263 84
                }
264
265
                $column->setAutoincrement(true);
266 132
            }
267
        }
268
269
        // inspect column collation and comments
270
        $createSql = $this->getCreateTableSQL($table) ?? '';
271 137
272
        foreach ($list as $columnName => $column) {
273 137
            $type = $column->getType();
274 137
275
            if ($type instanceof StringType || $type instanceof TextType) {
276 137
                $column->setPlatformOption('collation', $this->parseColumnCollationFromSQL($columnName, $createSql) ?: 'BINARY');
277 84
            }
278
279
            $comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
280 137
281
            $type = $this->extractDoctrineTypeFromComment($comment);
282 137
283 137
            if ($type !== null) {
284
                $column->setType(Type::getType($type));
285
            }
286 58
287
            $column->setComment($comment);
288 58
        }
289 57
290
        return $list;
291 57
    }
292
293
    /**
294 58
     * {@inheritdoc}
295
     */
296
    protected function _getPortableTableColumnDefinition(array $tableColumn) : Column
297 137
    {
298
        preg_match('/^([^\\s()]*)\\s*(\\(((\\d+)(,\\s*(\\d+))?)\\))?/', $tableColumn['type'], $matches);
299
300
        $dbType = strtolower($matches[1]);
301
302
        $length = $precision = $scale = $unsigned = $fixed = null;
303 137
304
        if (count($matches) >= 6) {
305 137
            $precision = (int) $matches[4];
306 137
            $scale     = (int) $matches[6];
307 137
        } elseif (count($matches) >= 4) {
308 84
            $length = (int) $matches[4];
309 84
        }
310
311
        if (strpos($tableColumn['type'], ' UNSIGNED') !== false) {
312 137
            $unsigned = true;
313 137
        }
314 137
315
        $type    = $this->_platform->getDoctrineTypeMapping($dbType);
316 137
        $default = $tableColumn['dflt_value'];
317 36
        if ($default === 'NULL') {
318 36
            $default = null;
319
        }
320
        if ($default !== null) {
321 137
            // SQLite returns strings wrapped in single quotes, so we need to strip them
322 137
            $default = preg_replace("/^'(.*)'$/", '\1', $default);
323 137
        }
324 137
        $notnull = (bool) $tableColumn['notnull'];
325 70
326
        if (! isset($tableColumn['name'])) {
327 137
            $tableColumn['name'] = '';
328
        }
329 89
330
        if ($dbType === 'char') {
331 137
            $fixed = true;
332
        }
333 137
334
        $options = [
335
            'length'   => $length,
336
            'notnull'  => $notnull,
337 137
            'default'  => $default,
338 137
            'precision' => $precision,
339
            'scale'     => $scale,
340 135
            'autoincrement' => false,
341 4
        ];
342 69
343 69
        if ($unsigned !== null) {
344 4
            $options['unsigned'] = $unsigned;
345 4
        }
346 4
347 4
        if ($fixed !== null) {
348 4
            $options['fixed'] = $fixed;
349 70
        }
350 70
351
        return new Column($tableColumn['name'], Type::getType($type), $options);
352
    }
353 70
354
    /**
355 70
     * {@inheritdoc}
356 70
     */
357
    protected function _getPortableViewDefinition(array $view) : View
358
    {
359
        return new View($view['name'], $view['sql']);
360 137
    }
361 137
362 137
    /**
363 137
     * {@inheritdoc}
364 137
     */
365 137
    protected function _getPortableTableForeignKeysList(array $tableForeignKeys) : array
366 137
    {
367
        $list = [];
368
        foreach ($tableForeignKeys as $value) {
369
            $value = array_change_key_case($value, CASE_LOWER);
370 137
            $name  = $value['constraint_name'];
371
            if (! isset($list[$name])) {
372
                if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') {
373
                    $value['on_delete'] = null;
374
                }
375
                if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') {
376 61
                    $value['on_update'] = null;
377
                }
378 61
379
                $list[$name] = [
380
                    'name' => $name,
381
                    'local' => [],
382
                    'foreign' => [],
383
                    'foreignTable' => $value['table'],
384 83
                    'onDelete' => $value['on_delete'],
385
                    'onUpdate' => $value['on_update'],
386 83
                    'deferrable' => $value['deferrable'],
387 83
                    'deferred'=> $value['deferred'],
388 83
                ];
389 83
            }
390 83
            $list[$name]['local'][] = $value['from'];
391 83
392
            if ($value['to'] === null) {
393
                continue;
394 83
            }
395
396
            $list[$name]['foreign'][] = $value['to'];
397
        }
398 83
399 83
        $result = [];
400
        foreach ($list as $constraint) {
401
            $result[] = new ForeignKeyConstraint(
402 83
                array_values($constraint['local']),
403 83
                $constraint['foreignTable'],
404 83
                array_values($constraint['foreign']),
405 83
                $constraint['name'],
406 83
                [
407
                    'onDelete' => $constraint['onDelete'],
408
                    'onUpdate' => $constraint['onUpdate'],
409 83
                    'deferrable' => $constraint['deferrable'],
410 83
                    'deferred'=> $constraint['deferred'],
411
                ]
412
            );
413 83
        }
414 83
415 83
        return $result;
416 83
    }
417 83
418 83
    /**
419 83
     * @param Table|string $table
420
     *
421 83
     * @throws DBALException
422 83
     */
423 83
    private function getTableDiffForAlterForeignKey($table) : TableDiff
424 83
    {
425
        if (! $table instanceof Table) {
426
            $tableDetails = $this->tryMethod('listTableDetails', $table);
427
428
            if ($tableDetails === false) {
429 83
                throw new DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table));
430
            }
431
432
            $table = $tableDetails;
433
        }
434
435
        $tableDiff            = new TableDiff($table->getName());
436
        $tableDiff->fromTable = $table;
437
438
        return $tableDiff;
439
    }
440
441
    private function parseColumnCollationFromSQL(string $column, string $sql) : ?string
442
    {
443
        $pattern = '{(?:\W' . preg_quote($column) . '\W|\W' . preg_quote($this->_platform->quoteSingleIdentifier($column))
444
            . '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
445
446
        if (preg_match($pattern, $sql, $match) !== 1) {
447
            return null;
448
        }
449
450
        return $match[1];
451
    }
452
453
    private function parseColumnCommentFromSQL(string $column, string $sql) : ?string
454
    {
455
        $pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column)) . '\W|\W' . preg_quote($column)
456
            . '\W)(?:\(.*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
457 905
458
        if (preg_match($pattern, $sql, $match) !== 1) {
459 905
            return null;
460 905
        }
461
462 905
        $comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
463 848
464
        return $comment === '' ? null : $comment;
465
    }
466 898
467
    private function getCreateTableSQL(string $table) : ?string
468
    {
469 619
        return $this->_conn->fetchColumn(
470
            <<<'SQL'
471 619
SELECT sql
472 619
  FROM (
473
      SELECT *
474 619
        FROM sqlite_master
475 608
   UNION ALL
476
      SELECT *
477
        FROM sqlite_temp_master
478 575
  )
479
WHERE type = 'table'
480 575
AND name = ?
481
SQL
482
            ,
483 137
            [$table]
484
        ) ?: null;
485 137
    }
486
}
487