Completed
Push — 2.10.x ( 61a6b9...f20ba1 )
by Grégoire
13:37 queued 11s
created

OracleSchemaManager   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 391
Duplicated Lines 0 %

Test Coverage

Coverage 93.06%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 54
eloc 171
dl 0
loc 391
ccs 161
cts 173
cp 0.9306
rs 6.4799
c 2
b 0
f 0

16 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 _getPortableSequenceDefinition() 0 8 1
A _getPortableTableForeignKeysList() 0 38 5
A _getPortableTableIndexesList() 0 24 3
A dropTable() 0 5 1
A createDatabase() 0 15 2
A getQuotedIdentifierName() 0 7 2
A dropAutoincrement() 0 10 2
F _getPortableTableColumnDefinition() 0 87 25
A killUserSessions() 0 24 2
A _getPortableFunctionDefinition() 0 5 1
A _getPortableDatabaseDefinition() 0 5 1
A listTableDetails() 0 15 2

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