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

OracleSchemaManager   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 356
Duplicated Lines 0 %

Test Coverage

Coverage 92.41%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 52
eloc 165
dl 0
loc 356
ccs 146
cts 158
cp 0.9241
rs 7.44
c 1
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A _getPortableTableIndexesList() 0 23 3
A _getPortableSequenceDefinition() 0 8 1
A _getPortableViewDefinition() 0 5 1
A _getPortableUserDefinition() 0 6 1
A _getPortableTableDefinition() 0 5 1
A dropDatabase() 0 23 4
A _getPortableTableForeignKeysList() 0 38 5
F _getPortableTableColumnDefinition() 0 86 26
A dropTable() 0 5 1
A createDatabase() 0 11 1
A getQuotedIdentifierName() 0 7 2
A dropAutoincrement() 0 10 2
A killUserSessions() 0 24 2
A _getPortableDatabaseDefinition() 0 5 1
A listTableDetails() 0 12 1

How to fix   Complexity   

Complex Class

Complex classes like OracleSchemaManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OracleSchemaManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Schema;
6
7
use Doctrine\DBAL\DBALException;
8
use Doctrine\DBAL\Driver\DriverException;
9
use Doctrine\DBAL\Platforms\OraclePlatform;
10
use Doctrine\DBAL\Types\Type;
11
use Throwable;
12
use const CASE_LOWER;
13
use function array_change_key_case;
14
use function array_values;
15
use function assert;
16
use function preg_match;
17
use function sprintf;
18
use function str_replace;
19
use function strpos;
20
use function strtolower;
21
use function strtoupper;
22
use function trim;
23
24
/**
25
 * Oracle Schema Manager.
26
 */
