Completed
Push — master ( af81c1...f3339a )
by Sergei
21s queued 15s
created

OracleSchemaManager   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 386
Duplicated Lines 0 %

Test Coverage

Coverage 86.98%

Importance

Changes 0
Metric Value
wmc 65
dl 0
loc 386
ccs 167
cts 192
cp 0.8698
rs 3.3333
c 0
b 0
f 0

15 Methods

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

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