Passed
Pull Request — master (#3070)
by Sergei
07:45
created

_getPortableTableColumnDefinition()   B

Complexity

Conditions 9
Paths 144

Size

Total Lines 57
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 9.002

Importance

Changes 0
Metric Value
eloc 35
dl 0
loc 57
ccs 33
cts 34
cp 0.9706
rs 7.6888
c 0
b 0
f 0
cc 9
nc 144
nop 1
crap 9.002

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