Failed Conditions
Push — master ( 01c22b...e42c1f )
by Marco
79:13 queued 10s
created

_getPortableTableColumnDefinition()   B

Complexity

Conditions 9
Paths 144

Size

Total Lines 57
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 9.0128

Importance

Changes 0
Metric Value
eloc 35
dl 0
loc 57
ccs 35
cts 37
cp 0.9459
rs 7.6888
c 0
b 0
f 0
cc 9
nc 144
nop 1
crap 9.0128

How to fix   Long Method   

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