Failed Conditions
Pull Request — develop (#2854)
by Michael
67:28 queued 05:38
created

OracleSchemaManager   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 359
Duplicated Lines 0 %

Test Coverage

Coverage 86.56%

Importance

Changes 0
Metric Value
wmc 53
eloc 163
dl 0
loc 359
ccs 161
cts 186
cp 0.8656
rs 6.96
c 0
b 0
f 0

15 Methods

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