Passed
Pull Request — master (#2920)
by Luís
09:30
created

OracleSchemaManager   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 387
Duplicated Lines 8.01 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 65
dl 31
loc 387
ccs 0
cts 191
cp 0
rs 3.3333
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A dropTable() 0 5 1
A createDatabase() 0 17 2
B dropDatabase() 22 22 4
A getQuotedIdentifierName() 0 7 2
A dropAutoincrement() 0 8 2
B _getPortableTableForeignKeysList() 0 36 5
B _getPortableTableIndexesList() 0 22 4
F _getPortableTableColumnDefinition() 7 116 37
A _getPortableSequenceDefinition() 0 8 1
A _getPortableViewDefinition() 0 5 1
B killUserSessions() 0 24 2
A _getPortableFunctionDefinition() 0 5 1
A _getPortableDatabaseDefinition() 0 5 1
A _getPortableUserDefinition() 0 6 1
A _getPortableTableDefinition() 0 5 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Schema;
21
22
use Doctrine\DBAL\DBALException;
23
use Doctrine\DBAL\Driver\DriverException;
24
use Doctrine\DBAL\Types\Type;
25
26
/**
27
 * Oracle Schema Manager.
28
 *
29
 * @author Konsta Vesterinen <[email protected]>
30
 * @author Lukas Smith <[email protected]> (PEAR MDB2 library)
31
 * @author Benjamin Eberlei <[email protected]>
32
 * @since  2.0
33
 */
34
class OracleSchemaManager extends AbstractSchemaManager
35
{
36
    /**
37
     * {@inheritdoc}
38
     */
39 View Code Duplication
    public function dropDatabase($database)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
40
    {
41
        try {
42
            parent::dropDatabase($database);
43
        } catch (DBALException $exception) {
44
            $exception = $exception->getPrevious();
45
46
            if (! $exception instanceof DriverException) {
47
                throw $exception;
48
            }
49
50
            // If we have a error code 1940 (ORA-01940), the drop database operation failed
51
            // because of active connections on the database.
52
            // To force dropping the database, we first have to close all active connections
53
            // on that database and issue the drop database operation again.
54
            if ($exception->getErrorCode() !== 1940) {
55
                throw $exception;
56
            }
57
58
            $this->killUserSessions($database);
59
60
            parent::dropDatabase($database);
61
        }
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67
    protected function _getPortableViewDefinition($view)
68
    {
69
        $view = \array_change_key_case($view, CASE_LOWER);
70
71
        return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']);
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    protected function _getPortableUserDefinition($user)
78
    {
79
        $user = \array_change_key_case($user, CASE_LOWER);
80
81
        return [
82
            'user' => $user['username'],
83
        ];
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    protected function _getPortableTableDefinition($table)
90
    {
91
        $table = \array_change_key_case($table, CASE_LOWER);
92
93
        return $this->getQuotedIdentifierName($table['table_name']);
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     *
99
     * @license New BSD License
100
     * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
101
     */
102
    protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
103
    {
104
        $indexBuffer = [];
105
        foreach ($tableIndexes as $tableIndex) {
106
            $tableIndex = \array_change_key_case($tableIndex, CASE_LOWER);
107
108
            $keyName = strtolower($tableIndex['name']);
109
110
            if (strtolower($tableIndex['is_primary']) == "p") {
111
                $keyName = 'primary';
112
                $buffer['primary'] = true;
113
                $buffer['non_unique'] = false;
114
            } else {
115
                $buffer['primary'] = false;
116
                $buffer['non_unique'] = ($tableIndex['is_unique'] == 0) ? true : false;
117
            }
118
            $buffer['key_name'] = $keyName;
119
            $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
120
            $indexBuffer[] = $buffer;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $buffer seems to be defined later in this foreach loop on line 112. Are you sure it is defined here?
Loading history...
121
        }
122
123
        return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    protected function _getPortableTableColumnDefinition($tableColumn)
130
    {
131
        $tableColumn = \array_change_key_case($tableColumn, CASE_LOWER);
132
133
        $dbType = strtolower($tableColumn['data_type']);
134
        if (strpos($dbType, "timestamp(") === 0) {
135
            if (strpos($dbType, "with time zone")) {
136
                $dbType = "timestamptz";
137
            } else {
138
                $dbType = "timestamp";
139
            }
140
        }
141
142
        $unsigned = $fixed = null;
143
144
        if ( ! isset($tableColumn['column_name'])) {
145
            $tableColumn['column_name'] = '';
146
        }
147
148
        // Default values returned from database sometimes have trailing spaces.
149
        $tableColumn['data_default'] = trim($tableColumn['data_default']);
150
151 View Code Duplication
        if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152
            $tableColumn['data_default'] = null;
153
        }
154
155 View Code Duplication
        if (null !== $tableColumn['data_default']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
156
            // Default values returned from database are enclosed in single quotes.
157
            $tableColumn['data_default'] = trim($tableColumn['data_default'], "'");
158
        }
159
160
        $precision = null;
161
        $scale = null;
162
163
        $type = $this->_platform->getDoctrineTypeMapping($dbType);
164
        $type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type);
165
        $tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type);
166
167
        switch ($dbType) {
168
            case 'number':
169
                if ($tableColumn['data_precision'] == 20 && $tableColumn['data_scale'] == 0) {
170
                    $precision = 20;
171
                    $scale = 0;
172
                    $type = 'bigint';
173
                } elseif ($tableColumn['data_precision'] == 5 && $tableColumn['data_scale'] == 0) {
174
                    $type = 'smallint';
175
                    $precision = 5;
176
                    $scale = 0;
177
                } elseif ($tableColumn['data_precision'] == 1 && $tableColumn['data_scale'] == 0) {
178
                    $precision = 1;
179
                    $scale = 0;
180
                    $type = 'boolean';
181
                } elseif ($tableColumn['data_scale'] > 0) {
182
                    $precision = $tableColumn['data_precision'];
183
                    $scale = $tableColumn['data_scale'];
184
                    $type = 'decimal';
185
                }
186
                $length = null;
187
                break;
188
            case 'pls_integer':
189
            case 'binary_integer':
190
                $length = null;
191
                break;
192
            case 'varchar':
193
            case 'varchar2':
194
            case 'nvarchar2':
195
                $length = $tableColumn['char_length'];
196
                $fixed = false;
197
                break;
198
            case 'char':
199
            case 'nchar':
200
                $length = $tableColumn['char_length'];
201
                $fixed = true;
202
                break;
203
            case 'date':
204
            case 'timestamp':
205
                $length = null;
206
                break;
207
            case 'float':
208
            case 'binary_float':
209
            case 'binary_double':
210
                $precision = $tableColumn['data_precision'];
211
                $scale = $tableColumn['data_scale'];
212
                $length = null;
213
                break;
214
            case 'clob':
215
            case 'nclob':
216
                $length = null;
217
                break;
218
            case 'blob':
219
            case 'raw':
220
            case 'long raw':
221
            case 'bfile':
222
                $length = null;
223
                break;
224
            case 'rowid':
225
            case 'urowid':
226
            default:
227
                $length = null;
228
        }
229
230
        $options = [
231
            'notnull'    => (bool) ($tableColumn['nullable'] === 'N'),
232
            'fixed'      => (bool) $fixed,
233
            'unsigned'   => (bool) $unsigned,
234
            'default'    => $tableColumn['data_default'],
235
            'length'     => $length,
236
            'precision'  => $precision,
237
            'scale'      => $scale,
238
            'comment'    => isset($tableColumn['comments']) && '' !== $tableColumn['comments']
239
                ? $tableColumn['comments']
240
                : null,
241
            'platformDetails' => [],
242
        ];
243
244
        return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     */
250
    protected function _getPortableTableForeignKeysList($tableForeignKeys)
251
    {
252
        $list = [];
253
        foreach ($tableForeignKeys as $value) {
254
            $value = \array_change_key_case($value, CASE_LOWER);
255
            if (!isset($list[$value['constraint_name']])) {
256
                if ($value['delete_rule'] == "NO ACTION") {
257
                    $value['delete_rule'] = null;
258
                }
259
260
                $list[$value['constraint_name']] = [
261
                    'name' => $this->getQuotedIdentifierName($value['constraint_name']),
262
                    'local' => [],
263
                    'foreign' => [],
264
                    'foreignTable' => $value['references_table'],
265
                    'onDelete' => $value['delete_rule'],
266
                ];
267
            }
268
269
            $localColumn = $this->getQuotedIdentifierName($value['local_column']);
270
            $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
271
272
            $list[$value['constraint_name']]['local'][$value['position']] = $localColumn;
273
            $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
274
        }
275
276
        $result = [];
277
        foreach ($list as $constraint) {
278
            $result[] = new ForeignKeyConstraint(
279
                array_values($constraint['local']), $this->getQuotedIdentifierName($constraint['foreignTable']),
280
                array_values($constraint['foreign']), $this->getQuotedIdentifierName($constraint['name']),
281
                ['onDelete' => $constraint['onDelete']]
282
            );
283
        }
284
285
        return $result;
286
    }
287
288
    /**
289
     * {@inheritdoc}
290
     */
291
    protected function _getPortableSequenceDefinition($sequence)
292
    {
293
        $sequence = \array_change_key_case($sequence, CASE_LOWER);
294
295
        return new Sequence(
296
            $this->getQuotedIdentifierName($sequence['sequence_name']),
297
            $sequence['increment_by'],
298
            $sequence['min_value']
299
        );
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305
    protected function _getPortableFunctionDefinition($function)
306
    {
307
        $function = \array_change_key_case($function, CASE_LOWER);
308
309
        return $function['name'];
310
    }
311
312
    /**
313
     * {@inheritdoc}
314
     */
315
    protected function _getPortableDatabaseDefinition($database)
316
    {
317
        $database = \array_change_key_case($database, CASE_LOWER);
318
319
        return $database['username'];
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325
    public function createDatabase($database = null)
326
    {
327
        if (is_null($database)) {
328
            $database = $this->_conn->getDatabase();
329
        }
330
331
        $params = $this->_conn->getParams();
332
        $username   = $database;
333
        $password   = $params['password'];
334
335
        $query  = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password;
336
        $this->_conn->executeUpdate($query);
337
338
        $query = 'GRANT DBA TO ' . $username;
339
        $this->_conn->executeUpdate($query);
340
341
        return true;
342
    }
343
344
    /**
345
     * @param string $table
346
     *
347
     * @return boolean
348
     */
349
    public function dropAutoincrement($table)
350
    {
351
        $sql = $this->_platform->getDropAutoincrementSql($table);
0 ignored issues
show
Bug introduced by
The method getDropAutoincrementSql() does not exist on Doctrine\DBAL\Platforms\AbstractPlatform. It seems like you code against a sub-type of Doctrine\DBAL\Platforms\AbstractPlatform such as Doctrine\DBAL\Platforms\OraclePlatform. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

351
        /** @scrutinizer ignore-call */ 
352
        $sql = $this->_platform->getDropAutoincrementSql($table);
Loading history...
352
        foreach ($sql as $query) {
353
            $this->_conn->executeUpdate($query);
354
        }
355
356
        return true;
357
    }
358
359
    /**
360
     * {@inheritdoc}
361
     */
362
    public function dropTable($name)
363
    {
364
        $this->tryMethod('dropAutoincrement', $name);
365
366
        parent::dropTable($name);
367
    }
368
369
    /**
370
     * Returns the quoted representation of the given identifier name.
371
     *
372
     * Quotes non-uppercase identifiers explicitly to preserve case
373
     * and thus make references to the particular identifier work.
374
     *
375
     * @param string $identifier The identifier to quote.
376
     *
377
     * @return string The quoted identifier.
378
     */
379
    private function getQuotedIdentifierName($identifier)
380
    {
381
        if (preg_match('/[a-z]/', $identifier)) {
382
            return $this->_platform->quoteIdentifier($identifier);
383
        }
384
385
        return $identifier;
386
    }
387
388
    /**
389
     * Kills sessions connected with the given user.
390
     *
391
     * This is useful to force DROP USER operations which could fail because of active user sessions.
392
     *
393
     * @param string $user The name of the user to kill sessions for.
394
     *
395
     * @return void
396
     */
397
    private function killUserSessions($user)
398
    {
399
        $sql = <<<SQL
400
SELECT
401
    s.sid,
402
    s.serial#
403
FROM
404
    gv\$session s,
405
    gv\$process p
406
WHERE
407
    s.username = ?
408
    AND p.addr(+) = s.paddr
409
SQL;
410
411
        $activeUserSessions = $this->_conn->fetchAll($sql, [strtoupper($user)]);
412
413
        foreach ($activeUserSessions as $activeUserSession) {
414
            $activeUserSession = array_change_key_case($activeUserSession, \CASE_LOWER);
415
416
            $this->_execSql(
417
                sprintf(
418
                    "ALTER SYSTEM KILL SESSION '%s, %s' IMMEDIATE",
419
                    $activeUserSession['sid'],
420
                    $activeUserSession['serial#']
421
                )
422
            );
423
        }
424
    }
425
}
426