27
class OracleSchemaManager extends AbstractSchemaManager
28
{
29
    /**
30 4
     * {@inheritdoc}
31
     */
32
    public function dropDatabase(string $database) : void
33 4
    {
34 4
        try {
35 4
            parent::dropDatabase($database);
36 4
        } catch (DBALException $exception) {
37
            $exception = $exception->getPrevious();
38 4
            assert($exception instanceof Throwable);
39
40
            if (! $exception instanceof DriverException) {
41
                throw $exception;
42
            }
43
44
            // If we have a error code 1940 (ORA-01940), the drop database operation failed
45
            // because of active connections on the database.
46 4
            // To force dropping the database, we first have to close all active connections
47 4
            // on that database and issue the drop database operation again.
48
            if ($exception->getCode() !== 1940) {
49
                throw $exception;
50 2
            }
51
52 2
            $this->killUserSessions($database);
53
54 2
            parent::dropDatabase($database);
55
        }
56
    }
57
58
    /**
59 2
     * {@inheritdoc}
60
     */
61 2
    protected function _getPortableViewDefinition(array $view) : View
62
    {
63 2
        $view = array_change_key_case($view, CASE_LOWER);
64
65
        return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']);
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    protected function _getPortableUserDefinition(array $user) : array
72
    {
73
        $user = array_change_key_case($user, CASE_LOWER);
74
75
        return [
76
            'user' => $user['username'],
77
        ];
78
    }
79
80
    /**
81 128
     * {@inheritdoc}
82
     */
83 128
    protected function _getPortableTableDefinition(array $table) : string
84
    {
85 128
        $table = array_change_key_case($table, CASE_LOWER);
86
87
        return $this->getQuotedIdentifierName($table['table_name']);
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     *
93 92
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
94
     */
95 92
    protected function _getPortableTableIndexesList(array $tableIndexRows, string $tableName) : array
96 92
    {
97 30
        $indexBuffer = [];
98
        foreach ($tableIndexRows as $tableIndex) {
99 30
            $tableIndex = array_change_key_case($tableIndex, CASE_LOWER);
100 30
101
            $keyName = strtolower($tableIndex['name']);
102 30
            $buffer  = [];
103 26
104 26
            if ($tableIndex['is_primary'] === 'P') {
105 26
                $keyName              = 'primary';
106
                $buffer['primary']    = true;
107 24
                $buffer['non_unique'] = false;
108 24
            } else {
109
                $buffer['primary']    = false;
110 30
                $buffer['non_unique'] = ! $tableIndex['is_unique'];
111 30
            }
112 30
            $buffer['key_name']    = $keyName;
113
            $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
114
            $indexBuffer[]         = $buffer;
115 92
        }
116
117
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
118
    }
119
120
    /**
121 106
     * {@inheritdoc}
122
     */
123 106
    protected function _getPortableTableColumnDefinition(array $tableColumn) : Column
124
    {
125 106
        $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
126 106
127 14
        $dbType = strtolower($tableColumn['data_type']);
128 6
        if (strpos($dbType, 'timestamp(') === 0) {
129
            if (strpos($dbType, 'with time zone')) {
130 14
                $dbType = 'timestamptz';
131
            } else {
132
                $dbType = 'timestamp';
133
            }
134 106
        }
135
136 106
        $length = $precision = null;
137
        $scale  = 0;
138
        $fixed  = false;
139
140
        if (! isset($tableColumn['column_name'])) {
141 106
            $tableColumn['column_name'] = '';
142
        }
143 106
144 100
        // Default values returned from database sometimes have trailing spaces.
145
        if ($tableColumn['data_default'] !== null) {
146
            $tableColumn['data_default'] = trim($tableColumn['data_default']);
147 106
        }
148
149 54
        if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
150 52
            $tableColumn['data_default'] = null;
151
        }
152
153
        if ($tableColumn['data_default'] !== null) {
154 106
            // Default values returned from database are represented as literal expressions
155 98
            if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches)) {
156
                $tableColumn['data_default'] = str_replace("''", "'", $matches[1]);
157
            }
158 106
        }
159 100
160
        if ($tableColumn['data_precision'] !== null) {
161
            $precision = (int) $tableColumn['data_precision'];
162 106
        }
163 106
164 106
        if ($tableColumn['data_scale'] !== null) {
165
            $scale = (int) $tableColumn['data_scale'];
166 106
        }
167 106
168 98
        $type = $this->extractDoctrineTypeFromComment($tableColumn['comments'])
169
            ?? $this->_platform->getDoctrineTypeMapping($dbType);
170 98
171
        switch ($dbType) {
172 98
            case 'number':
173 2
                if ($precision === 20 && $scale === 0) {
174 96
                    $type = 'bigint';
175 10
                } elseif ($precision === 5 && $scale === 0) {
176
                    $type = 'smallint';
177
                } elseif ($precision === 1 && $scale === 0) {
178 98
                    $type = 'boolean';
179 72
                } elseif ($scale > 0) {
180 72
                    $type = 'decimal';
181 28
                }
182 56
183 56
                break;
184 56
            case 'varchar':
185 28
            case 'varchar2':
186 24
            case 'nvarchar2':
187 10
                $length = (int) $tableColumn['char_length'];
188 10
                break;
189 10
            case 'char':
190
            case 'nchar':
191
                $length = (int) $tableColumn['char_length'];
192
                $fixed  = true;
193 106
                break;
194 106
        }
195 106
196 106
        $options = [
197 106
            'notnull'    => $tableColumn['nullable'] === 'N',
198 106
            'fixed'      => $fixed,
199 106
            'default'    => $tableColumn['data_default'],
200 106
            'length'     => $length,
201 34
            'precision'  => $precision,
202
            'scale'      => $scale,
203
            'comment'    => isset($tableColumn['comments']) && $tableColumn['comments'] !== ''
204
                ? $tableColumn['comments']
205 106
                : null,
206
        ];
207
208
        return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
209
    }
210
211 86
    /**
212
     * {@inheritdoc}
213 86
     */
214 86
    protected function _getPortableTableForeignKeysList(array $tableForeignKeys) : array
215 18
    {
216 18
        $list = [];
217 18
        foreach ($tableForeignKeys as $value) {
218 16
            $value = array_change_key_case($value, CASE_LOWER);
219
            if (! isset($list[$value['constraint_name']])) {
220
                if ($value['delete_rule'] === 'NO ACTION') {
221 18
                    $value['delete_rule'] = null;
222 18
                }
223
224
                $list[$value['constraint_name']] = [
225 18
                    'name' => $this->getQuotedIdentifierName($value['constraint_name']),
226 18
                    'local' => [],
227
                    'foreign' => [],
228
                    'foreignTable' => $value['references_table'],
229
                    'onDelete' => $value['delete_rule'],
230 18
                ];
231 18
            }
232
233 18
            $localColumn   = $this->getQuotedIdentifierName($value['local_column']);
234 18
            $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
235
236
            $list[$value['constraint_name']]['local'][$value['position']]   = $localColumn;
237 86
            $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
238 86
        }
239 18
240 18
        $result = [];
241 18
        foreach ($list as $constraint) {
242 18
            $result[] = new ForeignKeyConstraint(
243 18
                array_values($constraint['local']),
244 18
                $this->getQuotedIdentifierName($constraint['foreignTable']),
245
                array_values($constraint['foreign']),
246
                $this->getQuotedIdentifierName($constraint['name']),
247
                ['onDelete' => $constraint['onDelete']]
248 86
            );
249
        }
250
251
        return $result;
252
    }
253
254 9
    /**
255
     * {@inheritdoc}
256 9
     */
257
    protected function _getPortableSequenceDefinition(array $sequence) : Sequence
258 9
    {
259 9
        $sequence = array_change_key_case($sequence, CASE_LOWER);
260 9
261 9
        return new Sequence(
262
            $this->getQuotedIdentifierName($sequence['sequence_name']),
263
            (int) $sequence['increment_by'],
264
            (int) $sequence['min_value']
265
        );
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     *
271
     * @deprecated
272
     */
273
    protected function _getPortableDatabaseDefinition(array $database) : string
274
    {
275
        $database = array_change_key_case($database, CASE_LOWER);
276
277
        return $database['username'];
278
    }
279
280 4
    /**
281
     * {@inheritdoc}
282 4
     *
283
     * Calling this method without an argument or by passing NULL is deprecated.
284 4
     */
285
    public function createDatabase(string $database) : void
286
    {
287
        $params   = $this->_conn->getParams();
288
        $username = $database;
289
        $password = $params['password'];
290
291
        $query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password;
292 4
        $this->_conn->executeUpdate($query);
293
294 4
        $query = 'GRANT DBA TO ' . $username;
295
        $this->_conn->executeUpdate($query);
296
    }
297
298 4
    public function dropAutoincrement(string $table) : bool
299 4
    {
300 4
        assert($this->_platform instanceof OraclePlatform);
301
302 4
        $sql = $this->_platform->getDropAutoincrementSql($table);
303 4
        foreach ($sql as $query) {
304
            $this->_conn->executeUpdate($query);
305 4
        }
306 4
307 4
        return true;
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313
    public function dropTable(string $name) : void
314 228
    {
315
        $this->tryMethod('dropAutoincrement', $name);
316 228
317
        parent::dropTable($name);
318 228
    }
319 228
320 228
    /**
321
     * Returns the quoted representation of the given identifier name.
322
     *
323
     * Quotes non-uppercase identifiers explicitly to preserve case
324
     * and thus make references to the particular identifier work.
325
     */
326
    private function getQuotedIdentifierName(string $identifier) : string
327
    {
328
        if (preg_match('/[a-z]/', $identifier)) {
329 228
            return $this->_platform->quoteIdentifier($identifier);
330
        }
331 228
332
        return $identifier;
333 228
    }
334 132
335
    /**
336
     * Kills sessions connected with the given user.
337
     *
338
     * This is useful to force DROP USER operations which could fail because of active user sessions.
339
     *
340
     * @param string $user The name of the user to kill sessions for.
341
     */
342
    private function killUserSessions(string $user) : void
343
    {
344
        $sql = <<<SQL
345
SELECT
346 167
    s.sid,
347
    s.serial#
348 167
FROM
349 105
    gv\$session s,
350
    gv\$process p
351
WHERE
352 167
    s.username = ?
353
    AND p.addr(+) = s.paddr
354
SQL;
355
356
        $activeUserSessions = $this->_conn->fetchAll($sql, [strtoupper($user)]);
357
358
        foreach ($activeUserSessions as $activeUserSession) {
359
            $activeUserSession = array_change_key_case($activeUserSession, CASE_LOWER);
360
361
            $this->_execSql(
362
                sprintf(
363
                    "ALTER SYSTEM KILL SESSION '%s, %s' IMMEDIATE",
364 2
                    $activeUserSession['sid'],
365
                    $activeUserSession['serial#']
366
                )
367 2
            );
368
        }
369
    }
370
371
    public function listTableDetails(string $tableName) : Table
372
    {
373
        $table = parent::listTableDetails($tableName);
374
375
        /** @var OraclePlatform $platform */
376
        $platform = $this->_platform;
377
        $sql      = $platform->getListTableCommentsSQL($tableName);
378 2
379
        $tableOptions = $this->_conn->fetchAssoc($sql);
380 2
        $table->addOption('comment', $tableOptions['COMMENTS']);
381 2
382
        return $table;
383 2
    }
384
}
385